backport the intellij markdown renderer (Apache 2.0)
This commit is contained in:
parent
273d9f12f1
commit
0fb525ab67
10 changed files with 808 additions and 2 deletions
|
@ -13,6 +13,7 @@ plugins {
|
||||||
id("org.jetbrains.changelog") version("2.2.0")
|
id("org.jetbrains.changelog") version("2.2.0")
|
||||||
id("org.jetbrains.grammarkit") version("2022.3.2.2")
|
id("org.jetbrains.grammarkit") version("2022.3.2.2")
|
||||||
id("com.palantir.git-version") version("3.0.0")
|
id("com.palantir.git-version") version("3.0.0")
|
||||||
|
id("org.jetbrains.kotlin.jvm") version("1.9.22") //Only used by backport module
|
||||||
}
|
}
|
||||||
|
|
||||||
val gitVersion: groovy.lang.Closure<String> by extra
|
val gitVersion: groovy.lang.Closure<String> by extra
|
||||||
|
@ -74,6 +75,7 @@ allprojects {
|
||||||
apply {
|
apply {
|
||||||
plugin("org.jetbrains.grammarkit")
|
plugin("org.jetbrains.grammarkit")
|
||||||
plugin("org.jetbrains.intellij")
|
plugin("org.jetbrains.intellij")
|
||||||
|
plugin("org.jetbrains.kotlin.jvm")
|
||||||
}
|
}
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
@ -156,6 +158,14 @@ allprojects {
|
||||||
verifyPlugin {
|
verifyPlugin {
|
||||||
enabled = false
|
enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
compileKotlin {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
compileTestKotlin {
|
||||||
|
enabled = false
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,6 +191,20 @@ project(":") {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
project(":backports") {
|
||||||
|
tasks {
|
||||||
|
compileKotlin {
|
||||||
|
enabled = true
|
||||||
|
kotlinOptions.jvmTarget = "17"
|
||||||
|
}
|
||||||
|
|
||||||
|
compileTestKotlin {
|
||||||
|
enabled = true
|
||||||
|
kotlinOptions.jvmTarget = "17"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
project(":debugger") {
|
project(":debugger") {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":zig"))
|
implementation(project(":zig"))
|
||||||
|
@ -211,6 +235,7 @@ project(":lsp") {
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(project(":common"))
|
implementation(project(":common"))
|
||||||
|
implementation(project(":backports"))
|
||||||
api(project(":lsp-common"))
|
api(project(":lsp-common"))
|
||||||
api("org.apache.commons:commons-lang3:3.14.0")
|
api("org.apache.commons:commons-lang3:3.14.0")
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,133 @@
|
||||||
|
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
package com.falsepattern.zigbrains.backports.com.intellij.lang.documentation
|
||||||
|
|
||||||
|
import com.intellij.lang.Language
|
||||||
|
import com.intellij.lang.documentation.DocumentationMarkup.*
|
||||||
|
import com.intellij.lang.documentation.DocumentationSettings.InlineCodeHighlightingMode
|
||||||
|
import com.intellij.openapi.editor.colors.EditorColorsScheme
|
||||||
|
import com.intellij.openapi.editor.colors.TextAttributesKey
|
||||||
|
import com.intellij.openapi.editor.markup.TextAttributes
|
||||||
|
import com.intellij.openapi.editor.richcopy.HtmlSyntaxInfoUtil
|
||||||
|
import com.intellij.openapi.fileTypes.FileTypeManager
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.NlsSafe
|
||||||
|
import com.intellij.psi.PsiElement
|
||||||
|
import com.falsepattern.zigbrains.backports.com.intellij.ui.components.JBHtmlPaneStyleConfiguration
|
||||||
|
import com.falsepattern.zigbrains.backports.com.intellij.ui.components.JBHtmlPaneStyleConfiguration.*
|
||||||
|
import com.intellij.lang.documentation.DocumentationSettings
|
||||||
|
import com.intellij.util.concurrency.annotations.RequiresReadLock
|
||||||
|
import com.intellij.xml.util.XmlStringUtil
|
||||||
|
import org.jetbrains.annotations.ApiStatus.Internal
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class facilitates generation of highlighted text and code for Quick Documentation.
|
||||||
|
* It honors [DocumentationSettings], when highlighting code and links.
|
||||||
|
*/
|
||||||
|
object QuickDocHighlightingHelper {
|
||||||
|
|
||||||
|
const val CODE_BLOCK_PREFIX = "<pre><code>"
|
||||||
|
const val CODE_BLOCK_SUFFIX = "</code></pre>"
|
||||||
|
|
||||||
|
const val INLINE_CODE_PREFIX = "<code>"
|
||||||
|
const val INLINE_CODE_SUFFIX = "</code>"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The returned code block HTML (prefixed with [CODE_BLOCK_PREFIX] and suffixed with [CODE_BLOCK_SUFFIX])
|
||||||
|
* has syntax highlighted, if there is language provided and
|
||||||
|
* [DocumentationSettings.isHighlightingOfCodeBlocksEnabled] is `true`. The code block will
|
||||||
|
* be rendered with a special background if [DocumentationSettings.isCodeBackgroundEnabled] is `true`.
|
||||||
|
*
|
||||||
|
* Any special HTML characters, like `<` or `>` are escaped.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@RequiresReadLock
|
||||||
|
fun getStyledCodeBlock(project: Project, language: Language?, code: @NlsSafe CharSequence): @NlsSafe String =
|
||||||
|
StringBuilder().apply { appendStyledCodeBlock(project, language, code) }.toString()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends code block HTML (prefixed with [CODE_BLOCK_PREFIX] and suffixed with [CODE_BLOCK_SUFFIX]),
|
||||||
|
* which has syntax highlighted, if there is language provided and
|
||||||
|
* [DocumentationSettings.isHighlightingOfCodeBlocksEnabled] is `true`. The code block will
|
||||||
|
* be rendered with a special background if [DocumentationSettings.isCodeBackgroundEnabled] is `true`.
|
||||||
|
*
|
||||||
|
* Any special HTML characters, like `<` or `>` are escaped.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@RequiresReadLock
|
||||||
|
fun StringBuilder.appendStyledCodeBlock(project: Project, language: Language?, code: @NlsSafe CharSequence): @NlsSafe StringBuilder =
|
||||||
|
append(CODE_BLOCK_PREFIX)
|
||||||
|
.appendHighlightedCode(project, language, DocumentationSettings.isHighlightingOfCodeBlocksEnabled(), code, true)
|
||||||
|
.append(CODE_BLOCK_SUFFIX)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The returned inline code HTML (prefixed with [INLINE_CODE_PREFIX] and suffixed with [INLINE_CODE_SUFFIX])
|
||||||
|
* has syntax highlighted, if there is language provided and
|
||||||
|
* [DocumentationSettings.getInlineCodeHighlightingMode] is [DocumentationSettings.InlineCodeHighlightingMode.SEMANTIC_HIGHLIGHTING].
|
||||||
|
* The code block will be rendered with a special background if [DocumentationSettings.isCodeBackgroundEnabled] is `true` and
|
||||||
|
* [DocumentationSettings.getInlineCodeHighlightingMode] is not [DocumentationSettings.InlineCodeHighlightingMode.NO_HIGHLIGHTING].
|
||||||
|
*
|
||||||
|
* Any special HTML characters, like `<` or `>` are escaped.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@RequiresReadLock
|
||||||
|
fun getStyledInlineCode(project: Project, language: Language?, @NlsSafe code: String): @NlsSafe String =
|
||||||
|
StringBuilder().apply { appendStyledInlineCode(project, language, code) }.toString()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends inline code HTML (prefixed with [INLINE_CODE_PREFIX] and suffixed with [INLINE_CODE_SUFFIX]),
|
||||||
|
* which has syntax highlighted, if there is language provided and
|
||||||
|
* [DocumentationSettings.getInlineCodeHighlightingMode] is [DocumentationSettings.InlineCodeHighlightingMode.SEMANTIC_HIGHLIGHTING].
|
||||||
|
* The code block will be rendered with a special background if [DocumentationSettings.isCodeBackgroundEnabled] is `true` and
|
||||||
|
* [DocumentationSettings.getInlineCodeHighlightingMode] is not [DocumentationSettings.InlineCodeHighlightingMode.NO_HIGHLIGHTING].
|
||||||
|
*
|
||||||
|
* Any special HTML characters, like `<` or `>` are escaped.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
@RequiresReadLock
|
||||||
|
fun StringBuilder.appendStyledInlineCode(project: Project, language: Language?, @NlsSafe code: String): StringBuilder =
|
||||||
|
append(INLINE_CODE_PREFIX)
|
||||||
|
.appendHighlightedCode(
|
||||||
|
project, language, DocumentationSettings.getInlineCodeHighlightingMode() == InlineCodeHighlightingMode.SEMANTIC_HIGHLIGHTING, code,
|
||||||
|
true)
|
||||||
|
.append(INLINE_CODE_SUFFIX)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tries to guess a registered IDE language based. Useful e.g. for Markdown support
|
||||||
|
* to figure out a language to render a code block.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun guessLanguage(language: String?): Language? =
|
||||||
|
if (language == null)
|
||||||
|
null
|
||||||
|
else
|
||||||
|
Language
|
||||||
|
.findInstancesByMimeType(language)
|
||||||
|
.asSequence()
|
||||||
|
.plus(Language.findInstancesByMimeType("text/$language"))
|
||||||
|
.plus(
|
||||||
|
Language.getRegisteredLanguages()
|
||||||
|
.asSequence()
|
||||||
|
.filter { languageMatches(language, it) }
|
||||||
|
)
|
||||||
|
.firstOrNull()
|
||||||
|
|
||||||
|
|
||||||
|
private fun StringBuilder.appendHighlightedCode(project: Project, language: Language?, doHighlighting: Boolean,
|
||||||
|
code: CharSequence, isForRenderedDoc: Boolean): StringBuilder {
|
||||||
|
val processedCode = code.toString().trim('\n', '\r').replace(' ', ' ').trimEnd()
|
||||||
|
if (language != null && doHighlighting) {
|
||||||
|
HtmlSyntaxInfoUtil.appendHighlightedByLexerAndEncodedAsHtmlCodeSnippet(
|
||||||
|
this, project, language, processedCode,
|
||||||
|
DocumentationSettings.getHighlightingSaturation(isForRenderedDoc))
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
append(XmlStringUtil.escapeString(processedCode.trimIndent()))
|
||||||
|
}
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun languageMatches(langType: String, language: Language): Boolean =
|
||||||
|
langType.equals(language.id, ignoreCase = true)
|
||||||
|
|| FileTypeManager.getInstance().getFileTypeByExtension(langType) === language.associatedFileType
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,271 @@
|
||||||
|
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
package com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc
|
||||||
|
|
||||||
|
|
||||||
|
import com.intellij.lang.Language
|
||||||
|
import com.intellij.lang.documentation.DocumentationSettings
|
||||||
|
import com.falsepattern.zigbrains.backports.com.intellij.lang.documentation.QuickDocHighlightingHelper
|
||||||
|
import com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.impl.DocFlavourDescriptor
|
||||||
|
import com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.impl.DocTagRenderer
|
||||||
|
import com.intellij.openapi.diagnostic.ControlFlowException
|
||||||
|
import com.intellij.openapi.diagnostic.Logger
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.NlsSafe
|
||||||
|
import com.intellij.openapi.util.TextRange
|
||||||
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
|
import com.intellij.psi.PsiFile
|
||||||
|
import com.intellij.ui.ColorUtil
|
||||||
|
import com.intellij.util.concurrency.annotations.RequiresReadLock
|
||||||
|
import com.intellij.util.containers.CollectionFactory
|
||||||
|
import com.intellij.util.containers.ContainerUtil
|
||||||
|
import com.intellij.util.ui.UIUtil
|
||||||
|
import org.intellij.markdown.IElementType
|
||||||
|
import org.intellij.markdown.html.HtmlGenerator
|
||||||
|
import org.intellij.markdown.parser.MarkdownParser
|
||||||
|
import org.jetbrains.annotations.Contract
|
||||||
|
import org.jetbrains.annotations.Nls
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
|
||||||
|
/**
|
||||||
|
* [DocMarkdownToHtmlConverter] handles conversion of Markdown text to HTML, which is intended
|
||||||
|
* to be displayed in Quick Doc popup, or inline in an editor.
|
||||||
|
*/
|
||||||
|
object DocMarkdownToHtmlConverter {
|
||||||
|
private val LOG = Logger.getInstance(DocMarkdownToHtmlConverter::class.java)
|
||||||
|
private val TAG_START_OR_CLOSE_PATTERN: Pattern = Pattern.compile("(<)/?(\\w+)[> ]")
|
||||||
|
internal val TAG_PATTERN: Pattern = Pattern.compile("^</?([a-z][a-z-_0-9]*)[^>]*>$", Pattern.CASE_INSENSITIVE)
|
||||||
|
private val FENCE_PATTERN = "\\s+```.*".toRegex()
|
||||||
|
private const val FENCED_CODE_BLOCK = "```"
|
||||||
|
|
||||||
|
private val HTML_DOC_SUBSTITUTIONS: Map<String, String> = mapOf(
|
||||||
|
"<em>" to "<i>",
|
||||||
|
"</em>" to "</i>",
|
||||||
|
"<strong>" to "<b>",
|
||||||
|
"</strong>" to "</b>",
|
||||||
|
": //" to "://", // Fix URL
|
||||||
|
"<p></p>" to "",
|
||||||
|
"</p>" to "",
|
||||||
|
"<br />" to "",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
internal val ACCEPTABLE_BLOCK_TAGS: MutableSet<CharSequence> = CollectionFactory.createCharSequenceSet(false)
|
||||||
|
.apply {
|
||||||
|
addAll(listOf( // Text content
|
||||||
|
"blockquote", "dd", "dl", "dt",
|
||||||
|
"hr", "li", "ol", "ul", "pre", "p", // Table,
|
||||||
|
|
||||||
|
"caption", "col", "colgroup", "table", "tbody", "td", "tfoot", "th", "thead", "tr"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
internal val ACCEPTABLE_TAGS: Set<CharSequence> = CollectionFactory.createCharSequenceSet(false)
|
||||||
|
.apply {
|
||||||
|
addAll(ACCEPTABLE_BLOCK_TAGS)
|
||||||
|
addAll(listOf( // Content sectioning
|
||||||
|
"h1", "h2", "h3", "h4", "h5", "h6",
|
||||||
|
// Inline text semantic
|
||||||
|
"a", "b", "br", "code", "em", "i", "s", "span", "strong", "u", "wbr", "kbd", "samp",
|
||||||
|
// Image and multimedia
|
||||||
|
"img",
|
||||||
|
// Svg and math
|
||||||
|
"svg",
|
||||||
|
// Obsolete
|
||||||
|
"tt",
|
||||||
|
// special IJ tags
|
||||||
|
"shortcut", "icon"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts provided Markdown text to HTML. The results are intended to be used for Quick Documentation.
|
||||||
|
* If [defaultLanguage] is provided, it will be used for syntax coloring of inline code and code blocks, if language specifier is missing.
|
||||||
|
* Block and inline code syntax coloring is being done by [QuickDocHighlightingHelper], which honors [DocumentationSettings].
|
||||||
|
* Conversion must be run within a Read Action as it might require to create intermediate [PsiFile] to highlight block of code,
|
||||||
|
* or an inline code.
|
||||||
|
*/
|
||||||
|
@Contract(pure = true)
|
||||||
|
@JvmStatic
|
||||||
|
@RequiresReadLock
|
||||||
|
@JvmOverloads
|
||||||
|
fun convert(project: Project, @Nls markdownText: String, defaultLanguage: Language? = null): @Nls String {
|
||||||
|
val lines = markdownText.lines()
|
||||||
|
val minCommonIndent =
|
||||||
|
lines
|
||||||
|
.filter(String::isNotBlank)
|
||||||
|
.minOfOrNull { line -> line.indexOfFirst { !it.isWhitespace() }.let { if (it == -1) line.length else it } }
|
||||||
|
?: 0
|
||||||
|
val processedLines = ArrayList<String>(lines.size)
|
||||||
|
var isInCode = false
|
||||||
|
var isInTable = false
|
||||||
|
var tableFormats: List<String>? = null
|
||||||
|
for (i in lines.indices) {
|
||||||
|
var processedLine = lines[i].let { if (it.length <= minCommonIndent) "" else it.substring(minCommonIndent) }
|
||||||
|
processedLine = processedLine.trimEnd()
|
||||||
|
val count = StringUtil.getOccurrenceCount(processedLine, FENCED_CODE_BLOCK)
|
||||||
|
if (count > 0) {
|
||||||
|
isInCode = if (count % 2 == 0) isInCode else !isInCode
|
||||||
|
if (processedLine.matches(FENCE_PATTERN)) {
|
||||||
|
processedLine = processedLine.trim { it <= ' ' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (!isInCode) {
|
||||||
|
// TODO merge custom table generation code with Markdown parser
|
||||||
|
val tableDelimiterIndex = processedLine.indexOf('|')
|
||||||
|
if (tableDelimiterIndex != -1) {
|
||||||
|
if (!isInTable) {
|
||||||
|
if (i + 1 < lines.size) {
|
||||||
|
tableFormats = parseTableFormats(splitTableCols(lines[i + 1]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// create table only if we've successfully read the formats line
|
||||||
|
if (!ContainerUtil.isEmpty(tableFormats)) {
|
||||||
|
val parts = splitTableCols(processedLine)
|
||||||
|
if (isTableHeaderSeparator(parts)) continue
|
||||||
|
processedLine = getProcessedRow(project, defaultLanguage, isInTable, parts, tableFormats)
|
||||||
|
if (!isInTable) processedLine = "<table style=\"border: 0px;\" cellspacing=\"0\">$processedLine"
|
||||||
|
isInTable = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
if (isInTable) processedLine += "</table>"
|
||||||
|
isInTable = false
|
||||||
|
tableFormats = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
processedLines.add(processedLine)
|
||||||
|
}
|
||||||
|
var normalizedMarkdown = StringUtil.join(processedLines, "\n")
|
||||||
|
if (isInTable) normalizedMarkdown += "</table>" //NON-NLS
|
||||||
|
|
||||||
|
return performConversion(project, defaultLanguage, normalizedMarkdown)?.trimEnd()
|
||||||
|
?: adjustHtml(replaceProhibitedTags(convertNewLinePlaceholdersToTags(markdownText), ContainerUtil.emptyList()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convertNewLinePlaceholdersToTags(generatedDoc: String): String {
|
||||||
|
return StringUtil.replace(generatedDoc, "\n", "\n<p>")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseTableFormats(cols: List<String>): List<String>? {
|
||||||
|
val formats = ArrayList<String>()
|
||||||
|
for (col in cols) {
|
||||||
|
if (!isHeaderSeparator(col)) return null
|
||||||
|
formats.add(parseFormat(col.trim { it <= ' ' }))
|
||||||
|
}
|
||||||
|
return formats
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isTableHeaderSeparator(parts: List<String>): Boolean =
|
||||||
|
parts.all { isHeaderSeparator(it) }
|
||||||
|
|
||||||
|
private fun isHeaderSeparator(s: String): Boolean =
|
||||||
|
s.trim { it <= ' ' }.removePrefix(":").removeSuffix(":").chars().allMatch { it == '-'.code }
|
||||||
|
|
||||||
|
private fun splitTableCols(processedLine: String): List<String> {
|
||||||
|
val parts = ArrayList(StringUtil.split(processedLine, "|"))
|
||||||
|
if (parts.isEmpty()) return parts
|
||||||
|
if (parts[0].isNullOrBlank())
|
||||||
|
parts.removeAt(0)
|
||||||
|
if (!parts.isEmpty() && parts[parts.size - 1].isNullOrBlank())
|
||||||
|
parts.removeAt(parts.size - 1)
|
||||||
|
return parts
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getProcessedRow(project: Project,
|
||||||
|
defaultLanguage: Language?,
|
||||||
|
isInTable: Boolean,
|
||||||
|
parts: List<String>,
|
||||||
|
tableFormats: List<String>?): String {
|
||||||
|
val openingTagStart = if (isInTable)
|
||||||
|
"<td style=\"$border\" "
|
||||||
|
else
|
||||||
|
"<th style=\"$border\" "
|
||||||
|
val closingTag = if (isInTable) "</td>" else "</th>"
|
||||||
|
val resultBuilder = StringBuilder("<tr style=\"$border\">$openingTagStart")
|
||||||
|
resultBuilder.append("align=\"").append(getAlign(0, tableFormats)).append("\">")
|
||||||
|
for (i in parts.indices) {
|
||||||
|
if (i > 0) {
|
||||||
|
resultBuilder.append(closingTag).append(openingTagStart).append("align=\"").append(getAlign(i, tableFormats)).append("\">")
|
||||||
|
}
|
||||||
|
resultBuilder.append(performConversion(project, defaultLanguage, parts[i].trim { it <= ' ' }))
|
||||||
|
}
|
||||||
|
resultBuilder.append(closingTag).append("</tr>")
|
||||||
|
return resultBuilder.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getAlign(index: Int, formats: List<String>?): String {
|
||||||
|
return if (formats == null || index >= formats.size) "left" else formats[index]
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun parseFormat(format: String): String {
|
||||||
|
if (format.length <= 1) return "left"
|
||||||
|
val c0 = format[0]
|
||||||
|
val cE = format[format.length - 1]
|
||||||
|
return if (c0 == ':' && cE == ':') "center" else if (cE == ':') "right" else "left"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val embeddedHtmlType = IElementType("ROOT")
|
||||||
|
|
||||||
|
private fun performConversion(project: Project, defaultLanguage: Language?, text: @Nls String): @NlsSafe String? {
|
||||||
|
try {
|
||||||
|
val flavour = DocFlavourDescriptor(project, defaultLanguage)
|
||||||
|
val parsedTree = MarkdownParser(flavour).parse(embeddedHtmlType, text, true)
|
||||||
|
return HtmlGenerator(text, parsedTree, flavour, false)
|
||||||
|
.generateHtml(DocTagRenderer(text))
|
||||||
|
}
|
||||||
|
catch (e: Exception) {
|
||||||
|
if (e is ControlFlowException) throw e
|
||||||
|
LOG.warn(e.message, e)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun replaceProhibitedTags(line: String, skipRanges: List<TextRange>): @NlsSafe String {
|
||||||
|
val matcher = TAG_START_OR_CLOSE_PATTERN.matcher(line)
|
||||||
|
val builder = StringBuilder(line)
|
||||||
|
|
||||||
|
var diff = 0
|
||||||
|
l@ while (matcher.find()) {
|
||||||
|
val tagName = matcher.group(2)
|
||||||
|
|
||||||
|
if (ACCEPTABLE_TAGS.contains(tagName)) continue
|
||||||
|
|
||||||
|
val startOfTag = matcher.start(2)
|
||||||
|
for (range in skipRanges) {
|
||||||
|
if (range.contains(startOfTag)) {
|
||||||
|
continue@l
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val start = matcher.start(1) + diff
|
||||||
|
if (StringUtil.toLowerCase(tagName) == "div") {
|
||||||
|
val isOpenTag = !matcher.group(0).contains("/")
|
||||||
|
val end = start + (if (isOpenTag) 5 else 6)
|
||||||
|
val replacement = if (isOpenTag) "<span>" else "</span>"
|
||||||
|
builder.replace(start, end, replacement)
|
||||||
|
diff += 1
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
builder.replace(start, start + 1, "<")
|
||||||
|
diff += 3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return builder.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Contract(pure = true)
|
||||||
|
private fun adjustHtml(html: String): String {
|
||||||
|
var str = html
|
||||||
|
for ((key, value) in HTML_DOC_SUBSTITUTIONS) {
|
||||||
|
if (str.indexOf(key) > 0) {
|
||||||
|
str = str.replace(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return str.trim { it <= ' ' }
|
||||||
|
}
|
||||||
|
|
||||||
|
private val border: String
|
||||||
|
get() = "margin: 0; border: 1px solid; border-color: #" + ColorUtil
|
||||||
|
.toHex(UIUtil.getTooltipSeparatorColor()) + "; border-spacing: 0; border-collapse: collapse;vertical-align: baseline;"
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,83 @@
|
||||||
|
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
package com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.impl
|
||||||
|
|
||||||
|
import com.intellij.lang.Language
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import org.intellij.markdown.IElementType
|
||||||
|
import org.intellij.markdown.MarkdownElementTypes
|
||||||
|
import org.intellij.markdown.MarkdownTokenTypes
|
||||||
|
import org.intellij.markdown.flavours.commonmark.CommonMarkMarkerProcessor
|
||||||
|
import org.intellij.markdown.flavours.gfm.GFMConstraints
|
||||||
|
import org.intellij.markdown.flavours.gfm.GFMFlavourDescriptor
|
||||||
|
import org.intellij.markdown.html.GeneratingProvider
|
||||||
|
import org.intellij.markdown.html.ImageGeneratingProvider
|
||||||
|
import org.intellij.markdown.html.ReferenceLinksGeneratingProvider
|
||||||
|
import org.intellij.markdown.html.makeXssSafe
|
||||||
|
import org.intellij.markdown.parser.LinkMap
|
||||||
|
import org.intellij.markdown.parser.MarkerProcessor
|
||||||
|
import org.intellij.markdown.parser.MarkerProcessorFactory
|
||||||
|
import org.intellij.markdown.parser.ProductionHolder
|
||||||
|
import org.intellij.markdown.parser.constraints.CommonMarkdownConstraints
|
||||||
|
import org.intellij.markdown.parser.markerblocks.MarkerBlockProvider
|
||||||
|
import org.intellij.markdown.parser.markerblocks.providers.HtmlBlockProvider
|
||||||
|
import java.net.URI
|
||||||
|
|
||||||
|
private val baseHtmlGeneratingProvidersMap =
|
||||||
|
GFMFlavourDescriptor().createHtmlGeneratingProviders(LinkMap(emptyMap()), null) + hashMapOf(
|
||||||
|
MarkdownTokenTypes.HTML_TAG to DocSanitizingTagGeneratingProvider(),
|
||||||
|
MarkdownElementTypes.PARAGRAPH to DocParagraphGeneratingProvider(),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
internal class DocFlavourDescriptor(private val project: Project, private val defaultLanguage: Language?) : GFMFlavourDescriptor() {
|
||||||
|
override val markerProcessorFactory: MarkerProcessorFactory
|
||||||
|
get() = object : MarkerProcessorFactory {
|
||||||
|
override fun createMarkerProcessor(productionHolder: ProductionHolder): MarkerProcessor<*> =
|
||||||
|
DocumentationMarkerProcessor(productionHolder, GFMConstraints.BASE)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun createHtmlGeneratingProviders(linkMap: LinkMap, baseURI: URI?): Map<IElementType, GeneratingProvider> =
|
||||||
|
MergedMap(hashMapOf(MarkdownElementTypes.FULL_REFERENCE_LINK to
|
||||||
|
ReferenceLinksGeneratingProvider(linkMap, baseURI, absolutizeAnchorLinks).makeXssSafe(useSafeLinks),
|
||||||
|
MarkdownElementTypes.SHORT_REFERENCE_LINK to
|
||||||
|
ReferenceLinksGeneratingProvider(linkMap, baseURI, absolutizeAnchorLinks).makeXssSafe(useSafeLinks),
|
||||||
|
MarkdownElementTypes.IMAGE to ImageGeneratingProvider(linkMap, baseURI).makeXssSafe(useSafeLinks),
|
||||||
|
|
||||||
|
MarkdownElementTypes.CODE_BLOCK to DocCodeBlockGeneratingProvider(project, defaultLanguage),
|
||||||
|
MarkdownElementTypes.CODE_FENCE to DocCodeBlockGeneratingProvider(project, defaultLanguage),
|
||||||
|
MarkdownElementTypes.CODE_SPAN to DocCodeSpanGeneratingProvider(project, defaultLanguage)),
|
||||||
|
baseHtmlGeneratingProvidersMap)
|
||||||
|
|
||||||
|
private class DocumentationMarkerProcessor(productionHolder: ProductionHolder,
|
||||||
|
constraintsBase: CommonMarkdownConstraints) : CommonMarkMarkerProcessor(productionHolder,
|
||||||
|
constraintsBase) {
|
||||||
|
override fun getMarkerBlockProviders(): List<MarkerBlockProvider<StateInfo>> =
|
||||||
|
super.getMarkerBlockProviders().filter { it !is HtmlBlockProvider } + DocHtmlBlockProvider
|
||||||
|
}
|
||||||
|
|
||||||
|
private class MergedMap(val map1: Map<IElementType, GeneratingProvider>,
|
||||||
|
val map2: Map<IElementType, GeneratingProvider>) : Map<IElementType, GeneratingProvider> {
|
||||||
|
|
||||||
|
override fun isEmpty(): Boolean =
|
||||||
|
map1.isEmpty() && map2.isEmpty()
|
||||||
|
|
||||||
|
override fun get(key: IElementType): GeneratingProvider? =
|
||||||
|
map1[key] ?: map2[key]
|
||||||
|
|
||||||
|
override fun containsValue(value: GeneratingProvider): Boolean =
|
||||||
|
map1.containsValue(value) || map2.containsValue(value)
|
||||||
|
|
||||||
|
override fun containsKey(key: IElementType): Boolean =
|
||||||
|
map1.containsKey(key) || map2.containsKey(key)
|
||||||
|
|
||||||
|
override val entries: Set<Map.Entry<IElementType, GeneratingProvider>>
|
||||||
|
get() = throw UnsupportedOperationException()
|
||||||
|
override val keys: Set<IElementType>
|
||||||
|
get() = throw UnsupportedOperationException()
|
||||||
|
override val size: Int
|
||||||
|
get() = throw UnsupportedOperationException()
|
||||||
|
override val values: Collection<GeneratingProvider>
|
||||||
|
get() = throw UnsupportedOperationException()
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,104 @@
|
||||||
|
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
package com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.impl
|
||||||
|
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter
|
||||||
|
import com.intellij.lang.Language
|
||||||
|
import com.falsepattern.zigbrains.backports.com.intellij.lang.documentation.QuickDocHighlightingHelper
|
||||||
|
import com.falsepattern.zigbrains.backports.com.intellij.lang.documentation.QuickDocHighlightingHelper.guessLanguage
|
||||||
|
import com.intellij.openapi.project.Project
|
||||||
|
import com.intellij.openapi.util.text.StringUtil
|
||||||
|
import com.intellij.util.containers.CollectionFactory
|
||||||
|
import com.intellij.util.containers.ContainerUtil
|
||||||
|
import org.intellij.markdown.MarkdownTokenTypes
|
||||||
|
import org.intellij.markdown.ast.ASTNode
|
||||||
|
import org.intellij.markdown.ast.LeafASTNode
|
||||||
|
import org.intellij.markdown.ast.accept
|
||||||
|
import org.intellij.markdown.ast.getTextInNode
|
||||||
|
import org.intellij.markdown.html.GeneratingProvider
|
||||||
|
import org.intellij.markdown.html.HtmlGenerator
|
||||||
|
import org.intellij.markdown.html.TrimmingInlineHolderProvider
|
||||||
|
|
||||||
|
private val TAG_REPLACE_MAP = CollectionFactory.createCharSequenceMap<String>(false).also {
|
||||||
|
it["div"] = "span"
|
||||||
|
it["em"] = "i"
|
||||||
|
it["strong"] = "b"
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DocSanitizingTagGeneratingProvider : GeneratingProvider {
|
||||||
|
override fun processNode(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
|
||||||
|
val nodeText = node.getTextInNode(text)
|
||||||
|
if (nodeText.contentEquals("</p>", true)) return
|
||||||
|
val matcher = DocMarkdownToHtmlConverter.TAG_PATTERN.matcher(nodeText)
|
||||||
|
if (matcher.matches()) {
|
||||||
|
val tagName = matcher.group(1)
|
||||||
|
val replaceWith = TAG_REPLACE_MAP[tagName]
|
||||||
|
if (replaceWith != null) {
|
||||||
|
visitor.consumeHtml(nodeText.subSequence(0, matcher.start(1)))
|
||||||
|
visitor.consumeHtml(replaceWith)
|
||||||
|
visitor.consumeHtml(nodeText.subSequence(matcher.end(1), nodeText.length))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (DocMarkdownToHtmlConverter.ACCEPTABLE_TAGS.contains(tagName)) {
|
||||||
|
visitor.consumeHtml(nodeText)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visitor.consumeHtml(StringUtil.escapeXmlEntities(nodeText.toString()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DocCodeBlockGeneratingProvider(private val project: Project, private val defaultLanguage: Language?) : GeneratingProvider {
|
||||||
|
override fun processNode(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
|
||||||
|
val contents = StringBuilder()
|
||||||
|
var language: String? = null
|
||||||
|
node.children.forEach { child ->
|
||||||
|
when (child.type) {
|
||||||
|
MarkdownTokenTypes.CODE_FENCE_CONTENT, MarkdownTokenTypes.CODE_LINE, MarkdownTokenTypes.EOL ->
|
||||||
|
contents.append(child.getTextInNode(text))
|
||||||
|
MarkdownTokenTypes.FENCE_LANG ->
|
||||||
|
language = HtmlGenerator.leafText(text, child).toString().trim().takeWhile { !it.isWhitespace() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
visitor.consumeHtml(QuickDocHighlightingHelper.getStyledCodeBlock(
|
||||||
|
project, guessLanguage(language) ?: defaultLanguage, contents.toString()))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DocCodeSpanGeneratingProvider(private val project: Project, private val defaultLanguage: Language?) : GeneratingProvider {
|
||||||
|
override fun processNode(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
|
||||||
|
val nodes = node.children.subList(1, node.children.size - 1)
|
||||||
|
val output = nodes
|
||||||
|
.filter { it.type != MarkdownTokenTypes.BLOCK_QUOTE }
|
||||||
|
.joinToString(separator = "") { it.getTextInNode(text) }.trim()
|
||||||
|
visitor.consumeHtml(QuickDocHighlightingHelper.getStyledInlineCode(project, defaultLanguage, output))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DocParagraphGeneratingProvider : TrimmingInlineHolderProvider() {
|
||||||
|
override fun openTag(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
|
||||||
|
visitor.consumeTagOpen(node, "p")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun closeTag(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
|
||||||
|
visitor.consumeTagClose("p")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun processNode(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
|
||||||
|
val childrenToRender = childrenToRender(node)
|
||||||
|
if (childrenToRender.isEmpty()) return
|
||||||
|
|
||||||
|
openTag(visitor, text, node)
|
||||||
|
|
||||||
|
for (child in childrenToRender(node)) {
|
||||||
|
if (child is LeafASTNode) {
|
||||||
|
visitor.visitLeaf(child)
|
||||||
|
} else {
|
||||||
|
child.accept(visitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
closeTag(visitor, text, node)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,69 @@
|
||||||
|
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
package com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.impl
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter
|
||||||
|
import org.intellij.markdown.lexer.Compat.assert
|
||||||
|
import org.intellij.markdown.parser.LookaheadText
|
||||||
|
import org.intellij.markdown.parser.MarkerProcessor
|
||||||
|
import org.intellij.markdown.parser.ProductionHolder
|
||||||
|
import org.intellij.markdown.parser.constraints.MarkdownConstraints
|
||||||
|
import org.intellij.markdown.parser.markerblocks.MarkerBlock
|
||||||
|
import org.intellij.markdown.parser.markerblocks.MarkerBlockProvider
|
||||||
|
import org.intellij.markdown.parser.markerblocks.impl.HtmlBlockMarkerBlock
|
||||||
|
|
||||||
|
internal object DocHtmlBlockProvider : MarkerBlockProvider<MarkerProcessor.StateInfo> {
|
||||||
|
override fun createMarkerBlocks(pos: LookaheadText.Position,
|
||||||
|
productionHolder: ProductionHolder,
|
||||||
|
stateInfo: MarkerProcessor.StateInfo): List<MarkerBlock> {
|
||||||
|
val matchingGroup = matches(pos, stateInfo.currentConstraints)
|
||||||
|
if (matchingGroup in 0..3) {
|
||||||
|
return listOf(HtmlBlockMarkerBlock(stateInfo.currentConstraints, productionHolder, OPEN_CLOSE_REGEXES[matchingGroup].second, pos))
|
||||||
|
}
|
||||||
|
return emptyList()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun interruptsParagraph(pos: LookaheadText.Position, constraints: MarkdownConstraints): Boolean {
|
||||||
|
return matches(pos, constraints) in 0..4
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun matches(pos: LookaheadText.Position, constraints: MarkdownConstraints): Int {
|
||||||
|
if (!MarkerBlockProvider.isStartOfLineWithConstraints(pos, constraints)) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
val text = pos.currentLineFromPosition
|
||||||
|
val offset = MarkerBlockProvider.passSmallIndent(text)
|
||||||
|
if (offset >= text.length || text[offset] != '<') {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
val matchResult = FIND_START_REGEX.find(text.substring(offset))
|
||||||
|
?: return -1
|
||||||
|
assert(matchResult.groups.size == OPEN_CLOSE_REGEXES.size + 2) { "There are some excess capturing groups probably!" }
|
||||||
|
for (i in OPEN_CLOSE_REGEXES.indices) {
|
||||||
|
if (matchResult.groups[i + 2] != null) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
}
|
||||||
|
assert(false) { "Match found but all groups are empty!" }
|
||||||
|
return -1
|
||||||
|
}
|
||||||
|
|
||||||
|
/** see {@link http://spec.commonmark.org/0.21/#html-blocks}
|
||||||
|
*
|
||||||
|
* nulls mean "Next line should be blank"
|
||||||
|
* */
|
||||||
|
private val OPEN_CLOSE_REGEXES: List<Pair<Regex, Regex?>> = listOf(
|
||||||
|
Pair(Regex("<(?:script|pre|style)(?: |>|$)", RegexOption.IGNORE_CASE),
|
||||||
|
Regex("</(?:script|style|pre)>", RegexOption.IGNORE_CASE)),
|
||||||
|
Pair(Regex("<!--"), Regex("-->")),
|
||||||
|
Pair(Regex("<\\?"), Regex("\\?>")),
|
||||||
|
Pair(Regex("<![A-Z]"), Regex(">")),
|
||||||
|
Pair(Regex("<!\\[CDATA\\["), Regex("]]>")),
|
||||||
|
Pair(Regex("</?(?:${DocMarkdownToHtmlConverter.ACCEPTABLE_BLOCK_TAGS.joinToString("|")})(?: |/?>|$)", RegexOption.IGNORE_CASE), null)
|
||||||
|
)
|
||||||
|
|
||||||
|
private val FIND_START_REGEX = Regex(
|
||||||
|
"^(${OPEN_CLOSE_REGEXES.joinToString(separator = "|", transform = { "(${it.first.pattern})" })})"
|
||||||
|
)
|
||||||
|
|
||||||
|
}
|
|
@ -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 com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.impl
|
||||||
|
|
||||||
|
import com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter
|
||||||
|
import org.intellij.markdown.MarkdownTokenTypes
|
||||||
|
import org.intellij.markdown.ast.ASTNode
|
||||||
|
import org.intellij.markdown.ast.getTextInNode
|
||||||
|
import org.intellij.markdown.html.DUMMY_ATTRIBUTES_CUSTOMIZER
|
||||||
|
import org.intellij.markdown.html.HtmlGenerator
|
||||||
|
|
||||||
|
internal class DocTagRenderer(private val wholeText: String)
|
||||||
|
: HtmlGenerator.DefaultTagRenderer(DUMMY_ATTRIBUTES_CUSTOMIZER, false) {
|
||||||
|
|
||||||
|
override fun openTag(node: ASTNode, tagName: CharSequence,
|
||||||
|
vararg attributes: CharSequence?,
|
||||||
|
autoClose: Boolean): CharSequence {
|
||||||
|
if (tagName.contentEquals("p", true)) {
|
||||||
|
val first = node.children.firstOrNull()
|
||||||
|
if (first != null && first.type === MarkdownTokenTypes.HTML_TAG) {
|
||||||
|
val text = first.getTextInNode(wholeText)
|
||||||
|
val matcher = DocMarkdownToHtmlConverter.TAG_PATTERN.matcher(text)
|
||||||
|
if (matcher.matches()) {
|
||||||
|
val nestedTag = matcher.group(1)
|
||||||
|
if (DocMarkdownToHtmlConverter.ACCEPTABLE_BLOCK_TAGS.contains(nestedTag)) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tagName.contentEquals("code", true) && node.type === MarkdownTokenTypes.CODE_FENCE_CONTENT) {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return super.openTag(node, convertTag(tagName), *attributes, autoClose = autoClose)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun closeTag(tagName: CharSequence): CharSequence {
|
||||||
|
if (tagName.contentEquals("p", true)) return ""
|
||||||
|
return super.closeTag(convertTag(tagName))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun convertTag(tagName: CharSequence): CharSequence {
|
||||||
|
if (tagName.contentEquals("strong", true)) {
|
||||||
|
return "b"
|
||||||
|
}
|
||||||
|
else if (tagName.contentEquals("em", true)) {
|
||||||
|
return "i"
|
||||||
|
}
|
||||||
|
return tagName
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,71 @@
|
||||||
|
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||||
|
package com.falsepattern.zigbrains.backports.com.intellij.ui.components
|
||||||
|
|
||||||
|
|
||||||
|
import com.intellij.openapi.editor.colors.EditorColorsManager
|
||||||
|
import com.intellij.openapi.editor.colors.EditorColorsScheme
|
||||||
|
import com.intellij.openapi.editor.colors.TextAttributesKey
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
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,
|
||||||
|
) {
|
||||||
|
override fun equals(other: Any?): Boolean =
|
||||||
|
other is JBHtmlPaneStyleConfiguration
|
||||||
|
&& colorSchemesEqual(colorScheme, other.colorScheme)
|
||||||
|
&& inlineCodeParentSelectors == other.inlineCodeParentSelectors
|
||||||
|
&& largeCodeFontSizeSelectors == other.largeCodeFontSizeSelectors
|
||||||
|
&& enableInlineCodeBackground == other.enableInlineCodeBackground
|
||||||
|
&& enableCodeBlocksBackground == other.enableCodeBlocksBackground
|
||||||
|
&& useFontLigaturesInCode == other.useFontLigaturesInCode
|
||||||
|
&& spaceBeforeParagraph == other.spaceBeforeParagraph
|
||||||
|
&& spaceAfterParagraph == other.spaceAfterParagraph
|
||||||
|
|
||||||
|
private fun colorSchemesEqual(colorScheme: EditorColorsScheme, colorScheme2: EditorColorsScheme): Boolean =
|
||||||
|
// 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 {
|
||||||
|
colorScheme.getAttributes(it.colorSchemeKey) ==
|
||||||
|
colorScheme2.getAttributes(it.colorSchemeKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun hashCode(): Int =
|
||||||
|
Objects.hash(colorScheme.defaultBackground.rgb and 0xffffff,
|
||||||
|
colorScheme.defaultForeground.rgb and 0xffffff,
|
||||||
|
inlineCodeParentSelectors, largeCodeFontSizeSelectors,
|
||||||
|
enableInlineCodeBackground, enableCodeBlocksBackground,
|
||||||
|
useFontLigaturesInCode, spaceBeforeParagraph, spaceAfterParagraph)
|
||||||
|
|
||||||
|
data class ControlStyleOverrides(
|
||||||
|
val controlKindSuffix: String,
|
||||||
|
val overrides: Map<ControlKind, Collection<ControlProperty>>
|
||||||
|
)
|
||||||
|
|
||||||
|
enum class ControlKind(val id: String, val colorSchemeKey: TextAttributesKey) {
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ControlProperty(val id: String) {
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
val defaultSpaceBeforeParagraph: Int get() = 4
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
val defaultSpaceAfterParagraph: Int get() = 4
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -23,7 +23,7 @@ import com.falsepattern.zigbrains.lsp.requests.Timeout;
|
||||||
import com.falsepattern.zigbrains.lsp.requests.Timeouts;
|
import com.falsepattern.zigbrains.lsp.requests.Timeouts;
|
||||||
import com.falsepattern.zigbrains.lsp.utils.DocumentUtils;
|
import com.falsepattern.zigbrains.lsp.utils.DocumentUtils;
|
||||||
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
|
import com.falsepattern.zigbrains.lsp.utils.FileUtils;
|
||||||
import com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter;
|
import com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter;
|
||||||
import com.intellij.model.Pointer;
|
import com.intellij.model.Pointer;
|
||||||
import com.intellij.openapi.diagnostic.Logger;
|
import com.intellij.openapi.diagnostic.Logger;
|
||||||
import com.intellij.openapi.util.TextRange;
|
import com.intellij.openapi.util.TextRange;
|
||||||
|
|
|
@ -25,7 +25,7 @@ import com.intellij.codeInsight.hints.declarative.InlayTreeSink;
|
||||||
import com.intellij.codeInsight.hints.declarative.InlineInlayPosition;
|
import com.intellij.codeInsight.hints.declarative.InlineInlayPosition;
|
||||||
import com.intellij.codeInsight.hints.declarative.OwnBypassCollector;
|
import com.intellij.codeInsight.hints.declarative.OwnBypassCollector;
|
||||||
import com.intellij.codeInsight.hints.declarative.impl.DeclarativeInlayHintsPassFactory;
|
import com.intellij.codeInsight.hints.declarative.impl.DeclarativeInlayHintsPassFactory;
|
||||||
import com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter;
|
import com.falsepattern.zigbrains.backports.com.intellij.markdown.utils.doc.DocMarkdownToHtmlConverter;
|
||||||
import com.intellij.openapi.diagnostic.Logger;
|
import com.intellij.openapi.diagnostic.Logger;
|
||||||
import com.intellij.openapi.editor.Editor;
|
import com.intellij.openapi.editor.Editor;
|
||||||
import com.intellij.psi.PsiFile;
|
import com.intellij.psi.PsiFile;
|
||||||
|
|
Loading…
Add table
Reference in a new issue