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('- ')