From be29abb9b7d517a4bbdfc5dd36c91f5572e60b86 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20=C4=8C=C3=AD=C5=BEek?= Date: Fri, 13 Sep 2019 23:12:40 +0200 Subject: [PATCH] Match fence char and length when matching closing fence in fenced code blocks. --- CHANGELOG.md | 6 ++++ ext/redcarpet/markdown.c | 71 +++++++++++++++++++++++++++++----------- test/markdown_test.rb | 22 +++++++++++++ 3 files changed, 79 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e7203c68..e18a5991 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +* Match fence char and length when matching closing fence in fenced code blocks. + + Fixes #208. + + *Martin Cizek, Orchitech* + ## Version 3.5.0 * Avoid mutating the options hash passed to a render object. diff --git a/ext/redcarpet/markdown.c b/ext/redcarpet/markdown.c index 95222214..9a761c58 100644 --- a/ext/redcarpet/markdown.c +++ b/ext/redcarpet/markdown.c @@ -1353,13 +1353,13 @@ is_hrule(uint8_t *data, size_t size) return n >= 3; } -/* check if a line begins with a code fence; return the - * width of the code fence */ +/* check if a line begins with a code fence matching optional opendelim; + return the width of the code fence and store the delimiter string */ static size_t -prefix_codefence(uint8_t *data, size_t size) +prefix_codefence(uint8_t *data, size_t size, struct buf *delim, struct buf *opendelim) { - size_t i = 0, n = 0; - uint8_t c; + size_t i = 0, n = 0, min_n = 3; + uint8_t c, *delim_start; /* skipping initial spaces */ if (size < 3) return 0; @@ -1367,31 +1367,46 @@ prefix_codefence(uint8_t *data, size_t size) if (data[1] == ' ') { i++; if (data[2] == ' ') { i++; } } } + delim_start = data + i; + /* looking at the hrule uint8_t */ if (i + 2 >= size || !(data[i] == '~' || data[i] == '`')) return 0; - c = data[i]; + if (opendelim && opendelim->size) { + c = opendelim->data[0]; + min_n = opendelim->size; + } else { + c = data[i]; + } /* the whole line must be the uint8_t or whitespace */ while (i < size && data[i] == c) { n++; i++; } - if (n < 3) + if (n < min_n) return 0; + if (delim) { + delim->data = delim_start; + delim->size = n; + } + return i; } /* check if a line is a code fence; return its size if it is */ +/* checking is done in fence-pair matching mode if curdelim is provided */ static size_t -is_codefence(uint8_t *data, size_t size, struct buf *syntax) +is_codefence(uint8_t *data, size_t size, struct buf *curdelim, struct buf *syntax) { size_t i = 0, syn_len = 0; uint8_t *syn_start; + struct buf delim = { 0, 0, 0, 0 }; + + i = prefix_codefence(data, size, &delim, curdelim); - i = prefix_codefence(data, size); if (i == 0) return 0; @@ -1426,10 +1441,9 @@ is_codefence(uint8_t *data, size_t size, struct buf *syntax) } } - if (syntax) { - syntax->data = syn_start; - syntax->size = syn_len; - } + /* info string must not be present at the closing fence */ + if (curdelim && curdelim->size && syn_len) + return 0; while (i < size && data[i] != '\n') { if (!_isspace(data[i])) @@ -1438,6 +1452,21 @@ is_codefence(uint8_t *data, size_t size, struct buf *syntax) i++; } + if (curdelim) { + if (curdelim->size) { + curdelim->data = NULL; + curdelim->size = 0; + } else { + curdelim->data = delim.data; + curdelim->size = delim.size; + } + } + + if (syntax) { + syntax->data = syn_start; + syntax->size = syn_len; + } + return i + 1; } @@ -1673,7 +1702,7 @@ parse_paragraph(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t /* see if a code fence starts here */ if ((rndr->ext_flags & MKDEXT_FENCED_CODE) != 0 && - is_codefence(data + i, size - i, NULL) != 0) { + is_codefence(data + i, size - i, NULL, NULL) != 0) { end = i; break; } @@ -1739,19 +1768,19 @@ parse_fencedcode(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t { size_t beg, end; struct buf *work = 0; + struct buf delim = { 0, 0, 0, 0 }; struct buf lang = { 0, 0, 0, 0 }; - beg = is_codefence(data, size, &lang); + beg = is_codefence(data, size, &delim, &lang); if (beg == 0) return 0; work = rndr_newbuf(rndr, BUFFER_BLOCK); while (beg < size) { size_t fence_end; - struct buf fence_trail = { 0, 0, 0, 0 }; - fence_end = is_codefence(data + beg, size - beg, &fence_trail); - if (fence_end != 0 && fence_trail.size == 0) { + fence_end = is_codefence(data + beg, size - beg, &delim, NULL); + if (fence_end != 0) { beg += fence_end; break; } @@ -1827,6 +1856,7 @@ parse_listitem(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t s struct buf *work = 0, *inter = 0; size_t beg = 0, end, pre, sublist = 0, orgpre = 0, i; int in_empty = 0, has_inside_empty = 0, in_fence = 0; + struct buf fence_delim = { 0, 0, 0, 0 }; /* keeping track of the first indentation prefix */ while (orgpre < 3 && orgpre < size && data[orgpre] == ' ') @@ -1876,7 +1906,7 @@ parse_listitem(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t s pre = i; if (rndr->ext_flags & MKDEXT_FENCED_CODE) { - if (is_codefence(data + beg + i, end - beg - i, NULL) != 0) + if (is_codefence(data + beg + i, end - beg - i, &fence_delim, NULL) != 0) in_fence = !in_fence; } @@ -2804,6 +2834,7 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str struct buf *text; size_t beg, end; int in_fence = 0; + struct buf fence_delim = { 0, 0, 0, 0 }; text = bufnew(64); if (!text) @@ -2833,7 +2864,7 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str beg += 3; while (beg < doc_size) { /* iterating over lines */ - if (codefences_enabled && (is_codefence(document + beg, doc_size - beg, NULL) != 0)) + if (codefences_enabled && (is_codefence(document + beg, doc_size - beg, &fence_delim, NULL) != 0)) in_fence = !in_fence; if (!in_fence && footnotes_enabled && is_footnote(document, beg, doc_size, &end, &md->footnotes_found)) diff --git a/test/markdown_test.rb b/test/markdown_test.rb index 4347be9b..e6b9a7f6 100644 --- a/test/markdown_test.rb +++ b/test/markdown_test.rb @@ -286,6 +286,28 @@ def test_that_fenced_code_copies_language_verbatim assert_equal "
x = 'foo'\n
", html end + def test_that_fenced_flag_considers_fence_type_and_length + text = <<-fenced.strip_heredoc + This is a normal text + + ~~~~ + This is some code containing a fence string + ~~~ + protected by extending the enclosing fence + ~~~~ + ``` + This is some code containing a fence string + ~~~ + protected by using the other fence type + ``` + fenced + + html = render(text, with: [:fenced_code_blocks]) + tokens = html.scan %r{