diff --git a/src/commonMain/kotlin/org/intellij/markdown/html/GeneratingProviders.kt b/src/commonMain/kotlin/org/intellij/markdown/html/GeneratingProviders.kt index 11d5eae6..dc55f716 100644 --- a/src/commonMain/kotlin/org/intellij/markdown/html/GeneratingProviders.kt +++ b/src/commonMain/kotlin/org/intellij/markdown/html/GeneratingProviders.kt @@ -143,8 +143,6 @@ internal class HtmlBlockGeneratingProvider : GeneratingProvider { internal class CodeFenceGeneratingProvider : GeneratingProvider { override fun processNode(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) { - val indentBefore = node.getTextInNode(text).commonPrefixWith(" ".repeat(10)).length - visitor.consumeHtml("
") var state = 0 @@ -160,7 +158,7 @@ internal class CodeFenceGeneratingProvider : GeneratingProvider { for (child in childrenToConsider) { if (state == 1 && child.type in listOf(MarkdownTokenTypes.CODE_FENCE_CONTENT, MarkdownTokenTypes.EOL)) { - visitor.consumeHtml(HtmlGenerator.trimIndents(HtmlGenerator.leafText(text, child, false), indentBefore)) + visitor.consumeHtml(HtmlGenerator.leafText(text, child, false)) lastChildWasContent = child.type == MarkdownTokenTypes.CODE_FENCE_CONTENT } if (state == 0 && child.type == MarkdownTokenTypes.FENCE_LANG) { diff --git a/src/commonMain/kotlin/org/intellij/markdown/parser/markerblocks/impl/CodeFenceMarkerBlock.kt b/src/commonMain/kotlin/org/intellij/markdown/parser/markerblocks/impl/CodeFenceMarkerBlock.kt index 47be7c8e..a06813ca 100644 --- a/src/commonMain/kotlin/org/intellij/markdown/parser/markerblocks/impl/CodeFenceMarkerBlock.kt +++ b/src/commonMain/kotlin/org/intellij/markdown/parser/markerblocks/impl/CodeFenceMarkerBlock.kt @@ -10,12 +10,14 @@ import org.intellij.markdown.parser.constraints.* import org.intellij.markdown.parser.markerblocks.MarkerBlock import org.intellij.markdown.parser.markerblocks.MarkerBlockImpl import org.intellij.markdown.parser.sequentialparsers.SequentialParser -import kotlin.math.min import kotlin.text.Regex -class CodeFenceMarkerBlock(myConstraints: MarkdownConstraints, - private val productionHolder: ProductionHolder, - private val fenceStart: String) : MarkerBlockImpl(myConstraints, productionHolder.mark()) { +class CodeFenceMarkerBlock( + myConstraints: MarkdownConstraints, + private val productionHolder: ProductionHolder, + private val fenceStart: String, + private val fenceIndent: Int +) : MarkerBlockImpl(myConstraints, productionHolder.mark()) { override fun allowsSubBlocks(): Boolean = false override fun isInterestingOffset(pos: LookaheadText.Position): Boolean = true //pos.offsetInCurrentLine == -1 @@ -54,15 +56,25 @@ class CodeFenceMarkerBlock(myConstraints: MarkdownConstraints, realInterestingOffset = nextLineOffset val currentLine = nextLineConstraints.eatItselfFromString(pos.currentLine) + // Skip characters from current constraints and advance position + val charactersToSkip = 1 + constraints.getCharsEaten(pos.currentLine) + val advancedPosition = pos.nextPosition(charactersToSkip) ?: return MarkerBlock.ProcessingResult.CANCEL + // Calculate actual fence indent (it can not exceed the fenceIndent) + val indent = advancedPosition.charsToNonWhitespace()?.coerceAtMost(fenceIndent) ?: 0 + val startOffset = advancedPosition.offset + indent + val contentRange = startOffset.coerceAtMost(nextLineOffset)..nextLineOffset if (endsThisFence(currentLine)) { - productionHolder.addProduction(listOf(SequentialParser.Node(pos.offset + 1..pos.nextLineOrEofOffset, - MarkdownTokenTypes.CODE_FENCE_END))) + productionHolder.addProduction(listOf(SequentialParser.Node( + startOffset..nextLineOffset, + MarkdownTokenTypes.CODE_FENCE_END + ))) scheduleProcessingResult(nextLineOffset, MarkerBlock.ProcessingResult.DEFAULT) } else { - val contentRange = min(pos.offset + 1 + constraints.getCharsEaten(pos.currentLine), nextLineOffset)..nextLineOffset if (contentRange.first < contentRange.last) { productionHolder.addProduction(listOf(SequentialParser.Node( - contentRange, MarkdownTokenTypes.CODE_FENCE_CONTENT))) + contentRange, + MarkdownTokenTypes.CODE_FENCE_CONTENT + ))) } } diff --git a/src/commonMain/kotlin/org/intellij/markdown/parser/markerblocks/providers/CodeFenceProvider.kt b/src/commonMain/kotlin/org/intellij/markdown/parser/markerblocks/providers/CodeFenceProvider.kt index c0cc6a7c..50617c31 100644 --- a/src/commonMain/kotlin/org/intellij/markdown/parser/markerblocks/providers/CodeFenceProvider.kt +++ b/src/commonMain/kotlin/org/intellij/markdown/parser/markerblocks/providers/CodeFenceProvider.kt @@ -15,25 +15,34 @@ class CodeFenceProvider : MarkerBlockProvider{ override fun createMarkerBlocks(pos: LookaheadText.Position, productionHolder: ProductionHolder, stateInfo: MarkerProcessor.StateInfo): List { - val fenceAndInfo = getFenceStartAndInfo(pos, stateInfo.currentConstraints) - if (fenceAndInfo != null) { - createNodesForFenceStart(pos, fenceAndInfo, productionHolder) - return listOf(CodeFenceMarkerBlock(stateInfo.currentConstraints, productionHolder, fenceAndInfo.first)) - } else { - return emptyList() - } + val fenceAndInfo = getFenceStartAndInfo(pos, stateInfo.currentConstraints) ?: return emptyList() + val indent = createNodesForFenceStart(pos, fenceAndInfo, productionHolder) ?: return emptyList() + return listOf(CodeFenceMarkerBlock(stateInfo.currentConstraints, productionHolder, fenceAndInfo.first, indent)) } override fun interruptsParagraph(pos: LookaheadText.Position, constraints: MarkdownConstraints): Boolean { return getFenceStartAndInfo(pos, constraints) != null } - private fun createNodesForFenceStart(pos: LookaheadText.Position, fenceAndInfo: Pair , productionHolder: ProductionHolder) { + private fun createNodesForFenceStart( + pos: LookaheadText.Position, + fenceAndInfo: Pair , + productionHolder: ProductionHolder + ): Int? { val infoStartPosition = pos.nextLineOrEofOffset - fenceAndInfo.second.length + // Count the number of spaces before start element, so we can exclude them from resulting tree element + val indent = pos.charsToNonWhitespace() ?: 0 + // If the current fence is indented with more than 3 spaces, it becomes a code block + if (indent > 3) { + return null + } + @Suppress("NAME_SHADOWING") + val pos = pos.nextPosition(indent) ?: return null productionHolder.addProduction(listOf(SequentialParser.Node(pos.offset..infoStartPosition, MarkdownTokenTypes.CODE_FENCE_START))) - if (fenceAndInfo.second.length > 0) { + if (fenceAndInfo.second.isNotEmpty()) { productionHolder.addProduction(listOf(SequentialParser.Node(infoStartPosition..pos.nextLineOrEofOffset, MarkdownTokenTypes.FENCE_LANG))) } + return indent } private fun getFenceStartAndInfo(pos: LookaheadText.Position, constraints: MarkdownConstraints): Pair ? { @@ -48,4 +57,4 @@ class CodeFenceProvider : MarkerBlockProvider { companion object { val REGEX: Regex = Regex("^ {0,3}(~~~+|```+)([^`]*)$") } -} \ No newline at end of file +} diff --git a/src/fileBasedTest/resources/data/parser/codeFence.txt b/src/fileBasedTest/resources/data/parser/codeFence.txt index 66484292..2f1a378f 100644 --- a/src/fileBasedTest/resources/data/parser/codeFence.txt +++ b/src/fileBasedTest/resources/data/parser/codeFence.txt @@ -49,15 +49,20 @@ Markdown:MARKDOWN_FILE Markdown:EOL('\n') Markdown:EOL('\n') Markdown:CODE_FENCE - Markdown:CODE_FENCE_START(' ```') + WHITE_SPACE(' ') + Markdown:CODE_FENCE_START('```') Markdown:EOL('\n') - Markdown:CODE_FENCE_CONTENT(' aaa') + WHITE_SPACE(' ') + Markdown:CODE_FENCE_CONTENT('aaa') Markdown:EOL('\n') - Markdown:CODE_FENCE_CONTENT(' aaa') + WHITE_SPACE(' ') + Markdown:CODE_FENCE_CONTENT(' aaa') Markdown:EOL('\n') - Markdown:CODE_FENCE_CONTENT(' aaa') + WHITE_SPACE(' ') + Markdown:CODE_FENCE_CONTENT('aaa') Markdown:EOL('\n') - Markdown:CODE_FENCE_END(' ```') + WHITE_SPACE(' ') + Markdown:CODE_FENCE_END('```') Markdown:EOL('\n') Markdown:EOL('\n') Markdown:CODE_BLOCK diff --git a/src/fileBasedTest/resources/data/parser/example226.txt b/src/fileBasedTest/resources/data/parser/example226.txt index e6517903..f233d117 100644 --- a/src/fileBasedTest/resources/data/parser/example226.txt +++ b/src/fileBasedTest/resources/data/parser/example226.txt @@ -17,7 +17,8 @@ Markdown:MARKDOWN_FILE WHITE_SPACE(' ') Markdown:CODE_FENCE_CONTENT('bar') Markdown:EOL('\n') - Markdown:CODE_FENCE_END(' ```') + WHITE_SPACE(' ') + Markdown:CODE_FENCE_END('```') Markdown:EOL('\n') Markdown:LIST_ITEM Markdown:LIST_BULLET('-') diff --git a/src/fileBasedTest/resources/data/parser/tightLooseLists.txt b/src/fileBasedTest/resources/data/parser/tightLooseLists.txt index 4d9d90f4..83ac62fa 100644 --- a/src/fileBasedTest/resources/data/parser/tightLooseLists.txt +++ b/src/fileBasedTest/resources/data/parser/tightLooseLists.txt @@ -63,7 +63,8 @@ Markdown:MARKDOWN_FILE WHITE_SPACE(' ') Markdown:CODE_FENCE_CONTENT(' is not loose at all') Markdown:EOL('\n') - Markdown:CODE_FENCE_END(' ```') + WHITE_SPACE(' ') + Markdown:CODE_FENCE_END('```') Markdown:EOL('\n') Markdown:LIST_ITEM Markdown:LIST_BULLET('- ') @@ -187,7 +188,8 @@ Markdown:MARKDOWN_FILE WHITE_SPACE(' ') Markdown:CODE_FENCE_CONTENT('something second') Markdown:EOL('\n') - Markdown:CODE_FENCE_END(' ```') + WHITE_SPACE(' ') + Markdown:CODE_FENCE_END('```') Markdown:EOL('\n') Markdown:LIST_ITEM Markdown:LIST_BULLET('- ')