From 6fbf5812533240df865262c91d3d4337533554ae Mon Sep 17 00:00:00 2001 From: Yang Zhao Date: Wed, 2 Sep 2020 12:59:25 +1200 Subject: [PATCH] Add option emptyLines --- lib/configs/default.js | 6 ++-- lib/configs/full.js | 3 +- lib/parser_block.js | 11 ++++++- lib/parser_core.js | 2 ++ lib/rules_core/empty_lines.js | 60 +++++++++++++++++++++++++++++++++++ test/empty_lines.js | 36 +++++++++++++++++++++ 6 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 lib/rules_core/empty_lines.js create mode 100644 test/empty_lines.js diff --git a/lib/configs/default.js b/lib/configs/default.js index dfa8c5d7..70f1e232 100644 --- a/lib/configs/default.js +++ b/lib/configs/default.js @@ -22,7 +22,8 @@ export default { // highlight: null, - maxNesting: 20 // Internal protection, recursion limit + maxNesting: 20, // Internal protection, recursion limit + emptyLines: false // Preserve empty lines with empty

tag }, components: { @@ -36,7 +37,8 @@ export default { 'smartquotes', 'references', 'abbr2', - 'footnote_tail' + 'footnote_tail', + 'empty_lines' ] }, diff --git a/lib/configs/full.js b/lib/configs/full.js index a8abdb38..07eeb32d 100644 --- a/lib/configs/full.js +++ b/lib/configs/full.js @@ -22,7 +22,8 @@ export default { // highlight: null, - maxNesting: 20 // Internal protection, recursion limit + maxNesting: 20, // Internal protection, recursion limit + emptyLines: false // Preserve empty lines with empty

tag }, components: { diff --git a/lib/parser_block.js b/lib/parser_block.js index a3281f9e..afe4258e 100644 --- a/lib/parser_block.js +++ b/lib/parser_block.js @@ -65,7 +65,16 @@ ParserBlock.prototype.tokenize = function (state, startLine, endLine) { var ok, i; while (line < endLine) { - state.line = line = state.skipEmptyLines(line); + var newLine = state.skipEmptyLines(line); + // save the emtpy lines info into state.env.emptyLines + if (state.options.emptyLines === true && newLine > line) { + state.env.emptyLines = state.env.emptyLines || {}; + state.env.emptyLines[line] = newLine - line; + } + state.line = line = newLine; + if (line >= endLine) { + break; + } if (line >= endLine) { break; } diff --git a/lib/parser_core.js b/lib/parser_core.js index bcb965f3..496c8388 100644 --- a/lib/parser_core.js +++ b/lib/parser_core.js @@ -8,6 +8,7 @@ import footnote_tail from './rules_core/footnote_tail'; import abbr2 from './rules_core/abbr2'; import replacements from './rules_core/replacements'; import smartquotes from './rules_core/smartquotes'; +import empty_lines from "./rules_core/empty_lines"; /** * Core parser `rules` @@ -22,6 +23,7 @@ var _rules = [ [ 'abbr2', abbr2 ], [ 'replacements', replacements ], [ 'smartquotes', smartquotes ], + [ 'empty_lines', empty_lines ], ]; /** diff --git a/lib/rules_core/empty_lines.js b/lib/rules_core/empty_lines.js new file mode 100644 index 00000000..a0e90a10 --- /dev/null +++ b/lib/rules_core/empty_lines.js @@ -0,0 +1,60 @@ +// Transform empty lines into empty

tags +// empty lines data comes from state.env.emptyLines, + +export default function empty_lines_block(state) { + var i, ln; + var emptyLines = state.env.emptyLines; + if (!emptyLines || state.options.emptyLines !== true) { + return; + } + var tokens = state.tokens; + var pendingTokens = []; + var lastVisitedIndex = 0; + for (var lineNumber in emptyLines) { + for (i = lastVisitedIndex; i < tokens.length; i++) { + var token = tokens[i]; + ln = Number(lineNumber); + // find the first "paragraph" that after the current empty lines + if ( + token.type === 'paragraph_open' && + token.lines && + token.lines[0] >= ln + ) { + // push the index info of the found "paragraph" + pendingTokens.push({ + index: i, + lineNumber: ln, + level: token.level + }); + lastVisitedIndex = ln; + break; + } + } + } + + // insert the empty line from last to first + while (pendingTokens.length > 0) { + var t = pendingTokens.pop(); + var idx = t.index, lvl = t.level; + ln = t.lineNumber; + for (i = 0; i < emptyLines[ln] - 1; i++) { + tokens.splice( + idx, + 0, + { + type: 'paragraph_open', + tight: false, + lines: [ln + i, ln + i + emptyLines[ln]], + level: lvl + }, + { + type: 'paragraph_close', + tight: false, + level: lvl + } + ); + } + } + + state.tokens = tokens; +}; diff --git a/test/empty_lines.js b/test/empty_lines.js new file mode 100644 index 00000000..a0d113eb --- /dev/null +++ b/test/empty_lines.js @@ -0,0 +1,36 @@ + +import assert from 'assert'; +import { Remarkable } from '../lib/index'; + +describe('Test empty lines plugin', function() { + it('should render with empty lines when enabled', function() { + [ + ['', ''], + ['abc\n\ndef\n', '

abc

\n

def

\n'], + ['abc\n\n\ndef\n', '

abc

\n

def

\n'], + ['abc\n\n\n\ndef\n\n\n\nghi\n', '

abc

\n

\n

def

\n

\n

ghi

\n'], + ['line1\n\n\n\nline3\n', '

line1

\n

\n

line3

\n'], + ['* line1\n* line2\n\n\nline4\n', '\n

line4

\n'] + ].forEach(function(data) { + var [text, expected] = data; + var md = new Remarkable('full', { emptyLines: true }); + var rendered = md.render(text); + assert.strictEqual(rendered, expected); + }); + }); + + it('should render without empty lines when disabled', function() { + [ + ['', ''], + ['abc\n\ndef\n', '

abc

\n

def

\n'], + ['abc\n\n\ndef\n', '

abc

\n

def

\n'], + ['line1\n\n\n\nline3\n', '

line1

\n

line3

\n'], + ['* line1\n* line2\n\n\n\n\n\n\nline4\n', '\n

line4

\n'] + ].forEach(function(data) { + var [text, expected] = data; + var md = new Remarkable('full'); + var rendered = md.render(text); + assert.strictEqual(rendered, expected); + }); + }); +});