mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-01-08 15:09:39 +07:00
IJPL-787 JBHtmlPane - improve configuration API and move implementation details to impl module through dedicated service
GitOrigin-RevId: 3fe0f6be3a43f128bc95d5931bec2e1af24fa6af
This commit is contained in:
committed by
intellij-monorepo-bot
parent
2235b2f0c9
commit
d7bc671a54
@@ -5,7 +5,6 @@ import com.intellij.lang.documentation.DocumentationImageResolver;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.colors.*;
|
||||
import com.intellij.openapi.editor.impl.EditorCssFontResolver;
|
||||
import com.intellij.openapi.options.FontSize;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.ui.JBColor;
|
||||
@@ -34,7 +33,8 @@ import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.intellij.codeInsight.documentation.DocumentationHtmlUtil.*;
|
||||
import static com.intellij.lang.documentation.DocumentationMarkup.*;
|
||||
import static com.intellij.lang.documentation.DocumentationMarkup.CLASS_BOTTOM;
|
||||
import static com.intellij.lang.documentation.DocumentationMarkup.CLASS_DEFINITION;
|
||||
import static com.intellij.lang.documentation.QuickDocHighlightingHelper.getDefaultDocStyleOptions;
|
||||
|
||||
@Internal
|
||||
@@ -63,14 +63,13 @@ public abstract class DocumentationEditorPane extends JBHtmlPane implements Disp
|
||||
) {
|
||||
super(
|
||||
getDefaultDocStyleOptions(EditorColorsManager.getInstance().getGlobalScheme(), false),
|
||||
new JBHtmlPaneConfiguration(
|
||||
keyboardActions,
|
||||
component -> new DocumentationImageProvider(component, imageResolver),
|
||||
getModuleIconResolver(iconResolver),
|
||||
bg -> getDocumentationPaneAdditionalCssRules(),
|
||||
EditorCssFontResolver.getGlobalInstance(),
|
||||
Collections.singletonList(ExtendableHTMLViewFactory.Extensions.FIT_TO_WIDTH_IMAGES)
|
||||
)
|
||||
JBHtmlPaneConfiguration.builder()
|
||||
.keyboardActions(keyboardActions)
|
||||
.imageResolverFactory(component -> new DocumentationImageProvider(component, imageResolver))
|
||||
.iconResolver(name -> iconResolver.apply(name))
|
||||
.customStyleSheetProvider(bg -> getDocumentationPaneAdditionalCssRules())
|
||||
.extensions(Collections.singletonList(ExtendableHTMLViewFactory.Extensions.FIT_TO_WIDTH_IMAGES))
|
||||
.build()
|
||||
);
|
||||
setBackground(BACKGROUND_COLOR);
|
||||
}
|
||||
|
||||
@@ -404,9 +404,11 @@ public final class DocRenderer implements CustomFoldRegionRenderer {
|
||||
EditorInlineHtmlPane(boolean trackMemory, Editor editor) {
|
||||
super(
|
||||
QuickDocHighlightingHelper.getDefaultDocStyleOptions(editor.getColorsScheme(), true),
|
||||
new JBHtmlPaneConfiguration(Collections.emptyMap(), pane -> IMAGE_MANAGER.getImageProvider(),
|
||||
url -> null, bg -> getStyleSheet(editor),
|
||||
EditorCssFontResolver.getInstance(editor), Collections.emptyList())
|
||||
JBHtmlPaneConfiguration.builder()
|
||||
.imageResolverFactory(pane -> IMAGE_MANAGER.getImageProvider())
|
||||
.customStyleSheetProvider(bg -> getStyleSheet(editor))
|
||||
.fontResolver(EditorCssFontResolver.getInstance(editor))
|
||||
.build()
|
||||
);
|
||||
if (trackMemory) {
|
||||
MEMORY_MANAGER.register(DocRenderer.this, 50 /* rough size estimation */);
|
||||
|
||||
@@ -13,7 +13,8 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.psi.PsiElement
|
||||
import com.intellij.ui.components.JBHtmlPaneStyleConfiguration
|
||||
import com.intellij.ui.components.JBHtmlPaneStyleConfiguration.*
|
||||
import com.intellij.ui.components.JBHtmlPaneStyleConfiguration.ElementKind
|
||||
import com.intellij.ui.components.JBHtmlPaneStyleConfiguration.ElementProperty
|
||||
import com.intellij.util.applyIf
|
||||
import com.intellij.util.concurrency.annotations.RequiresReadLock
|
||||
import com.intellij.xml.util.XmlStringUtil
|
||||
@@ -272,26 +273,22 @@ object QuickDocHighlightingHelper {
|
||||
@Internal
|
||||
@JvmStatic
|
||||
fun getDefaultDocStyleOptions(colorScheme: EditorColorsScheme, editorInlineContext: Boolean): JBHtmlPaneStyleConfiguration =
|
||||
JBHtmlPaneStyleConfiguration(
|
||||
colorScheme = colorScheme,
|
||||
editorInlineContext = editorInlineContext,
|
||||
JBHtmlPaneStyleConfiguration {
|
||||
this.colorScheme = colorScheme
|
||||
this.editorInlineContext = editorInlineContext
|
||||
inlineCodeParentSelectors = listOf(".$CLASS_CONTENT", ".$CLASS_CONTENT div:not(.$CLASS_BOTTOM)",
|
||||
".$CLASS_CONTENT div:not(.$CLASS_TOP)", ".$CLASS_SECTIONS"),
|
||||
largeCodeFontSizeSelectors = listOf(".$CLASS_DEFINITION code", ".$CLASS_DEFINITION pre", ".$CLASS_BOTTOM code", ".$CLASS_TOP code"),
|
||||
enableInlineCodeBackground = DocumentationSettings.isCodeBackgroundEnabled()
|
||||
&& DocumentationSettings.getInlineCodeHighlightingMode() !== InlineCodeHighlightingMode.NO_HIGHLIGHTING,
|
||||
".$CLASS_CONTENT div:not(.$CLASS_TOP)", ".$CLASS_SECTIONS")
|
||||
largeCodeFontSizeSelectors = listOf(".$CLASS_DEFINITION code", ".$CLASS_DEFINITION pre", ".$CLASS_BOTTOM code", ".$CLASS_TOP code")
|
||||
enableInlineCodeBackground = (DocumentationSettings.isCodeBackgroundEnabled()
|
||||
&& DocumentationSettings.getInlineCodeHighlightingMode() !== InlineCodeHighlightingMode.NO_HIGHLIGHTING)
|
||||
enableCodeBlocksBackground = DocumentationSettings.isCodeBackgroundEnabled()
|
||||
&& DocumentationSettings.isHighlightingOfCodeBlocksEnabled(),
|
||||
useFontLigaturesInCode = false,
|
||||
controlStyleOverrides = if (editorInlineContext)
|
||||
ControlStyleOverrides(
|
||||
controlKindSuffix = "EditorPane",
|
||||
overrides = mapOf(
|
||||
ControlKind.CodeBlock to listOf(ControlProperty.BackgroundColor, ControlProperty.BackgroundOpacity, ControlProperty.BorderColor)
|
||||
)
|
||||
)
|
||||
else null
|
||||
)
|
||||
&& DocumentationSettings.isHighlightingOfCodeBlocksEnabled()
|
||||
if (editorInlineContext)
|
||||
overrideElementStyle {
|
||||
elementKindThemePropertySuffix = "EditorPane"
|
||||
overrideThemeProperties(ElementKind.CodeBlock, ElementProperty.BackgroundColor, ElementProperty.BackgroundOpacity, ElementProperty.BorderColor)
|
||||
}
|
||||
}
|
||||
|
||||
private fun StringBuilder.appendHighlightedCode(project: Project, language: Language?, doHighlighting: Boolean,
|
||||
code: CharSequence, isForRenderedDoc: Boolean, trim: Boolean): StringBuilder {
|
||||
|
||||
@@ -15,7 +15,6 @@ import org.jetbrains.annotations.Nls
|
||||
import org.jsoup.Jsoup
|
||||
import java.awt.Color
|
||||
import java.io.IOException
|
||||
import java.util.*
|
||||
import javax.swing.JEditorPane
|
||||
|
||||
/**
|
||||
@@ -25,12 +24,9 @@ import javax.swing.JEditorPane
|
||||
*/
|
||||
open class DescriptionEditorPane : JBHtmlPane(
|
||||
JBHtmlPaneStyleConfiguration(),
|
||||
JBHtmlPaneConfiguration(
|
||||
emptyMap(), { null }, { null },
|
||||
{ StyleSheetUtil.loadStyleSheet("pre {white-space: pre-wrap;} code, pre, a {overflow-wrap: anywhere;}") },
|
||||
null, Collections.emptyList()
|
||||
)
|
||||
) {
|
||||
JBHtmlPaneConfiguration {
|
||||
customStyleSheetProvider { StyleSheetUtil.loadStyleSheet("pre {white-space: pre-wrap;} code, pre, a {overflow-wrap: anywhere;}") }
|
||||
}) {
|
||||
|
||||
init {
|
||||
isEditable = false
|
||||
|
||||
@@ -36,6 +36,5 @@
|
||||
<orderEntry type="library" name="caffeine" level="project" />
|
||||
<orderEntry type="module" module-name="intellij.platform.ide.progress" exported="" />
|
||||
<orderEntry type="module" module-name="intellij.platform.util.diff" />
|
||||
<orderEntry type="library" name="jsoup" level="project" />
|
||||
</component>
|
||||
</module>
|
||||
@@ -1,16 +1,10 @@
|
||||
// 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.ui.components
|
||||
|
||||
import com.intellij.ide.ui.text.ShortcutsRenderingUtil
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.editor.impl.EditorCssFontResolver
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.ui.components.JBHtmlPaneStyleSheetRulesProvider.buildCodeBlock
|
||||
import com.intellij.ui.components.JBHtmlPaneStyleSheetRulesProvider.getStyleSheet
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.asSafely
|
||||
import com.intellij.util.containers.CollectionFactory
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.editor.colors.EditorColorsScheme
|
||||
import com.intellij.util.containers.addAllIfNotNull
|
||||
import com.intellij.util.ui.*
|
||||
import com.intellij.util.ui.ExtendableHTMLViewFactory.Extensions.icons
|
||||
@@ -20,13 +14,9 @@ import com.intellij.util.ui.html.cssPadding
|
||||
import com.intellij.util.ui.html.width
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import org.jetbrains.annotations.ApiStatus.Experimental
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.nodes.Node
|
||||
import org.jsoup.nodes.TextNode
|
||||
import org.jsoup.select.NodeVisitor
|
||||
import java.awt.AWTEvent
|
||||
import java.awt.Color
|
||||
import java.awt.Graphics
|
||||
@@ -123,7 +113,7 @@ open class JBHtmlPane(
|
||||
private val myPaneConfiguration: JBHtmlPaneConfiguration
|
||||
) : JEditorPane(), Disposable {
|
||||
|
||||
|
||||
private val service: ImplService = ApplicationManager.getApplication().service()
|
||||
private var myText: @Nls String = "" // getText() surprisingly crashes…, let's cache the text
|
||||
private var myCurrentDefaultStyleSheet: StyleSheet? = null
|
||||
private val mutableBackgroundFlow: MutableStateFlow<Color>
|
||||
@@ -158,7 +148,7 @@ open class JBHtmlPane(
|
||||
|
||||
val editorKit = HTMLEditorKitBuilder()
|
||||
.replaceViewFactoryExtensions(*extensions.toTypedArray())
|
||||
.withFontResolver(myPaneConfiguration.fontResolver ?: EditorCssFontResolver.getGlobalInstance())
|
||||
.withFontResolver(myPaneConfiguration.fontResolver ?: service.defaultEditorCssFontResolver())
|
||||
.build()
|
||||
updateDocumentationPaneDefaultCssRules(editorKit)
|
||||
|
||||
@@ -183,7 +173,7 @@ open class JBHtmlPane(
|
||||
}
|
||||
|
||||
override fun setText(t: @Nls String?) {
|
||||
myText = t?.let { transpileHtmlPaneInput(it) } ?: ""
|
||||
myText = t?.let { service.transpileHtmlPaneInput(it) } ?: ""
|
||||
super.setText(myText)
|
||||
}
|
||||
|
||||
@@ -201,8 +191,8 @@ open class JBHtmlPane(
|
||||
val newStyleSheet = StyleSheet()
|
||||
.also { myCurrentDefaultStyleSheet = it }
|
||||
val background = background
|
||||
newStyleSheet.addStyleSheet(getStyleSheet(background, myStyleConfiguration))
|
||||
newStyleSheet.addStyleSheet(EditorColorsSchemeStyleProvider(myStyleConfiguration.colorScheme))
|
||||
newStyleSheet.addStyleSheet(service.getDefaultStyleSheet(background, myStyleConfiguration))
|
||||
newStyleSheet.addStyleSheet(service.getEditorColorsSchemeStyleSheet(myStyleConfiguration.colorScheme))
|
||||
myPaneConfiguration.customStyleSheetProvider(background)?.let {
|
||||
newStyleSheet.addStyleSheet(it)
|
||||
}
|
||||
@@ -260,196 +250,17 @@ open class JBHtmlPane(
|
||||
return null
|
||||
}
|
||||
|
||||
/**
|
||||
* Transpiler pane input to fit to limited AWT HTML toolkit support.
|
||||
*/
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
private fun transpileHtmlPaneInput(text: @Nls String): @Nls String {
|
||||
val document = Jsoup.parse(text)
|
||||
document.traverse(NodeVisitor { node, _ ->
|
||||
when (node) {
|
||||
is TextNode -> {
|
||||
transpileTextNode(node)
|
||||
}
|
||||
is Element -> {
|
||||
when {
|
||||
node.nameIs("p") -> transpileParagraph(node)
|
||||
node.nameIs("shortcut") -> transpileShortcut(node)
|
||||
node.nameIs("blockquote") -> transpileBlockquote(node)
|
||||
node.nameIs("pre") -> transpilePre(node)
|
||||
node.nameIs("icon") -> transpileIcon(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
document.outputSettings().prettyPrint(false)
|
||||
return document.html()
|
||||
@ApiStatus.Internal
|
||||
interface ImplService {
|
||||
|
||||
fun transpileHtmlPaneInput(text: @Nls String): @Nls String
|
||||
|
||||
fun defaultEditorCssFontResolver(): CSSFontResolver
|
||||
|
||||
fun getDefaultStyleSheet(paneBackgroundColor: Color, configuration: JBHtmlPaneStyleConfiguration): StyleSheet
|
||||
|
||||
fun getEditorColorsSchemeStyleSheet(editorColorsScheme: EditorColorsScheme): StyleSheet
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove empty `<p>` before some tags - workaround for Swing html renderer not removing empty paragraphs before non-inline tags
|
||||
*/
|
||||
private fun transpileParagraph(node: Element) {
|
||||
if (node.childNodeSize() == 0
|
||||
|| (node.childNodeSize() == 1
|
||||
&& node.childNode(0).asSafely<TextNode>()?.wholeText?.isBlank() == true)
|
||||
&& node.nextElementSibling()?.let { dropPrecedingEmptyParagraphTags.contains(it.tagName()) } == true
|
||||
) {
|
||||
node.remove()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand `<shortcut raw|actionId="*"/>` tag into a sequence of `<kbd>` tags
|
||||
*/
|
||||
private fun transpileShortcut(node: Element) {
|
||||
val actionId = node.attributes().getIgnoreCase("actionid")
|
||||
.takeIf { it.isNotEmpty() }
|
||||
val raw = node.attributes().getIgnoreCase("raw")
|
||||
.takeIf { it.isNotEmpty() }
|
||||
|
||||
if (actionId != null || raw != null) {
|
||||
val shortcutData =
|
||||
if (actionId != null)
|
||||
ShortcutsRenderingUtil.getShortcutByActionId(actionId)
|
||||
?.let { ShortcutsRenderingUtil.getKeyboardShortcutData(it) }?.first
|
||||
?: ShortcutsRenderingUtil.getGotoActionData(actionId, false)
|
||||
.takeIf { ActionManager.getInstance().getAction(actionId) != null }
|
||||
?.first
|
||||
else
|
||||
KeyStroke.getKeyStroke(raw)
|
||||
?.let { ShortcutsRenderingUtil.getKeyStrokeData(it) }
|
||||
?.first
|
||||
if (shortcutData != null) {
|
||||
val replacement = shortcutData
|
||||
.splitToSequence(ShortcutsRenderingUtil.SHORTCUT_PART_SEPARATOR)
|
||||
.fold(mutableListOf<Node>()) { acc, s ->
|
||||
if (acc.isNotEmpty()) {
|
||||
acc.add(TextNode(StringUtil.NON_BREAK_SPACE))
|
||||
}
|
||||
acc.add(Element("kbd").text(s))
|
||||
acc
|
||||
}
|
||||
node.replaceWith(replacement)
|
||||
}
|
||||
else {
|
||||
node.replaceWith(Element("kbd").text(actionId ?: raw!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace `<pre><code>(...)</code></pre>` with [JBHtmlPaneStyleSheetRulesProvider.buildCodeBlock]
|
||||
*/
|
||||
private fun transpilePre(node: Element) {
|
||||
if (node.childNodeSize() != 1) return
|
||||
val childNodes =
|
||||
node.childNode(0)
|
||||
.asSafely<Element>()
|
||||
?.takeIf { it.nameIs("code") }
|
||||
?.childNodes()
|
||||
?: return
|
||||
node.replaceWith(buildCodeBlock(childNodes))
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace `<blockquote>\\s*<pre>(...)</pre>\\s*</blockquote>` with [JBHtmlPaneStyleSheetRulesProvider.buildCodeBlock]
|
||||
*/
|
||||
private fun transpileBlockquote(node: Element) {
|
||||
if (node.childNodeSize() > 3 || node.childNodeSize() < 1) return
|
||||
val nodes = node.childNodes()
|
||||
val wsNode1 = nodes.getOrNull(0).asSafely<TextNode>()
|
||||
val preNode = nodes.getOrNull(if (wsNode1 == null) 0 else 1).asSafely<Element>()
|
||||
?: return
|
||||
val wsNode2 = nodes.getOrNull(if (wsNode1 == null) 1 else 2)
|
||||
|
||||
if (wsNode1?.wholeText.isNullOrBlank()
|
||||
&& preNode.nameIs("pre")
|
||||
&& wsNode2.let { it == null || it is TextNode && it.wholeText.isBlank() }) {
|
||||
|
||||
val preNodes = preNode.childNodes()
|
||||
preNodes.getOrNull(0)?.asSafely<TextNode>()?.let {
|
||||
it.text(it.wholeText.trim('\n', '\r'))
|
||||
}
|
||||
preNodes.lastOrNull()?.asSafely<TextNode>()?.let {
|
||||
it.text(it.wholeText.trimEnd())
|
||||
}
|
||||
node.replaceWith(buildCodeBlock(preNodes))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move icon children to parent node
|
||||
*/
|
||||
private fun transpileIcon(node: Element) {
|
||||
node.parent()
|
||||
?.insertChildren(node.siblingIndex() + 1, node.childNodes())
|
||||
}
|
||||
|
||||
/**
|
||||
* - Add `<wbr>` after `.` if surrounded by letters
|
||||
* - Add `<wbr>` after `]`, `)` or `/` followed by a char or digit
|
||||
*/
|
||||
private fun transpileTextNode(node: TextNode) {
|
||||
val builder = StringBuilder()
|
||||
|
||||
val text = node.wholeText
|
||||
val codePoints = text.codePoints().iterator()
|
||||
if (!codePoints.hasNext()) return
|
||||
var codePoint = codePoints.nextInt()
|
||||
|
||||
fun next() {
|
||||
builder.appendCodePoint(codePoint)
|
||||
codePoint = if (codePoints.hasNext())
|
||||
codePoints.nextInt()
|
||||
else
|
||||
-1
|
||||
}
|
||||
|
||||
val replacement = SmartList<Node>()
|
||||
|
||||
while (codePoint >= 0) {
|
||||
when {
|
||||
// break after dot if surrounded by letters
|
||||
Character.isLetter(codePoint) -> {
|
||||
next()
|
||||
if (codePoint == '.'.code) {
|
||||
next()
|
||||
if (Character.isLetter(codePoint)) {
|
||||
replacement.add(TextNode(builder.toString()))
|
||||
replacement.add(Element("wbr"))
|
||||
builder.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
// break after ], ) or / followed by a char or digit
|
||||
codePoint == ')'.code || codePoint == ']'.code || codePoint == '/'.code -> {
|
||||
next()
|
||||
if (Character.isLetterOrDigit(codePoint)) {
|
||||
replacement.add(TextNode(builder.toString()))
|
||||
replacement.add(Element("wbr"))
|
||||
builder.clear()
|
||||
}
|
||||
}
|
||||
else -> next()
|
||||
}
|
||||
}
|
||||
if (!replacement.isEmpty()) {
|
||||
replacement.add(TextNode(builder.toString()))
|
||||
node.replaceWith(replacement)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Node.replaceWith(nodes: List<Node>) {
|
||||
val parent = parent() as? Element ?: return
|
||||
parent.insertChildren(siblingIndex(), nodes)
|
||||
remove()
|
||||
}
|
||||
|
||||
companion object {
|
||||
private val dropPrecedingEmptyParagraphTags = CollectionFactory.createCharSequenceSet(false).also {
|
||||
it.addAll(listOf("ul", "ol", "dl", "h1", "h2", "h3", "h4", "h5", "h6", "p", "tr", "td",
|
||||
"table", "pre", "blockquote", "div"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,54 @@ import javax.swing.KeyStroke
|
||||
import javax.swing.text.html.StyleSheet
|
||||
|
||||
@Experimental
|
||||
data class JBHtmlPaneConfiguration(
|
||||
val keyboardActions: Map<KeyStroke, ActionListener> = emptyMap(),
|
||||
val imageResolverFactory: (JBHtmlPane) -> Dictionary<URL, Image>? = { null },
|
||||
val iconResolver: (String) -> Icon? = { null },
|
||||
val customStyleSheetProvider: (backgroundColor: Color) -> StyleSheet? = { null },
|
||||
val fontResolver: CSSFontResolver? = null,
|
||||
val extensions: List<ExtendableHTMLViewFactory.Extension> = emptyList()
|
||||
)
|
||||
class JBHtmlPaneConfiguration private constructor(builder: Builder) {
|
||||
val keyboardActions: Map<KeyStroke, ActionListener> = builder.keyboardActions
|
||||
val imageResolverFactory: (JBHtmlPane) -> Dictionary<URL, Image>? = builder.imageResolverFactory
|
||||
val iconResolver: (String) -> Icon? = builder.iconResolver
|
||||
val customStyleSheetProvider: (backgroundColor: Color) -> StyleSheet? = builder.customStyleSheetProvider
|
||||
val fontResolver: CSSFontResolver? = builder.fontResolver
|
||||
val extensions: List<ExtendableHTMLViewFactory.Extension> = builder.extensions
|
||||
|
||||
constructor() : this(builder())
|
||||
|
||||
constructor(configure: Builder.() -> Unit) : this(builder().also { configure(it) })
|
||||
|
||||
class Builder {
|
||||
var keyboardActions: Map<KeyStroke, ActionListener> = emptyMap()
|
||||
var imageResolverFactory: (JBHtmlPane) -> Dictionary<URL, Image>? = { null }
|
||||
var iconResolver: (String) -> Icon? = { null }
|
||||
var customStyleSheetProvider: (backgroundColor: Color) -> StyleSheet? = { null }
|
||||
var fontResolver: CSSFontResolver? = null
|
||||
var extensions: List<ExtendableHTMLViewFactory.Extension> = emptyList()
|
||||
|
||||
fun build(): JBHtmlPaneConfiguration = JBHtmlPaneConfiguration(this)
|
||||
|
||||
fun keyboardActions(keyboardActions: Map<KeyStroke, ActionListener>): Builder =
|
||||
apply { this.keyboardActions = keyboardActions }
|
||||
|
||||
fun imageResolverFactory(imageResolverFactory: (JBHtmlPane) -> Dictionary<URL, Image>?): Builder =
|
||||
apply { this.imageResolverFactory = imageResolverFactory }
|
||||
|
||||
fun iconResolver(iconResolver: (String) -> Icon?): Builder =
|
||||
apply { this.iconResolver = iconResolver }
|
||||
|
||||
fun customStyleSheetProvider(customStyleSheetProvider: (backgroundColor: Color) -> StyleSheet?): Builder =
|
||||
apply { this.customStyleSheetProvider = customStyleSheetProvider }
|
||||
|
||||
fun fontResolver(fontResolver: CSSFontResolver?): Builder =
|
||||
apply { this.fontResolver = fontResolver }
|
||||
|
||||
fun extensions(extensions: List<ExtendableHTMLViewFactory.Extension>): Builder =
|
||||
apply { this.extensions = extensions }
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun builder(): Builder =
|
||||
Builder()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -9,20 +9,26 @@ import org.jetbrains.annotations.ApiStatus.Experimental
|
||||
import java.util.*
|
||||
|
||||
@Experimental
|
||||
data class JBHtmlPaneStyleConfiguration(
|
||||
val colorScheme: EditorColorsScheme = EditorColorsManager.getInstance().globalScheme,
|
||||
val editorInlineContext: Boolean = false,
|
||||
val inlineCodeParentSelectors: List<String> = listOf(""),
|
||||
val largeCodeFontSizeSelectors: List<String> = emptyList(),
|
||||
val enableInlineCodeBackground: Boolean = true,
|
||||
val enableCodeBlocksBackground: Boolean = true,
|
||||
val useFontLigaturesInCode: Boolean = false,
|
||||
/** Unscaled */
|
||||
val spaceBeforeParagraph: Int = defaultSpaceBeforeParagraph,
|
||||
/** Unscaled */
|
||||
val spaceAfterParagraph: Int = defaultSpaceAfterParagraph,
|
||||
val controlStyleOverrides: ControlStyleOverrides? = null,
|
||||
) {
|
||||
class JBHtmlPaneStyleConfiguration private constructor(builder: Builder) {
|
||||
val colorScheme: EditorColorsScheme = builder.colorScheme
|
||||
val editorInlineContext: Boolean = builder.editorInlineContext
|
||||
val inlineCodeParentSelectors: List<String> = builder.inlineCodeParentSelectors
|
||||
val largeCodeFontSizeSelectors: List<String> = builder.largeCodeFontSizeSelectors
|
||||
val enableInlineCodeBackground: Boolean = builder.enableInlineCodeBackground
|
||||
val enableCodeBlocksBackground: Boolean = builder.enableCodeBlocksBackground
|
||||
val useFontLigaturesInCode: Boolean = builder.useFontLigaturesInCode
|
||||
|
||||
/** unscaled */
|
||||
val spaceBeforeParagraph: Int = builder.spaceBeforeParagraph
|
||||
|
||||
/** unscaled */
|
||||
val spaceAfterParagraph: Int = builder.spaceAfterParagraph
|
||||
val elementStyleOverrides: ElementStyleOverrides? = builder.elementStyleOverrides
|
||||
|
||||
constructor() : this(builder())
|
||||
|
||||
constructor(configure: Builder.() -> Unit) : this(builder().also { configure(it) })
|
||||
|
||||
override fun equals(other: Any?): Boolean =
|
||||
other is JBHtmlPaneStyleConfiguration
|
||||
&& colorSchemesEqual(colorScheme, other.colorScheme)
|
||||
@@ -38,12 +44,11 @@ data class JBHtmlPaneStyleConfiguration(
|
||||
// Update here when more colors are used from the colorScheme
|
||||
colorScheme.defaultBackground.rgb == colorScheme2.defaultBackground.rgb
|
||||
&& colorScheme.defaultForeground.rgb == colorScheme2.defaultForeground.rgb
|
||||
&& ControlKind.entries.all {
|
||||
&& ElementKind.entries.all {
|
||||
colorScheme.getAttributes(it.colorSchemeKey, false) ==
|
||||
colorScheme2.getAttributes(it.colorSchemeKey, false)
|
||||
}
|
||||
|
||||
|
||||
override fun hashCode(): Int =
|
||||
Objects.hash(colorScheme.defaultBackground.rgb and 0xffffff,
|
||||
colorScheme.defaultForeground.rgb and 0xffffff,
|
||||
@@ -51,18 +56,51 @@ data class JBHtmlPaneStyleConfiguration(
|
||||
enableInlineCodeBackground, enableCodeBlocksBackground,
|
||||
useFontLigaturesInCode, spaceBeforeParagraph, spaceAfterParagraph)
|
||||
|
||||
data class ControlStyleOverrides(
|
||||
val controlKindSuffix: String,
|
||||
val overrides: Map<ControlKind, Collection<ControlProperty>>
|
||||
)
|
||||
class ElementStyleOverrides(builder: Builder) {
|
||||
val elementKindThemePropertySuffix: String = builder.elementKindThemePropertySuffix?.takeUnless { it.isBlank() }
|
||||
?: throw IllegalStateException("elementKindThemePropertySuffix must not be null or blank")
|
||||
val overrides: Map<ElementKind, Collection<ElementProperty>> = builder.overrides.mapValues { it.value.toList() }
|
||||
|
||||
enum class ControlKind(val id: String, val colorSchemeKey: TextAttributesKey) {
|
||||
override fun equals(other: Any?): Boolean =
|
||||
other is ElementStyleOverrides
|
||||
&& other.elementKindThemePropertySuffix == elementKindThemePropertySuffix
|
||||
&& other.overrides == overrides
|
||||
|
||||
override fun hashCode(): Int =
|
||||
Objects.hash(elementKindThemePropertySuffix, overrides)
|
||||
|
||||
class Builder {
|
||||
|
||||
var elementKindThemePropertySuffix: String? = null
|
||||
|
||||
internal val overrides: MutableMap<ElementKind, MutableCollection<ElementProperty>> = mutableMapOf()
|
||||
|
||||
fun overrideThemeProperties(elementKind: ElementKind, vararg properties: ElementProperty): Builder =
|
||||
apply { overrides.getOrPut(elementKind) { mutableListOf() } += properties }
|
||||
|
||||
fun elementKindThemePropertySuffix(elementKindThemePropertySuffix: String): Builder =
|
||||
apply { this.elementKindThemePropertySuffix = elementKindThemePropertySuffix }
|
||||
|
||||
fun build(): ElementStyleOverrides =
|
||||
ElementStyleOverrides(this)
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
@JvmStatic
|
||||
fun builder(): Builder =
|
||||
Builder()
|
||||
}
|
||||
}
|
||||
|
||||
enum class ElementKind(val id: String, val colorSchemeKey: TextAttributesKey) {
|
||||
CodeInline("Code.Inline", DefaultLanguageHighlighterColors.DOC_CODE_INLINE),
|
||||
CodeBlock("Code.Block", DefaultLanguageHighlighterColors.DOC_CODE_BLOCK),
|
||||
Shortcut("Shortcut", DefaultLanguageHighlighterColors.DOC_TIPS_SHORTCUT),
|
||||
}
|
||||
|
||||
enum class ControlProperty(val id: String) {
|
||||
enum class ElementProperty(val id: String) {
|
||||
BackgroundColor("backgroundColor"),
|
||||
ForegroundColor("foregroundColor"),
|
||||
BorderColor("borderColor"),
|
||||
@@ -71,6 +109,59 @@ data class JBHtmlPaneStyleConfiguration(
|
||||
BorderRadius("borderRadius"),
|
||||
}
|
||||
|
||||
class Builder {
|
||||
var colorScheme: EditorColorsScheme = EditorColorsManager.getInstance().globalScheme
|
||||
var editorInlineContext: Boolean = false
|
||||
var inlineCodeParentSelectors: List<String> = listOf("")
|
||||
var largeCodeFontSizeSelectors: List<String> = emptyList()
|
||||
var enableInlineCodeBackground: Boolean = true
|
||||
var enableCodeBlocksBackground: Boolean = true
|
||||
var useFontLigaturesInCode: Boolean = false
|
||||
|
||||
/** unscaled */
|
||||
var spaceBeforeParagraph: Int = defaultSpaceBeforeParagraph
|
||||
|
||||
/** Unscaled */
|
||||
var spaceAfterParagraph: Int = defaultSpaceAfterParagraph
|
||||
var elementStyleOverrides: ElementStyleOverrides? = null
|
||||
|
||||
fun build(): JBHtmlPaneStyleConfiguration = JBHtmlPaneStyleConfiguration(this)
|
||||
|
||||
fun colorScheme(colorScheme: EditorColorsScheme): Builder =
|
||||
apply { this.colorScheme = colorScheme }
|
||||
|
||||
fun editorInlineContext(editorInlineContext: Boolean): Builder =
|
||||
apply { this.editorInlineContext = editorInlineContext }
|
||||
|
||||
fun inlineCodeParentSelectors(inlineCodeParentSelectors: List<String>): Builder =
|
||||
apply { this.inlineCodeParentSelectors = inlineCodeParentSelectors }
|
||||
|
||||
fun largeCodeFontSizeSelectors(largeCodeFontSizeSelectors: List<String>): Builder =
|
||||
apply { this.largeCodeFontSizeSelectors = largeCodeFontSizeSelectors }
|
||||
|
||||
fun enableInlineCodeBackground(enableInlineCodeBackground: Boolean): Builder =
|
||||
apply { this.enableInlineCodeBackground = enableInlineCodeBackground }
|
||||
|
||||
fun enableCodeBlocksBackground(enableCodeBlocksBackground: Boolean): Builder =
|
||||
apply { this.enableCodeBlocksBackground = enableCodeBlocksBackground }
|
||||
|
||||
fun useFontLigaturesInCode(useFontLigaturesInCode: Boolean): Builder =
|
||||
apply { this.useFontLigaturesInCode = useFontLigaturesInCode }
|
||||
|
||||
fun spaceBeforeParagraph(spaceBeforeParagraph: Int): Builder =
|
||||
apply { this.spaceBeforeParagraph = spaceBeforeParagraph }
|
||||
|
||||
fun spaceAfterParagraph(spaceAfterParagraph: Int): Builder =
|
||||
apply { this.spaceAfterParagraph = spaceAfterParagraph }
|
||||
|
||||
fun overrideElementStyle(elementStyleOverrides: ElementStyleOverrides): Builder =
|
||||
apply { this.elementStyleOverrides = elementStyleOverrides }
|
||||
|
||||
fun overrideElementStyle(configuration: ElementStyleOverrides.Builder.() -> Unit): Builder =
|
||||
apply { this.elementStyleOverrides = ElementStyleOverrides.builder().also(configuration).build() }
|
||||
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
val defaultSpaceBeforeParagraph: Int get() = 4
|
||||
@@ -80,6 +171,11 @@ data class JBHtmlPaneStyleConfiguration(
|
||||
|
||||
@JvmStatic
|
||||
val editorColorClassPrefix: String = "editor-color-"
|
||||
|
||||
@JvmStatic
|
||||
fun builder(): Builder =
|
||||
Builder()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -41,7 +41,6 @@ import javax.swing.border.Border;
|
||||
import javax.swing.tree.TreePath;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.util.Collections;
|
||||
|
||||
// Android team doesn't want to use new mockito for now, so, class cannot be final
|
||||
public class IdeTooltipManager implements Disposable {
|
||||
@@ -713,11 +712,9 @@ public class IdeTooltipManager implements Disposable {
|
||||
boolean limitWidthToScreen) {
|
||||
|
||||
JBHtmlPaneStyleConfiguration styleConfiguration = new JBHtmlPaneStyleConfiguration();
|
||||
JBHtmlPaneConfiguration paneConfiguration = new JBHtmlPaneConfiguration(
|
||||
Collections.emptyMap(), url -> null, icon -> null,
|
||||
color -> StyleSheetUtil.loadStyleSheet("pre {white-space: pre-wrap;} code, pre {overflow-wrap: anywhere;}"),
|
||||
null, Collections.emptyList()
|
||||
);
|
||||
JBHtmlPaneConfiguration paneConfiguration = JBHtmlPaneConfiguration.builder()
|
||||
.customStyleSheetProvider(color -> StyleSheetUtil.loadStyleSheet("pre {white-space: pre-wrap;} code, pre {overflow-wrap: anywhere;}"))
|
||||
.build();
|
||||
|
||||
|
||||
Ref<Boolean> prefSizeWasComputed = new Ref<>(false);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.ui.components
|
||||
package com.intellij.ui.components.impl
|
||||
|
||||
import com.intellij.openapi.editor.colors.EditorColorsScheme
|
||||
import com.intellij.openapi.editor.colors.TextAttributesKey
|
||||
@@ -16,7 +16,7 @@ import javax.swing.text.html.CSS
|
||||
import javax.swing.text.html.HTML
|
||||
import javax.swing.text.html.StyleSheet
|
||||
|
||||
internal class EditorColorsSchemeStyleProvider(private val editorColorsScheme: EditorColorsScheme) : StyleSheet() {
|
||||
internal class EditorColorsSchemeStyleSheet(private val editorColorsScheme: EditorColorsScheme) : StyleSheet() {
|
||||
|
||||
private val computedStyles = mutableMapOf<String, Style>()
|
||||
|
||||
@@ -113,10 +113,6 @@ internal class EditorColorsSchemeStyleProvider(private val editorColorsScheme: E
|
||||
ColorValueConstructor.newInstance()
|
||||
.also { ColorValueColorField.set(it, color) }
|
||||
|
||||
private fun stringValue(value: String): Any =
|
||||
StringValueConstructor.newInstance()
|
||||
.also { CssCssValueSvalueField.set(it, value) }
|
||||
|
||||
private fun lengthValue(value: Float): Any =
|
||||
LengthValueConstructor.newInstance()
|
||||
.also { LengthValueSpanField.set(it, value) }
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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.ui.components.impl
|
||||
|
||||
import com.intellij.openapi.editor.colors.EditorColorsScheme
|
||||
import com.intellij.openapi.editor.impl.EditorCssFontResolver
|
||||
import com.intellij.ui.components.JBHtmlPane
|
||||
import com.intellij.ui.components.JBHtmlPaneStyleConfiguration
|
||||
import com.intellij.util.ui.CSSFontResolver
|
||||
import org.jetbrains.annotations.Nls
|
||||
import java.awt.Color
|
||||
import javax.swing.text.html.StyleSheet
|
||||
|
||||
internal class JBHtmlPaneImplService: JBHtmlPane.ImplService {
|
||||
|
||||
override fun transpileHtmlPaneInput(@Nls text: String): @Nls String =
|
||||
JBHtmlPaneInputTranspiler.transpileHtmlPaneInput(text)
|
||||
|
||||
override fun defaultEditorCssFontResolver(): CSSFontResolver =
|
||||
EditorCssFontResolver.getGlobalInstance()
|
||||
|
||||
override fun getDefaultStyleSheet(paneBackgroundColor: Color, configuration: JBHtmlPaneStyleConfiguration): StyleSheet =
|
||||
JBHtmlPaneStyleSheetRulesProvider.getStyleSheet(paneBackgroundColor, configuration)
|
||||
|
||||
override fun getEditorColorsSchemeStyleSheet(editorColorsScheme: EditorColorsScheme): StyleSheet =
|
||||
EditorColorsSchemeStyleSheet(editorColorsScheme)
|
||||
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
// 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.ui.components.impl
|
||||
|
||||
import com.intellij.ide.ui.text.ShortcutsRenderingUtil
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.util.text.StringUtil
|
||||
import com.intellij.ui.components.impl.JBHtmlPaneStyleSheetRulesProvider.buildCodeBlock
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.asSafely
|
||||
import com.intellij.util.containers.CollectionFactory
|
||||
import org.jetbrains.annotations.Nls
|
||||
import org.jsoup.Jsoup
|
||||
import org.jsoup.nodes.Element
|
||||
import org.jsoup.nodes.Node
|
||||
import org.jsoup.nodes.TextNode
|
||||
import org.jsoup.select.NodeVisitor
|
||||
import javax.swing.KeyStroke
|
||||
|
||||
internal object JBHtmlPaneInputTranspiler {
|
||||
|
||||
private val dropPrecedingEmptyParagraphTags = CollectionFactory.createCharSequenceSet(false).also {
|
||||
it.addAll(listOf("ul", "ol", "dl", "h1", "h2", "h3", "h4", "h5", "h6", "p", "tr", "td",
|
||||
"table", "pre", "blockquote", "div"))
|
||||
}
|
||||
|
||||
/**
|
||||
* Transpiler pane input to fit to limited AWT HTML toolkit support.
|
||||
*/
|
||||
@Suppress("HardCodedStringLiteral")
|
||||
fun transpileHtmlPaneInput(text: @Nls String): @Nls String {
|
||||
val document = Jsoup.parse(text)
|
||||
document.traverse(NodeVisitor { node, _ ->
|
||||
when (node) {
|
||||
is TextNode -> {
|
||||
transpileTextNode(node)
|
||||
}
|
||||
is Element -> {
|
||||
when {
|
||||
node.nameIs("p") -> transpileParagraph(node)
|
||||
node.nameIs("shortcut") -> transpileShortcut(node)
|
||||
node.nameIs("blockquote") -> transpileBlockquote(node)
|
||||
node.nameIs("pre") -> transpilePre(node)
|
||||
node.nameIs("icon") -> transpileIcon(node)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
document.outputSettings().prettyPrint(false)
|
||||
return document.html()
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove empty `<p>` before some tags - workaround for Swing html renderer not removing empty paragraphs before non-inline tags
|
||||
*/
|
||||
private fun transpileParagraph(node: Element) {
|
||||
if (node.childNodeSize() == 0
|
||||
|| (node.childNodeSize() == 1
|
||||
&& node.childNode(0).asSafely<TextNode>()?.wholeText?.isBlank() == true)
|
||||
&& node.nextElementSibling()?.let { dropPrecedingEmptyParagraphTags.contains(it.tagName()) } == true
|
||||
) {
|
||||
node.remove()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expand `<shortcut raw|actionId="*"/>` tag into a sequence of `<kbd>` tags
|
||||
*/
|
||||
private fun transpileShortcut(node: Element) {
|
||||
val actionId = node.attributes().getIgnoreCase("actionid")
|
||||
.takeIf { it.isNotEmpty() }
|
||||
val raw = node.attributes().getIgnoreCase("raw")
|
||||
.takeIf { it.isNotEmpty() }
|
||||
|
||||
if (actionId != null || raw != null) {
|
||||
val shortcutData =
|
||||
if (actionId != null)
|
||||
ShortcutsRenderingUtil.getShortcutByActionId(actionId)
|
||||
?.let { ShortcutsRenderingUtil.getKeyboardShortcutData(it) }?.first
|
||||
?: ShortcutsRenderingUtil.getGotoActionData(actionId, false)
|
||||
.takeIf { ActionManager.getInstance().getAction(actionId) != null }
|
||||
?.first
|
||||
else
|
||||
KeyStroke.getKeyStroke(raw)
|
||||
?.let { ShortcutsRenderingUtil.getKeyStrokeData(it) }
|
||||
?.first
|
||||
if (shortcutData != null) {
|
||||
val replacement = shortcutData
|
||||
.splitToSequence(ShortcutsRenderingUtil.SHORTCUT_PART_SEPARATOR)
|
||||
.fold(mutableListOf<Node>()) { acc, s ->
|
||||
if (acc.isNotEmpty()) {
|
||||
acc.add(TextNode(StringUtil.NON_BREAK_SPACE))
|
||||
}
|
||||
acc.add(Element("kbd").text(s))
|
||||
acc
|
||||
}
|
||||
node.replaceWith(replacement)
|
||||
}
|
||||
else {
|
||||
node.replaceWith(Element("kbd").text(actionId ?: raw!!))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace `<pre><code>(...)</code></pre>` with [JBHtmlPaneStyleSheetRulesProvider.buildCodeBlock]
|
||||
*/
|
||||
private fun transpilePre(node: Element) {
|
||||
if (node.childNodeSize() != 1) return
|
||||
val childNodes =
|
||||
node.childNode(0)
|
||||
.asSafely<Element>()
|
||||
?.takeIf { it.nameIs("code") }
|
||||
?.childNodes()
|
||||
?: return
|
||||
node.replaceWith(buildCodeBlock(childNodes))
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace `<blockquote>\\s*<pre>(...)</pre>\\s*</blockquote>` with [JBHtmlPaneStyleSheetRulesProvider.buildCodeBlock]
|
||||
*/
|
||||
private fun transpileBlockquote(node: Element) {
|
||||
if (node.childNodeSize() > 3 || node.childNodeSize() < 1) return
|
||||
val nodes = node.childNodes()
|
||||
val wsNode1 = nodes.getOrNull(0).asSafely<TextNode>()
|
||||
val preNode = nodes.getOrNull(if (wsNode1 == null) 0 else 1).asSafely<Element>()
|
||||
?: return
|
||||
val wsNode2 = nodes.getOrNull(if (wsNode1 == null) 1 else 2)
|
||||
|
||||
if (wsNode1?.wholeText.isNullOrBlank()
|
||||
&& preNode.nameIs("pre")
|
||||
&& wsNode2.let { it == null || it is TextNode && it.wholeText.isBlank() }) {
|
||||
|
||||
val preNodes = preNode.childNodes()
|
||||
preNodes.getOrNull(0)?.asSafely<TextNode>()?.let {
|
||||
it.text(it.wholeText.trim('\n', '\r'))
|
||||
}
|
||||
preNodes.lastOrNull()?.asSafely<TextNode>()?.let {
|
||||
it.text(it.wholeText.trimEnd())
|
||||
}
|
||||
node.replaceWith(buildCodeBlock(preNodes))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move icon children to parent node
|
||||
*/
|
||||
private fun transpileIcon(node: Element) {
|
||||
node.parent()
|
||||
?.insertChildren(node.siblingIndex() + 1, node.childNodes())
|
||||
}
|
||||
|
||||
/**
|
||||
* - Add `<wbr>` after `.` if surrounded by letters
|
||||
* - Add `<wbr>` after `]`, `)` or `/` followed by a char or digit
|
||||
*/
|
||||
private fun transpileTextNode(node: TextNode) {
|
||||
val builder = StringBuilder()
|
||||
|
||||
val text = node.wholeText
|
||||
val codePoints = text.codePoints().iterator()
|
||||
if (!codePoints.hasNext()) return
|
||||
var codePoint = codePoints.nextInt()
|
||||
|
||||
fun next() {
|
||||
builder.appendCodePoint(codePoint)
|
||||
codePoint = if (codePoints.hasNext())
|
||||
codePoints.nextInt()
|
||||
else
|
||||
-1
|
||||
}
|
||||
|
||||
val replacement = SmartList<Node>()
|
||||
|
||||
while (codePoint >= 0) {
|
||||
when {
|
||||
// break after dot if surrounded by letters
|
||||
Character.isLetter(codePoint) -> {
|
||||
next()
|
||||
if (codePoint == '.'.code) {
|
||||
next()
|
||||
if (Character.isLetter(codePoint)) {
|
||||
replacement.add(TextNode(builder.toString()))
|
||||
replacement.add(Element("wbr"))
|
||||
builder.clear()
|
||||
}
|
||||
}
|
||||
}
|
||||
// break after ], ) or / followed by a char or digit
|
||||
codePoint == ')'.code || codePoint == ']'.code || codePoint == '/'.code -> {
|
||||
next()
|
||||
if (Character.isLetterOrDigit(codePoint)) {
|
||||
replacement.add(TextNode(builder.toString()))
|
||||
replacement.add(Element("wbr"))
|
||||
builder.clear()
|
||||
}
|
||||
}
|
||||
else -> next()
|
||||
}
|
||||
}
|
||||
if (!replacement.isEmpty()) {
|
||||
replacement.add(TextNode(builder.toString()))
|
||||
node.replaceWith(replacement)
|
||||
}
|
||||
}
|
||||
|
||||
private fun Node.replaceWith(nodes: List<Node>) {
|
||||
val parent = parent() as? Element ?: return
|
||||
parent.insertChildren(siblingIndex(), nodes)
|
||||
remove()
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
// 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.ui.components
|
||||
package com.intellij.ui.components.impl
|
||||
|
||||
import com.github.benmanes.caffeine.cache.Caffeine
|
||||
import com.github.benmanes.caffeine.cache.LoadingCache
|
||||
@@ -11,8 +11,9 @@ import com.intellij.openapi.editor.impl.EditorCssFontResolver.EDITOR_FONT_NAME_P
|
||||
import com.intellij.openapi.editor.markup.EffectType
|
||||
import com.intellij.ui.ColorUtil
|
||||
import com.intellij.ui.Gray
|
||||
import com.intellij.ui.components.JBHtmlPaneStyleConfiguration.ControlKind
|
||||
import com.intellij.ui.components.JBHtmlPaneStyleConfiguration.ControlProperty
|
||||
import com.intellij.ui.components.JBHtmlPaneStyleConfiguration
|
||||
import com.intellij.ui.components.JBHtmlPaneStyleConfiguration.ElementKind
|
||||
import com.intellij.ui.components.JBHtmlPaneStyleConfiguration.ElementProperty
|
||||
import com.intellij.ui.scale.JBUIScale.scale
|
||||
import com.intellij.util.containers.addAllIfNotNull
|
||||
import com.intellij.util.ui.StartupUiUtil
|
||||
@@ -46,7 +47,7 @@ internal object JBHtmlPaneStyleSheetRulesProvider {
|
||||
)
|
||||
|
||||
private val inlineCodeStyling = ControlColorStyleBuilder(
|
||||
ControlKind.CodeInline,
|
||||
ElementKind.CodeInline,
|
||||
defaultBackgroundColor = Color(0x5A5D6B),
|
||||
defaultBackgroundOpacity = 10,
|
||||
defaultBorderRadius = 10,
|
||||
@@ -54,7 +55,7 @@ internal object JBHtmlPaneStyleSheetRulesProvider {
|
||||
)
|
||||
|
||||
private val blockCodeStyling = ControlColorStyleBuilder(
|
||||
ControlKind.CodeBlock,
|
||||
ElementKind.CodeBlock,
|
||||
defaultBorderColor = Color(0xEBECF0),
|
||||
defaultBorderRadius = 10,
|
||||
defaultBorderWidth = 1,
|
||||
@@ -63,7 +64,7 @@ internal object JBHtmlPaneStyleSheetRulesProvider {
|
||||
)
|
||||
|
||||
private val shortcutStyling = ControlColorStyleBuilder(
|
||||
ControlKind.Shortcut,
|
||||
ElementKind.Shortcut,
|
||||
defaultBorderColor = Color(0xA8ADBD),
|
||||
defaultBorderRadius = 7,
|
||||
defaultBorderWidth = 1,
|
||||
@@ -199,7 +200,7 @@ internal object JBHtmlPaneStyleSheetRulesProvider {
|
||||
toHexString(color.rgb and 0xFFFFFF)
|
||||
|
||||
private data class ControlColorStyleBuilder(
|
||||
val controlKind: ControlKind,
|
||||
val elementKind: ElementKind,
|
||||
val defaultBackgroundColor: Color? = null,
|
||||
val defaultBackgroundOpacity: Int = 100,
|
||||
val defaultForegroundColor: Color? = null,
|
||||
@@ -211,23 +212,23 @@ internal object JBHtmlPaneStyleSheetRulesProvider {
|
||||
val fallbackToEditorBorder: Boolean = false
|
||||
) {
|
||||
|
||||
private fun getBackgroundColor(configuration: JBHtmlPaneStyleConfiguration): Color? = getColor(configuration, ControlProperty.BackgroundColor)
|
||||
private fun getBackgroundColor(configuration: JBHtmlPaneStyleConfiguration): Color? = getColor(configuration, ElementProperty.BackgroundColor)
|
||||
|
||||
private fun getForegroundColor(configuration: JBHtmlPaneStyleConfiguration): Color? = getColor(configuration, ControlProperty.ForegroundColor)
|
||||
private fun getForegroundColor(configuration: JBHtmlPaneStyleConfiguration): Color? = getColor(configuration, ElementProperty.ForegroundColor)
|
||||
|
||||
private fun getBorderColor(configuration: JBHtmlPaneStyleConfiguration): Color? = getColor(configuration, ControlProperty.BorderColor)
|
||||
private fun getBorderColor(configuration: JBHtmlPaneStyleConfiguration): Color? = getColor(configuration, ElementProperty.BorderColor)
|
||||
|
||||
private fun getBackgroundOpacity(configuration: JBHtmlPaneStyleConfiguration): Int? = getInt(configuration, ControlProperty.BackgroundOpacity)
|
||||
private fun getBackgroundOpacity(configuration: JBHtmlPaneStyleConfiguration): Int? = getInt(configuration, ElementProperty.BackgroundOpacity)
|
||||
|
||||
private fun getBorderWidth(configuration: JBHtmlPaneStyleConfiguration): Int? = getInt(configuration, ControlProperty.BorderWidth)
|
||||
private fun getBorderWidth(configuration: JBHtmlPaneStyleConfiguration): Int? = getInt(configuration, ElementProperty.BorderWidth)
|
||||
|
||||
private fun getBorderRadius(configuration: JBHtmlPaneStyleConfiguration): Int? = getInt(configuration, ControlProperty.BorderRadius)
|
||||
private fun getBorderRadius(configuration: JBHtmlPaneStyleConfiguration): Int? = getInt(configuration, ElementProperty.BorderRadius)
|
||||
|
||||
fun getCssStyle(editorPaneBackgroundColor: Color, configuration: JBHtmlPaneStyleConfiguration): String {
|
||||
val result = StringBuilder()
|
||||
|
||||
if (configuration.editorInlineContext) {
|
||||
val attributes = configuration.colorScheme.getAttributes(controlKind.colorSchemeKey, false)
|
||||
val attributes = configuration.colorScheme.getAttributes(elementKind.colorSchemeKey, false)
|
||||
if (attributes != null) {
|
||||
attributes.backgroundColor?.let { result.append("background-color: #${toHtmlColor(it)};") }
|
||||
attributes.foregroundColor?.let { result.append("color: #${toHtmlColor(it)};") }
|
||||
@@ -300,19 +301,19 @@ internal object JBHtmlPaneStyleSheetRulesProvider {
|
||||
)
|
||||
}
|
||||
|
||||
private fun getColor(configuration: JBHtmlPaneStyleConfiguration, property: ControlProperty): Color? =
|
||||
private fun getColor(configuration: JBHtmlPaneStyleConfiguration, property: ElementProperty): Color? =
|
||||
UIManager.getColor(getKey(configuration, property))
|
||||
|
||||
private fun getInt(configuration: JBHtmlPaneStyleConfiguration, property: ControlProperty): Int? =
|
||||
private fun getInt(configuration: JBHtmlPaneStyleConfiguration, property: ElementProperty): Int? =
|
||||
UIManager.get(getKey(configuration, property)) as Int?
|
||||
|
||||
private fun getKey(configuration: JBHtmlPaneStyleConfiguration, property: ControlProperty): String {
|
||||
val themeOverrides = configuration.controlStyleOverrides
|
||||
val suffix = if (themeOverrides != null && themeOverrides.overrides[controlKind]?.contains(property) == true) {
|
||||
"." + themeOverrides.controlKindSuffix
|
||||
private fun getKey(configuration: JBHtmlPaneStyleConfiguration, property: ElementProperty): String {
|
||||
val themeOverrides = configuration.elementStyleOverrides
|
||||
val suffix = if (themeOverrides != null && themeOverrides.overrides[elementKind]?.contains(property) == true) {
|
||||
"." + themeOverrides.elementKindThemePropertySuffix
|
||||
}
|
||||
else ""
|
||||
return "${controlKind.id}$suffix.${property.id}"
|
||||
return "${elementKind.id}$suffix.${property.id}"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -286,6 +286,8 @@
|
||||
serviceImplementation="com.intellij.ui.TreeUIHelperImpl"/>
|
||||
<applicationService serviceInterface="com.intellij.ui.ExpandableItemsHandlerFactory"
|
||||
serviceImplementation="com.intellij.ui.ExpandableItemsHandlerFactoryImpl"/>
|
||||
<applicationService serviceInterface="com.intellij.ui.components.JBHtmlPane$ImplService"
|
||||
serviceImplementation="com.intellij.ui.components.impl.JBHtmlPaneImplService"/>
|
||||
<applicationService
|
||||
serviceInterface="com.intellij.openapi.ui.messages.MessagesService"
|
||||
serviceImplementation="com.intellij.ui.messages.MessagesServiceImpl"/>
|
||||
|
||||
Reference in New Issue
Block a user