Skip to content

Commit 03b664d

Browse files
authored
Merge pull request #681 from orchitech/match-closing-fence-type-and-length
Match closing fence char and length in fenced code blocks
2 parents 2357f9a + 838d2e2 commit 03b664d

3 files changed

Lines changed: 81 additions & 20 deletions

File tree

CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
11
# Changelog
22

3+
4+
* Match fence char and length when matching closing fence in fenced code blocks.
5+
6+
Fixes #208.
7+
8+
*Martin Cizek, Orchitech*
9+
310
* Consider `<center>` as a block-level element.
411

512
Refs #702.
@@ -26,6 +33,7 @@
2633

2734
Reported by *Johan Smits*.
2835

36+
2937
## Version 3.5.0
3038

3139
* Avoid mutating the options hash passed to a render object.

ext/redcarpet/markdown.c

Lines changed: 51 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1353,45 +1353,60 @@ is_hrule(uint8_t *data, size_t size)
13531353
return n >= 3;
13541354
}
13551355

1356-
/* check if a line begins with a code fence; return the
1357-
* width of the code fence */
1356+
/* check if a line begins with a code fence matching optional opendelim;
1357+
return the width of the code fence and store the delimiter string */
13581358
static size_t
1359-
prefix_codefence(uint8_t *data, size_t size)
1359+
prefix_codefence(uint8_t *data, size_t size, struct buf *delim, struct buf *opendelim)
13601360
{
1361-
size_t i = 0, n = 0;
1362-
uint8_t c;
1361+
size_t i = 0, n = 0, min_n = 3;
1362+
uint8_t c, *delim_start;
13631363

13641364
/* skipping initial spaces */
13651365
if (size < 3) return 0;
13661366
if (data[0] == ' ') { i++;
13671367
if (data[1] == ' ') { i++;
13681368
if (data[2] == ' ') { i++; } } }
13691369

1370+
delim_start = data + i;
1371+
13701372
/* looking at the hrule uint8_t */
13711373
if (i + 2 >= size || !(data[i] == '~' || data[i] == '`'))
13721374
return 0;
13731375

1374-
c = data[i];
1376+
if (opendelim && opendelim->size) {
1377+
c = opendelim->data[0];
1378+
min_n = opendelim->size;
1379+
} else {
1380+
c = data[i];
1381+
}
13751382

13761383
/* the whole line must be the uint8_t or whitespace */
13771384
while (i < size && data[i] == c) {
13781385
n++; i++;
13791386
}
13801387

1381-
if (n < 3)
1388+
if (n < min_n)
13821389
return 0;
13831390

1391+
if (delim) {
1392+
delim->data = delim_start;
1393+
delim->size = n;
1394+
}
1395+
13841396
return i;
13851397
}
13861398

13871399
/* check if a line is a code fence; return its size if it is */
1400+
/* checking is done in fence-pair matching mode if curdelim is provided */
13881401
static size_t
1389-
is_codefence(uint8_t *data, size_t size, struct buf *syntax)
1402+
is_codefence(uint8_t *data, size_t size, struct buf *curdelim, struct buf *syntax)
13901403
{
13911404
size_t i = 0, syn_len = 0;
13921405
uint8_t *syn_start;
1406+
struct buf delim = { 0, 0, 0, 0 };
1407+
1408+
i = prefix_codefence(data, size, &delim, curdelim);
13931409

1394-
i = prefix_codefence(data, size);
13951410
if (i == 0)
13961411
return 0;
13971412

@@ -1426,10 +1441,9 @@ is_codefence(uint8_t *data, size_t size, struct buf *syntax)
14261441
}
14271442
}
14281443

1429-
if (syntax) {
1430-
syntax->data = syn_start;
1431-
syntax->size = syn_len;
1432-
}
1444+
/* info string must not be present at the closing fence */
1445+
if (curdelim && curdelim->size && syn_len)
1446+
return 0;
14331447

14341448
while (i < size && data[i] != '\n') {
14351449
if (!_isspace(data[i]))
@@ -1438,6 +1452,21 @@ is_codefence(uint8_t *data, size_t size, struct buf *syntax)
14381452
i++;
14391453
}
14401454

1455+
if (curdelim) {
1456+
if (curdelim->size) {
1457+
curdelim->data = NULL;
1458+
curdelim->size = 0;
1459+
} else {
1460+
curdelim->data = delim.data;
1461+
curdelim->size = delim.size;
1462+
}
1463+
}
1464+
1465+
if (syntax) {
1466+
syntax->data = syn_start;
1467+
syntax->size = syn_len;
1468+
}
1469+
14411470
return i + 1;
14421471
}
14431472

@@ -1673,7 +1702,7 @@ parse_paragraph(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t
16731702

16741703
/* see if a code fence starts here */
16751704
if ((rndr->ext_flags & MKDEXT_FENCED_CODE) != 0 &&
1676-
is_codefence(data + i, size - i, NULL) != 0) {
1705+
is_codefence(data + i, size - i, NULL, NULL) != 0) {
16771706
end = i;
16781707
break;
16791708
}
@@ -1739,19 +1768,19 @@ parse_fencedcode(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t
17391768
{
17401769
size_t beg, end;
17411770
struct buf *work = 0;
1771+
struct buf delim = { 0, 0, 0, 0 };
17421772
struct buf lang = { 0, 0, 0, 0 };
17431773

1744-
beg = is_codefence(data, size, &lang);
1774+
beg = is_codefence(data, size, &delim, &lang);
17451775
if (beg == 0) return 0;
17461776

17471777
work = rndr_newbuf(rndr, BUFFER_BLOCK);
17481778

17491779
while (beg < size) {
17501780
size_t fence_end;
1751-
struct buf fence_trail = { 0, 0, 0, 0 };
17521781

1753-
fence_end = is_codefence(data + beg, size - beg, &fence_trail);
1754-
if (fence_end != 0 && fence_trail.size == 0) {
1782+
fence_end = is_codefence(data + beg, size - beg, &delim, NULL);
1783+
if (fence_end != 0) {
17551784
beg += fence_end;
17561785
break;
17571786
}
@@ -1827,6 +1856,7 @@ parse_listitem(struct buf *ob, struct sd_markdown *rndr, uint8_t *data, size_t s
18271856
struct buf *work = 0, *inter = 0;
18281857
size_t beg = 0, end, pre, sublist = 0, orgpre = 0, i;
18291858
int in_empty = 0, has_inside_empty = 0, in_fence = 0;
1859+
struct buf fence_delim = { 0, 0, 0, 0 };
18301860

18311861
/* keeping track of the first indentation prefix */
18321862
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
18761906
pre = i;
18771907

18781908
if (rndr->ext_flags & MKDEXT_FENCED_CODE) {
1879-
if (is_codefence(data + beg + i, end - beg - i, NULL) != 0)
1909+
if (is_codefence(data + beg + i, end - beg - i, &fence_delim, NULL) != 0)
18801910
in_fence = !in_fence;
18811911
}
18821912

@@ -2804,6 +2834,7 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
28042834
struct buf *text;
28052835
size_t beg, end;
28062836
int in_fence = 0;
2837+
struct buf fence_delim = { 0, 0, 0, 0 };
28072838

28082839
text = bufnew(64);
28092840
if (!text)
@@ -2833,7 +2864,7 @@ sd_markdown_render(struct buf *ob, const uint8_t *document, size_t doc_size, str
28332864
beg += 3;
28342865

28352866
while (beg < doc_size) { /* iterating over lines */
2836-
if (codefences_enabled && (is_codefence(document + beg, doc_size - beg, NULL) != 0))
2867+
if (codefences_enabled && (is_codefence(document + beg, doc_size - beg, &fence_delim, NULL) != 0))
28372868
in_fence = !in_fence;
28382869

28392870
if (!in_fence && footnotes_enabled && is_footnote(document, beg, doc_size, &end, &md->footnotes_found))

test/markdown_test.rb

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,28 @@ def test_that_fenced_code_copies_language_verbatim
310310
assert_equal "<pre><code class=\"rust,no_run\">x = &#39;foo&#39;\n</code></pre>", html
311311
end
312312

313+
def test_that_fenced_flag_considers_fence_type_and_length
314+
text = <<-fenced.strip_heredoc
315+
This is a normal text
316+
317+
~~~~
318+
This is some code containing a fence string
319+
~~~
320+
protected by extending the enclosing fence
321+
~~~~
322+
```
323+
This is some code containing a fence string
324+
~~~
325+
protected by using the other fence type
326+
```
327+
fenced
328+
329+
html = render(text, with: [:fenced_code_blocks])
330+
tokens = html.scan %r{<code|</code|[`~]+}
331+
expected = ['<code', '~~~', '</code'] * 2
332+
assert_equal expected, tokens
333+
end
334+
313335
def test_that_indented_flag_works
314336
text = <<-indented.strip_heredoc
315337
This is a simple text

0 commit comments

Comments
 (0)