Skip to content

Commit e822f2b

Browse files
dwellemarijnh
authored andcommitted
[markdown mode] improve fencedCodeBlocks and code behavior
1 parent c06c273 commit e822f2b

File tree

2 files changed

+73
-17
lines changed

2 files changed

+73
-17
lines changed

mode/markdown/markdown.js

+20-16
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
8989
, atxHeaderRE = modeCfg.allowAtxHeaderWithoutSpace ? /^(#+)/ : /^(#+)(?: |$)/
9090
, setextHeaderRE = /^ *(?:\={1,}|-{1,})\s*$/
9191
, textRE = /^[^#!\[\]*_\\<>` "'(~:]+/
92-
, fencedCodeRE = /^(~~~+|```+)[ \t]*([\w+#-]*)/
92+
, fencedCodeRE = /^(~~~+|```+)[ \t]*([\w+#-]*)[^\n`]*$/
9393
, linkDefRE = /^\s*\[[^\]]+?\]:\s*\S+(\s*\S*\s*)?$/ // naive link-definition
9494
, punctuation = /[!\"#$%&\'()*+,\-\.\/:;<=>?@\[\\\]^_`{|}~]/
9595
, expandedTab = " " // CommonMark specifies tab as 4 spaces
@@ -133,13 +133,13 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
133133
state.trailingSpaceNewLine = false;
134134
// Mark this line as blank
135135
state.prevLine = state.thisLine
136-
state.thisLine = null
136+
state.thisLine = {stream: null}
137137
return null;
138138
}
139139

140140
function blockNormal(stream, state) {
141141
var firstTokenOnLine = stream.column() === state.indentation;
142-
var prevLineLineIsEmpty = lineIsEmpty(state.prevLine);
142+
var prevLineLineIsEmpty = lineIsEmpty(state.prevLine.stream);
143143
var prevLineIsIndentedCode = state.indentedCode;
144144
var prevLineIsHr = state.hr;
145145
var prevLineIsList = state.list !== false;
@@ -176,7 +176,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
176176
state.indentation <= maxNonCodeIndentation && stream.match(hrRE);
177177

178178
var match = null;
179-
if (state.indentationDiff >= 4 && (prevLineIsIndentedCode || prevLineLineIsEmpty)) {
179+
if (state.indentationDiff >= 4 && (prevLineIsIndentedCode || state.prevLine.fencedCodeEnd ||
180+
state.prevLine.header || prevLineLineIsEmpty)) {
180181
stream.skipToEnd();
181182
state.indentedCode = true;
182183
return tokenTypes.code;
@@ -185,6 +186,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
185186
} else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(atxHeaderRE)) && match[1].length <= 6) {
186187
state.quote = 0;
187188
state.header = match[1].length;
189+
state.thisLine.header = true;
188190
if (modeCfg.highlightFormatting) state.formatting = "header";
189191
state.f = state.inline;
190192
return getType(state);
@@ -211,7 +213,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
211213
return getType(state);
212214
} else if (firstTokenOnLine && state.indentation <= maxNonCodeIndentation && (match = stream.match(fencedCodeRE, true))) {
213215
state.quote = 0;
214-
state.fencedChars = match[1]
216+
state.fencedEndRE = new RegExp(match[1] + "+ *$");
215217
// try switching mode
216218
state.localMode = modeCfg.fencedCodeBlockHighlighting && getMode(match[2]);
217219
if (state.localMode) state.localState = CodeMirror.startState(state.localMode);
@@ -240,6 +242,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
240242
stream.skipToEnd();
241243
if (modeCfg.highlightFormatting) state.formatting = "header";
242244
}
245+
state.thisLine.header = true;
243246
state.f = state.inline;
244247
return getType(state);
245248
} else if (isHr) {
@@ -269,20 +272,21 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
269272
}
270273

271274
function local(stream, state) {
272-
var hasExitedList = state.indentation < state.listStack[state.listStack.length - 1];
273-
if (state.fencedChars && (hasExitedList || stream.match(state.fencedChars))) {
275+
var currListInd = state.listStack[state.listStack.length - 1] || 0;
276+
var hasExitedList = state.indentation < currListInd;
277+
var maxFencedEndInd = currListInd + 3;
278+
if (state.fencedEndRE && state.indentation <= maxFencedEndInd && (hasExitedList || stream.match(state.fencedEndRE))) {
274279
if (modeCfg.highlightFormatting) state.formatting = "code-block";
275280
var returnType;
276281
if (!hasExitedList) returnType = getType(state)
277282
state.localMode = state.localState = null;
278283
state.block = blockNormal;
279284
state.f = inlineNormal;
280-
state.fencedChars = null;
285+
state.fencedEndRE = null;
281286
state.code = 0
287+
state.thisLine.fencedCodeEnd = true;
282288
if (hasExitedList) return switchBlock(stream, state, state.block);
283289
return returnType;
284-
} else if (state.fencedChars && stream.skipTo(state.fencedChars)) {
285-
return "comment"
286290
} else if (state.localMode) {
287291
return state.localMode.token(stream, state.localState);
288292
} else {
@@ -716,8 +720,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
716720
return {
717721
f: blockNormal,
718722

719-
prevLine: null,
720-
thisLine: null,
723+
prevLine: {stream: null},
724+
thisLine: {stream: null},
721725

722726
block: blockNormal,
723727
htmlState: null,
@@ -744,7 +748,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
744748
trailingSpaceNewLine: false,
745749
strikethrough: false,
746750
emoji: false,
747-
fencedChars: null
751+
fencedEndRE: null
748752
};
749753
},
750754

@@ -783,7 +787,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
783787
trailingSpace: s.trailingSpace,
784788
trailingSpaceNewLine: s.trailingSpaceNewLine,
785789
md_inside: s.md_inside,
786-
fencedChars: s.fencedChars
790+
fencedEndRE: s.fencedEndRE
787791
};
788792
},
789793

@@ -792,8 +796,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
792796
// Reset state.formatting
793797
state.formatting = false;
794798

795-
if (stream != state.thisLine) {
796799
// Reset state.header
800+
if (stream != state.thisLine.stream) {
797801
state.header = 0;
798802

799803
if (stream.match(/^\s*$/, true)) {
@@ -802,7 +806,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
802806
}
803807

804808
state.prevLine = state.thisLine
805-
state.thisLine = stream
809+
state.thisLine = {stream: stream}
806810

807811
// Reset state.taskList
808812
state.taskList = false;

mode/markdown/test.js

+53-1
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,21 @@
151151
"Foo",
152152
" Bar");
153153

154+
MT("codeBlocksAfterATX",
155+
"[header&header-1 # foo]",
156+
" [comment code]");
157+
158+
MT("codeBlocksAfterSetext",
159+
"[header&header-2 foo]",
160+
"[header&header-2 ---]",
161+
" [comment code]");
162+
163+
MT("codeBlocksAfterFencedCode",
164+
"[comment ```]",
165+
"[comment foo]",
166+
"[comment ```]",
167+
" [comment code]");
168+
154169
// Inline code using backticks
155170
MT("inlineCodeUsingBackticks",
156171
"foo [comment `bar`]");
@@ -192,6 +207,10 @@
192207
MT("closedBackticks",
193208
"[comment ``foo ``` bar` hello``] world");
194209

210+
// info string cannot contain backtick, thus should result in inline code
211+
MT("closingFencedMarksOnSameLine",
212+
"[comment ``` code ```] foo");
213+
195214
// atx headers
196215
// http://daringfireball.net/projects/markdown/syntax#header
197216

@@ -237,7 +256,7 @@
237256

238257
MT("atxIndentedTooMuch",
239258
"[header&header-1 # foo]",
240-
" # bar");
259+
" [comment # bar]");
241260

242261
// disable atx inside blockquote until we implement proper blockquote inner mode
243262
// TODO: fix to be CommonMark-compliant
@@ -1100,6 +1119,39 @@
11001119
"[comment ```]",
11011120
"baz");
11021121

1122+
MT("fencedCodeBlocks_invalidClosingFence_trailingText",
1123+
"[comment ```]",
1124+
"[comment foo]",
1125+
"[comment ``` must not have trailing text]",
1126+
"[comment baz]");
1127+
1128+
MT("fencedCodeBlocks_invalidClosingFence_trailingTabs",
1129+
"[comment ```]",
1130+
"[comment foo]",
1131+
"[comment ```\t]",
1132+
"[comment baz]");
1133+
1134+
MT("fencedCodeBlocks_validClosingFence",
1135+
"[comment ```]",
1136+
"[comment foo]",
1137+
// may have trailing spaces
1138+
"[comment ``` ]",
1139+
"baz");
1140+
1141+
MT("fencedCodeBlocksInList_closingFenceIndented",
1142+
"[variable-2 - list]",
1143+
" [variable-2&comment ```]",
1144+
" [comment foo]",
1145+
" [variable-2&comment ```]",
1146+
" [variable-2 baz]");
1147+
1148+
MT("fencedCodeBlocksInList_closingFenceIndentedTooMuch",
1149+
"[variable-2 - list]",
1150+
" [variable-2&comment ```]",
1151+
" [comment foo]",
1152+
" [comment ```]",
1153+
" [comment baz]");
1154+
11031155
MT("fencedCodeBlockModeSwitching",
11041156
"[comment ```javascript]",
11051157
"[variable foo]",

0 commit comments

Comments
 (0)