[markdown] IJPL-157064 initial compose-based preview implementation

Now it's hidden under a registry key ("markdown.experimental.use.compose.for.preview") and a checkbox in Markdown settings. It's a very early stage of development, lots of inlines are not unsupported yet, and styling is far from the JCEF-based renderer.


Merge-request: IJ-MR-140719
Merged-by: Alexander Kuznetsov <Aleksander.Kuznetsov@jetbrains.com>

GitOrigin-RevId: b4ad8a44e17aee1cfff32d20a3d2adc52f9fa9c5
This commit is contained in:
Alexander Kuznetsov
2024-08-28 10:16:18 +00:00
committed by intellij-monorepo-bot
parent a9860a447e
commit ca86a0a4cc
19 changed files with 360 additions and 29 deletions

1
.idea/modules.xml generated
View File

@@ -526,6 +526,7 @@
<module fileurl="file://$PROJECT_DIR$/plugins/lombok/intellij.lombok.iml" filepath="$PROJECT_DIR$/plugins/lombok/intellij.lombok.iml" /> <module fileurl="file://$PROJECT_DIR$/plugins/lombok/intellij.lombok.iml" filepath="$PROJECT_DIR$/plugins/lombok/intellij.lombok.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/lombok/generated/intellij.lombok.generated.iml" filepath="$PROJECT_DIR$/plugins/lombok/generated/intellij.lombok.generated.iml" /> <module fileurl="file://$PROJECT_DIR$/plugins/lombok/generated/intellij.lombok.generated.iml" filepath="$PROJECT_DIR$/plugins/lombok/generated/intellij.lombok.generated.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/markdown/core/intellij.markdown.iml" filepath="$PROJECT_DIR$/plugins/markdown/core/intellij.markdown.iml" /> <module fileurl="file://$PROJECT_DIR$/plugins/markdown/core/intellij.markdown.iml" filepath="$PROJECT_DIR$/plugins/markdown/core/intellij.markdown.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/markdown/compose/intellij.markdown.compose.preview.iml" filepath="$PROJECT_DIR$/plugins/markdown/compose/intellij.markdown.compose.preview.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/markdown/fenceInjection/intellij.markdown.fenceInjection.iml" filepath="$PROJECT_DIR$/plugins/markdown/fenceInjection/intellij.markdown.fenceInjection.iml" /> <module fileurl="file://$PROJECT_DIR$/plugins/markdown/fenceInjection/intellij.markdown.fenceInjection.iml" filepath="$PROJECT_DIR$/plugins/markdown/fenceInjection/intellij.markdown.fenceInjection.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/markdown/frontmatter/intellij.markdown.frontmatter.iml" filepath="$PROJECT_DIR$/plugins/markdown/frontmatter/intellij.markdown.frontmatter.iml" /> <module fileurl="file://$PROJECT_DIR$/plugins/markdown/frontmatter/intellij.markdown.frontmatter.iml" filepath="$PROJECT_DIR$/plugins/markdown/frontmatter/intellij.markdown.frontmatter.iml" />
<module fileurl="file://$PROJECT_DIR$/plugins/markdown/frontmatter/toml/intellij.markdown.frontmatter.toml.iml" filepath="$PROJECT_DIR$/plugins/markdown/frontmatter/toml/intellij.markdown.frontmatter.toml.iml" /> <module fileurl="file://$PROJECT_DIR$/plugins/markdown/frontmatter/toml/intellij.markdown.frontmatter.toml.iml" filepath="$PROJECT_DIR$/plugins/markdown/frontmatter/toml/intellij.markdown.frontmatter.toml.iml" />

View File

@@ -583,7 +583,7 @@ object CommunityLibraryLicenses {
) )
.apache("https://github.com/JetBrains/jewel/blob/master/LICENSE") .apache("https://github.com/JetBrains/jewel/blob/master/LICENSE")
.suppliedByOrganizations(Suppliers.JETBRAINS), .suppliedByOrganizations(Suppliers.JETBRAINS),
LibraryLicense(name = "Jetbrains Jewel Markdown LaF Standalone", LibraryLicense(name = "Jetbrains Jewel Markdown IDE LaF Bridge",
url = "https://github.com/JetBrains/jewel", url = "https://github.com/JetBrains/jewel",
libraryName= "jetbrains-jewel-markdown-laf-bridge-styling", libraryName= "jetbrains-jewel-markdown-laf-bridge-styling",
) )

View File

@@ -0,0 +1,2 @@
f:com.intellij.platform.compose.JBComposePanelKt
- *sf:JBComposePanel(kotlin.jvm.functions.Function2):javax.swing.JComponent

View File

@@ -22,7 +22,7 @@ import javax.swing.JComponent
@Suppress("FunctionName") @Suppress("FunctionName")
@OptIn(ExperimentalComposeUiApi::class, ExperimentalJewelApi::class) @OptIn(ExperimentalComposeUiApi::class, ExperimentalJewelApi::class)
@Experimental @Experimental
internal fun JBComposePanel( fun JBComposePanel(
content: @Composable () -> Unit content: @Composable () -> Unit
): JComponent { ): JComponent {
if (ApplicationManager.getApplication().isInternal) { if (ApplicationManager.getApplication().isInternal) {

View File

@@ -0,0 +1,71 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="FacetManager">
<facet type="kotlin-language" name="Kotlin">
<configuration version="5" platform="JVM 17" allPlatforms="JVM [17]" useProjectSettings="false">
<compilerSettings>
<option name="additionalArguments" value="-Xjvm-default=all" />
</compilerSettings>
<compilerArguments>
<stringArguments>
<stringArg name="jvmTarget" arg="17" />
<stringArg name="apiVersion" arg="1.9" />
<stringArg name="languageVersion" arg="1.9" />
</stringArguments>
<arrayArguments>
<arrayArg name="pluginClasspaths">
<args>$MAVEN_REPOSITORY$/org/jetbrains/compose/compiler/compiler-hosted/1.5.14/compiler-hosted-1.5.14.jar</args>
</arrayArg>
<arrayArg name="pluginOptions">
<args>plugin:androidx.compose.compiler.plugins.kotlin:suppressKotlinVersionCompatibilityCheck=1.9.25</args>
</arrayArg>
</arrayArguments>
</compilerArguments>
</configuration>
</facet>
</component>
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/kotlin" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="kotlin-stdlib" level="project" />
<orderEntry type="module" module-name="intellij.markdown" />
<orderEntry type="module" module-name="intellij.platform.compose" />
<orderEntry type="module-library">
<library name="jetbrains-jewel-markdown-laf-bridge-styling" type="repository">
<properties maven-id="org.jetbrains.jewel:jewel-markdown-ide-laf-bridge-styling-241:0.19.7">
<verification>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/jewel/jewel-markdown-ide-laf-bridge-styling-241/0.19.7/jewel-markdown-ide-laf-bridge-styling-241-0.19.7.jar">
<sha256sum>44f90aace7a3f8b9c811294f7705a4591d169f13b5d2299eed9f922be30dafce</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/jetbrains/jewel/jewel-markdown-core-241/0.19.7/jewel-markdown-core-241-0.19.7.jar">
<sha256sum>422b42dab33648dcb2b413a06707e100c6e70470918a42a1d760e4b02a1401a3</sha256sum>
</artifact>
<artifact url="file://$MAVEN_REPOSITORY$/org/commonmark/commonmark/0.22.0/commonmark-0.22.0.jar">
<sha256sum>3417909f2997bc8c61d90d64c6af29f4a3f5b6729caceaef97221ceac93df814</sha256sum>
</artifact>
</verification>
<exclude>
<dependency maven-id="org.jetbrains.jewel:jewel-ui-241" />
<dependency maven-id="org.jetbrains.jewel:jewel-ide-laf-bridge-241" />
</exclude>
</properties>
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/jewel/jewel-markdown-ide-laf-bridge-styling-241/0.19.7/jewel-markdown-ide-laf-bridge-styling-241-0.19.7.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/jewel/jewel-markdown-core-241/0.19.7/jewel-markdown-core-241-0.19.7.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/commonmark/commonmark/0.22.0/commonmark-0.22.0.jar!/" />
</CLASSES>
<JAVADOC />
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/jewel/jewel-markdown-ide-laf-bridge-styling-241/0.19.7/jewel-markdown-ide-laf-bridge-styling-241-0.19.7-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/jetbrains/jewel/jewel-markdown-core-241/0.19.7/jewel-markdown-core-241-0.19.7-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/commonmark/commonmark/0.22.0/commonmark-0.22.0-sources.jar!/" />
</SOURCES>
</library>
</orderEntry>
</component>
</module>

View File

@@ -0,0 +1,33 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.markdown.compose.preview
import com.intellij.idea.AppMode
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.vfs.VirtualFile
import org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanel
import org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanelProvider
import org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanelProvider.AvailabilityInfo
import org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanelProvider.ProviderInfo
import org.jetbrains.jewel.foundation.ExperimentalJewelApi
@OptIn(ExperimentalJewelApi::class)
private class ComposePanelProvider : MarkdownHtmlPanelProvider() {
override fun createHtmlPanel(): MarkdownHtmlPanel {
return MarkdownComposePanel()
}
override fun createHtmlPanel(project: Project, virtualFile: VirtualFile): MarkdownHtmlPanel {
return MarkdownComposePanel(project, virtualFile)
}
override fun isAvailable(): AvailabilityInfo {
if (Registry.`is`("markdown.experimental.use.compose.for.preview", false) && !AppMode.isRemoteDevHost()) {
return AvailabilityInfo.AVAILABLE
}
return AvailabilityInfo.UNAVAILABLE
}
override fun getProviderInfo() = ProviderInfo("Compose-based", ComposePanelProvider::class.java.name)
}

View File

@@ -0,0 +1,115 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.markdown.compose.preview
import androidx.compose.foundation.ScrollState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.intellij.ide.BrowserUtil
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.UserDataHolder
import com.intellij.openapi.util.UserDataHolderBase
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.platform.compose.JBComposePanel
import org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanel
import org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanelEx
import org.intellij.plugins.markdown.ui.preview.MarkdownUpdateHandler
import org.intellij.plugins.markdown.ui.preview.MarkdownUpdateHandler.PreviewRequest
import org.jetbrains.annotations.ApiStatus
import org.jetbrains.jewel.foundation.ExperimentalJewelApi
import org.jetbrains.jewel.intui.markdown.bridge.ProvideMarkdownStyling
import org.jetbrains.jewel.markdown.Markdown
import org.jetbrains.jewel.ui.component.VerticalScrollbar
import javax.swing.JComponent
@ExperimentalJewelApi
class MarkdownComposePanel(
private val project: Project?,
private val virtualFile: VirtualFile?,
private val updateHandler: MarkdownUpdateHandler = MarkdownUpdateHandler.Debounced()
) : MarkdownHtmlPanelEx, UserDataHolder by UserDataHolderBase() {
constructor() : this(null, null)
private val panelComponent by lazy {
JBComposePanel {
// TODO temporary styling, we will likely need our own in the future for JCEF-like rendering
ProvideMarkdownStyling {
MarkdownPanel()
}
}
}
@Suppress("FunctionName")
@Composable
private fun MarkdownPanel() {
Box {
val scrollState = rememberScrollState(0)
MarkdownPreviewPanel(scrollState)
VerticalScrollbar(
modifier = Modifier
.align(Alignment.CenterEnd),
adapter = rememberScrollbarAdapter(scrollState),
)
}
}
@Suppress("FunctionName")
@Composable
private fun MarkdownPreviewPanel(scrollState: ScrollState) {
val request by updateHandler.requests.collectAsState(null)
(request as? PreviewRequest.Update)?.let {
Markdown(
it.content,
modifier = Modifier
.fillMaxWidth()
.padding(8.dp)
.verticalScroll(scrollState),
enabled = true,
onUrlClick = { url -> BrowserUtil.open(url) },
)
}
}
override fun setHtml(html: String, initialScrollOffset: Int, document: VirtualFile?) {
updateHandler.setContent(html, initialScrollOffset, document)
}
override fun reloadWithOffset(offset: Int) {
updateHandler.reloadWithOffset(offset)
}
override fun getComponent(): JComponent {
return panelComponent
}
override fun dispose() {
}
@ApiStatus.Experimental
override fun getProject(): Project? = project
@ApiStatus.Experimental
override fun getVirtualFile(): VirtualFile? = virtualFile
override fun addScrollListener(listener: MarkdownHtmlPanel.ScrollListener) {
}
override fun removeScrollListener(listener: MarkdownHtmlPanel.ScrollListener) {
}
override fun scrollToMarkdownSrcOffset(offset: Int, smooth: Boolean) {
}
override fun scrollBy(horizontalUnits: Int, verticalUnits: Int) {
}
}

View File

@@ -0,0 +1,14 @@
<idea-plugin package="com.intellij.markdown.compose.preview">
<dependencies>
<module name="intellij.platform.compose"/>
</dependencies>
<extensions defaultExtensionNs="org.intellij.markdown">
<html.panel.provider implementation="com.intellij.markdown.compose.preview.ComposePanelProvider"/>
</extensions>
<extensions defaultExtensionNs="com.intellij">
<registryKey key="markdown.experimental.use.compose.for.preview"
defaultValue="false"
description="Renders Markdown Preview using Compose instead of JCEF (experimental feature, many inlines are unsupported yet)."/>
</extensions>
</idea-plugin>

View File

@@ -9,4 +9,15 @@
- name: intellij.markdown.images - name: intellij.markdown.images
- name: intellij.markdown.xml - name: intellij.markdown.xml
- name: intellij.markdown.model - name: intellij.markdown.model
- name: intellij.markdown.spellchecker - name: intellij.markdown.spellchecker
- name: lib/modules/intellij.markdown.compose.preview.jar
contentModules:
- name: intellij.markdown.compose.preview
libraries:
jetbrains-jewel-markdown-laf-bridge-styling:
- name: $MAVEN_REPOSITORY$/org/jetbrains/jewel/jewel-markdown-ide-laf-bridge-styling-241/0.19.7/jewel-markdown-ide-laf-bridge-styling-241-0.19.7.jar
size: 28320
- name: $MAVEN_REPOSITORY$/org/jetbrains/jewel/jewel-markdown-core-241/0.19.7/jewel-markdown-core-241-0.19.7.jar
size: 294476
- name: $MAVEN_REPOSITORY$/org/commonmark/commonmark/0.22.0/commonmark-0.22.0.jar
size: 193845

View File

@@ -17,6 +17,7 @@
<module name="intellij.markdown.xml"/> <module name="intellij.markdown.xml"/>
<module name="intellij.markdown.model"/> <module name="intellij.markdown.model"/>
<module name="intellij.markdown.spellchecker"/> <module name="intellij.markdown.spellchecker"/>
<module name="intellij.markdown.compose.preview"/>
</content> </content>
<description><![CDATA[ <description><![CDATA[

View File

@@ -9,7 +9,7 @@ import org.jetbrains.annotations.ApiStatus
@ApiStatus.Experimental @ApiStatus.Experimental
@Service(Service.Level.APP) @Service(Service.Level.APP)
@State(name = "MarkdownSettings", storages = [(Storage("markdown.xml"))]) @State(name = "MarkdownSettings", storages = [(Storage("markdown.xml"))])
internal class MarkdownPreviewSettings: SimplePersistentStateComponent<MarkdownPreviewSettings.State>(State()) { class MarkdownPreviewSettings: SimplePersistentStateComponent<MarkdownPreviewSettings.State>(State()) {
class State: BaseState() { class State: BaseState() {
var fontSize by property(defaultFontSize) var fontSize by property(defaultFontSize)
} }

View File

@@ -40,6 +40,10 @@ public abstract class MarkdownHtmlPanelProvider {
public abstract @NotNull ProviderInfo getProviderInfo(); public abstract @NotNull ProviderInfo getProviderInfo();
public @NotNull SourceTextPreprocessor getSourceTextPreprocessor() {
return SourceTextPreprocessor.getDefault();
}
public static @NotNull List<MarkdownHtmlPanelProvider> getProviders() { public static @NotNull List<MarkdownHtmlPanelProvider> getProviders() {
return EP_NAME.getExtensionList(); return EP_NAME.getExtensionList();
} }

View File

@@ -37,7 +37,6 @@ import kotlinx.coroutines.launch
import org.intellij.plugins.markdown.MarkdownBundle import org.intellij.plugins.markdown.MarkdownBundle
import org.intellij.plugins.markdown.settings.MarkdownExtensionsSettings import org.intellij.plugins.markdown.settings.MarkdownExtensionsSettings
import org.intellij.plugins.markdown.settings.MarkdownSettings import org.intellij.plugins.markdown.settings.MarkdownSettings
import org.intellij.plugins.markdown.ui.preview.html.MarkdownUtil.generateMarkdownHtml
import org.intellij.plugins.markdown.ui.preview.jcef.MarkdownJCEFHtmlPanel import org.intellij.plugins.markdown.ui.preview.jcef.MarkdownJCEFHtmlPanel
import org.intellij.plugins.markdown.util.MarkdownPluginScope import org.intellij.plugins.markdown.util.MarkdownPluginScope
import org.jetbrains.annotations.ApiStatus.Internal import org.jetbrains.annotations.ApiStatus.Internal
@@ -204,10 +203,12 @@ class MarkdownPreviewFileEditor(
return return
} }
val html = readAction { generateMarkdownHtml(file, document.text, project) } val settings = MarkdownSettings.getInstance(project)
val textPreprocessor = retrievePanelProvider(settings).sourceTextPreprocessor
lastRenderedHtml = readAction {
textPreprocessor.preprocessText(project, document, file)
}
val currentHtml = "<html><head></head>$html</html>"
lastRenderedHtml = currentHtml
val editor = mainEditor.firstOrNull() ?: return val editor = mainEditor.firstOrNull() ?: return
writeIntentReadAction { writeIntentReadAction {
val offset = editor.caretModel.offset val offset = editor.caretModel.offset
@@ -229,7 +230,8 @@ class MarkdownPreviewFileEditor(
@RequiresEdt @RequiresEdt
private suspend fun attachHtmlPanel() { private suspend fun attachHtmlPanel() {
val settings = MarkdownSettings.getInstance(project) val settings = MarkdownSettings.getInstance(project)
val panel = retrievePanelProvider(settings).createHtmlPanel(project, file) val panelProvider = retrievePanelProvider(settings)
val panel = panelProvider.createHtmlPanel(project, file)
this.panel = panel this.panel = panel
htmlPanelWrapper.add(panel.component, BorderLayout.CENTER) htmlPanelWrapper.add(panel.component, BorderLayout.CENTER)
if (htmlPanelWrapper.isShowing) htmlPanelWrapper.validate() if (htmlPanelWrapper.isShowing) htmlPanelWrapper.validate()

View File

@@ -0,0 +1,50 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.plugins.markdown.ui.preview
import com.intellij.openapi.vfs.VirtualFile
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.debounce
import kotlin.time.Duration
import kotlin.time.Duration.Companion.milliseconds
abstract class MarkdownUpdateHandler {
abstract val requests: Flow<PreviewRequest>
protected abstract fun addRequest(request: PreviewRequest): Boolean
fun setContent(content: String, initialScrollOffset: Int, document: VirtualFile?) {
doRequest(PreviewRequest.Update(content, initialScrollOffset, document))
}
fun reloadWithOffset(offset: Int) {
doRequest(PreviewRequest.ReloadWithOffset(offset))
}
private fun doRequest(request: PreviewRequest) {
check(addRequest(request))
}
sealed interface PreviewRequest {
data class Update(
val content: String,
val initialScrollOffset: Int,
val document: VirtualFile?
) : PreviewRequest
data class ReloadWithOffset(val offset: Int) : PreviewRequest
}
@OptIn(FlowPreview::class)
class Debounced(private val debounceTimeout: Duration = 20.milliseconds) : MarkdownUpdateHandler() {
private val _updateViewRequests = MutableSharedFlow<PreviewRequest>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
override val requests: Flow<PreviewRequest>
get() = _updateViewRequests.debounce(debounceTimeout)
override fun addRequest(request: PreviewRequest): Boolean = _updateViewRequests.tryEmit(request)
}
}

View File

@@ -0,0 +1,19 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.plugins.markdown.ui.preview
import com.intellij.openapi.editor.Document
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
interface SourceTextPreprocessor {
fun preprocessText(project: Project, document: Document, file: VirtualFile): String
companion object {
@JvmStatic
val default: SourceTextPreprocessor = object : SourceTextPreprocessor {
override fun preprocessText(project: Project, document: Document, file: VirtualFile): String {
return document.text
}
}
}
}

View File

@@ -0,0 +1,15 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package org.intellij.plugins.markdown.ui.preview.jcef
import com.intellij.openapi.editor.Document
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
import org.intellij.plugins.markdown.ui.preview.SourceTextPreprocessor
import org.intellij.plugins.markdown.ui.preview.html.MarkdownUtil.generateMarkdownHtml
class HtmlSourceTextPreprocessor : SourceTextPreprocessor {
override fun preprocessText(project: Project, document: Document, file: VirtualFile): String {
val html = generateMarkdownHtml(file, document.text, project)
return "<html><head></head>$html</html>"
}
}

View File

@@ -7,10 +7,9 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.ui.jcef.JBCefApp; import com.intellij.ui.jcef.JBCefApp;
import org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanel; import org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanel;
import org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanelProvider; import org.intellij.plugins.markdown.ui.preview.MarkdownHtmlPanelProvider;
import org.intellij.plugins.markdown.ui.preview.SourceTextPreprocessor;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import javax.swing.*;
public final class JCEFHtmlPanelProvider extends MarkdownHtmlPanelProvider { public final class JCEFHtmlPanelProvider extends MarkdownHtmlPanelProvider {
@NotNull @NotNull
@@ -39,6 +38,11 @@ public final class JCEFHtmlPanelProvider extends MarkdownHtmlPanelProvider {
return new ProviderInfo("JCEF Browser", JCEFHtmlPanelProvider.class.getName()); return new ProviderInfo("JCEF Browser", JCEFHtmlPanelProvider.class.getName());
} }
@Override
public @NotNull SourceTextPreprocessor getSourceTextPreprocessor() {
return new HtmlSourceTextPreprocessor();
}
public static boolean canBeUsed() { public static boolean canBeUsed() {
return !AppMode.isRemoteDevHost() && JBCefApp.isSupported(); return !AppMode.isRemoteDevHost() && JBCefApp.isSupported();
} }

View File

@@ -24,10 +24,7 @@ import com.intellij.ui.jcef.JCEFHtmlPanel
import com.intellij.util.application import com.intellij.util.application
import com.intellij.util.net.NetUtils import com.intellij.util.net.NetUtils
import kotlinx.coroutines.* import kotlinx.coroutines.*
import kotlinx.coroutines.channels.BufferOverflow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.debounce
import org.cef.browser.CefBrowser import org.cef.browser.CefBrowser
import org.cef.browser.CefFrame import org.cef.browser.CefFrame
import org.cef.handler.CefRequestHandlerAdapter import org.cef.handler.CefRequestHandlerAdapter
@@ -41,6 +38,7 @@ import org.intellij.plugins.markdown.extensions.MarkdownConfigurableExtension
import org.intellij.plugins.markdown.settings.MarkdownPreviewSettings import org.intellij.plugins.markdown.settings.MarkdownPreviewSettings
import org.intellij.plugins.markdown.settings.MarkdownSettingsConfigurable.Companion.fontSizeOptions import org.intellij.plugins.markdown.settings.MarkdownSettingsConfigurable.Companion.fontSizeOptions
import org.intellij.plugins.markdown.ui.preview.* import org.intellij.plugins.markdown.ui.preview.*
import org.intellij.plugins.markdown.ui.preview.MarkdownUpdateHandler.PreviewRequest
import org.intellij.plugins.markdown.ui.preview.jcef.impl.* import org.intellij.plugins.markdown.ui.preview.jcef.impl.*
import org.intellij.plugins.markdown.ui.preview.jcef.zoomIndicator.PreviewZoomIndicatorManager import org.intellij.plugins.markdown.ui.preview.jcef.zoomIndicator.PreviewZoomIndicatorManager
import org.intellij.plugins.markdown.util.MarkdownApplicationScope import org.intellij.plugins.markdown.util.MarkdownApplicationScope
@@ -101,6 +99,8 @@ class MarkdownJCEFHtmlPanel(
styles.map { PreviewStaticServer.getStaticUrl(resourceProvider, it) } styles.map { PreviewStaticServer.getStaticUrl(resourceProvider, it) }
) )
private val updateHandler = MarkdownUpdateHandler.Debounced()
private fun buildIndexContent(): String { private fun buildIndexContent(): String {
// language=HTML // language=HTML
return """ return """
@@ -126,18 +126,6 @@ class MarkdownJCEFHtmlPanel(
private val coroutineScope = project?.let(MarkdownPluginScope::createChildScope) ?: MarkdownApplicationScope.createChildScope() private val coroutineScope = project?.let(MarkdownPluginScope::createChildScope) ?: MarkdownApplicationScope.createChildScope()
private sealed interface PreviewRequest {
data class Update(
val content: String,
val initialScrollOffset: Int,
val document: VirtualFile?,
) : PreviewRequest
data class ReloadWithOffset(val offset: Int) : PreviewRequest
}
private val updateViewRequests = MutableSharedFlow<PreviewRequest>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
private val projectRoot = coroutineScope.async(context = Dispatchers.Default) { private val projectRoot = coroutineScope.async(context = Dispatchers.Default) {
if (virtualFile != null && project != null) { if (virtualFile != null && project != null) {
BaseProjectDirectories.getInstance(project).getBaseDirectoryFor(virtualFile) BaseProjectDirectories.getInstance(project).getBaseDirectoryFor(virtualFile)
@@ -186,7 +174,7 @@ class MarkdownJCEFHtmlPanel(
val fileSchemeResourcesProcessor = createFileSchemeResourcesProcessor(projectRoot) val fileSchemeResourcesProcessor = createFileSchemeResourcesProcessor(projectRoot)
loadIndexContent() loadIndexContent()
updateViewRequests.debounce(20.milliseconds).collectLatest { request -> updateHandler.requests.collectLatest { request ->
when (request) { when (request) {
is PreviewRequest.Update -> { is PreviewRequest.Update -> {
val (html, initialScrollOffset, document) = request val (html, initialScrollOffset, document) = request
@@ -239,7 +227,7 @@ class MarkdownJCEFHtmlPanel(
} }
override fun setHtml(html: String, initialScrollOffset: Int, document: VirtualFile?) { override fun setHtml(html: String, initialScrollOffset: Int, document: VirtualFile?) {
check(updateViewRequests.tryEmit(PreviewRequest.Update(content = html, initialScrollOffset, document))) updateHandler.setContent(html, initialScrollOffset, document)
} }
@ApiStatus.Internal @ApiStatus.Internal
@@ -253,7 +241,7 @@ class MarkdownJCEFHtmlPanel(
} }
override fun reloadWithOffset(offset: Int) { override fun reloadWithOffset(offset: Int) {
check(updateViewRequests.tryEmit(PreviewRequest.ReloadWithOffset(offset))) updateHandler.reloadWithOffset(offset)
} }
override fun dispose() { override fun dispose() {

View File

@@ -12,5 +12,6 @@
<orderEntry type="module" module-name="intellij.markdown.model" /> <orderEntry type="module" module-name="intellij.markdown.model" />
<orderEntry type="module" module-name="intellij.markdown.spellchecker" /> <orderEntry type="module" module-name="intellij.markdown.spellchecker" />
<orderEntry type="module" module-name="intellij.markdown.xml" /> <orderEntry type="module" module-name="intellij.markdown.xml" />
<orderEntry type="module" module-name="intellij.markdown.compose.preview" />
</component> </component>
</module> </module>