diff --git a/src/commonMain/kotlin/org/intellij/markdown/flavours/gfm/table/GitHubTableMarkerBlock.kt b/src/commonMain/kotlin/org/intellij/markdown/flavours/gfm/table/GitHubTableMarkerBlock.kt index f89c23f9..74fda6ae 100644 --- a/src/commonMain/kotlin/org/intellij/markdown/flavours/gfm/table/GitHubTableMarkerBlock.kt +++ b/src/commonMain/kotlin/org/intellij/markdown/flavours/gfm/table/GitHubTableMarkerBlock.kt @@ -1,5 +1,6 @@ package org.intellij.markdown.flavours.gfm.table +import org.intellij.markdown.MarkdownTokenTypes import org.intellij.markdown.flavours.gfm.GFMElementTypes import org.intellij.markdown.flavours.gfm.GFMTokenTypes import org.intellij.markdown.parser.LookaheadText @@ -34,6 +35,7 @@ class GitHubTableMarkerBlock(pos: LookaheadText.Position, } // That means it's table header separator line if (currentLine == 1) { + addBlockQuoteMarkersProduction(pos, lineConstraints) val separatorStart = pos.offset + 1 + lineConstraints.getCharsEaten(pos.currentLine) productionHolder.addProduction(listOf(SequentialParser.Node(separatorStart..pos.nextLineOrEofOffset, GFMTokenTypes.TABLE_SEPARATOR))) @@ -48,6 +50,7 @@ class GitHubTableMarkerBlock(pos: LookaheadText.Position, if (cellsAndSeps.isEmpty()) { return MarkerBlock.ProcessingResult.DEFAULT } + addBlockQuoteMarkersProduction(pos, lineConstraints) productionHolder.addProduction( listOf(SequentialParser.Node(cellsAndSeps.first().range.first..cellsAndSeps.last().range.last, GFMElementTypes.ROW)) @@ -65,6 +68,35 @@ class GitHubTableMarkerBlock(pos: LookaheadText.Position, override fun allowsSubBlocks() = false + /** + * Emits [MarkdownTokenTypes.BLOCK_QUOTE] tokens for the block quote markers eaten by the constraints + * of a table continuation line. Without these productions the `>` markers would become part of + * the whitespace between the table rows and could be lost on a whitespace-normalizing tree mutation. + */ + private fun addBlockQuoteMarkersProduction(pos: LookaheadText.Position, lineConstraints: MarkdownConstraints) { + val line = pos.currentLine + val prefixLength = lineConstraints.getCharsEaten(line) + val lineStartOffset = pos.offset + 1 + val nodes = ArrayList(0) + var index = 0 + while (index < prefixLength) { + if (line[index] == '>') { + var end = index + 1 + if (end < prefixLength && line[end] == ' ') { + end++ + } + nodes.add(SequentialParser.Node(lineStartOffset + index..lineStartOffset + end, + MarkdownTokenTypes.BLOCK_QUOTE)) + index = end + } else { + index++ + } + } + if (nodes.isNotEmpty()) { + productionHolder.addProduction(nodes) + } + } + private fun fillCells(pos: LookaheadText.Position, lineConstraints: MarkdownConstraints = constraints): List { val result = ArrayList() diff --git a/src/fileBasedTest/kotlin/org/intellij/markdown/MarkdownParsingTest.kt b/src/fileBasedTest/kotlin/org/intellij/markdown/MarkdownParsingTest.kt index c6295fa3..4acf452d 100644 --- a/src/fileBasedTest/kotlin/org/intellij/markdown/MarkdownParsingTest.kt +++ b/src/fileBasedTest/kotlin/org/intellij/markdown/MarkdownParsingTest.kt @@ -368,6 +368,13 @@ Markdown:MARKDOWN_FILE defaultTest(GFMFlavourDescriptor()) } + // IJPL-95908: block quote markers of table lines should be parsed as BLOCK_QUOTE tokens, + // not whitespace, so that they survive whitespace-normalizing tree mutations + @Test + fun testTableInsideBlockQuote() { + defaultTest(GFMFlavourDescriptor()) + } + @Test fun testTableInsideOrderedListItem() { defaultTest(GFMFlavourDescriptor()) diff --git a/src/fileBasedTest/resources/data/parser/tableInsideBlockQuote.md b/src/fileBasedTest/resources/data/parser/tableInsideBlockQuote.md new file mode 100644 index 00000000..1292e0c9 --- /dev/null +++ b/src/fileBasedTest/resources/data/parser/tableInsideBlockQuote.md @@ -0,0 +1,8 @@ +>| one | two | three | four | +>|------|-----|-------|---------| +>| 1 | 2 | 3 | 4 | +>| odin | dva | tri | chetyre | + +>>| nested | quote | +>>|--------|-------| +>>| a | b | diff --git a/src/fileBasedTest/resources/data/parser/tableInsideBlockQuote.txt b/src/fileBasedTest/resources/data/parser/tableInsideBlockQuote.txt new file mode 100644 index 00000000..23e509d7 --- /dev/null +++ b/src/fileBasedTest/resources/data/parser/tableInsideBlockQuote.txt @@ -0,0 +1,116 @@ +Markdown:MARKDOWN_FILE + Markdown:BLOCK_QUOTE + Markdown:BLOCK_QUOTE('>') + Markdown:TABLE + Markdown:HEADER + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('one') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('two') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('three') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('four') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:EOL('\n') + Markdown:BLOCK_QUOTE('>') + Markdown:TABLE_SEPARATOR('|------|-----|-------|---------|') + Markdown:EOL('\n') + Markdown:BLOCK_QUOTE('>') + Markdown:ROW + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('1') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('2') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('3') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('4') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:EOL('\n') + Markdown:BLOCK_QUOTE('>') + Markdown:ROW + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('odin') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('dva') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('tri') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('chetyre') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:EOL('\n') + Markdown:EOL('\n') + Markdown:BLOCK_QUOTE + Markdown:BLOCK_QUOTE('>') + Markdown:BLOCK_QUOTE + Markdown:BLOCK_QUOTE('>') + Markdown:TABLE + Markdown:HEADER + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('nested') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('quote') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:EOL('\n') + Markdown:BLOCK_QUOTE('>') + Markdown:BLOCK_QUOTE('>') + Markdown:TABLE_SEPARATOR('|--------|-------|') + Markdown:EOL('\n') + Markdown:BLOCK_QUOTE('>') + Markdown:BLOCK_QUOTE('>') + Markdown:ROW + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('a') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:CELL + WHITE_SPACE(' ') + Markdown:TEXT('b') + WHITE_SPACE(' ') + Markdown:TABLE_SEPARATOR('|') + Markdown:EOL('\n') \ No newline at end of file diff --git a/src/fileBasedTest/resources/data/parser/tableInsideBlockQuoteInListItem.txt b/src/fileBasedTest/resources/data/parser/tableInsideBlockQuoteInListItem.txt index d4d931db..c93c4dcd 100644 --- a/src/fileBasedTest/resources/data/parser/tableInsideBlockQuoteInListItem.txt +++ b/src/fileBasedTest/resources/data/parser/tableInsideBlockQuoteInListItem.txt @@ -21,10 +21,12 @@ Markdown:MARKDOWN_FILE WHITE_SPACE(' ') Markdown:TABLE_SEPARATOR('|') Markdown:EOL('\n') - WHITE_SPACE(' > ') + WHITE_SPACE(' ') + Markdown:BLOCK_QUOTE('> ') Markdown:TABLE_SEPARATOR('| --- |') Markdown:EOL('\n') - WHITE_SPACE(' > ') + WHITE_SPACE(' ') + Markdown:BLOCK_QUOTE('> ') Markdown:ROW Markdown:TABLE_SEPARATOR('|') Markdown:CELL diff --git a/src/fileBasedTest/resources/data/parser/tableInsideBlockQuoteWithMissingLastPipe.txt b/src/fileBasedTest/resources/data/parser/tableInsideBlockQuoteWithMissingLastPipe.txt index 070966f8..87978d2b 100644 --- a/src/fileBasedTest/resources/data/parser/tableInsideBlockQuoteWithMissingLastPipe.txt +++ b/src/fileBasedTest/resources/data/parser/tableInsideBlockQuoteWithMissingLastPipe.txt @@ -20,10 +20,10 @@ Markdown:MARKDOWN_FILE WHITE_SPACE(' ') Markdown:TABLE_SEPARATOR('|') Markdown:EOL('\n') - WHITE_SPACE('> ') + Markdown:BLOCK_QUOTE('> ') Markdown:TABLE_SEPARATOR('| --- | ------ | ---- |') Markdown:EOL('\n') - WHITE_SPACE('> ') + Markdown:BLOCK_QUOTE('> ') Markdown:ROW Markdown:TABLE_SEPARATOR('|') Markdown:CELL @@ -42,7 +42,7 @@ Markdown:MARKDOWN_FILE WHITE_SPACE(' ') Markdown:TABLE_SEPARATOR('|') Markdown:EOL('\n') - WHITE_SPACE('> ') + Markdown:BLOCK_QUOTE('> ') Markdown:ROW Markdown:TABLE_SEPARATOR('|') Markdown:CELL