From c553ffe3b31411b83e928500cc68531f1425a715 Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Fri, 28 Aug 2020 02:44:27 +0800 Subject: [PATCH 01/22] Highlighting: Implement slice format for text-only highlight --- packages/core-web/src/styles/markbind.css | 2 +- packages/core/src/lib/markdown-it/index.js | 86 +++++++++++++++++----- 2 files changed, 69 insertions(+), 19 deletions(-) diff --git a/packages/core-web/src/styles/markbind.css b/packages/core-web/src/styles/markbind.css index 4e9466b937..94c62268e5 100644 --- a/packages/core-web/src/styles/markbind.css +++ b/packages/core-web/src/styles/markbind.css @@ -59,7 +59,7 @@ pre > code.hljs[heading] { display: block; } -code > span.highlighted { +code span.highlighted { background: lavender; } diff --git a/packages/core/src/lib/markdown-it/index.js b/packages/core/src/lib/markdown-it/index.js index a4869edd0d..2461512f50 100644 --- a/packages/core/src/lib/markdown-it/index.js +++ b/packages/core/src/lib/markdown-it/index.js @@ -5,6 +5,9 @@ const markdownIt = require('markdown-it')({ }); const slugify = require('@sindresorhus/slugify'); +const logger = require('../../utils/logger'); +const LINESLICE_REGEX = new RegExp('(\\d+)\\[(\\d*):(\\d*)]', 'g') + // markdown-it plugins markdownIt.use(require('markdown-it-mark')) .use(require('markdown-it-ins')) @@ -86,43 +89,90 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { // counter is incremented on each span, so we need to subtract 1 token.attrJoin('style', `counter-reset: line ${startFromZeroBased};`); } - + const highlightLinesInput = getAttributeAndDelete(token, 'highlight-lines'); - let lineNumbersAndRanges = []; + let highlightRules = []; if (highlightLinesInput) { - // example input format: "1,4-7,8,11-55" - // output: [[1],[4,7],[8],[11,55]] - // the output is an array contaning either single line numbers [lineNum] or ranges [start, end] - // ',' delimits either single line numbers (eg: 1) or ranges (eg: 4-7) + // example input format: "1,4-7,8,11-55,2[5:7],4[:]" + // output: [[1],[4,7],[8],[11,55],[2,5,7],[4,-1,-1]] + // the output is an array containing either single line numbers [lineNum], ranges [start, end] + // or single line number with slice [lineNum, start, end] + // ',' delimits either single line numbers (eg: 1), ranges (eg: 4-7), or line number with slice + // (e.g. 2[5:7]) const highlightLines = highlightLinesInput.split(','); - // if it's the single number, it will just be parsed as an int, (eg: ['1'] --> [1] ) - // if it's a range, it will be parsed as as an array of two ints (eg: ['4-7'] --> [4,6]) + // if it's a single number, it will just be parsed as an int, (eg: ['1'] --> [1] ) + // if it's a range, it will be parsed as as an array of two ints (eg: ['4-7'] --> [4,7]) + // if it's a single number with a slice, it will be parsed as an array of three ints, with the + // latter two having a default of -1 if not specified (eg: ['2[5:7]'] --> [2, 5, 7]) function parseAndZeroBaseLineNumber(numberString) { // authors provide line numbers to highlight based on the 'start-from' attribute if it exists // so we need to shift them all back down to start at 0 return parseInt(numberString, 10) - startFromZeroBased; } - lineNumbersAndRanges = highlightLines.map(elem => elem.split('-').map(parseAndZeroBaseLineNumber)); + highlightRules = highlightLines.map(elem => { + // tries to match to the line slice pattern + const matchGroups = [...elem.matchAll(LINESLICE_REGEX)]; + if (matchGroups.length > 0) { + let numbers = matchGroups[0].slice(1); // keep the group matches only + logger.info(numbers); + + // only the first number is a line number, the rest are regular numbers + numbers[0] = parseAndZeroBaseLineNumber(numbers[0]); + numbers = numbers.map(x => x !== '' ? parseInt(x, 10) : -1); + return numbers; + } + + // match fails, either single number or line ranges + return elem.split('-').map(parseAndZeroBaseLineNumber) + }); + logger.info(highlightRules); } - + lines.pop(); // last line is always a single '\n' newline, so we remove it // wrap all lines with so we can number them str = lines.map((line, index) => { const currentLineNumber = index + 1; // check if there is at least one range or line number that matches the current line number - // Note: The algorithm is based off markdown-it-highlight-lines (https://github.com/egoist/markdown-it-highlight-lines/blob/master/src/index.js) + // Note: The algorithm is based off markdown-it-highlight-lines (https://github.com/egoist/markdown-it-highlight-lines/blob/master/src/index.js) // This is an O(n^2) solution wrt to the number of lines // I opt to use this approach because it's simple, and it is unlikely that the number of elements in `lineNumbersAndRanges` will be large // There is possible room for improvement for a more efficient algo that is O(n). - const inRange = lineNumbersAndRanges.some(([start, end]) => { - if (start && end) { - return currentLineNumber >= start && currentLineNumber <= end; + // Edit (28/8/2020): I changed the approach from using some() to a simple for-loop. It is still O(n^2). + // Reason: now with different highlighting methods (whole line/text only), + // checks must be done to determine what method a particular rule follows. + // As now checking has to be done at the rule-matching AND the return handling, + // it's more succinct to write a for-loop so that we can do both without + // being redundant (wrt. writing if-else conditions). + for (let i = 0; i < highlightRules.length; i++) { + const [a, b, c] = highlightRules[i]; + + // "line slice" format + if (a && b && c) { + if (currentLineNumber === a) { + const contentIdx = line.search(/\S|$/) + const indents = line.substr(0, contentIdx) + const content = line.substr(contentIdx) + if (b === -1 && c === -1) { + // whole text + return `${indents}${content}`; + } + } + } + + // "line range" format + if (a && b) { + if (currentLineNumber >= a && currentLineNumber <= b) { + return `${line}`; + } + } + + // "line number" format + if (currentLineNumber === a) { + return `${line}`; } - return currentLineNumber === start; - }); - if (inRange) { - return `${line}\n`; } + + // not highlighted return `${line}\n`; }).join(''); From 013f546c84492912b6d02f9471adb3b2abf658e7 Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Fri, 28 Aug 2020 02:53:35 +0800 Subject: [PATCH 02/22] Remove logger --- packages/core/src/lib/markdown-it/index.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/core/src/lib/markdown-it/index.js b/packages/core/src/lib/markdown-it/index.js index 2461512f50..00bd08e37a 100644 --- a/packages/core/src/lib/markdown-it/index.js +++ b/packages/core/src/lib/markdown-it/index.js @@ -5,7 +5,6 @@ const markdownIt = require('markdown-it')({ }); const slugify = require('@sindresorhus/slugify'); -const logger = require('../../utils/logger'); const LINESLICE_REGEX = new RegExp('(\\d+)\\[(\\d*):(\\d*)]', 'g') // markdown-it plugins @@ -114,7 +113,6 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { const matchGroups = [...elem.matchAll(LINESLICE_REGEX)]; if (matchGroups.length > 0) { let numbers = matchGroups[0].slice(1); // keep the group matches only - logger.info(numbers); // only the first number is a line number, the rest are regular numbers numbers[0] = parseAndZeroBaseLineNumber(numbers[0]); @@ -125,7 +123,6 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { // match fails, either single number or line ranges return elem.split('-').map(parseAndZeroBaseLineNumber) }); - logger.info(highlightRules); } lines.pop(); // last line is always a single '\n' newline, so we remove it From 7f24ffd5260bd0868bf78a6897e6993f83616a46 Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Fri, 28 Aug 2020 09:27:36 +0800 Subject: [PATCH 03/22] Add newlines on highlighting return values --- packages/core/src/lib/markdown-it/index.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/core/src/lib/markdown-it/index.js b/packages/core/src/lib/markdown-it/index.js index 00bd08e37a..f1c1e30750 100644 --- a/packages/core/src/lib/markdown-it/index.js +++ b/packages/core/src/lib/markdown-it/index.js @@ -149,9 +149,10 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { const contentIdx = line.search(/\S|$/) const indents = line.substr(0, contentIdx) const content = line.substr(contentIdx) + if (b === -1 && c === -1) { // whole text - return `${indents}${content}`; + return `${indents}${content}\n`; } } } @@ -159,13 +160,13 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { // "line range" format if (a && b) { if (currentLineNumber >= a && currentLineNumber <= b) { - return `${line}`; + return `${line}\n`; } } // "line number" format if (currentLineNumber === a) { - return `${line}`; + return `${line}\n`; } } From 130837156a97d4ae1cd57391ef67080fca3f5f19 Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Fri, 28 Aug 2020 09:42:40 +0800 Subject: [PATCH 04/22] Use match instead of matchAll --- packages/core/src/lib/markdown-it/index.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/core/src/lib/markdown-it/index.js b/packages/core/src/lib/markdown-it/index.js index f1c1e30750..700f86a94c 100644 --- a/packages/core/src/lib/markdown-it/index.js +++ b/packages/core/src/lib/markdown-it/index.js @@ -5,7 +5,7 @@ const markdownIt = require('markdown-it')({ }); const slugify = require('@sindresorhus/slugify'); -const LINESLICE_REGEX = new RegExp('(\\d+)\\[(\\d*):(\\d*)]', 'g') +const LINESLICE_REGEX = new RegExp('(\\d+)\\[(\\d*):(\\d*)]') // markdown-it plugins markdownIt.use(require('markdown-it-mark')) @@ -110,9 +110,10 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { } highlightRules = highlightLines.map(elem => { // tries to match to the line slice pattern - const matchGroups = [...elem.matchAll(LINESLICE_REGEX)]; - if (matchGroups.length > 0) { - let numbers = matchGroups[0].slice(1); // keep the group matches only + const matches = elem.match(LINESLICE_REGEX); + if (matches) { + // keep the capturing group matches only + let numbers = matches.slice(1); // only the first number is a line number, the rest are regular numbers numbers[0] = parseAndZeroBaseLineNumber(numbers[0]); From fede374f6228ad459975f735da480ca38b746c5d Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Fri, 28 Aug 2020 20:43:21 +0800 Subject: [PATCH 05/22] Update comments --- packages/core/src/lib/markdown-it/index.js | 28 ++++++++++------------ 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/packages/core/src/lib/markdown-it/index.js b/packages/core/src/lib/markdown-it/index.js index 700f86a94c..f7aab21bd6 100644 --- a/packages/core/src/lib/markdown-it/index.js +++ b/packages/core/src/lib/markdown-it/index.js @@ -92,17 +92,16 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { const highlightLinesInput = getAttributeAndDelete(token, 'highlight-lines'); let highlightRules = []; if (highlightLinesInput) { - // example input format: "1,4-7,8,11-55,2[5:7],4[:]" - // output: [[1],[4,7],[8],[11,55],[2,5,7],[4,-1,-1]] - // the output is an array containing either single line numbers [lineNum], ranges [start, end] - // or single line number with slice [lineNum, start, end] - // ',' delimits either single line numbers (eg: 1), ranges (eg: 4-7), or line number with slice - // (e.g. 2[5:7]) + // INPUT: a comma-delimited string with each entry be a line number (eg: 1), a range (eg: 4-7), + // or a slice of a line (eg: 4[3:]) + // OUTPUT: an array containing arrays of one, two, or three ints + // if it's a single number, it will just be parsed as an int + // if it's a range, it will be parsed as as an array of two ints + // if it's a single number with a slice, it will be parsed as an array of three ints, with the + // latter two having a default of -1 if not specified + // EXAMPLE: input "1,4-7,8,11-55,2[5:7],4[3:]" + // output [[1],[4,7],[8],[11,55],[2,5,7],[4,3,-1]] const highlightLines = highlightLinesInput.split(','); - // if it's a single number, it will just be parsed as an int, (eg: ['1'] --> [1] ) - // if it's a range, it will be parsed as as an array of two ints (eg: ['4-7'] --> [4,7]) - // if it's a single number with a slice, it will be parsed as an array of three ints, with the - // latter two having a default of -1 if not specified (eg: ['2[5:7]'] --> [2, 5, 7]) function parseAndZeroBaseLineNumber(numberString) { // authors provide line numbers to highlight based on the 'start-from' attribute if it exists // so we need to shift them all back down to start at 0 @@ -130,17 +129,16 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { // wrap all lines with so we can number them str = lines.map((line, index) => { const currentLineNumber = index + 1; - // check if there is at least one range or line number that matches the current line number + // check if there is a highlight rule that is applicable to the line number, and handle accordingly // Note: The algorithm is based off markdown-it-highlight-lines (https://github.com/egoist/markdown-it-highlight-lines/blob/master/src/index.js) // This is an O(n^2) solution wrt to the number of lines // I opt to use this approach because it's simple, and it is unlikely that the number of elements in `lineNumbersAndRanges` will be large // There is possible room for improvement for a more efficient algo that is O(n). // Edit (28/8/2020): I changed the approach from using some() to a simple for-loop. It is still O(n^2). - // Reason: now with different highlighting methods (whole line/text only), + // Reason, now with different highlighting methods (whole-line/text-only), // checks must be done to determine what method a particular rule follows. - // As now checking has to be done at the rule-matching AND the return handling, - // it's more succinct to write a for-loop so that we can do both without - // being redundant (wrt. writing if-else conditions). + // As now checking has to be done at rule matching and return handling, + // it's more concise to write a for-loop so that we can perform both in one block. for (let i = 0; i < highlightRules.length; i++) { const [a, b, c] = highlightRules[i]; From 4922de9cd5eb9e363cb5a53994e6f98f4c64ce86 Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Fri, 28 Aug 2020 20:53:47 +0800 Subject: [PATCH 06/22] Tidy parseAndZeroLineNumber --- packages/core/src/lib/markdown-it/index.js | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/core/src/lib/markdown-it/index.js b/packages/core/src/lib/markdown-it/index.js index f7aab21bd6..09b8ce5c13 100644 --- a/packages/core/src/lib/markdown-it/index.js +++ b/packages/core/src/lib/markdown-it/index.js @@ -102,27 +102,26 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { // EXAMPLE: input "1,4-7,8,11-55,2[5:7],4[3:]" // output [[1],[4,7],[8],[11,55],[2,5,7],[4,3,-1]] const highlightLines = highlightLinesInput.split(','); - function parseAndZeroBaseLineNumber(numberString) { + function parseAndZeroBaseLineNumber(ruleString) { // authors provide line numbers to highlight based on the 'start-from' attribute if it exists // so we need to shift them all back down to start at 0 - return parseInt(numberString, 10) - startFromZeroBased; - } - highlightRules = highlightLines.map(elem => { + // tries to match to the line slice pattern - const matches = elem.match(LINESLICE_REGEX); + const matches = ruleString.match(LINESLICE_REGEX); if (matches) { // keep the capturing group matches only let numbers = matches.slice(1); // only the first number is a line number, the rest are regular numbers - numbers[0] = parseAndZeroBaseLineNumber(numbers[0]); numbers = numbers.map(x => x !== '' ? parseInt(x, 10) : -1); + numbers[0] -= startFromZeroBased; return numbers; } - // match fails, either single number or line ranges - return elem.split('-').map(parseAndZeroBaseLineNumber) - }); + // match fails, so it is just line numbers + return parseInt(ruleString, 10) - startFromZeroBased; + } + highlightRules = highlightLines.map(elem => elem.split('-').map(parseAndZeroBaseLineNumber)); } lines.pop(); // last line is always a single '\n' newline, so we remove it From ad5beb32d7fb48338aef916ec6e9ee827ef7cad1 Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Fri, 28 Aug 2020 21:05:57 +0800 Subject: [PATCH 07/22] Add test for line-slice highlight format --- .../test/functional/test_site/expected/testCodeBlocks.html | 5 +++++ packages/cli/test/functional/test_site/testCodeBlocks.md | 7 +++++++ 2 files changed, 12 insertions(+) diff --git a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html index b74beaee0c..2f88e0f1ef 100644 --- a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html +++ b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html @@ -71,6 +71,11 @@ 18 highlighted 19 20 + +

**highlight-lines attr with line-slice format should not highlight leading/trailing spaces

+
<foo>
+  <bar type="name">goo</bar>
+</foo>
 

Should render correctly with heading

diff --git a/packages/cli/test/functional/test_site/testCodeBlocks.md b/packages/cli/test/functional/test_site/testCodeBlocks.md index a5605b5b13..50c810c278 100644 --- a/packages/cli/test/functional/test_site/testCodeBlocks.md +++ b/packages/cli/test/functional/test_site/testCodeBlocks.md @@ -56,6 +56,13 @@ Content in a fenced code block 20 ``` +**`highlight-lines` attr with line-slice format should not highlight leading/trailing spaces +```xml {highlight-lines="2[:],3[:]"} + + goo + +``` + **Should render correctly with heading** ```{heading="A heading"} From 95601fcace8529f5543946105e7788eeac9c095c Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Fri, 28 Aug 2020 22:02:26 +0800 Subject: [PATCH 08/22] Fix parseAndZeroLineNumber and rename to parseRule --- packages/core/src/lib/markdown-it/index.js | 44 ++++++++++++---------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/packages/core/src/lib/markdown-it/index.js b/packages/core/src/lib/markdown-it/index.js index 09b8ce5c13..461c810c43 100644 --- a/packages/core/src/lib/markdown-it/index.js +++ b/packages/core/src/lib/markdown-it/index.js @@ -5,7 +5,7 @@ const markdownIt = require('markdown-it')({ }); const slugify = require('@sindresorhus/slugify'); -const LINESLICE_REGEX = new RegExp('(\\d+)\\[(\\d*):(\\d*)]') +const LINESLICE_REGEX = new RegExp('(\\d+)\\[(\\d*):(\\d*)]'); // markdown-it plugins markdownIt.use(require('markdown-it-mark')) @@ -102,26 +102,32 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { // EXAMPLE: input "1,4-7,8,11-55,2[5:7],4[3:]" // output [[1],[4,7],[8],[11,55],[2,5,7],[4,3,-1]] const highlightLines = highlightLinesInput.split(','); - function parseAndZeroBaseLineNumber(ruleString) { - // authors provide line numbers to highlight based on the 'start-from' attribute if it exists - // so we need to shift them all back down to start at 0 - - // tries to match to the line slice pattern - const matches = ruleString.match(LINESLICE_REGEX); - if (matches) { - // keep the capturing group matches only - let numbers = matches.slice(1); - - // only the first number is a line number, the rest are regular numbers - numbers = numbers.map(x => x !== '' ? parseInt(x, 10) : -1); - numbers[0] -= startFromZeroBased; - return numbers; - } + function parseRule(ruleString) { + // Note: authors provide line numbers based on the 'start-from' attribute if it exists, + // so we need to shift line numbers back down to start at 0 + + const ruleComponents = ruleString.split('-').map(comp => { + // tries to match to the line slice pattern + const matches = comp.match(LINESLICE_REGEX); + if (matches) { + // keep the capturing group matches only + let numbers = matches.slice(1); + + // only the first number is a line number, the rest are regular numbers + numbers = numbers.map(x => x !== '' ? parseInt(x, 10) : -1); + numbers[0] -= startFromZeroBased; + return numbers; + } + + // match fails, so it is just line numbers + return comp.split('-').map(x => parseInt(x, 10) - startFromZeroBased); + }); - // match fails, so it is just line numbers - return parseInt(ruleString, 10) - startFromZeroBased; + // if length is only one, that means split didn't do anything but create an unnecessary wrapping + // array, so unwrap the component from that + return ruleComponents.length === 1 ? ruleComponents[0] : ruleComponents; } - highlightRules = highlightLines.map(elem => elem.split('-').map(parseAndZeroBaseLineNumber)); + highlightRules = highlightLines.map(parseRule); } lines.pop(); // last line is always a single '\n' newline, so we remove it From d3772b8564336f19bdbf86d3e7f6d5b6aa2fecee Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Fri, 28 Aug 2020 22:03:53 +0800 Subject: [PATCH 09/22] Fix test --- .../test/functional/test_site/expected/testCodeBlocks.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html index 2f88e0f1ef..2040bcec0f 100644 --- a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html +++ b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html @@ -74,8 +74,8 @@

**highlight-lines attr with line-slice format should not highlight leading/trailing spaces

<foo>
-  <bar type="name">goo</bar>
-</foo>
+  <bar type="name">goo</bar>
+</foo>
 

Should render correctly with heading

From 8a79480977385d0e95d011e6686357ced86123fe Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Sat, 29 Aug 2020 14:15:14 +0800 Subject: [PATCH 10/22] Fix parseRule --- packages/core/src/lib/markdown-it/index.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/packages/core/src/lib/markdown-it/index.js b/packages/core/src/lib/markdown-it/index.js index 461c810c43..29427eb245 100644 --- a/packages/core/src/lib/markdown-it/index.js +++ b/packages/core/src/lib/markdown-it/index.js @@ -46,6 +46,12 @@ function getAttributeAndDelete(token, attr) { return value; } +function isLineSlice(ruleComponent) { + return Array.isArray(ruleComponent) + && ruleComponent.length === 3 + && ruleComponent.every(Number.isInteger); +} + // syntax highlight code fences and add line numbers markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { const token = tokens[idx]; @@ -106,7 +112,7 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { // Note: authors provide line numbers based on the 'start-from' attribute if it exists, // so we need to shift line numbers back down to start at 0 - const ruleComponents = ruleString.split('-').map(comp => { + let ruleComponents = ruleString.split('-').map(comp => { // tries to match to the line slice pattern const matches = comp.match(LINESLICE_REGEX); if (matches) { @@ -120,12 +126,13 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { } // match fails, so it is just line numbers - return comp.split('-').map(x => parseInt(x, 10) - startFromZeroBased); + return parseInt(comp, 10) - startFromZeroBased; }); - // if length is only one, that means split didn't do anything but create an unnecessary wrapping - // array, so unwrap the component from that - return ruleComponents.length === 1 ? ruleComponents[0] : ruleComponents; + // If the only component is a line-slice, then the array wrap is unnecessary as the component itself + // is already an array + const firstComponent = ruleComponents[0]; + return ruleComponents.length === 1 && isLineSlice(firstComponent) ? firstComponent : ruleComponents; } highlightRules = highlightLines.map(parseRule); } From 89170f72159e1471e29a85cab077662733030b91 Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Sat, 29 Aug 2020 14:22:08 +0800 Subject: [PATCH 11/22] Add text-only range highlighting --- packages/core/src/lib/markdown-it/index.js | 37 +++++++++++++++------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/packages/core/src/lib/markdown-it/index.js b/packages/core/src/lib/markdown-it/index.js index 29427eb245..99c518614a 100644 --- a/packages/core/src/lib/markdown-it/index.js +++ b/packages/core/src/lib/markdown-it/index.js @@ -52,6 +52,13 @@ function isLineSlice(ruleComponent) { && ruleComponent.every(Number.isInteger); } +function splitCodeAndIndentation(codeStr) { + const codeStartIdx = codeStr.search(/\S|$/); + const indents = codeStr.substr(0, codeStartIdx); + const content = codeStr.substr(codeStartIdx); + return [indents, content]; +} + // syntax highlight code fences and add line numbers markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { const token = tokens[idx]; @@ -152,30 +159,38 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { // As now checking has to be done at rule matching and return handling, // it's more concise to write a for-loop so that we can perform both in one block. for (let i = 0; i < highlightRules.length; i++) { - const [a, b, c] = highlightRules[i]; + const rule = highlightRules[i]; + const [a, b, c] = rule; // can be up to three items - // "line slice" format - if (a && b && c) { + // "line slice" type + if (isLineSlice(rule)) { if (currentLineNumber === a) { - const contentIdx = line.search(/\S|$/) - const indents = line.substr(0, contentIdx) - const content = line.substr(contentIdx) + const [indents, content] = splitCodeAndIndentation(line); + // whole text highlight if (b === -1 && c === -1) { - // whole text return `${indents}${content}\n`; } } } - // "line range" format + // "line range" type if (a && b) { - if (currentLineNumber >= a && currentLineNumber <= b) { - return `${line}\n`; + const isTextOnlyHighlight = isLineSlice(a) || isLineSlice(b); + const lineStart = isLineSlice(a) ? a[0] : a; + const lineEnd = isLineSlice(b) ? b[0] : b; + + if (lineStart <= currentLineNumber && currentLineNumber <= lineEnd) { + if (isTextOnlyHighlight) { + const [indents, content] = splitCodeAndIndentation(line); + return `${indents}${content}\n`; + } + + return `${line}\n` } } - // "line number" format + // "line number" type if (currentLineNumber === a) { return `${line}\n`; } From 1857b23c16e884d754753480ee2e1acec42c3a08 Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Sat, 29 Aug 2020 14:33:16 +0800 Subject: [PATCH 12/22] Update comment --- packages/core/src/lib/markdown-it/index.js | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/core/src/lib/markdown-it/index.js b/packages/core/src/lib/markdown-it/index.js index 99c518614a..228e4b17b4 100644 --- a/packages/core/src/lib/markdown-it/index.js +++ b/packages/core/src/lib/markdown-it/index.js @@ -106,14 +106,16 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { let highlightRules = []; if (highlightLinesInput) { // INPUT: a comma-delimited string with each entry be a line number (eg: 1), a range (eg: 4-7), - // or a slice of a line (eg: 4[3:]) - // OUTPUT: an array containing arrays of one, two, or three ints - // if it's a single number, it will just be parsed as an int + // a slice of a line (eg: 8[2:5]), a range with line slice (eg: 11[:]-20) + // OUTPUT: an array containing arrays of one, two, or three items + // if it's a single number, it will just be parsed as an array of one int // if it's a range, it will be parsed as as an array of two ints // if it's a single number with a slice, it will be parsed as an array of three ints, with the // latter two having a default of -1 if not specified - // EXAMPLE: input "1,4-7,8,11-55,2[5:7],4[3:]" - // output [[1],[4,7],[8],[11,55],[2,5,7],[4,3,-1]] + // if it's a range with slice, it will be parsed as an array of two items whose types correspond + // to the formats above (for single number and number with slice) + // EXAMPLE: input "1,4-7,8[2:5],10[2:],11[:]-20" + // output [[1],[4,7],[8,2,5],[10,2,-1],[[11,-1,-1], 20]] const highlightLines = highlightLinesInput.split(','); function parseRule(ruleString) { // Note: authors provide line numbers based on the 'start-from' attribute if it exists, @@ -136,7 +138,7 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { return parseInt(comp, 10) - startFromZeroBased; }); - // If the only component is a line-slice, then the array wrap is unnecessary as the component itself + // If the only component is a line-slice, then the outer array is unnecessary as the component itself // is already an array const firstComponent = ruleComponents[0]; return ruleComponents.length === 1 && isLineSlice(firstComponent) ? firstComponent : ruleComponents; From 8f0f345743e9b0084cebb59aa13b2b7b5a803480 Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Sat, 29 Aug 2020 16:53:51 +0800 Subject: [PATCH 13/22] Update test to include range text-only highlight --- .../test/functional/test_site/expected/testCodeBlocks.html | 7 +++++-- packages/cli/test/functional/test_site/testCodeBlocks.md | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html index 2040bcec0f..176fe44024 100644 --- a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html +++ b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html @@ -72,10 +72,13 @@ 19 20 -

**highlight-lines attr with line-slice format should not highlight leading/trailing spaces

+

**highlight-lines attr with line-slice syntax should not highlight leading/trailing spaces

<foo>
   <bar type="name">goo</bar>
-</foo>
+  <baz type="name">goo</baz>
+  <qux type="name">goo</qux>
+  <quux type="name">goo</quux>
+</foo>
 

Should render correctly with heading

diff --git a/packages/cli/test/functional/test_site/testCodeBlocks.md b/packages/cli/test/functional/test_site/testCodeBlocks.md index 50c810c278..ee174d545a 100644 --- a/packages/cli/test/functional/test_site/testCodeBlocks.md +++ b/packages/cli/test/functional/test_site/testCodeBlocks.md @@ -56,10 +56,13 @@ Content in a fenced code block 20 ``` -**`highlight-lines` attr with line-slice format should not highlight leading/trailing spaces -```xml {highlight-lines="2[:],3[:]"} +**`highlight-lines` attr with line-slice syntax should not highlight leading/trailing spaces +```xml {highlight-lines="2[:],4[:]-5"} goo + goo + goo + goo ``` From f7f3662e20a46283d0e43b6770b3891f8765c75a Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Mon, 31 Aug 2020 13:50:17 +0800 Subject: [PATCH 14/22] Remove comment on higlight approach --- packages/core/src/lib/markdown-it/index.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/packages/core/src/lib/markdown-it/index.js b/packages/core/src/lib/markdown-it/index.js index 228e4b17b4..36dbc344a4 100644 --- a/packages/core/src/lib/markdown-it/index.js +++ b/packages/core/src/lib/markdown-it/index.js @@ -150,16 +150,6 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { // wrap all lines with so we can number them str = lines.map((line, index) => { const currentLineNumber = index + 1; - // check if there is a highlight rule that is applicable to the line number, and handle accordingly - // Note: The algorithm is based off markdown-it-highlight-lines (https://github.com/egoist/markdown-it-highlight-lines/blob/master/src/index.js) - // This is an O(n^2) solution wrt to the number of lines - // I opt to use this approach because it's simple, and it is unlikely that the number of elements in `lineNumbersAndRanges` will be large - // There is possible room for improvement for a more efficient algo that is O(n). - // Edit (28/8/2020): I changed the approach from using some() to a simple for-loop. It is still O(n^2). - // Reason, now with different highlighting methods (whole-line/text-only), - // checks must be done to determine what method a particular rule follows. - // As now checking has to be done at rule matching and return handling, - // it's more concise to write a for-loop so that we can perform both in one block. for (let i = 0; i < highlightRules.length; i++) { const rule = highlightRules[i]; const [a, b, c] = rule; // can be up to three items From 3dc92708573d3a1065be7e47c78b8e07b45cab95 Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Sun, 4 Oct 2020 23:00:55 +0800 Subject: [PATCH 15/22] Swap behaviour of line number and line empty slice --- .../test_site/expected/testCodeBlocks.html | 32 +++++++++---------- .../functional/test_site/testCodeBlocks.md | 4 +-- packages/core/src/lib/markdown-it/index.js | 16 ++++------ 3 files changed, 25 insertions(+), 27 deletions(-) diff --git a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html index 176fe44024..07043d1ac5 100644 --- a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html +++ b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html @@ -49,35 +49,35 @@ -----

highlight-lines attr causes corresponding lines to have 'highlighted' class

-
1  highlighted
+        
1  highlighted
 2
-3  highlighted
+3  highlighted
 4
-5  highlighted
-6  highlighted
-7  highlighted
-8  highlighted
+5  highlighted
+6  highlighted
+7  highlighted
+8  highlighted
 9
 10
 

highlight-lines attr with start-from attr should cause corresponding lines to have 'highlighted' class based on start-from

-
11  highlighted
+        
11  highlighted
 12
-13  highlighted
+13  highlighted
 14
-15  highlighted
-16  highlighted
-17  highlighted
-18  highlighted
+15  highlighted
+16  highlighted
+17  highlighted
+18  highlighted
 19
 20
 
-

**highlight-lines attr with line-slice syntax should not highlight leading/trailing spaces

+

**highlight-lines attr with line-slice syntax of empty indices should highlight leading/trailing spaces

<foo>
-  <bar type="name">goo</bar>
+  <bar type="name">goo</bar>
   <baz type="name">goo</baz>
-  <qux type="name">goo</qux>
-  <quux type="name">goo</quux>
+  <qux type="name">goo</qux>
+  <quux type="name">goo</quux>
 </foo>
 

Should render correctly with heading

diff --git a/packages/cli/test/functional/test_site/testCodeBlocks.md b/packages/cli/test/functional/test_site/testCodeBlocks.md index ee174d545a..89ba1f2451 100644 --- a/packages/cli/test/functional/test_site/testCodeBlocks.md +++ b/packages/cli/test/functional/test_site/testCodeBlocks.md @@ -56,8 +56,8 @@ Content in a fenced code block 20 ``` -**`highlight-lines` attr with line-slice syntax should not highlight leading/trailing spaces -```xml {highlight-lines="2[:],4[:]-5"} +**`highlight-lines` attr with line-slice syntax of empty indices should highlight leading/trailing spaces +```xml {highlight-lines="2[:],4[:]-5[:]"} goo goo diff --git a/packages/core/src/lib/markdown-it/index.js b/packages/core/src/lib/markdown-it/index.js index 36dbc344a4..5b7a0a6a65 100644 --- a/packages/core/src/lib/markdown-it/index.js +++ b/packages/core/src/lib/markdown-it/index.js @@ -157,18 +157,15 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { // "line slice" type if (isLineSlice(rule)) { if (currentLineNumber === a) { - const [indents, content] = splitCodeAndIndentation(line); - - // whole text highlight - if (b === -1 && c === -1) { - return `${indents}${content}\n`; - } + // Currently only highlights whole-line + // TODO: Implement highlighting by slice index + return `${line}\n`; } } // "line range" type if (a && b) { - const isTextOnlyHighlight = isLineSlice(a) || isLineSlice(b); + const isTextOnlyHighlight = !isLineSlice(a) && !isLineSlice(b); const lineStart = isLineSlice(a) ? a[0] : a; const lineEnd = isLineSlice(b) ? b[0] : b; @@ -182,9 +179,10 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { } } - // "line number" type + // "line number" type: highlight text-only if (currentLineNumber === a) { - return `${line}\n`; + const [indents, content] = splitCodeAndIndentation(line); + return `${indents}${content}\n`; } } From 26e8de27d525c46f04b050c2804b344460a813e3 Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Mon, 5 Oct 2020 11:19:49 +0800 Subject: [PATCH 16/22] Refactor highlighting utility --- .../markdown-it/highlight/HighlightRule.js | 82 ++++++++++++++++ .../highlight/HighlightRuleComponent.js | 57 +++++++++++ packages/core/src/lib/markdown-it/index.js | 94 ++----------------- 3 files changed, 147 insertions(+), 86 deletions(-) create mode 100644 packages/core/src/lib/markdown-it/highlight/HighlightRule.js create mode 100644 packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRule.js b/packages/core/src/lib/markdown-it/highlight/HighlightRule.js new file mode 100644 index 0000000000..6f1c97771d --- /dev/null +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRule.js @@ -0,0 +1,82 @@ +const { HighlightRuleComponent } = require('./HighlightRuleComponent.js'); + +class HighlightRule { + constructor(rule) { + /** + * @type Array + */ + this.rule = rule; + } + + isLineNumber() { + return this.rule.length === 1 && this.rule[0].isNumber() + } + + isLineSlice() { + return this.rule.length === 1 && this.rule[0].isSlice(); + } + + isLineRange() { + if (this.rule.length !== 2) { + return false; + } + + const [isFirstNumber, isSecondNumber] = this.rule.map(comp => comp.isNumber()); + const [isFirstSlice, isSecondSlice] = this.rule.map(comp => comp.isSlice()); + + return (isFirstNumber || isFirstSlice) && (isSecondNumber || isSecondSlice); + } + + static parseRule(ruleString) { + const components = ruleString.split('-').map(HighlightRuleComponent.parseRuleComponent); + return new HighlightRule(components); + } + + offsetLines(offset) { + this.rule.map(comp => comp.offsetLines(offset)); + } + + shouldApplyHighlight(lineNumber) { + const compares = this.rule.map(comp => comp.compareLine(lineNumber)); + if (this.isLineRange()) { + return (compares[0] <= 0) && (compares[1] >= 0); + } + return compares[0] === 0; + } + + applyHighlight(line) { + if (this.isLineSlice()) { + // TODO: Implement slice-index based highlighting + return HighlightRule._highlightWholeLine(line); + } + + if (this.isLineNumber()) { + return HighlightRule._highlightTextOnly(line); + } + + const shouldWholeLine = this.rule.some(comp => comp.isUnboundedSlice()); + return shouldWholeLine + ? HighlightRule._highlightWholeLine(line) + : HighlightRule._highlightTextOnly(line); + } + + static _splitCodeAndIndentation(codeStr) { + const codeStartIdx = codeStr.search(/\S|$/); + const indents = codeStr.substr(0, codeStartIdx); + const content = codeStr.substr(codeStartIdx); + return [indents, content]; + } + + static _highlightWholeLine(codeStr) { + return `${codeStr}\n`; + } + + static _highlightTextOnly(codeStr) { + const [indents, content] = HighlightRule._splitCodeAndIndentation(codeStr); + return `${indents}${content}\n` + } +} + +module.exports = { + HighlightRule +}; diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js new file mode 100644 index 0000000000..c378ba7d85 --- /dev/null +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js @@ -0,0 +1,57 @@ +const LINESLICE_REGEX = new RegExp('(\\d+)\\[(\\d*):(\\d*)]'); + +class HighlightRuleComponent { + constructor(components) { + /** + * @type Number | Array + */ + this.components = components; + } + + isNumber() { + return Number.isInteger(this.components); + } + + isSlice() { + return Array.isArray(this.components) + && this.components.length === 3 + && this.components.every(Number.isInteger); + } + + isUnboundedSlice() { + return this.isSlice() + && this.components[1] === -1 + && this.components[2] === -1; + } + + static parseRuleComponent(compString) { + // tries to match with the line slice pattern + const matches = compString.match(LINESLICE_REGEX); + if (matches) { + const numbers = matches.slice(1) // keep the capturing group matches only + .map(x => x !== '' ? parseInt(x, 10) : -1); + return new HighlightRuleComponent(numbers); + } + + // match fails, so it is just line numbers + const number = parseInt(compString, 10); + return new HighlightRuleComponent(number); + } + + offsetLines(offset) { + if (this.isNumber()) { + this.components += offset; + } else { + this.components[0] += offset; + } + } + + compareLine(lineNumber) { + const lineRule = this.isSlice() ? this.components[0] : this.components; + return lineRule - lineNumber; + } +} + +module.exports = { + HighlightRuleComponent +}; diff --git a/packages/core/src/lib/markdown-it/index.js b/packages/core/src/lib/markdown-it/index.js index 5b7a0a6a65..2f170b0db0 100644 --- a/packages/core/src/lib/markdown-it/index.js +++ b/packages/core/src/lib/markdown-it/index.js @@ -5,7 +5,7 @@ const markdownIt = require('markdown-it')({ }); const slugify = require('@sindresorhus/slugify'); -const LINESLICE_REGEX = new RegExp('(\\d+)\\[(\\d*):(\\d*)]'); +const { HighlightRule } = require('./highlight/HighlightRule.js'); // markdown-it plugins markdownIt.use(require('markdown-it-mark')) @@ -46,19 +46,6 @@ function getAttributeAndDelete(token, attr) { return value; } -function isLineSlice(ruleComponent) { - return Array.isArray(ruleComponent) - && ruleComponent.length === 3 - && ruleComponent.every(Number.isInteger); -} - -function splitCodeAndIndentation(codeStr) { - const codeStartIdx = codeStr.search(/\S|$/); - const indents = codeStr.substr(0, codeStartIdx); - const content = codeStr.substr(codeStartIdx); - return [indents, content]; -} - // syntax highlight code fences and add line numbers markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { const token = tokens[idx]; @@ -105,85 +92,20 @@ markdownIt.renderer.rules.fence = (tokens, idx, options, env, slf) => { const highlightLinesInput = getAttributeAndDelete(token, 'highlight-lines'); let highlightRules = []; if (highlightLinesInput) { - // INPUT: a comma-delimited string with each entry be a line number (eg: 1), a range (eg: 4-7), - // a slice of a line (eg: 8[2:5]), a range with line slice (eg: 11[:]-20) - // OUTPUT: an array containing arrays of one, two, or three items - // if it's a single number, it will just be parsed as an array of one int - // if it's a range, it will be parsed as as an array of two ints - // if it's a single number with a slice, it will be parsed as an array of three ints, with the - // latter two having a default of -1 if not specified - // if it's a range with slice, it will be parsed as an array of two items whose types correspond - // to the formats above (for single number and number with slice) - // EXAMPLE: input "1,4-7,8[2:5],10[2:],11[:]-20" - // output [[1],[4,7],[8,2,5],[10,2,-1],[[11,-1,-1], 20]] const highlightLines = highlightLinesInput.split(','); - function parseRule(ruleString) { - // Note: authors provide line numbers based on the 'start-from' attribute if it exists, - // so we need to shift line numbers back down to start at 0 - - let ruleComponents = ruleString.split('-').map(comp => { - // tries to match to the line slice pattern - const matches = comp.match(LINESLICE_REGEX); - if (matches) { - // keep the capturing group matches only - let numbers = matches.slice(1); - - // only the first number is a line number, the rest are regular numbers - numbers = numbers.map(x => x !== '' ? parseInt(x, 10) : -1); - numbers[0] -= startFromZeroBased; - return numbers; - } - - // match fails, so it is just line numbers - return parseInt(comp, 10) - startFromZeroBased; - }); - - // If the only component is a line-slice, then the outer array is unnecessary as the component itself - // is already an array - const firstComponent = ruleComponents[0]; - return ruleComponents.length === 1 && isLineSlice(firstComponent) ? firstComponent : ruleComponents; - } - highlightRules = highlightLines.map(parseRule); + highlightRules = highlightLines.map(HighlightRule.parseRule); + // Note: authors provide line numbers based on the 'start-from' attribute if it exists, + // so we need to shift line numbers back down to start at 0 + highlightRules.forEach(rule => rule.offsetLines(-startFromZeroBased)); } lines.pop(); // last line is always a single '\n' newline, so we remove it // wrap all lines with so we can number them str = lines.map((line, index) => { const currentLineNumber = index + 1; - for (let i = 0; i < highlightRules.length; i++) { - const rule = highlightRules[i]; - const [a, b, c] = rule; // can be up to three items - - // "line slice" type - if (isLineSlice(rule)) { - if (currentLineNumber === a) { - // Currently only highlights whole-line - // TODO: Implement highlighting by slice index - return `${line}\n`; - } - } - - // "line range" type - if (a && b) { - const isTextOnlyHighlight = !isLineSlice(a) && !isLineSlice(b); - const lineStart = isLineSlice(a) ? a[0] : a; - const lineEnd = isLineSlice(b) ? b[0] : b; - - if (lineStart <= currentLineNumber && currentLineNumber <= lineEnd) { - if (isTextOnlyHighlight) { - const [indents, content] = splitCodeAndIndentation(line); - return `${indents}${content}\n`; - } - - return `${line}\n` - } - } - - // "line number" type: highlight text-only - if (currentLineNumber === a) { - const [indents, content] = splitCodeAndIndentation(line); - return `${indents}${content}\n`; - } + const rule = highlightRules.find(rule => rule.shouldApplyHighlight(currentLineNumber)) + if (rule) { + return rule.applyHighlight(line); } // not highlighted From 0acb0895f82e5545126f04ac4926f151917472e5 Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Mon, 5 Oct 2020 12:01:31 +0800 Subject: [PATCH 17/22] Update highlight syntax reference --- docs/userGuide/syntax/code.mbdf | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/docs/userGuide/syntax/code.mbdf b/docs/userGuide/syntax/code.mbdf index 8e0ddefa6a..dfc8b336eb 100644 --- a/docs/userGuide/syntax/code.mbdf +++ b/docs/userGuide/syntax/code.mbdf @@ -53,11 +53,26 @@ function add(a, b) { ##### Line highlighting -To highlight lines, add the attribute `highlight-lines` with the line numbers as value, as shown below. You can specify ranges or individual line numbers. +To highlight lines, add the attribute `highlight-lines` as shown below. + +You can specify highlighting in many different ways, depending on how you want it to be. There are two main variants: + +- Text-only highlighting + + To highlight only the text portion of the line, you can just use the line numbers as is. + + For ranges of lines, join the two line numbers with a dash sign (`-`). +- Whole-line highlighting + + If you wish to highlight a full line (including whitespaces) or ranges of it, you can leverage MarkBind's own _line-slice_ syntax. Line-slices are in the form of `lineNumber[:]`, e.g. `2[:]`. + + This variant's format is very similar to the previous, but instead use line-slices rather than line numbers. + + For ranges, you only need to use line-slices on either ends. -```java {highlight-lines="2,4,6-8"} +```java {highlight-lines="1,3[:],6-8,10[:]-12"} import java.util.List; public class Inventory { @@ -66,6 +81,10 @@ public class Inventory { public int getItemCount(){ return items.size(); } + + public bool isEmpty() { + return items.isEmpty(); + } //... } From 25ad0bb81cac8a1a08e2eb9bf07cfd8ab265a9d3 Mon Sep 17 00:00:00 2001 From: Ryo Armanda Date: Mon, 5 Oct 2020 12:17:30 +0800 Subject: [PATCH 18/22] Update codeblock CSS to select all highlighted elements --- .../markbind/css/codeblock-dark.min.css | 2 +- .../markbind/css/codeblock-light.min.css | 2 +- .../test_site/expected/testCodeBlocks.html | 28 +++++++++---------- .../markbind/css/codeblock-dark.min.css | 2 +- .../markbind/css/codeblock-light.min.css | 2 +- .../markbind/css/codeblock-dark.min.css | 2 +- .../markbind/css/codeblock-light.min.css | 2 +- .../markbind/css/codeblock-dark.min.css | 2 +- .../markbind/css/codeblock-light.min.css | 2 +- .../markbind/css/codeblock-dark.min.css | 2 +- .../markbind/css/codeblock-light.min.css | 2 +- .../markbind/css/codeblock-dark.min.css | 2 +- .../markbind/css/codeblock-light.min.css | 2 +- .../markbind/css/codeblock-dark.min.css | 2 +- .../markbind/css/codeblock-light.min.css | 2 +- .../core-web/asset/css/codeblock-dark.min.css | 2 +- .../asset/css/codeblock-light.min.css | 2 +- packages/core-web/src/styles/markbind.css | 2 +- 18 files changed, 31 insertions(+), 31 deletions(-) diff --git a/packages/cli/test/functional/test_site/expected/markbind/css/codeblock-dark.min.css b/packages/cli/test/functional/test_site/expected/markbind/css/codeblock-dark.min.css index 25af447195..1a1f424be5 100644 --- a/packages/cli/test/functional/test_site/expected/markbind/css/codeblock-dark.min.css +++ b/packages/cli/test/functional/test_site/expected/markbind/css/codeblock-dark.min.css @@ -1,2 +1,2 @@ .hljs{display:block;overflow-x:auto;padding:.5em;background:#2b2b2b;color:#dcdcdc}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-symbol{color:#569cd6}.hljs-link{color:#569cd6;text-decoration:underline}.hljs-built_in,.hljs-type{color:#4ec9b0}.hljs-class,.hljs-number{color:#b8d7a3}.hljs-meta-string,.hljs-string{color:#d69d85}.hljs-regexp,.hljs-template-tag{color:#9a5334}.hljs-formula,.hljs-function,.hljs-params,.hljs-subst,.hljs-title{color:#dcdcdc}.hljs-comment,.hljs-quote{color:#57a64a;font-style:italic}.hljs-doctag{color:#608b4e}.hljs-meta,.hljs-meta-keyword,.hljs-tag{color:#9b9b9b}.hljs-template-variable,.hljs-variable{color:#bd63c5}.hljs-attr,.hljs-attribute,.hljs-builtin-name{color:#9cdcfe}.hljs-section{color:gold}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-bullet,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag{color:#d7ba7d}.hljs-addition{background-color:#144212;display:inline-block;width:100%}.hljs-deletion{background-color:#600;display:inline-block;width:100%} -/* MarkBind-customized styles */.hljs>span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc} +/* MarkBind-customized styles */.hljs span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc} diff --git a/packages/cli/test/functional/test_site/expected/markbind/css/codeblock-light.min.css b/packages/cli/test/functional/test_site/expected/markbind/css/codeblock-light.min.css index 72a97c76ab..5e20b4e805 100644 --- a/packages/cli/test/functional/test_site/expected/markbind/css/codeblock-light.min.css +++ b/packages/cli/test/functional/test_site/expected/markbind/css/codeblock-light.min.css @@ -1,2 +1,2 @@ .hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8ff}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700} -/* MarkBind-customized styles */.hljs>span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5} +/* MarkBind-customized styles */.hljs span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5} diff --git a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html index 045d8b1566..fd837dec02 100644 --- a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html +++ b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html @@ -49,30 +49,30 @@ -----

highlight-lines attr causes corresponding lines to have 'highlighted' class

-
1  highlighted
+        
1  highlighted
 2
-3  highlighted
+3  highlighted
 4
-5  highlighted
-6  highlighted
-7  highlighted
-8  highlighted
+5  highlighted
+6  highlighted
+7  highlighted
+8  highlighted
 9
 10
 

highlight-lines attr with start-from attr should cause corresponding lines to have 'highlighted' class based on start-from

-
11  highlighted
+        
11  highlighted
 12
-13  highlighted
+13  highlighted
 14
-15  highlighted
-16  highlighted
-17  highlighted
-18  highlighted
+15  highlighted
+16  highlighted
+17  highlighted
+18  highlighted
 19
 20
 
-

**highlight-lines attr with line-slice syntax of empty indices should highlight leading/trailing spaces

+

**highlight-lines attr with line-slice syntax of empty indices should highlight leading/trailing spaces

<foo>
   <bar type="name">goo</bar>
   <baz type="name">goo</baz>
@@ -173,4 +173,4 @@
   if (MarkBind.AfterSetup) MarkBind.AfterSetup()
 
 
-
+
\ No newline at end of file
diff --git a/packages/cli/test/functional/test_site_algolia_plugin/expected/markbind/css/codeblock-dark.min.css b/packages/cli/test/functional/test_site_algolia_plugin/expected/markbind/css/codeblock-dark.min.css
index 25af447195..1a1f424be5 100644
--- a/packages/cli/test/functional/test_site_algolia_plugin/expected/markbind/css/codeblock-dark.min.css
+++ b/packages/cli/test/functional/test_site_algolia_plugin/expected/markbind/css/codeblock-dark.min.css
@@ -1,2 +1,2 @@
 .hljs{display:block;overflow-x:auto;padding:.5em;background:#2b2b2b;color:#dcdcdc}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-symbol{color:#569cd6}.hljs-link{color:#569cd6;text-decoration:underline}.hljs-built_in,.hljs-type{color:#4ec9b0}.hljs-class,.hljs-number{color:#b8d7a3}.hljs-meta-string,.hljs-string{color:#d69d85}.hljs-regexp,.hljs-template-tag{color:#9a5334}.hljs-formula,.hljs-function,.hljs-params,.hljs-subst,.hljs-title{color:#dcdcdc}.hljs-comment,.hljs-quote{color:#57a64a;font-style:italic}.hljs-doctag{color:#608b4e}.hljs-meta,.hljs-meta-keyword,.hljs-tag{color:#9b9b9b}.hljs-template-variable,.hljs-variable{color:#bd63c5}.hljs-attr,.hljs-attribute,.hljs-builtin-name{color:#9cdcfe}.hljs-section{color:gold}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-bullet,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag{color:#d7ba7d}.hljs-addition{background-color:#144212;display:inline-block;width:100%}.hljs-deletion{background-color:#600;display:inline-block;width:100%}
-/* MarkBind-customized styles */.hljs>span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc}
+/* MarkBind-customized styles */.hljs span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc}
diff --git a/packages/cli/test/functional/test_site_algolia_plugin/expected/markbind/css/codeblock-light.min.css b/packages/cli/test/functional/test_site_algolia_plugin/expected/markbind/css/codeblock-light.min.css
index 72a97c76ab..5e20b4e805 100644
--- a/packages/cli/test/functional/test_site_algolia_plugin/expected/markbind/css/codeblock-light.min.css
+++ b/packages/cli/test/functional/test_site_algolia_plugin/expected/markbind/css/codeblock-light.min.css
@@ -1,2 +1,2 @@
 .hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8ff}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
-/* MarkBind-customized styles */.hljs>span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5}
+/* MarkBind-customized styles */.hljs span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5}
diff --git a/packages/cli/test/functional/test_site_convert/expected/markbind/css/codeblock-dark.min.css b/packages/cli/test/functional/test_site_convert/expected/markbind/css/codeblock-dark.min.css
index 25af447195..1a1f424be5 100644
--- a/packages/cli/test/functional/test_site_convert/expected/markbind/css/codeblock-dark.min.css
+++ b/packages/cli/test/functional/test_site_convert/expected/markbind/css/codeblock-dark.min.css
@@ -1,2 +1,2 @@
 .hljs{display:block;overflow-x:auto;padding:.5em;background:#2b2b2b;color:#dcdcdc}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-symbol{color:#569cd6}.hljs-link{color:#569cd6;text-decoration:underline}.hljs-built_in,.hljs-type{color:#4ec9b0}.hljs-class,.hljs-number{color:#b8d7a3}.hljs-meta-string,.hljs-string{color:#d69d85}.hljs-regexp,.hljs-template-tag{color:#9a5334}.hljs-formula,.hljs-function,.hljs-params,.hljs-subst,.hljs-title{color:#dcdcdc}.hljs-comment,.hljs-quote{color:#57a64a;font-style:italic}.hljs-doctag{color:#608b4e}.hljs-meta,.hljs-meta-keyword,.hljs-tag{color:#9b9b9b}.hljs-template-variable,.hljs-variable{color:#bd63c5}.hljs-attr,.hljs-attribute,.hljs-builtin-name{color:#9cdcfe}.hljs-section{color:gold}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-bullet,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag{color:#d7ba7d}.hljs-addition{background-color:#144212;display:inline-block;width:100%}.hljs-deletion{background-color:#600;display:inline-block;width:100%}
-/* MarkBind-customized styles */.hljs>span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc}
+/* MarkBind-customized styles */.hljs span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc}
diff --git a/packages/cli/test/functional/test_site_convert/expected/markbind/css/codeblock-light.min.css b/packages/cli/test/functional/test_site_convert/expected/markbind/css/codeblock-light.min.css
index 72a97c76ab..5e20b4e805 100644
--- a/packages/cli/test/functional/test_site_convert/expected/markbind/css/codeblock-light.min.css
+++ b/packages/cli/test/functional/test_site_convert/expected/markbind/css/codeblock-light.min.css
@@ -1,2 +1,2 @@
 .hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8ff}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
-/* MarkBind-customized styles */.hljs>span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5}
+/* MarkBind-customized styles */.hljs span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5}
diff --git a/packages/cli/test/functional/test_site_expressive_layout/expected/markbind/css/codeblock-dark.min.css b/packages/cli/test/functional/test_site_expressive_layout/expected/markbind/css/codeblock-dark.min.css
index 25af447195..1a1f424be5 100644
--- a/packages/cli/test/functional/test_site_expressive_layout/expected/markbind/css/codeblock-dark.min.css
+++ b/packages/cli/test/functional/test_site_expressive_layout/expected/markbind/css/codeblock-dark.min.css
@@ -1,2 +1,2 @@
 .hljs{display:block;overflow-x:auto;padding:.5em;background:#2b2b2b;color:#dcdcdc}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-symbol{color:#569cd6}.hljs-link{color:#569cd6;text-decoration:underline}.hljs-built_in,.hljs-type{color:#4ec9b0}.hljs-class,.hljs-number{color:#b8d7a3}.hljs-meta-string,.hljs-string{color:#d69d85}.hljs-regexp,.hljs-template-tag{color:#9a5334}.hljs-formula,.hljs-function,.hljs-params,.hljs-subst,.hljs-title{color:#dcdcdc}.hljs-comment,.hljs-quote{color:#57a64a;font-style:italic}.hljs-doctag{color:#608b4e}.hljs-meta,.hljs-meta-keyword,.hljs-tag{color:#9b9b9b}.hljs-template-variable,.hljs-variable{color:#bd63c5}.hljs-attr,.hljs-attribute,.hljs-builtin-name{color:#9cdcfe}.hljs-section{color:gold}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-bullet,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag{color:#d7ba7d}.hljs-addition{background-color:#144212;display:inline-block;width:100%}.hljs-deletion{background-color:#600;display:inline-block;width:100%}
-/* MarkBind-customized styles */.hljs>span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc}
+/* MarkBind-customized styles */.hljs span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc}
diff --git a/packages/cli/test/functional/test_site_expressive_layout/expected/markbind/css/codeblock-light.min.css b/packages/cli/test/functional/test_site_expressive_layout/expected/markbind/css/codeblock-light.min.css
index 72a97c76ab..5e20b4e805 100644
--- a/packages/cli/test/functional/test_site_expressive_layout/expected/markbind/css/codeblock-light.min.css
+++ b/packages/cli/test/functional/test_site_expressive_layout/expected/markbind/css/codeblock-light.min.css
@@ -1,2 +1,2 @@
 .hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8ff}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
-/* MarkBind-customized styles */.hljs>span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5}
+/* MarkBind-customized styles */.hljs span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5}
diff --git a/packages/cli/test/functional/test_site_special_tags/expected/markbind/css/codeblock-dark.min.css b/packages/cli/test/functional/test_site_special_tags/expected/markbind/css/codeblock-dark.min.css
index 25af447195..1a1f424be5 100644
--- a/packages/cli/test/functional/test_site_special_tags/expected/markbind/css/codeblock-dark.min.css
+++ b/packages/cli/test/functional/test_site_special_tags/expected/markbind/css/codeblock-dark.min.css
@@ -1,2 +1,2 @@
 .hljs{display:block;overflow-x:auto;padding:.5em;background:#2b2b2b;color:#dcdcdc}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-symbol{color:#569cd6}.hljs-link{color:#569cd6;text-decoration:underline}.hljs-built_in,.hljs-type{color:#4ec9b0}.hljs-class,.hljs-number{color:#b8d7a3}.hljs-meta-string,.hljs-string{color:#d69d85}.hljs-regexp,.hljs-template-tag{color:#9a5334}.hljs-formula,.hljs-function,.hljs-params,.hljs-subst,.hljs-title{color:#dcdcdc}.hljs-comment,.hljs-quote{color:#57a64a;font-style:italic}.hljs-doctag{color:#608b4e}.hljs-meta,.hljs-meta-keyword,.hljs-tag{color:#9b9b9b}.hljs-template-variable,.hljs-variable{color:#bd63c5}.hljs-attr,.hljs-attribute,.hljs-builtin-name{color:#9cdcfe}.hljs-section{color:gold}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-bullet,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag{color:#d7ba7d}.hljs-addition{background-color:#144212;display:inline-block;width:100%}.hljs-deletion{background-color:#600;display:inline-block;width:100%}
-/* MarkBind-customized styles */.hljs>span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc}
+/* MarkBind-customized styles */.hljs span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc}
diff --git a/packages/cli/test/functional/test_site_special_tags/expected/markbind/css/codeblock-light.min.css b/packages/cli/test/functional/test_site_special_tags/expected/markbind/css/codeblock-light.min.css
index 72a97c76ab..5e20b4e805 100644
--- a/packages/cli/test/functional/test_site_special_tags/expected/markbind/css/codeblock-light.min.css
+++ b/packages/cli/test/functional/test_site_special_tags/expected/markbind/css/codeblock-light.min.css
@@ -1,2 +1,2 @@
 .hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8ff}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
-/* MarkBind-customized styles */.hljs>span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5}
+/* MarkBind-customized styles */.hljs span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5}
diff --git a/packages/cli/test/functional/test_site_templates/test_default/expected/markbind/css/codeblock-dark.min.css b/packages/cli/test/functional/test_site_templates/test_default/expected/markbind/css/codeblock-dark.min.css
index 25af447195..1a1f424be5 100644
--- a/packages/cli/test/functional/test_site_templates/test_default/expected/markbind/css/codeblock-dark.min.css
+++ b/packages/cli/test/functional/test_site_templates/test_default/expected/markbind/css/codeblock-dark.min.css
@@ -1,2 +1,2 @@
 .hljs{display:block;overflow-x:auto;padding:.5em;background:#2b2b2b;color:#dcdcdc}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-symbol{color:#569cd6}.hljs-link{color:#569cd6;text-decoration:underline}.hljs-built_in,.hljs-type{color:#4ec9b0}.hljs-class,.hljs-number{color:#b8d7a3}.hljs-meta-string,.hljs-string{color:#d69d85}.hljs-regexp,.hljs-template-tag{color:#9a5334}.hljs-formula,.hljs-function,.hljs-params,.hljs-subst,.hljs-title{color:#dcdcdc}.hljs-comment,.hljs-quote{color:#57a64a;font-style:italic}.hljs-doctag{color:#608b4e}.hljs-meta,.hljs-meta-keyword,.hljs-tag{color:#9b9b9b}.hljs-template-variable,.hljs-variable{color:#bd63c5}.hljs-attr,.hljs-attribute,.hljs-builtin-name{color:#9cdcfe}.hljs-section{color:gold}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-bullet,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag{color:#d7ba7d}.hljs-addition{background-color:#144212;display:inline-block;width:100%}.hljs-deletion{background-color:#600;display:inline-block;width:100%}
-/* MarkBind-customized styles */.hljs>span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc}
+/* MarkBind-customized styles */.hljs span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc}
diff --git a/packages/cli/test/functional/test_site_templates/test_default/expected/markbind/css/codeblock-light.min.css b/packages/cli/test/functional/test_site_templates/test_default/expected/markbind/css/codeblock-light.min.css
index 72a97c76ab..5e20b4e805 100644
--- a/packages/cli/test/functional/test_site_templates/test_default/expected/markbind/css/codeblock-light.min.css
+++ b/packages/cli/test/functional/test_site_templates/test_default/expected/markbind/css/codeblock-light.min.css
@@ -1,2 +1,2 @@
 .hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8ff}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
-/* MarkBind-customized styles */.hljs>span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5}
+/* MarkBind-customized styles */.hljs span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5}
diff --git a/packages/cli/test/functional/test_site_templates/test_minimal/expected/markbind/css/codeblock-dark.min.css b/packages/cli/test/functional/test_site_templates/test_minimal/expected/markbind/css/codeblock-dark.min.css
index 25af447195..1a1f424be5 100644
--- a/packages/cli/test/functional/test_site_templates/test_minimal/expected/markbind/css/codeblock-dark.min.css
+++ b/packages/cli/test/functional/test_site_templates/test_minimal/expected/markbind/css/codeblock-dark.min.css
@@ -1,2 +1,2 @@
 .hljs{display:block;overflow-x:auto;padding:.5em;background:#2b2b2b;color:#dcdcdc}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-symbol{color:#569cd6}.hljs-link{color:#569cd6;text-decoration:underline}.hljs-built_in,.hljs-type{color:#4ec9b0}.hljs-class,.hljs-number{color:#b8d7a3}.hljs-meta-string,.hljs-string{color:#d69d85}.hljs-regexp,.hljs-template-tag{color:#9a5334}.hljs-formula,.hljs-function,.hljs-params,.hljs-subst,.hljs-title{color:#dcdcdc}.hljs-comment,.hljs-quote{color:#57a64a;font-style:italic}.hljs-doctag{color:#608b4e}.hljs-meta,.hljs-meta-keyword,.hljs-tag{color:#9b9b9b}.hljs-template-variable,.hljs-variable{color:#bd63c5}.hljs-attr,.hljs-attribute,.hljs-builtin-name{color:#9cdcfe}.hljs-section{color:gold}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-bullet,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag{color:#d7ba7d}.hljs-addition{background-color:#144212;display:inline-block;width:100%}.hljs-deletion{background-color:#600;display:inline-block;width:100%}
-/* MarkBind-customized styles */.hljs>span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc}
+/* MarkBind-customized styles */.hljs span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc}
diff --git a/packages/cli/test/functional/test_site_templates/test_minimal/expected/markbind/css/codeblock-light.min.css b/packages/cli/test/functional/test_site_templates/test_minimal/expected/markbind/css/codeblock-light.min.css
index 72a97c76ab..5e20b4e805 100644
--- a/packages/cli/test/functional/test_site_templates/test_minimal/expected/markbind/css/codeblock-light.min.css
+++ b/packages/cli/test/functional/test_site_templates/test_minimal/expected/markbind/css/codeblock-light.min.css
@@ -1,2 +1,2 @@
 .hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8ff}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
-/* MarkBind-customized styles */.hljs>span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5}
+/* MarkBind-customized styles */.hljs span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5}
diff --git a/packages/core-web/asset/css/codeblock-dark.min.css b/packages/core-web/asset/css/codeblock-dark.min.css
index 25af447195..1a1f424be5 100644
--- a/packages/core-web/asset/css/codeblock-dark.min.css
+++ b/packages/core-web/asset/css/codeblock-dark.min.css
@@ -1,2 +1,2 @@
 .hljs{display:block;overflow-x:auto;padding:.5em;background:#2b2b2b;color:#dcdcdc}.hljs-keyword,.hljs-literal,.hljs-name,.hljs-symbol{color:#569cd6}.hljs-link{color:#569cd6;text-decoration:underline}.hljs-built_in,.hljs-type{color:#4ec9b0}.hljs-class,.hljs-number{color:#b8d7a3}.hljs-meta-string,.hljs-string{color:#d69d85}.hljs-regexp,.hljs-template-tag{color:#9a5334}.hljs-formula,.hljs-function,.hljs-params,.hljs-subst,.hljs-title{color:#dcdcdc}.hljs-comment,.hljs-quote{color:#57a64a;font-style:italic}.hljs-doctag{color:#608b4e}.hljs-meta,.hljs-meta-keyword,.hljs-tag{color:#9b9b9b}.hljs-template-variable,.hljs-variable{color:#bd63c5}.hljs-attr,.hljs-attribute,.hljs-builtin-name{color:#9cdcfe}.hljs-section{color:gold}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}.hljs-bullet,.hljs-selector-attr,.hljs-selector-class,.hljs-selector-id,.hljs-selector-pseudo,.hljs-selector-tag{color:#d7ba7d}.hljs-addition{background-color:#144212;display:inline-block;width:100%}.hljs-deletion{background-color:#600;display:inline-block;width:100%}
-/* MarkBind-customized styles */.hljs>span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc}
+/* MarkBind-customized styles */.hljs span.highlighted{background:#474949}.hljs.inline{background:#444}.hljs:not(.inline){border:1px solid #1e1e1e}.code-block-heading{background:#3f3f3f;color:#dcdcdc}
diff --git a/packages/core-web/asset/css/codeblock-light.min.css b/packages/core-web/asset/css/codeblock-light.min.css
index 72a97c76ab..5e20b4e805 100644
--- a/packages/core-web/asset/css/codeblock-light.min.css
+++ b/packages/core-web/asset/css/codeblock-light.min.css
@@ -1,2 +1,2 @@
 .hljs{display:block;overflow-x:auto;padding:.5em;color:#333;background:#f8f8ff}.hljs-comment,.hljs-quote{color:#998;font-style:italic}.hljs-keyword,.hljs-selector-tag,.hljs-subst{color:#333;font-weight:700}.hljs-literal,.hljs-number,.hljs-tag .hljs-attr,.hljs-template-variable,.hljs-variable{color:teal}.hljs-doctag,.hljs-string{color:#d14}.hljs-section,.hljs-selector-id,.hljs-title{color:#900;font-weight:700}.hljs-subst{font-weight:400}.hljs-class .hljs-title,.hljs-type{color:#458;font-weight:700}.hljs-attribute,.hljs-name,.hljs-tag{color:navy;font-weight:400}.hljs-link,.hljs-regexp{color:#009926}.hljs-bullet,.hljs-symbol{color:#990073}.hljs-built_in,.hljs-builtin-name{color:#0086b3}.hljs-meta{color:#999;font-weight:700}.hljs-deletion{background:#fdd}.hljs-addition{background:#dfd}.hljs-emphasis{font-style:italic}.hljs-strong{font-weight:700}
-/* MarkBind-customized styles */.hljs>span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5}
+/* MarkBind-customized styles */.hljs span.highlighted{background:#e6e6fa}.hljs.inline{background:#f8f8f8}.hljs.inline.no-lang{color:#e83e8c}.hljs:not(.inline){border:1px solid #c8c8c8;border:1px solid rgba(200,200,200,.3)}.code-block-heading{background:#f2f2ff;color:#8787a5}
diff --git a/packages/core-web/src/styles/markbind.css b/packages/core-web/src/styles/markbind.css
index fde7418016..8cb7bad42c 100644
--- a/packages/core-web/src/styles/markbind.css
+++ b/packages/core-web/src/styles/markbind.css
@@ -61,7 +61,7 @@ pre > code.hljs[heading] {
         color: #333;
     }
 
-    code > span.highlighted {
+    code span.highlighted {
         background: lavender;
     }
 }

From 546ed137c052df058d36f25fdb952b8093a324cf Mon Sep 17 00:00:00 2001
From: Ryo Armanda 
Date: Mon, 5 Oct 2020 12:19:20 +0800
Subject: [PATCH 19/22] Improve docs

---
 docs/userGuide/syntax/code.mbdf | 15 ++++++++-------
 1 file changed, 8 insertions(+), 7 deletions(-)

diff --git a/docs/userGuide/syntax/code.mbdf b/docs/userGuide/syntax/code.mbdf
index dfc8b336eb..fce691bfc4 100644
--- a/docs/userGuide/syntax/code.mbdf
+++ b/docs/userGuide/syntax/code.mbdf
@@ -57,18 +57,19 @@ To highlight lines, add the attribute `highlight-lines` as shown below.
 
 You can specify highlighting in many different ways, depending on how you want it to be. There are two main variants:
 
-- Text-only highlighting
+##### Text-only highlighting
 
-    To highlight only the text portion of the line, you can just use the line numbers as is.
+To highlight only the text portion of the line, you can just use the line numbers as is.
 
-    For ranges of lines, join the two line numbers with a dash sign (`-`).
-- Whole-line highlighting
+For ranges of lines, join the two line numbers with a dash sign (`-`).
 
-    If you wish to highlight a full line (including whitespaces) or ranges of it, you can leverage MarkBind's own _line-slice_ syntax. Line-slices are in the form of `lineNumber[:]`, e.g. `2[:]`.
+##### Whole-line highlighting
+
+If you wish to highlight a full line (including whitespaces) or ranges of it, you can leverage MarkBind's own _line-slice_ syntax. Line-slices are in the form of `lineNumber[:]`, e.g. `2[:]`.
     
-    This variant's format is very similar to the previous, but instead use line-slices rather than line numbers.
+This variant's format is very similar to the previous, but instead use line-slices rather than line numbers.
 
-    For ranges, you only need to use line-slices on either ends.
+For ranges, you only need to use line-slices on either ends.
 
 
 

From ffebdf71623ad7a594804e4a7674e6be9981b8be Mon Sep 17 00:00:00 2001
From: Ryo Armanda 
Date: Tue, 6 Oct 2020 21:40:22 +0800
Subject: [PATCH 20/22] Address review

---
 .../markdown-it/highlight/HighlightRule.js    | 47 ++++++---------
 .../highlight/HighlightRuleComponent.js       | 60 +++++++++----------
 2 files changed, 47 insertions(+), 60 deletions(-)

diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRule.js b/packages/core/src/lib/markdown-it/highlight/HighlightRule.js
index 6f1c97771d..9b599167dc 100644
--- a/packages/core/src/lib/markdown-it/highlight/HighlightRule.js
+++ b/packages/core/src/lib/markdown-it/highlight/HighlightRule.js
@@ -7,24 +7,9 @@ class HighlightRule {
      */
     this.rule = rule;
   }
-
-  isLineNumber() {
-    return this.rule.length === 1 && this.rule[0].isNumber()
-  }
-  
-  isLineSlice() {
-    return this.rule.length === 1 && this.rule[0].isSlice();
-  }
   
   isLineRange() {
-    if (this.rule.length !== 2) {
-      return false;
-    }
-
-    const [isFirstNumber, isSecondNumber] = this.rule.map(comp => comp.isNumber());
-    const [isFirstSlice, isSecondSlice] = this.rule.map(comp => comp.isSlice());
-
-    return (isFirstNumber || isFirstSlice) && (isSecondNumber || isSecondSlice);
+    return this.rule.length === 2;
   }
   
   static parseRule(ruleString) {
@@ -33,31 +18,37 @@ class HighlightRule {
   }
   
   offsetLines(offset) {
-    this.rule.map(comp => comp.offsetLines(offset));
+    this.rule.map(comp => comp.offsetLineNumber(offset));
   }
   
   shouldApplyHighlight(lineNumber) {
     const compares = this.rule.map(comp => comp.compareLine(lineNumber));
     if (this.isLineRange()) {
-      return (compares[0] <= 0) && (compares[1] >= 0);
+      const withinRangeStart = compares[0] <= 0;
+      const withinRangeEnd = compares[1] >= 0;
+      return withinRangeStart && withinRangeEnd;
     }
-    return compares[0] === 0;
+    
+    const atLineNumber = compares[0] === 0;
+    return atLineNumber;
   }
   
   applyHighlight(line) {
-    if (this.isLineSlice()) {
-      // TODO: Implement slice-index based highlighting
-      return HighlightRule._highlightWholeLine(line);
+    const isLineSlice = this.rule.length === 1 && this.rule[0].isSlice;
+    
+    if (this.isLineRange()) {
+      const shouldWholeLine = this.rule.some(comp => comp.isUnboundedSlice());
+      return shouldWholeLine
+        ? HighlightRule._highlightWholeLine(line)
+        : HighlightRule._highlightTextOnly(line);
     }
     
-    if (this.isLineNumber()) {
-      return HighlightRule._highlightTextOnly(line);
+    if (isLineSlice) {
+      // TODO: Implement slice-index based highlighting
+      return HighlightRule._highlightWholeLine(line);
     }
     
-    const shouldWholeLine = this.rule.some(comp => comp.isUnboundedSlice());
-    return shouldWholeLine
-      ? HighlightRule._highlightWholeLine(line)
-      : HighlightRule._highlightTextOnly(line);
+    return HighlightRule._highlightTextOnly(line);
   }
 
   static _splitCodeAndIndentation(codeStr) {
diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js
index c378ba7d85..d0ee9abf78 100644
--- a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js
+++ b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js
@@ -1,54 +1,50 @@
 const LINESLICE_REGEX = new RegExp('(\\d+)\\[(\\d*):(\\d*)]');
 
 class HighlightRuleComponent {
-  constructor(components) {
-    /**
-     * @type Number | Array
-     */
-    this.components = components;
-  }
-  
-  isNumber() {
-    return Number.isInteger(this.components);
-  }
-  
-  isSlice() {
-    return Array.isArray(this.components)
-      && this.components.length === 3
-      && this.components.every(Number.isInteger);
+  constructor(lineNumber, isSlice, bounds) {
+    this.lineNumber = lineNumber;
+    this.isSlice = isSlice || false;
+    this.bounds = bounds || [];
   }
   
   isUnboundedSlice() {
-    return this.isSlice()
-      && this.components[1] === -1
-      && this.components[2] === -1;
+    return this.isSlice && this.bounds.length === 0;
   }
   
   static parseRuleComponent(compString) {
     // tries to match with the line slice pattern
     const matches = compString.match(LINESLICE_REGEX);
     if (matches) {
-      const numbers = matches.slice(1) // keep the capturing group matches only
-        .map(x => x !== '' ? parseInt(x, 10) : -1);
-      return new HighlightRuleComponent(numbers);
+      const groups = matches.slice(1); // keep the capturing group matches only
+      const lineNumber = parseInt(groups.shift(), 10);
+      
+      const isUnbounded = groups.every(x => x === '');
+      if (isUnbounded) {
+        return new HighlightRuleComponent(lineNumber, true);
+      }
+      
+      const bounds = groups.map(x => x !== '' ? parseInt(x, 10) : -1);
+      return new HighlightRuleComponent(lineNumber, true, bounds);
     }
 
     // match fails, so it is just line numbers
-    const number = parseInt(compString, 10);
-    return new HighlightRuleComponent(number);
+    const lineNumber = parseInt(compString, 10);
+    return new HighlightRuleComponent(lineNumber);
   }
   
-  offsetLines(offset) {
-    if (this.isNumber()) {
-      this.components += offset;
-    } else {
-      this.components[0] += offset;
-    }
+  offsetLineNumber(offset) {
+    this.lineNumber += offset;
   }
-  
+
+  /**
+   * Compares the component's line number to a given line number.
+   * 
+   * @param lineNumber The line number to compare
+   * @returns {number} A negative number, zero, or a positive number when the given line number
+   *  is after, at, or before the component's line number
+   */
   compareLine(lineNumber) {
-    const lineRule = this.isSlice() ? this.components[0] : this.components;
-    return lineRule - lineNumber;
+    return this.lineNumber - lineNumber;
   }
 }
 

From 0c38f1aa15ffe752175a5a4215463c5b5ec48054 Mon Sep 17 00:00:00 2001
From: Ryo Armanda 
Date: Tue, 13 Oct 2020 18:17:55 +0800
Subject: [PATCH 21/22] Address reviews

---
 docs/userGuide/syntax/code.mbdf               |  4 ++--
 .../markdown-it/highlight/HighlightRule.js    | 22 +++++++++----------
 2 files changed, 13 insertions(+), 13 deletions(-)

diff --git a/docs/userGuide/syntax/code.mbdf b/docs/userGuide/syntax/code.mbdf
index fce691bfc4..7a561845e5 100644
--- a/docs/userGuide/syntax/code.mbdf
+++ b/docs/userGuide/syntax/code.mbdf
@@ -57,13 +57,13 @@ To highlight lines, add the attribute `highlight-lines` as shown below.
 
 You can specify highlighting in many different ways, depending on how you want it to be. There are two main variants:
 
-##### Text-only highlighting
+**Text-only highlighting**
 
 To highlight only the text portion of the line, you can just use the line numbers as is.
 
 For ranges of lines, join the two line numbers with a dash sign (`-`).
 
-##### Whole-line highlighting
+**Whole-line highlighting**
 
 If you wish to highlight a full line (including whitespaces) or ranges of it, you can leverage MarkBind's own _line-slice_ syntax. Line-slices are in the form of `lineNumber[:]`, e.g. `2[:]`.
     
diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRule.js b/packages/core/src/lib/markdown-it/highlight/HighlightRule.js
index 9b599167dc..7985b71280 100644
--- a/packages/core/src/lib/markdown-it/highlight/HighlightRule.js
+++ b/packages/core/src/lib/markdown-it/highlight/HighlightRule.js
@@ -1,15 +1,15 @@
 const { HighlightRuleComponent } = require('./HighlightRuleComponent.js');
 
 class HighlightRule {
-  constructor(rule) {
+  constructor(ruleComponents) {
     /**
      * @type Array
      */
-    this.rule = rule;
+    this.ruleComponents = ruleComponents;
   }
   
   isLineRange() {
-    return this.rule.length === 2;
+    return this.ruleComponents.length === 2;
   }
   
   static parseRule(ruleString) {
@@ -18,11 +18,11 @@ class HighlightRule {
   }
   
   offsetLines(offset) {
-    this.rule.map(comp => comp.offsetLineNumber(offset));
+    this.ruleComponents.forEach(comp => comp.offsetLineNumber(offset));
   }
   
   shouldApplyHighlight(lineNumber) {
-    const compares = this.rule.map(comp => comp.compareLine(lineNumber));
+    const compares = this.ruleComponents.map(comp => comp.compareLine(lineNumber));
     if (this.isLineRange()) {
       const withinRangeStart = compares[0] <= 0;
       const withinRangeEnd = compares[1] >= 0;
@@ -34,10 +34,10 @@ class HighlightRule {
   }
   
   applyHighlight(line) {
-    const isLineSlice = this.rule.length === 1 && this.rule[0].isSlice;
+    const isLineSlice = this.ruleComponents.length === 1 && this.ruleComponents[0].isSlice;
     
     if (this.isLineRange()) {
-      const shouldWholeLine = this.rule.some(comp => comp.isUnboundedSlice());
+      const shouldWholeLine = this.ruleComponents.some(comp => comp.isUnboundedSlice());
       return shouldWholeLine
         ? HighlightRule._highlightWholeLine(line)
         : HighlightRule._highlightTextOnly(line);
@@ -50,6 +50,10 @@ class HighlightRule {
     
     return HighlightRule._highlightTextOnly(line);
   }
+  
+  static _highlightWholeLine(codeStr) {
+    return `${codeStr}\n`;
+  }
 
   static _splitCodeAndIndentation(codeStr) {
     const codeStartIdx = codeStr.search(/\S|$/);
@@ -58,10 +62,6 @@ class HighlightRule {
     return [indents, content];
   }
   
-  static _highlightWholeLine(codeStr) {
-    return `${codeStr}\n`;
-  }
-  
   static _highlightTextOnly(codeStr) {
     const [indents, content] = HighlightRule._splitCodeAndIndentation(codeStr);
     return `${indents}${content}\n`

From 4f2c6af81cc44bf4e899b528be946596a61e2450 Mon Sep 17 00:00:00 2001
From: Ryo Armanda 
Date: Mon, 19 Oct 2020 19:48:00 +0800
Subject: [PATCH 22/22] Shift structure checks to bottom

---
 .../core/src/lib/markdown-it/highlight/HighlightRule.js   | 8 ++++----
 .../lib/markdown-it/highlight/HighlightRuleComponent.js   | 8 ++++----
 2 files changed, 8 insertions(+), 8 deletions(-)

diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRule.js b/packages/core/src/lib/markdown-it/highlight/HighlightRule.js
index 7985b71280..2602b12353 100644
--- a/packages/core/src/lib/markdown-it/highlight/HighlightRule.js
+++ b/packages/core/src/lib/markdown-it/highlight/HighlightRule.js
@@ -8,10 +8,6 @@ class HighlightRule {
     this.ruleComponents = ruleComponents;
   }
   
-  isLineRange() {
-    return this.ruleComponents.length === 2;
-  }
-  
   static parseRule(ruleString) {
     const components = ruleString.split('-').map(HighlightRuleComponent.parseRuleComponent);
     return new HighlightRule(components);
@@ -66,6 +62,10 @@ class HighlightRule {
     const [indents, content] = HighlightRule._splitCodeAndIndentation(codeStr);
     return `${indents}${content}\n`
   }
+
+  isLineRange() {
+    return this.ruleComponents.length === 2;
+  }
 }
 
 module.exports = {
diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js
index d0ee9abf78..63e0073028 100644
--- a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js
+++ b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.js
@@ -7,10 +7,6 @@ class HighlightRuleComponent {
     this.bounds = bounds || [];
   }
   
-  isUnboundedSlice() {
-    return this.isSlice && this.bounds.length === 0;
-  }
-  
   static parseRuleComponent(compString) {
     // tries to match with the line slice pattern
     const matches = compString.match(LINESLICE_REGEX);
@@ -46,6 +42,10 @@ class HighlightRuleComponent {
   compareLine(lineNumber) {
     return this.lineNumber - lineNumber;
   }
+
+  isUnboundedSlice() {
+    return this.isSlice && this.bounds.length === 0;
+  }
 }
 
 module.exports = {