Skip to content

Commit

Permalink
[markdown mode] More improvements to parsing and tester.
Browse files Browse the repository at this point in the history
Markdown mode

- Add additional link tests and correctly match link titles on next
  line.

- Adjust tests code so sections (including "Basics") can be rearranged
  and removed easily without breaking the script.

- Add tests and fix highlighting for lists.

Test harness

- Sort styles in mode test script so "quote string" = "string quote".
  • Loading branch information
Brandon Frohs authored and marijnh committed Sep 5, 2012
1 parent 2189810 commit 367cfc6
Show file tree
Hide file tree
Showing 3 changed files with 276 additions and 10 deletions.
53 changes: 48 additions & 5 deletions mode/markdown/markdown.js
Expand Up @@ -24,7 +24,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
, ulRE = /^[*\-+]\s+/
, olRE = /^[0-9]+\.\s+/
, headerRE = /^(?:\={1,}|-{1,})$/
, textRE = /^[^\[*_\\<>` ]+/;
, textRE = /^[^\[*_\\<>` "'(]+/;

function switchInline(stream, state, f) {
state.f = state.inline = f;
Expand All @@ -40,6 +40,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
// Blocks

function blankLine(state) {
// Reset linkTitle state
state.linkTitle = false;
// Reset CODE state
state.code = false;
// Reset EM state
Expand All @@ -57,8 +59,18 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {

function blockNormal(stream, state) {
var match;

if (state.list !== false && state.indentationDiff >= 0) { // Continued list
if (state.indentationDiff < 4) { // Only adjust indentation if *not* a code block
state.indentation -= state.indentationDiff;
}
state.list = null;
} else { // No longer a list
state.list = false;
}

if (state.indentationDiff >= 4) {
state.indentation -= state.indentationDiff;
state.indentation -= 4;
stream.skipToEnd();
return code;
} else if (stream.eatSpace()) {
Expand All @@ -73,8 +85,8 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
} else if (stream.match(hrRE, true)) {
return hr;
} else if (match = stream.match(ulRE, true) || stream.match(olRE, true)) {
state.indentation += match[0].length;
return list;
state.indentation += 4;
state.list = true;
}

return switchInline(stream, state, state.inline);
Expand Down Expand Up @@ -106,6 +118,7 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {

if (state.header) { styles.push(header); }
if (state.quote) { styles.push(quote); }
if (state.list !== false) { styles.push(list); }

return styles.length ? styles.join(' ') : null;
}
Expand All @@ -122,13 +135,32 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
if (typeof style !== 'undefined')
return style;

if (state.list) { // List marker (*, +, -, 1., etc)
state.list = null;
return list;
}

var ch = stream.next();

if (ch === '\\') {
stream.next();
return getType(state);
}

// Matches link titles present on next line
if (state.linkTitle) {
state.linkTitle = false;
var matchCh = ch;
if (ch === '(') {
matchCh = ')';
}
matchCh = (matchCh+'').replace(/([.?*+^$[\]\\(){}|-])/g, "\\$1");
var regex = '^\\s*(?:[^' + matchCh + '\\\\]+|\\\\\\\\|\\\\.)' + matchCh;
if (stream.match(new RegExp(regex), true)) {
return linkhref;
}
}

if (ch === '`') {
var t = getType(state);
var before = stream.pos;
Expand Down Expand Up @@ -243,7 +275,14 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {
if(stream.eatSpace()){
return null;
}
stream.match(/^[^\s]+(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true);
// Match URL
stream.match(/^[^\s]+/, true);
// Check for link title
if (stream.peek() === undefined) { // End of line, set flag to check next line
state.linkTitle = true;
} else { // More content on line, check if link title
stream.match(/^(?:\s+(?:"(?:[^"\\]|\\\\|\\.)+"|'(?:[^'\\]|\\\\|\\.)+'|\((?:[^)\\]|\\\\|\\.)+\)))?/, true);
}
state.f = state.inline = inlineNormal;
return linkhref;
}
Expand Down Expand Up @@ -279,9 +318,11 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {

inline: inlineNormal,
text: handleText,
linkTitle: false,
em: false,
strong: false,
header: false,
list: false,
quote: false
};
},
Expand All @@ -296,9 +337,11 @@ CodeMirror.defineMode("markdown", function(cmCfg, modeCfg) {

inline: s.inline,
text: s.text,
linkTitle: s.linkTitle,
em: s.em,
strong: s.strong,
header: s.header,
list: s.list,
quote: s.quote,
md_inside: s.md_inside
};
Expand Down
231 changes: 226 additions & 5 deletions mode/markdown/test.html
Expand Up @@ -14,11 +14,14 @@
</head>
<body>
<h1>Tests for the Markdown Mode</h1>
<h2>Basics</h2>
<script language="javascript">
// Initiate ModeTest and set defaults
MT = ModeTest;
MT.modeName = 'markdown';

</script>

<h2>Basics</h2>
<script language="javascript">
MT.test('foo',
null, 'foo');
</script>
Expand Down Expand Up @@ -167,6 +170,203 @@ <h2>Blockquotes</h2>
null, 'hello');
</script>

<h2>Lists</h2>
<script language="javascript">

// Check list types
MT.test('* foo\n* bar',
'string', '* foo',
'string', '* bar');
MT.test('+ foo\n+ bar',
'string', '+ foo',
'string', '+ bar');
MT.test('- foo\n- bar',
'string', '- foo',
'string', '- bar');
MT.test('1. foo\n2. bar',
'string', '1. foo',
'string', '2. bar');

// Formatting in lists (*)
MT.test('* *foo* bar\n\n* **foo** bar\n\n* ***foo*** bar\n\n* `foo` bar',
'string', '* ',
'string em', '*foo*',
'string', ' bar',
'string', '* ',
'string strong', '**foo**',
'string', ' bar',
'string', '* ',
'string strong', '**',
'string emstrong', '*foo**',
'string em', '*',
'string', ' bar',
'string', '* ',
'string comment', '`foo`',
'string', ' bar');
// Formatting in lists (+)
MT.test('+ *foo* bar\n\n+ **foo** bar\n\n+ ***foo*** bar\n\n+ `foo` bar',
'string', '+ ',
'string em', '*foo*',
'string', ' bar',
'string', '+ ',
'string strong', '**foo**',
'string', ' bar',
'string', '+ ',
'string strong', '**',
'string emstrong', '*foo**',
'string em', '*',
'string', ' bar',
'string', '+ ',
'string comment', '`foo`',
'string', ' bar');
// Formatting in lists (-)
MT.test('- *foo* bar\n\n- **foo** bar\n\n- ***foo*** bar\n\n- `foo` bar',
'string', '- ',
'string em', '*foo*',
'string', ' bar',
'string', '- ',
'string strong', '**foo**',
'string', ' bar',
'string', '- ',
'string strong', '**',
'string emstrong', '*foo**',
'string em', '*',
'string', ' bar',
'string', '- ',
'string comment', '`foo`',
'string', ' bar');
// Formatting in lists (1.)
MT.test('1. *foo* bar\n\n2. **foo** bar\n\n3. ***foo*** bar\n\n4. `foo` bar',
'string', '1. ',
'string em', '*foo*',
'string', ' bar',
'string', '2. ',
'string strong', '**foo**',
'string', ' bar',
'string', '3. ',
'string strong', '**',
'string emstrong', '*foo**',
'string em', '*',
'string', ' bar',
'string', '4. ',
'string comment', '`foo`',
'string', ' bar');

// Paragraph lists
MT.test('* foo\n\n* bar',
'string', '* foo',
'string', '* bar');

// Multi-paragraph lists
//
// 4 spaces
MT.test('* foo\n\n* bar\n\n hello',
'string', '* foo',
'string', '* bar',
null, ' ',
'string', 'hello');
// 4 spaces, extra blank lines (should still be list, per Dingus)
MT.test('* foo\n\n* bar\n\n\n hello',
'string', '* foo',
'string', '* bar',
null, ' ',
'string', 'hello');
// 4 spaces, plus 1 space (should still be list, per Dingus)
MT.test('* foo\n\n* bar\n\n hello\n\n world',
'string', '* foo',
'string', '* bar',
null, ' ',
'string', 'hello',
null, ' ',
'string', 'world');
// 1 tab
MT.test('* foo\n\n* bar\n\n\thello',
'string', '* foo',
'string', '* bar',
null, '\t',
'string', 'hello');
// No indent
MT.test('* foo\n\n* bar\n\nhello',
'string', '* foo',
'string', '* bar',
null, 'hello');
// Blockquote
MT.test('* foo\n\n* bar\n\n > hello',
'string', '* foo',
'string', '* bar',
null, ' ',
'string quote', '> hello');
// Code block
MT.test('* foo\n\n* bar\n\n > hello\n\n world',
'string', '* foo',
'string', '* bar',
null, ' ',
'comment', '> hello',
null, ' ',
'string', 'world');
// Code block followed by text
MT.test('* foo\n\n bar\n\n hello\n\n world',
'string', '* foo',
null, ' ',
'string', 'bar',
null, ' ',
'comment', 'hello',
null, ' ',
'string', 'world');

// Nested list
//
// *
MT.test('* foo\n\n * bar',
'string', '* foo',
null, ' ',
'string', '* bar');
// +
MT.test('+ foo\n\n + bar',
'string', '+ foo',
null, ' ',
'string', '+ bar');
// -
MT.test('- foo\n\n - bar',
'string', '- foo',
null, ' ',
'string', '- bar');
// 1.
MT.test('1. foo\n\n 2. bar',
'string', '1. foo',
null, ' ',
'string', '2. bar');
// Mixed
MT.test('* foo\n\n + bar\n\n - hello\n\n 1. world',
'string', '* foo',
null, ' ',
'string', '+ bar',
null, ' ',
'string', '- hello',
null, ' ',
'string', '1. world');
// Blockquote
MT.test('* foo\n\n + bar\n\n > hello',
'string', '* foo',
null, ' ',
'string', '+ bar',
null, ' ',
'quote string', '> hello');
// Code
MT.test('* foo\n\n + bar\n\n hello',
'string', '* foo',
null, ' ',
'string', '+ bar',
null, ' ',
'comment', 'hello');
// Code followed by text
MT.test('* foo\n\n bar\n\nhello',
'string', '* foo',
null, ' ',
'comment', 'bar',
null, 'hello');
</script>

<h2>Horizontal rules</h2>
<script language="javascript">

Expand Down Expand Up @@ -273,12 +473,33 @@ <h2>Links</h2>
'link', '[foo]:',
null, ' ',
'string', '<http://example.com/> "bar"');
// Title on next line per documentation
MT.test('[foo]: http://example.com/\n"bar"',
// Title on next line per documentation (double quotes)
MT.test('[foo]: http://example.com/\n"bar" hello',
'link', '[foo]:',
null, ' ',
'string', 'http://example.com/',
'string', '"bar"',
null, ' hello');
// Title on next line per documentation (single quotes)
MT.test('[foo]: http://example.com/\n\'bar\' hello',
'link', '[foo]:',
null, ' ',
'string', 'http://example.com/',
'string', '\'bar\'',
null, ' hello');
// Title on next line per documentation (parentheses)
MT.test('[foo]: http://example.com/\n(bar) hello',
'link', '[foo]:',
null, ' ',
'string', 'http://example.com/',
'string', '(bar)',
null, ' hello');
// Title on next line per documentation (mixed)
MT.test('[foo]: http://example.com/\n(bar" hello',
'link', '[foo]:',
null, ' ',
'string', 'http://example.com/',
'string', '"bar"');
null, '(bar" hello');

// Automatic links
MT.test('<http://example.com/> foo',
Expand Down

0 comments on commit 367cfc6

Please sign in to comment.