Skip to content

Commit

Permalink
Fix code span parser in GFM
Browse files Browse the repository at this point in the history
  • Loading branch information
Ekaterina Berezhko authored and Ekaterina Berezhko committed May 7, 2024
1 parent 76cec19 commit 0642c8a
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 63 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package org.intellij.markdown.flavours.gfm

import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.MarkdownTokenTypes.Companion.ESCAPED_BACKTICKS
import org.intellij.markdown.ast.*
import org.intellij.markdown.ast.impl.ListCompositeNode
import org.intellij.markdown.ast.impl.ListItemCompositeNode
Expand Down Expand Up @@ -93,16 +92,7 @@ open class TableAwareCodeSpanGeneratingProvider : GeneratingProvider {
override fun processNode(visitor: HtmlGenerator.HtmlGeneratingVisitor, text: String, node: ASTNode) {
val isInsideTable = isInsideTable(node)
val nodes = collectContentNodes(node)
val output = nodes.withIndex().joinToString(separator = "") { (i, it) ->
if (i == nodes.lastIndex && it.type == ESCAPED_BACKTICKS) {
// Backslash escapes do not work in code spans.
// Yet, a code span like `this\` is recognized as "BACKTICK", "TEXT", "ESCAPED_BACKTICKS"
// So if the last node is ESCAPED_BACKTICKS, we need to manually render it as "\"
return@joinToString "\\"
}

processChild(it, text, isInsideTable).replaceNewLines()
}.trimForCodeSpan()
val output = nodes.joinToString(separator = "") { processChild(it, text, isInsideTable).replaceNewLines() }.trimForCodeSpan()
visitor.consumeTagOpen(node, "code")
visitor.consumeHtml(output)
visitor.consumeTagClose("code")
Expand Down Expand Up @@ -130,14 +120,6 @@ open class TableAwareCodeSpanGeneratingProvider : GeneratingProvider {

protected fun collectContentNodes(node: ASTNode): List<ASTNode> {
check(node.children.size >= 2)

// Backslash escapes do not work in code spans.
// Yet, a code span like `this\` is recognized as "BACKTICK", "TEXT", "ESCAPED_BACKTICKS"
// Let's keep the last ESCAPED_BACKTICKS and manually render it as "\"
if (node.children.last().type == ESCAPED_BACKTICKS) {
return node.children.drop(1)
}

return node.children.subList(1, node.children.size - 1)
}

Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import org.intellij.markdown.lexer.GeneratedLexer;

private boolean isHeader = false;

private int codeSpanBacktickslength = 0;

private ParseDelimited parseDelimited = new ParseDelimited();

private static class ParseDelimited {
Expand Down Expand Up @@ -79,8 +81,8 @@ import org.intellij.markdown.lexer.GeneratedLexer;

parseDelimited.exitChar = last;
parseDelimited.returnType = contentsType;
// parseDelimited.inlinesAllowed = allowInlines;
parseDelimited.inlinesAllowed = true;
parseDelimited.inlinesAllowed = allowInlines;
// parseDelimited.inlinesAllowed = true;

yybegin(PARSE_DELIMITED);

Expand Down Expand Up @@ -133,7 +135,7 @@ import org.intellij.markdown.lexer.GeneratedLexer;
}

private boolean canInline() {
return yystate() == AFTER_LINE_START || yystate() == PARSE_DELIMITED && parseDelimited.inlinesAllowed;
return yystate() == AFTER_LINE_START || yystate() == PARSE_DELIMITED && parseDelimited.inlinesAllowed || yystate() == CODE_SPAN && parseDelimited.inlinesAllowed;
}

private IElementType getReturnGeneralized(IElementType defaultType) {
Expand Down Expand Up @@ -244,7 +246,7 @@ PATH=({PATH_PART}+ | ("(" {PATH_PART}* ")"? {PATH_PART}*)) ("(" {PATH_PART}* ")"
// See pushbackAutolink method
GFM_AUTOLINK = (("http" "s"? | "ftp" | "file")"://" | "www.") {HOST_PART} ("." {HOST_PART})* (":" [0-9]+)? ("/" {PATH})? "/"?

%state TAG_START, AFTER_LINE_START, PARSE_DELIMITED, CODE
%state TAG_START, AFTER_LINE_START, PARSE_DELIMITED, CODE_SPAN

%%

Expand All @@ -266,13 +268,7 @@ GFM_AUTOLINK = (("http" "s"? | "ftp" | "file")"://" | "www.") {HOST_PART} ("." {
}
}


<AFTER_LINE_START, PARSE_DELIMITED> {
// The special case of a backtick
\\"`"+ {
return getReturnGeneralized(MarkdownTokenTypes.ESCAPED_BACKTICKS);
}

// Escaping
\\[\\\"'`*_{}\[\]()#+.,!:@#$%&~<>/-] {
return getReturnGeneralized(MarkdownTokenTypes.TEXT);
Expand All @@ -281,6 +277,9 @@ GFM_AUTOLINK = (("http" "s"? | "ftp" | "file")"://" | "www.") {HOST_PART} ("." {
// Backticks (code span)
"`"+ {
if (canInline()) {
codeSpanBacktickslength = yylength();
stateStack.push(yystate());
yybegin(CODE_SPAN);
return MarkdownTokenTypes.BACKTICK;
}
return parseDelimited.returnType;
Expand All @@ -293,6 +292,19 @@ GFM_AUTOLINK = (("http" "s"? | "ftp" | "file")"://" | "www.") {HOST_PART} ("." {
}
return parseDelimited.returnType;
}
}

<CODE_SPAN> {
"`"+ {
if (yylength() == codeSpanBacktickslength) {
codeSpanBacktickslength = 0;
popState();
}
return MarkdownTokenTypes.BACKTICK;
}
}

<AFTER_LINE_START, PARSE_DELIMITED, CODE_SPAN> {

// Emphasis
{WHITE_SPACE}+ ("*" | "_") {WHITE_SPACE}+ {
Expand All @@ -314,7 +326,7 @@ GFM_AUTOLINK = (("http" "s"? | "ftp" | "file")"://" | "www.") {HOST_PART} ("." {

}

<AFTER_LINE_START> {
<AFTER_LINE_START, CODE_SPAN> {

{WHITE_SPACE}+ {
return MarkdownTokenTypes.WHITE_SPACE;
Expand All @@ -341,6 +353,9 @@ GFM_AUTOLINK = (("http" "s"? | "ftp" | "file")"://" | "www.") {HOST_PART} ("." {
return MarkdownTokenTypes.WHITE_SPACE;
}

if (yystate() == CODE_SPAN) {
popState();
}
processEol();
return MarkdownTokenTypes.EOL;
}
Expand All @@ -363,11 +378,14 @@ GFM_AUTOLINK = (("http" "s"? | "ftp" | "file")"://" | "www.") {HOST_PART} ("." {

}

<PARSE_DELIMITED> {
<PARSE_DELIMITED, CODE_SPAN> {
{EOL} { resetState(); }

{EOL} | {ANY_CHAR} {
if (yycharat(0) == parseDelimited.exitChar) {
if (yystate() == CODE_SPAN) {
stateStack.pop();
}
yybegin(stateStack.pop());
return getDelimiterTokenType(yycharat(0));
}
Expand Down
1 change: 0 additions & 1 deletion src/commonTest/kotlin/org/intellij/markdown/GfmSpecTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2101,7 +2101,6 @@ class GfmSpecTest : SpecTest(org.intellij.markdown.flavours.gfm.GFMFlavourDescri
)

@Test
@Ignore
fun testCodeSpansExample343() = doTest(
markdown = "` b `\n",
html = "<p><code> b </code></p>\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,11 +146,12 @@ class MarkdownParsingTest : TestCase() {
@Test
fun testCodeFence() {
defaultTest()
defaultTest(GFMFlavourDescriptor())
}

@Test
fun testCodeSpan() {
defaultTest()
defaultTest(GFMFlavourDescriptor())
}

@Test
Expand Down
8 changes: 3 additions & 5 deletions src/fileBasedTest/resources/data/parser/codeSpan.txt
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ Markdown:MARKDOWN_FILE
Markdown:PARAGRAPH
Markdown:CODE_SPAN
Markdown:BACKTICK('`')
Markdown:TEXT('foo')
Markdown:ESCAPED_BACKTICKS('\`')
Markdown:TEXT('foo\')
Markdown:BACKTICK('`')
Markdown:TEXT('bar')
Markdown:CODE_SPAN
Markdown:BACKTICK('`')
Expand All @@ -50,9 +50,7 @@ Markdown:MARKDOWN_FILE
Markdown:PARAGRAPH
Markdown:AUTOLINK
Markdown:<('<')
Markdown:AUTOLINK('http://foo.bar.')
Markdown:BACKTICK('`')
Markdown:AUTOLINK('baz')
Markdown:AUTOLINK('http://foo.bar.`baz')
Markdown:>('>')
Markdown:BACKTICK('`')
Markdown:EOL('\n')
Expand Down

0 comments on commit 0642c8a

Please sign in to comment.