From 8b28c8243659daf60334ba1a26499b23c8536137 Mon Sep 17 00:00:00 2001 From: Nisemono Date: Fri, 4 Aug 2023 19:40:52 +0800 Subject: [PATCH 01/16] Migrate to typescript --- .../markdown-it/highlight/HighlightRule.ts | 48 +++++---------- .../highlight/HighlightRuleComponent.ts | 37 ++++++------ .../lib/markdown-it/highlight/Highlighter.ts | 27 +++++++++ .../src/lib/markdown-it/highlight/helper.ts | 58 +++++++++++++++++++ packages/core/src/lib/markdown-it/index.ts | 35 ++++++++--- 5 files changed, 146 insertions(+), 59 deletions(-) create mode 100644 packages/core/src/lib/markdown-it/highlight/Highlighter.ts diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts b/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts index 6f115f0053..d0c3457933 100644 --- a/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts @@ -1,5 +1,11 @@ import { HighlightRuleComponent } from './HighlightRuleComponent'; -import { splitCodeAndIndentation } from './helper'; +import { Boundary } from './helper' + +export enum HIGHLIGHT_TYPES { + WholeLine, + WholeText, + PartialText, +}; export class HighlightRule { ruleComponents: HighlightRuleComponent[]; @@ -61,56 +67,30 @@ export class HighlightRule { return atLineNumber; } - applyHighlight(line: string, lineNumber: number) { - // Applied rule is the first component until deduced otherwise + getHighlightType(lineNumber: number) { let [appliedRule] = this.ruleComponents; - if (this.isLineRange()) { - // For cases like 2[:]-3 (or 2-3[:]), the highlight would be line highlight - // across all the ranges - const shouldWholeLine = this.ruleComponents.some(comp => comp.isUnboundedSlice()); - if (shouldWholeLine) { - return HighlightRule._highlightWholeLine(line); + if (this.ruleComponents.some(comp => comp.isUnboundedSlice())) { + return {highlightType: HIGHLIGHT_TYPES.WholeLine, boundaries: null}; } const [startCompare, endCompare] = this.ruleComponents.map(comp => comp.compareLine(lineNumber)); if (startCompare < 0 && endCompare > 0) { // In-between range - return HighlightRule._highlightWholeText(line); + return {highlightType: HIGHLIGHT_TYPES.WholeText, boundaries: null}; } - // At the range boundaries const [startRule, endRule] = this.ruleComponents; appliedRule = startCompare === 0 ? startRule : endRule; } if (appliedRule.isSlice) { return appliedRule.isUnboundedSlice() - ? HighlightRule._highlightWholeLine(line) - : HighlightRule._highlightPartOfText(line, appliedRule.bounds); + ? {highlightType: HIGHLIGHT_TYPES.WholeLine, boundaries: null} + : {highlightType: HIGHLIGHT_TYPES.PartialText, boundaries: appliedRule.boundaries}; } - // Line number only - return HighlightRule._highlightWholeText(line); - } - - static _highlightWholeLine(codeStr: string) { - return `${codeStr}\n`; - } - - static _highlightWholeText(codeStr: string) { - const [indents, content] = splitCodeAndIndentation(codeStr); - return `${indents}${content}\n`; - } - - static _highlightPartOfText(codeStr: string, bounds: number[][]) { - /* - * Note: As part-of-text highlighting requires walking over the node of the generated - * html by highlight.js, highlighting will be applied in NodeProcessor instead. - * hl-data is used to pass over the bounds. - */ - const dataStr = bounds.map(bound => bound.join('-')).join(','); - return `${codeStr}\n`; + return {highlightType: HIGHLIGHT_TYPES.WholeText, boundaries: null}; } isLineRange() { diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts index 20e097616f..d59b86d736 100644 --- a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts @@ -1,4 +1,4 @@ -import { splitCodeAndIndentation } from './helper'; +import { splitCodeAndIndentation, Boundary, BOUNDARY_TYPE } from './helper'; const LINESLICE_CHAR_REGEX = /(\d+)\[(\d*):(\d*)]/; const LINESLICE_WORD_REGEX = /(\d+)\[(\d*)::(\d*)]/; @@ -8,11 +8,11 @@ const UNBOUNDED = -1; export class HighlightRuleComponent { lineNumber: number; isSlice: boolean; - bounds: number[][]; - constructor(lineNumber: number, isSlice: boolean = false, bounds: number[][] = []) { + boundaries: Boundary[]; + constructor(lineNumber: number, isSlice: boolean = false, boundaries: Boundary[] = []) { this.lineNumber = lineNumber; this.isSlice = isSlice; - this.bounds = bounds; + this.boundaries = boundaries; } static parseRuleComponent(compString: string, lineNumberOffset: number, lines: string[]) { @@ -28,9 +28,9 @@ export class HighlightRuleComponent { lineNumber += lineNumberOffset; const linePart = linePartWithQuotes.replace(/\\'/g, '\'').replace(/\\"/g, '"'); // unescape quotes - const bounds = HighlightRuleComponent.computeLinePartBounds(linePart, lines[lineNumber - 1]); + const boundaries = HighlightRuleComponent.computeLinePartBounds(linePart, lines[lineNumber - 1]); - return new HighlightRuleComponent(lineNumber, true, bounds); + return new HighlightRuleComponent(lineNumber, true, boundaries); } // Match line-slice (character and word variant) syntax @@ -52,13 +52,13 @@ export class HighlightRuleComponent { return new HighlightRuleComponent(lineNumber, true, []); } - let bound = groups.map(x => (x !== '' ? parseInt(x, 10) : UNBOUNDED)); + const bound = groups.map(x => (x !== '' ? parseInt(x, 10) : UNBOUNDED)); const isCharSlice = sliceMatch === linesliceCharMatch; - bound = isCharSlice + const boundaries = isCharSlice ? HighlightRuleComponent.computeCharBounds(bound, lines[lineNumber - 1]) : HighlightRuleComponent.computeWordBounds(bound, lines[lineNumber - 1]); - return new HighlightRuleComponent(lineNumber, true, [bound]); + return new HighlightRuleComponent(lineNumber, true, boundaries); } // Match line-number syntax @@ -83,7 +83,7 @@ export class HighlightRuleComponent { } isUnboundedSlice() { - return this.isSlice && this.bounds.length === 0; + return this.isSlice && this.boundaries.length === 0; } /** @@ -125,7 +125,7 @@ export class HighlightRuleComponent { } } - return [start, end]; + return [{ index: start, type: BOUNDARY_TYPE.Start }, { index: end, type: BOUNDARY_TYPE.End }]; } /** @@ -137,7 +137,7 @@ export class HighlightRuleComponent { * * @param bound The user-defined bound * @param line The given line - * @returns {[number, number]} The actual bound computed + * @returns {Array} The actual bound computed */ static computeWordBounds(bound: number[], line: string) { const [indents, content] = splitCodeAndIndentation(line); @@ -173,7 +173,7 @@ export class HighlightRuleComponent { end = wordEnd; } - return [start, end]; + return [{ index: start, type: BOUNDARY_TYPE.Start }, { index: end, type: BOUNDARY_TYPE.End }]; } /** @@ -181,7 +181,7 @@ export class HighlightRuleComponent { * * @param linePart The user-defined line part * @param line The given line - * @returns {Array<[number, number]>} The bounds computed, each indicates the range of each + * @returns {Array} The bounds computed, each indicates the range of each * occurrences of the line part in the line */ static computeLinePartBounds(linePart: string, line: string) { @@ -190,19 +190,20 @@ export class HighlightRuleComponent { let start = contentRemaining.indexOf(linePart); if (linePart === '' || start === -1) { - return [[0, 0]]; + return [{ index: 0, type: BOUNDARY_TYPE.Start }, { index: 0, type: BOUNDARY_TYPE.End }]; } - const bounds = []; + const boundaries = []; let curr = indents.length; while (start !== -1) { const end = start + linePart.length; - bounds.push([curr + start, curr + end]); + boundaries.push({ index: curr + start, type: BOUNDARY_TYPE.Start }); + boundaries.push({ index: curr + end, type: BOUNDARY_TYPE.End }); curr += end; contentRemaining = contentRemaining.substring(end); start = contentRemaining.indexOf(linePart); } - return bounds; + return boundaries; } } diff --git a/packages/core/src/lib/markdown-it/highlight/Highlighter.ts b/packages/core/src/lib/markdown-it/highlight/Highlighter.ts new file mode 100644 index 0000000000..73106d862e --- /dev/null +++ b/packages/core/src/lib/markdown-it/highlight/Highlighter.ts @@ -0,0 +1,27 @@ +import { Boundary, collateAllIntervals, splitCodeAndIndentation } from './helper'; + +export class Highlighter { + static highlightWholeLine(codeStr: string) { + return `${codeStr}\n`; + } + + static highlightWholeText(codeStr: string) { + const [indents, content] = splitCodeAndIndentation(codeStr); + return `${indents}${content}\n`; + } + + static highlightPartOfText(codeStr: string, boundaries: Boundary[]) { + /* + * Note: As part-of-text highlighting requires walking over the node of the generated + * html by highlight.js, highlighting will be applied in NodeProcessor instead. + * hl-data is used to pass over the bounds. + */ + const mergedBounds = collateAllIntervals(boundaries); + const dataStr = mergedBounds.map(bound => bound.join('-')).join(','); + return `${codeStr}\n`; + } +} + +module.exports = { + Highlighter, +}; \ No newline at end of file diff --git a/packages/core/src/lib/markdown-it/highlight/helper.ts b/packages/core/src/lib/markdown-it/highlight/helper.ts index 6d5762b524..24e3bc0c29 100644 --- a/packages/core/src/lib/markdown-it/highlight/helper.ts +++ b/packages/core/src/lib/markdown-it/highlight/helper.ts @@ -6,3 +6,61 @@ export function splitCodeAndIndentation(codeStr: string) { const content = codeStr.substring(codeStartIdx); return [indents, content]; } + +export enum BOUNDARY_TYPE { + Start, + End +}; + +export interface Boundary { + index: number; + type: BOUNDARY_TYPE; +} + +// Simplifies multiple bounds applied on a single line to an array of disjointed bounds +// boundaryCollection: +// e.g [{index: 1, type: BOUNDARY_TYPE.Start}, +// {index:3, type: BOUNDARY_TYPE.End}, +// {index: 5, type: BOUNDARY_TYPE.Start}, +// {index: 7, type: BOUNDARY_TYPE.End}] +export function collateAllIntervals(boundaryCollection: Boundary[]) { + let startCount = 0; + let endCount = 0; + let boundStart; + let boundEnd; + const output = []; + boundaryCollection.sort((boundaryA, boundaryB) => boundaryA.index - boundaryB.index); + for (let i = 0; i < boundaryCollection.length; i += 1) { + const currBoundary = boundaryCollection[i]; + + if (currBoundary.type === BOUNDARY_TYPE.Start) { + startCount += 1; + if (startCount === 1) { + // First start point that will anchor this interval + boundStart = currBoundary.index; + } + } else { + endCount += 1; + if (endCount === startCount) { + // Last end point that will conclude this interval + boundEnd = currBoundary.index; + if (boundEnd !== boundStart) { + // boundEnd should not be equal to boundStart + output.push([boundStart, boundEnd]); + } + endCount = 0; + startCount = 0; + } + } + } + return output; +} + +// console.log(collateAllIntervals([ +// { index: 1, type: BOUNDARY_TYPE.Start }, +// { index: 3, type: BOUNDARY_TYPE.End }, +// { index: 5, type: BOUNDARY_TYPE.Start }, +// { index: 8, type: BOUNDARY_TYPE.End }, +// { index: 7, type: BOUNDARY_TYPE.Start }, +// { index: 13, type: BOUNDARY_TYPE.End }, +// ])); diff --git a/packages/core/src/lib/markdown-it/index.ts b/packages/core/src/lib/markdown-it/index.ts index fe872a410d..b6bf034510 100644 --- a/packages/core/src/lib/markdown-it/index.ts +++ b/packages/core/src/lib/markdown-it/index.ts @@ -7,7 +7,9 @@ import Token from 'markdown-it/lib/token'; import * as logger from '../../utils/logger'; -import { HighlightRule } from './highlight/HighlightRule'; +import { HighlightRule, HIGHLIGHT_TYPES } from './highlight/HighlightRule'; +import { Highlighter } from './highlight/Highlighter'; +import { Boundary } from './highlight/helper' const createDoubleDelimiterInlineRule = require('./plugins/markdown-it-double-delimiter'); @@ -144,14 +146,33 @@ markdownIt.renderer.rules.fence = (tokens: Token[], // wrap all lines with so we can number them str = lines.map((line, index) => { const currentLineNumber = index + 1; - const rule = highlightRules.find(highlightRule => - highlightRule.shouldApplyHighlight(currentLineNumber)); - if (rule) { - return rule.applyHighlight(line, currentLineNumber); + // Rules that affects this line + const rules = highlightRules.filter( + highlightRule => highlightRule.shouldApplyHighlight(currentLineNumber)); + if (rules.length === 0) { + // not highlighted + return `${line}\n`; + } + // FIXME: Reason why two rules cannot be applied on the same line + const rawBoundaries: Boundary[] = []; + let lineHighlightType = HIGHLIGHT_TYPES.PartialText; + // Per line WholeLine override WholeText overrides PartialText + for (let i = 0; i < rules.length; i += 1) { + const {highlightType, boundaries} = rules[i].getHighlightType(currentLineNumber); + if (highlightType === HIGHLIGHT_TYPES.WholeLine) { + lineHighlightType = HIGHLIGHT_TYPES.WholeLine; + return Highlighter.highlightWholeLine(line); + } else if (highlightType === HIGHLIGHT_TYPES.WholeText) { + lineHighlightType = HIGHLIGHT_TYPES.WholeText; + } else if (boundaries !== null) { + boundaries.forEach(boundary => rawBoundaries.push(boundary)); + } } - // not highlighted - return `${line}\n`; + if (lineHighlightType === HIGHLIGHT_TYPES.WholeText) { + return Highlighter.highlightWholeText(line); + } + return Highlighter.highlightPartOfText(line, rawBoundaries); }).join(''); token.attrJoin('class', 'hljs'); From 359e148e8e6da00fa0a024bbc220beb729ce9257 Mon Sep 17 00:00:00 2001 From: Nisemono Date: Fri, 4 Aug 2023 22:06:13 +0800 Subject: [PATCH 02/16] Add docs --- docs/userGuide/syntax/code.md | 67 +++++++++++++++++++++++++++++++++-- 1 file changed, 65 insertions(+), 2 deletions(-) diff --git a/docs/userGuide/syntax/code.md b/docs/userGuide/syntax/code.md index b7ee723991..3bd71781e5 100644 --- a/docs/userGuide/syntax/code.md +++ b/docs/userGuide/syntax/code.md @@ -95,10 +95,74 @@ function add(a, b) { You can add the `highlight-lines` attribute to add highlighting to your code block. Refer to the example code block below for a visual demonstration of all the possible ways of highlighting a code block. +**Full text highlight** + +```js {start-from=6 .line-numbers highlight-lines="7, 9"} +function add(a, b) { + const sum = a + b; + console.log(`${a} + ${b} = ${sum}`); + return sum; +} +``` + + + +**Substring highlight** + + +```js {.line-numbers highlight-lines="1['function'], 4['diff']"} +function subtract(a, b) { + const diff = a - b; + console.log(`${a} + ${b} = ${diff}`); + return diff; +} +``` + + + +**Character-bounded highlight** + + +```js {.line-numbers highlight-lines="1[0:3], 1[6:10], 2[5:], 3[:6]"} +function multiply(a, b) { + const product = a * b; + console.log('Product = ${product}'); + return product; +} +``` + + + +**Word-bounded highlight** + + +```js {.line-numbers highlight-lines="1[1::3], 1[5::7], 2[2::], 3[::3]"} +// Function returns the distance travelled assuming constant speed +function calculateDistance(speed, time) { + const distance = speed * time; + console.log(`Distance travelled = ${distance}`); + return distance; +} +``` + + +**Full-line highlight** + -```java {.line-numbers highlight-lines="1[:],3['Inventory'],4['It\'s designed'],5,6[8:18],8[0::2],12[:]-14,16-18,20[12:]-22,24[1::]-26"} +```js {start-from=10 .line-numbers highlight-lines="11[:]"} +function add(a, b) { + return a + b; +} +``` + + + +**Sample Usage** + + +```java {.line-numbers highlight-lines="1[:],3['Inventory'],4['It\'s designed'],5,6[8:15],6[18:],8[0::2],12[:]-14,16-18,20[12:]-22,24[1::]-26"} import java.util.List; // Inventory is a class that stores inventory items in a list. @@ -126,7 +190,6 @@ public class Inventory { return items.remove(item); } } - ``` From ede79ec62e35899436c9cd39af3e2303782bb42e Mon Sep 17 00:00:00 2001 From: Nisemono Date: Fri, 4 Aug 2023 22:29:54 +0800 Subject: [PATCH 03/16] Fix lint errors --- .../src/lib/markdown-it/highlight/HighlightRule.ts | 13 ++++++------- .../src/lib/markdown-it/highlight/Highlighter.ts | 4 ---- .../core/src/lib/markdown-it/highlight/helper.ts | 4 ++-- packages/core/src/lib/markdown-it/index.ts | 4 ++-- 4 files changed, 10 insertions(+), 15 deletions(-) diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts b/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts index d0c3457933..0da475e622 100644 --- a/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts @@ -1,11 +1,10 @@ import { HighlightRuleComponent } from './HighlightRuleComponent'; -import { Boundary } from './helper' export enum HIGHLIGHT_TYPES { WholeLine, WholeText, PartialText, -}; +} export class HighlightRule { ruleComponents: HighlightRuleComponent[]; @@ -71,13 +70,13 @@ export class HighlightRule { let [appliedRule] = this.ruleComponents; if (this.isLineRange()) { if (this.ruleComponents.some(comp => comp.isUnboundedSlice())) { - return {highlightType: HIGHLIGHT_TYPES.WholeLine, boundaries: null}; + return { highlightType: HIGHLIGHT_TYPES.WholeLine, boundaries: null }; } const [startCompare, endCompare] = this.ruleComponents.map(comp => comp.compareLine(lineNumber)); if (startCompare < 0 && endCompare > 0) { // In-between range - return {highlightType: HIGHLIGHT_TYPES.WholeText, boundaries: null}; + return { highlightType: HIGHLIGHT_TYPES.WholeText, boundaries: null }; } const [startRule, endRule] = this.ruleComponents; @@ -86,11 +85,11 @@ export class HighlightRule { if (appliedRule.isSlice) { return appliedRule.isUnboundedSlice() - ? {highlightType: HIGHLIGHT_TYPES.WholeLine, boundaries: null} - : {highlightType: HIGHLIGHT_TYPES.PartialText, boundaries: appliedRule.boundaries}; + ? { highlightType: HIGHLIGHT_TYPES.WholeLine, boundaries: null } + : { highlightType: HIGHLIGHT_TYPES.PartialText, boundaries: appliedRule.boundaries }; } // Line number only - return {highlightType: HIGHLIGHT_TYPES.WholeText, boundaries: null}; + return { highlightType: HIGHLIGHT_TYPES.WholeText, boundaries: null }; } isLineRange() { diff --git a/packages/core/src/lib/markdown-it/highlight/Highlighter.ts b/packages/core/src/lib/markdown-it/highlight/Highlighter.ts index 73106d862e..b26eeaa346 100644 --- a/packages/core/src/lib/markdown-it/highlight/Highlighter.ts +++ b/packages/core/src/lib/markdown-it/highlight/Highlighter.ts @@ -21,7 +21,3 @@ export class Highlighter { return `${codeStr}\n`; } } - -module.exports = { - Highlighter, -}; \ No newline at end of file diff --git a/packages/core/src/lib/markdown-it/highlight/helper.ts b/packages/core/src/lib/markdown-it/highlight/helper.ts index 24e3bc0c29..ce7a2593b5 100644 --- a/packages/core/src/lib/markdown-it/highlight/helper.ts +++ b/packages/core/src/lib/markdown-it/highlight/helper.ts @@ -9,8 +9,8 @@ export function splitCodeAndIndentation(codeStr: string) { export enum BOUNDARY_TYPE { Start, - End -}; + End, +} export interface Boundary { index: number; diff --git a/packages/core/src/lib/markdown-it/index.ts b/packages/core/src/lib/markdown-it/index.ts index b6bf034510..c977461126 100644 --- a/packages/core/src/lib/markdown-it/index.ts +++ b/packages/core/src/lib/markdown-it/index.ts @@ -9,7 +9,7 @@ import * as logger from '../../utils/logger'; import { HighlightRule, HIGHLIGHT_TYPES } from './highlight/HighlightRule'; import { Highlighter } from './highlight/Highlighter'; -import { Boundary } from './highlight/helper' +import { Boundary } from './highlight/helper'; const createDoubleDelimiterInlineRule = require('./plugins/markdown-it-double-delimiter'); @@ -158,7 +158,7 @@ markdownIt.renderer.rules.fence = (tokens: Token[], let lineHighlightType = HIGHLIGHT_TYPES.PartialText; // Per line WholeLine override WholeText overrides PartialText for (let i = 0; i < rules.length; i += 1) { - const {highlightType, boundaries} = rules[i].getHighlightType(currentLineNumber); + const { highlightType, boundaries } = rules[i].getHighlightType(currentLineNumber); if (highlightType === HIGHLIGHT_TYPES.WholeLine) { lineHighlightType = HIGHLIGHT_TYPES.WholeLine; return Highlighter.highlightWholeLine(line); From df1e667b084a908a7c52a7046410ef3bf6b4914c Mon Sep 17 00:00:00 2001 From: Nisemono Date: Fri, 4 Aug 2023 23:40:19 +0800 Subject: [PATCH 04/16] Remove empty rules --- .../functional/test_site/testCodeBlocks.md | 39 +++++++++++++++++-- .../highlight/HighlightRuleComponent.ts | 8 +++- 2 files changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/cli/test/functional/test_site/testCodeBlocks.md b/packages/cli/test/functional/test_site/testCodeBlocks.md index 52c01cb18e..ae9c5168ef 100644 --- a/packages/cli/test/functional/test_site/testCodeBlocks.md +++ b/packages/cli/test/functional/test_site/testCodeBlocks.md @@ -83,7 +83,7 @@ Content in a fenced code block ``` **`highlight-lines` attr with full character-variant line-slice syntax should highlight only at specified range** -```xml {highlight-lines="1[1:4],2[5:13],3[2:10]-4,5-6[1:4]"} +```xml {highlight-lines="1[1:4],2[5:8],2[12:17],3[2:10]-4,5-6[1:4]"} goo goo @@ -93,7 +93,7 @@ Content in a fenced code block ``` **`highlight-lines` attr with partial character-variant line-slice syntax should default highlight to start/end of line** -```xml {highlight-lines="1[1:],2[:13],3[2:]-4,5-6[:2]"} +```xml {highlight-lines="1[1:],2[8:],2[:5],3[2:]-4,5-6[:2]"} goo goo @@ -115,7 +115,7 @@ Content in a fenced code block ``` **`highlight-lines` attr with full word-variant line-slice syntax should highlight only at specified word ranges** -```xml {highlight-lines="1[0::1],2[3::4],3[0::2],4[2::4],5[1::3]"} +```xml {highlight-lines="1[0::1],2[0::1],2[2::3],3[0::2],4[2::4],5[1::3]"} goo goo @@ -125,7 +125,7 @@ Content in a fenced code block ``` **`highlight-lines` attr with partial word-variant line-slice syntax should default highlight to start/end of line** -```xml {highlight-lines="1[0::],2[3::],3[::2],4[2::],5[::3]"} +```xml {highlight-lines="1[0::],2[::2],2[3::],3[::2],4[2::],5[::3]"} goo goo @@ -134,6 +134,37 @@ Content in a fenced code block ``` +**`highlight-lines` all attr should behave as expected** +```java {.line-numbers highlight-lines="1[:],3['items'],3['Inventory'],3[2::4],4['It\'s designed'],4[5::8],4[6::11],6[8:18],6[:]-8,8,8[0::2],12[:]-14,16-18,20[::1],20[12:]-21[::2],24[1::]-26"} +import java.util.List; + +// Inventory is a class that stores inventory items in a list. Big Inventory. +// It's designed as a thin wrapper on the List interface. +public class Inventory { + private List items; + + public int getItemCount(){ + return items.size(); + } + + public bool isEmpty() { + return items.isEmpty(); + } + + public Item getItem(idx: int) { + return items.get(idx); + } + + public void addItem(item: Item) { + return items.add(item); + } + + public void removeItem(item: Item) { + return items.remove(item); + } +} +``` + **Should render correctly with heading** ```{heading="A heading"} diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts index d59b86d736..d58f87adba 100644 --- a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts @@ -30,7 +30,11 @@ export class HighlightRuleComponent { const linePart = linePartWithQuotes.replace(/\\'/g, '\'').replace(/\\"/g, '"'); // unescape quotes const boundaries = HighlightRuleComponent.computeLinePartBounds(linePart, lines[lineNumber - 1]); - return new HighlightRuleComponent(lineNumber, true, boundaries); + if (boundaries != null) { + return new HighlightRuleComponent(lineNumber, true, boundaries); + } else { + return null; + } } // Match line-slice (character and word variant) syntax @@ -190,7 +194,7 @@ export class HighlightRuleComponent { let start = contentRemaining.indexOf(linePart); if (linePart === '' || start === -1) { - return [{ index: 0, type: BOUNDARY_TYPE.Start }, { index: 0, type: BOUNDARY_TYPE.End }]; + return null; } const boundaries = []; From aad2d49ec53c3ad1abdfb41465b798df0028bf1d Mon Sep 17 00:00:00 2001 From: Nisemono Date: Fri, 4 Aug 2023 23:43:16 +0800 Subject: [PATCH 05/16] Add tests --- .../test_site/expected/testCodeBlocks.html | 37 +++++++++++++++++-- .../testCodeBlocks.page-vue-render.js | 16 +++++--- .../highlight/HighlightRuleComponent.ts | 4 +- 3 files changed, 46 insertions(+), 11 deletions(-) diff --git a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html index 014661003b..659efa0d32 100644 --- a/packages/cli/test/functional/test_site/expected/testCodeBlocks.html +++ b/packages/cli/test/functional/test_site/expected/testCodeBlocks.html @@ -270,7 +270,7 @@

Testing Site-Navhighlight-lines attr with full character-variant line-slice syntax should highlight only at specified range

<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>
@@ -278,7 +278,7 @@ 

Testing Site-Navhighlight-lines attr with partial character-variant line-slice syntax should default highlight to start/end of line

<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>
@@ -296,7 +296,7 @@ 

Testing Site-Navhighlight-lines attr with full word-variant line-slice syntax should highlight only at specified word ranges

<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>
@@ -304,11 +304,40 @@ 

Testing Site-Navhighlight-lines attr with partial word-variant line-slice syntax should default highlight to start/end of line

<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>
 </foo>
+
+

highlight-lines all attr should behave as expected

+
import java.util.List;
+
+// Inventory is a class that stores inventory items in a list. Big Inventory.
+// It's designed as a thin wrapper on the List interface.
+public class Inventory {
+    private List<Item> items;
+
+    public int getItemCount(){
+        return items.size();
+    }
+
+    public bool isEmpty() {
+        return items.isEmpty();
+    }
+
+    public Item getItem(idx: int) {
+        return items.get(idx);
+    }
+
+    public void addItem(item: Item) {
+        return items.add(item);
+    }
+
+    public void removeItem(item: Item) {
+        return items.remove(item);
+    }
+}
 

Should render correctly with heading

diff --git a/packages/cli/test/functional/test_site/expected/testCodeBlocks.page-vue-render.js b/packages/cli/test/functional/test_site/expected/testCodeBlocks.page-vue-render.js index 1cf224bb28..de35714dee 100644 --- a/packages/cli/test/functional/test_site/expected/testCodeBlocks.page-vue-render.js +++ b/packages/cli/test/functional/test_site/expected/testCodeBlocks.page-vue-render.js @@ -1,7 +1,7 @@ var pageVueRenderFn = function anonymous( ) { -with(this){return _c('div',{attrs:{"id":"app"}},[_c('div',[_c('header',[_c('navbar',{attrs:{"type":"dark","default-highlight-on":"sibling-or-child"},scopedSlots:_u([{key:"brand",fn:function(){return [_c('a',{staticClass:"navbar-brand",attrs:{"href":"/","title":"Home"}},[_v("MarkBind Test Site")])]},proxy:true}])},[_v(" "),_c('li',[_c('a',{staticClass:"nav-link",attrs:{"href":"/test_site/bugs/index.html"}},[_v("Open Bugs")])])]),_v(" "),_m(0)],1),_v(" "),_m(1)]),_v(" "),_c('div',{attrs:{"id":"flex-body"}},[_c('overlay-source',{attrs:{"id":"site-nav","tag-name":"nav","to":"site-nav"}},[_c('div',{staticClass:"site-nav-top"},[_c('div',{staticClass:"fw-bold mb-2",staticStyle:{"font-size":"1.25rem"}},[_c('div',[_c('h2',{attrs:{"id":"default-layout"}},[_v("Default Layout"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#default-layout","onclick":"event.stopPropagation()"}})])])])]),_v(" "),_c('div',{staticClass:"nav-component slim-scroll"},[_c('div',[_c('site-nav',[_c('overlay-source',{staticClass:"site-nav-list site-nav-list-root",attrs:{"tag-name":"ul","to":"mb-site-nav"}},[_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-0",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"/test_site/index.html"}},[_v("Home 🏠")])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-0",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"/test_site/bugs/index.html"}},[_v("Open Bugs 🐛")])])]),_v(" "),_c('li',{staticClass:"site-nav-custom-list-item site-nav-list-item-0"},[_c('h3',{attrs:{"id":"testing-site-nav"}},[_v("Testing Site-Nav"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#testing-site-nav","onclick":"event.stopPropagation()"}})])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-0",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('strong',[_v("Dropdown ")]),_v(" "),_c('span',{staticClass:"glyphicon glyphicon-search",attrs:{"aria-hidden":"true"}}),_v(" title ✏️ "),_v(" "),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon site-nav-rotate-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-dropdown-container-open site-nav-list"},[_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.google.com/"}},[_v("Dropdown link one")])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.google.com/"}},[_v("Html within site-nav "),_c('span',{staticStyle:{"color":"red"}},[_v("should")]),_v(" be displayed properly")])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_v("Nested Dropdown title 📐\n\n"),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-list"},[_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-2",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.google.com/"}},[_c('strong',[_v("Nested")]),_v(" Dropdown link one")])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-2",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.google.com/"}},[_c('strong',[_v("Nested")]),_v(" Dropdown link two")])])])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.google.com/"}},[_v("Dropdown link two")])])])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-0",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.google.com/"}},[_c('mark',[_v("Third Link")]),_v(" 📋")])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-0",attrs:{"onclick":"handleSiteNavClick(this)"}},[_v("Filler text "),_c('a',{attrs:{"href":"https://www.youtube.com/"}},[_c('span',{staticClass:"glyphicon glyphicon-facetime-video",attrs:{"aria-hidden":"true"}}),_v(" Youtube 📺")]),_v(" filler text"),_v(" "),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-list"},[_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"}},[_v("The answer to everything in the universe")])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('mark',[_v("Dropdown title")]),_v(" "),_c('span',{staticClass:"glyphicon glyphicon-comment",attrs:{"aria-hidden":"true"}}),_v(" ✏️ "),_v(" "),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon site-nav-rotate-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-dropdown-container-open site-nav-list"},[_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-2",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.google.com/"}},[_c('strong',[_v("Nested")]),_v(" Dropdown link one")])])])])])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-0",attrs:{"onclick":"handleSiteNavClick(this)"}},[_v("Really Long Dropdown Title Really Long Dropdown Title Really Long Dropdown Title Really Long Dropdown\n\n"),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-list"},[_c('li',{staticClass:"site-nav-custom-list-item site-nav-list-item-1"},[_v("Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text")]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_v("Nested Dropdown Title\n\n"),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-list"},[_c('li',{staticClass:"site-nav-custom-list-item site-nav-list-item-2"},[_v("Hello Doge Hello Doge 🐶")]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-2",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"/test_site/index.html"}},[_c('strong',[_v("NESTED LINK")]),_v(" Home 🏠")])])]),_v(" "),_c('li',{staticClass:"site-nav-custom-list-item site-nav-list-item-2"},[_v("Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit")])])])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-0",attrs:{"onclick":"handleSiteNavClick(this)"}},[_v("Test line break in navigation layout\n\n"),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-list"},[_c('li',{staticClass:"site-nav-custom-list-item site-nav-list-item-1"},[_v("Nested line break text ✂️")]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"/test_site/index.html"}},[_v("Nested line break href")]),_v(" "),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-list"},[_c('li',{staticClass:"site-nav-custom-list-item site-nav-list-item-2"},[_v("Nested Nested line break text ✂️")])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_v("Nested line break dropdown menu\n\n"),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-list"},[_c('li',{staticClass:"site-nav-custom-list-item site-nav-list-item-2"},[_v("Line break item 2 📘")])])])])])])],1)],1)])]),_v(" "),_c('div',{attrs:{"id":"content-wrapper"}},[_c('breadcrumb'),_v(" "),_m(2),_v(" "),_m(3),_v(" "),_m(4),_m(5),_v(" "),_m(6),_m(7),_v(" "),_m(8),_m(9),_v(" "),_m(10),_m(11),_v(" "),_m(12),_m(13),_v(" "),_m(14),_m(15),_v(" "),_m(16),_m(17),_v(" "),_m(18),_m(19),_v(" "),_m(20),_m(21),_v(" "),_m(22),_m(23),_v(" "),_m(24),_m(25),_v(" "),_m(26),_m(27),_v(" "),_m(28),_m(29),_v(" "),_m(30),_m(31),_v(" "),_m(32),_m(33),_v(" "),_m(34),_m(35),_v(" "),_m(36),_m(37),_v(" "),_m(38),_m(39),_v(" "),_m(40),_m(41),_v(" "),_m(42),_m(43),_v(" "),_m(44),_m(45),_m(46),_v(" "),_m(47)],1),_v(" "),_c('overlay-source',{attrs:{"id":"page-nav","tag-name":"nav","to":"page-nav"}},[_c('div',{staticClass:"nav-component slim-scroll"})]),_v(" "),_c('scroll-top-button')],1),_v(" "),_m(48)])} +with(this){return _c('div',{attrs:{"id":"app"}},[_c('div',[_c('header',[_c('navbar',{attrs:{"type":"dark","default-highlight-on":"sibling-or-child"},scopedSlots:_u([{key:"brand",fn:function(){return [_c('a',{staticClass:"navbar-brand",attrs:{"href":"/","title":"Home"}},[_v("MarkBind Test Site")])]},proxy:true}])},[_v(" "),_c('li',[_c('a',{staticClass:"nav-link",attrs:{"href":"/test_site/bugs/index.html"}},[_v("Open Bugs")])])]),_v(" "),_m(0)],1),_v(" "),_m(1)]),_v(" "),_c('div',{attrs:{"id":"flex-body"}},[_c('overlay-source',{attrs:{"id":"site-nav","tag-name":"nav","to":"site-nav"}},[_c('div',{staticClass:"site-nav-top"},[_c('div',{staticClass:"fw-bold mb-2",staticStyle:{"font-size":"1.25rem"}},[_c('div',[_c('h2',{attrs:{"id":"default-layout"}},[_v("Default Layout"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#default-layout","onclick":"event.stopPropagation()"}})])])])]),_v(" "),_c('div',{staticClass:"nav-component slim-scroll"},[_c('div',[_c('site-nav',[_c('overlay-source',{staticClass:"site-nav-list site-nav-list-root",attrs:{"tag-name":"ul","to":"mb-site-nav"}},[_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-0",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"/test_site/index.html"}},[_v("Home 🏠")])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-0",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"/test_site/bugs/index.html"}},[_v("Open Bugs 🐛")])])]),_v(" "),_c('li',{staticClass:"site-nav-custom-list-item site-nav-list-item-0"},[_c('h3',{attrs:{"id":"testing-site-nav"}},[_v("Testing Site-Nav"),_c('a',{staticClass:"fa fa-anchor",attrs:{"href":"#testing-site-nav","onclick":"event.stopPropagation()"}})])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-0",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('strong',[_v("Dropdown ")]),_v(" "),_c('span',{staticClass:"glyphicon glyphicon-search",attrs:{"aria-hidden":"true"}}),_v(" title ✏️ "),_v(" "),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon site-nav-rotate-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-dropdown-container-open site-nav-list"},[_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.google.com/"}},[_v("Dropdown link one")])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.google.com/"}},[_v("Html within site-nav "),_c('span',{staticStyle:{"color":"red"}},[_v("should")]),_v(" be displayed properly")])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_v("Nested Dropdown title 📐\n\n"),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-list"},[_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-2",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.google.com/"}},[_c('strong',[_v("Nested")]),_v(" Dropdown link one")])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-2",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.google.com/"}},[_c('strong',[_v("Nested")]),_v(" Dropdown link two")])])])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.google.com/"}},[_v("Dropdown link two")])])])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-0",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.google.com/"}},[_c('mark',[_v("Third Link")]),_v(" 📋")])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-0",attrs:{"onclick":"handleSiteNavClick(this)"}},[_v("Filler text "),_c('a',{attrs:{"href":"https://www.youtube.com/"}},[_c('span',{staticClass:"glyphicon glyphicon-facetime-video",attrs:{"aria-hidden":"true"}}),_v(" Youtube 📺")]),_v(" filler text"),_v(" "),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-list"},[_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.youtube.com/watch?v=dQw4w9WgXcQ"}},[_v("The answer to everything in the universe")])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('mark',[_v("Dropdown title")]),_v(" "),_c('span',{staticClass:"glyphicon glyphicon-comment",attrs:{"aria-hidden":"true"}}),_v(" ✏️ "),_v(" "),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon site-nav-rotate-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-dropdown-container-open site-nav-list"},[_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-2",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"https://www.google.com/"}},[_c('strong',[_v("Nested")]),_v(" Dropdown link one")])])])])])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-0",attrs:{"onclick":"handleSiteNavClick(this)"}},[_v("Really Long Dropdown Title Really Long Dropdown Title Really Long Dropdown Title Really Long Dropdown\n\n"),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-list"},[_c('li',{staticClass:"site-nav-custom-list-item site-nav-list-item-1"},[_v("Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text Really Really Long Text")]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_v("Nested Dropdown Title\n\n"),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-list"},[_c('li',{staticClass:"site-nav-custom-list-item site-nav-list-item-2"},[_v("Hello Doge Hello Doge 🐶")]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-2",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"/test_site/index.html"}},[_c('strong',[_v("NESTED LINK")]),_v(" Home 🏠")])])]),_v(" "),_c('li',{staticClass:"site-nav-custom-list-item site-nav-list-item-2"},[_v("Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit Text cut off from height limit")])])])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-0",attrs:{"onclick":"handleSiteNavClick(this)"}},[_v("Test line break in navigation layout\n\n"),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-list"},[_c('li',{staticClass:"site-nav-custom-list-item site-nav-list-item-1"},[_v("Nested line break text ✂️")]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_c('a',{attrs:{"href":"/test_site/index.html"}},[_v("Nested line break href")]),_v(" "),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-list"},[_c('li',{staticClass:"site-nav-custom-list-item site-nav-list-item-2"},[_v("Nested Nested line break text ✂️")])])]),_v(" "),_c('li',[_c('div',{staticClass:"site-nav-default-list-item site-nav-list-item-1",attrs:{"onclick":"handleSiteNavClick(this)"}},[_v("Nested line break dropdown menu\n\n"),_c('div',{staticClass:"site-nav-dropdown-btn-container"},[_c('i',{staticClass:"site-nav-dropdown-btn-icon",attrs:{"onclick":"handleSiteNavClick(this.parentNode.parentNode, false); event.stopPropagation();"}},[_c('span',{staticClass:"glyphicon glyphicon-menu-down",attrs:{"aria-hidden":"true"}})])])]),_c('ul',{staticClass:"site-nav-dropdown-container site-nav-list"},[_c('li',{staticClass:"site-nav-custom-list-item site-nav-list-item-2"},[_v("Line break item 2 📘")])])])])])])],1)],1)])]),_v(" "),_c('div',{attrs:{"id":"content-wrapper"}},[_c('breadcrumb'),_v(" "),_m(2),_v(" "),_m(3),_v(" "),_m(4),_m(5),_v(" "),_m(6),_m(7),_v(" "),_m(8),_m(9),_v(" "),_m(10),_m(11),_v(" "),_m(12),_m(13),_v(" "),_m(14),_m(15),_v(" "),_m(16),_m(17),_v(" "),_m(18),_m(19),_v(" "),_m(20),_m(21),_v(" "),_m(22),_m(23),_v(" "),_m(24),_m(25),_v(" "),_m(26),_m(27),_v(" "),_m(28),_m(29),_v(" "),_m(30),_m(31),_v(" "),_m(32),_m(33),_v(" "),_m(34),_m(35),_v(" "),_m(36),_m(37),_v(" "),_m(38),_m(39),_v(" "),_m(40),_m(41),_v(" "),_m(42),_m(43),_v(" "),_m(44),_m(45),_v(" "),_m(46),_m(47),_m(48),_v(" "),_m(49)],1),_v(" "),_c('overlay-source',{attrs:{"id":"page-nav","tag-name":"nav","to":"page-nav"}},[_c('div',{staticClass:"nav-component slim-scroll"})]),_v(" "),_c('scroll-top-button')],1),_v(" "),_m(50)])} }; var pageVueStaticRenderFns = [function anonymous( ) { @@ -71,13 +71,13 @@ with(this){return _c('pre',[_c('code',{pre:true,attrs:{"class":"line-numbers hlj with(this){return _c('p',[_c('strong',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs inline no-lang"}},[_v("highlight-lines")]),_v(" attr with full character-variant line-slice syntax should highlight only at specified range")])])} },function anonymous( ) { -with(this){return _c('pre',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs xml"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name highlighted"}},[_v("foo")]),_v(">")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("bar")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr highlighted"}},[_v("type")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("=")]),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("\"na")]),_v("me\"")])]),_v(">")]),_v("goo"),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_c('span',[_v("b"),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("az")])])]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" ")]),_c('span',{pre:true,attrs:{"class":"hljs-attr highlighted"}},[_v("type")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("=")]),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v("goo"),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("qux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v("goo"),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")])]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("quux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v("goo"),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")])]),_v("\n")]),_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_c('span',[_v("<"),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("/")])]),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("fo")]),_v("o")])]),_v(">")]),_v("\n")])])])} +with(this){return _c('pre',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs xml"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name highlighted"}},[_v("foo")]),_v(">")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("bar")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("typ")]),_v("e")])]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_c('span',[_v("\"n"),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("ame\"")])])]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(">")])]),_v("goo"),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_c('span',[_v("b"),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("az")])])]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" ")]),_c('span',{pre:true,attrs:{"class":"hljs-attr highlighted"}},[_v("type")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("=")]),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v("goo"),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("qux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v("goo"),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")])]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("quux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v("goo"),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")])]),_v("\n")]),_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_c('span',[_v("<"),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("/")])]),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("fo")]),_v("o")])]),_v(">")]),_v("\n")])])])} },function anonymous( ) { with(this){return _c('p',[_c('strong',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs inline no-lang"}},[_v("highlight-lines")]),_v(" attr with partial character-variant line-slice syntax should default highlight to start/end of line")])])} },function anonymous( ) { -with(this){return _c('pre',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs xml"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name highlighted"}},[_v("foo")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(">")])]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("<")]),_c('span',{pre:true,attrs:{"class":"hljs-name highlighted"}},[_v("bar")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" ")]),_c('span',{pre:true,attrs:{"class":"hljs-attr highlighted"}},[_v("type")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("=")]),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("\"na")]),_v("me\"")])]),_v(">")]),_v("goo"),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_c('span',[_v("b"),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("az")])])]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" ")]),_c('span',{pre:true,attrs:{"class":"hljs-attr highlighted"}},[_v("type")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("=")]),_c('span',{pre:true,attrs:{"class":"hljs-string highlighted"}},[_v("\"name\"")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(">")])]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("goo")]),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("qux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v("goo"),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")])]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("quux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v("goo"),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")])]),_v("\n")]),_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("")]),_v("\n")])])])} +with(this){return _c('pre',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs xml"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name highlighted"}},[_v("foo")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(">")])]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("<")]),_c('span',{pre:true,attrs:{"class":"hljs-name highlighted"}},[_v("bar")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" ")]),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_c('span',[_v("typ"),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("e")])])]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("=")]),_c('span',{pre:true,attrs:{"class":"hljs-string highlighted"}},[_v("\"name\"")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(">")])]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("goo")]),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_c('span',[_v("b"),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("az")])])]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" ")]),_c('span',{pre:true,attrs:{"class":"hljs-attr highlighted"}},[_v("type")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("=")]),_c('span',{pre:true,attrs:{"class":"hljs-string highlighted"}},[_v("\"name\"")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(">")])]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("goo")]),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("qux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v("goo"),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")])]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("quux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v("goo"),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")])]),_v("\n")]),_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("")]),_v("\n")])])])} },function anonymous( ) { with(this){return _c('p',[_c('strong',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs inline no-lang"}},[_v("highlight-lines")]),_v(" attr with line-part syntax should highlight only at specified substring")])])} @@ -89,13 +89,19 @@ with(this){return _c('pre',[_c('code',{pre:true,attrs:{"class":"line-numbers hlj with(this){return _c('p',[_c('strong',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs inline no-lang"}},[_v("highlight-lines")]),_v(" attr with full word-variant line-slice syntax should highlight only at specified word ranges")])])} },function anonymous( ) { -with(this){return _c('pre',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs xml"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("foo")]),_v(">")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("bar")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v(" goo "),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("baz")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v(" goo "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("qux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("goo ")])]),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("quux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr highlighted"}},[_v("type")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("=")]),_c('span',{pre:true,attrs:{"class":"hljs-string highlighted"}},[_v("\"name\"")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(">")])]),_c('span',[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" goo")]),_v(" ")]),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")]),_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")])])])} +with(this){return _c('pre',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs xml"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("foo")]),_v(">")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("<")]),_c('span',{pre:true,attrs:{"class":"hljs-name highlighted"}},[_v("bar")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("goo")]),_v(" ")]),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("baz")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v(" goo "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("qux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("goo ")])]),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("quux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr highlighted"}},[_v("type")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("=")]),_c('span',{pre:true,attrs:{"class":"hljs-string highlighted"}},[_v("\"name\"")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(">")])]),_c('span',[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" goo")]),_v(" ")]),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")]),_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")])])])} },function anonymous( ) { with(this){return _c('p',[_c('strong',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs inline no-lang"}},[_v("highlight-lines")]),_v(" attr with partial word-variant line-slice syntax should default highlight to start/end of line")])])} },function anonymous( ) { -with(this){return _c('pre',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs xml"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("foo")]),_v(">")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("bar")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v(" goo "),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("baz")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v(" goo "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("qux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("goo ")])]),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("quux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_c('span',[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" goo")]),_v(" ")]),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")]),_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")])])])} +with(this){return _c('pre',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs xml"}},[_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("foo")]),_v(">")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("bar")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v(" goo "),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("baz")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_v(" goo "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("qux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("goo ")])]),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-tag highlighted"}},[_v("<"),_c('span',{pre:true,attrs:{"class":"hljs-name"}},[_v("quux")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-attr"}},[_v("type")]),_v("="),_c('span',{pre:true,attrs:{"class":"hljs-string"}},[_v("\"name\"")]),_v(">")]),_c('span',[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" goo")]),_v(" ")]),_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")]),_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-tag"}},[_v("")]),_v("\n")])])])} +},function anonymous( +) { +with(this){return _c('p',[_c('strong',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs inline no-lang"}},[_v("highlight-lines")]),_v(" all attr should behave as expected")])])} +},function anonymous( +) { +with(this){return _c('pre',[_c('code',{pre:true,attrs:{"class":"line-numbers hljs java"}},[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("import")]),_v(" java.util.List;\n")]),_c('span',[_v("\n")]),_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-comment"}},[_c('span',[_v("// "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("Inventory")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("is a")]),_c('span',[_v(" class that stores inventory "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("items")]),_c('span',[_v(" in a list. Big "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("Inventory")]),_v(".")])])])])]),_v("\n")]),_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-comment"}},[_c('span',[_v("// "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("It's designed")]),_c('span',[_v(" as a "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("thin wrapper on the List interface.")])])])]),_v("\n")]),_c('span',[_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("public")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-class"}},[_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("class")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-title"}},[_v("Inventory")]),_v(" ")]),_v("{\n")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("private")]),_v(" List items;\n")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("\n")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-function"}},[_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("public")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("int")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-title"}},[_v("getItemCount")]),_c('span',{pre:true,attrs:{"class":"hljs-params"}},[_v("()")])]),_v("{\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("return")]),_v(" items.size();\n")]),_c('span',[_v(" }\n")]),_c('span',[_v("\n")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-function"}},[_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("public")]),_v(" bool "),_c('span',{pre:true,attrs:{"class":"hljs-title"}},[_v("isEmpty")]),_c('span',{pre:true,attrs:{"class":"hljs-params"}},[_v("()")]),_v(" ")]),_v("{\n")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("return")]),_v(" items.isEmpty();\n")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" }\n")]),_c('span',[_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_c('span',{pre:true,attrs:{"class":"hljs-function"}},[_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("public")]),_v(" Item "),_c('span',{pre:true,attrs:{"class":"hljs-title"}},[_v("getItem")]),_c('span',{pre:true,attrs:{"class":"hljs-params"}},[_v("(idx: "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("int")]),_v(")")]),_v(" ")]),_v("{")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("return")]),_v(" items.get(idx);")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("}")]),_v("\n")]),_c('span',[_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-function"}},[_c('span',{pre:true,attrs:{"class":"hljs-keyword highlighted"}},[_v("public")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("void")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-title highlighted"}},[_v("addItem")]),_c('span',{pre:true,attrs:{"class":"hljs-params highlighted"}},[_v("(item: Item)")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" ")])]),_c('span',[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("{")]),_v("\n")])]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-keyword highlighted"}},[_v("return")]),_c('span',[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" items.add(item);")]),_v("\n")])]),_c('span',[_v(" }\n")]),_c('span',[_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-function"}},[_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("public")]),_v(" "),_c('span',{pre:true,attrs:{"class":"hljs-keyword highlighted"}},[_v("void")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" ")]),_c('span',{pre:true,attrs:{"class":"hljs-title highlighted"}},[_v("removeItem")]),_c('span',{pre:true,attrs:{"class":"hljs-params highlighted"}},[_v("(item: Item)")]),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v(" ")])]),_c('span',[_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("{")]),_v("\n")])]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_c('span',{pre:true,attrs:{"class":"hljs-keyword"}},[_v("return")]),_v(" items.remove(item);")]),_v("\n")]),_c('span',[_v(" "),_c('span',{pre:true,attrs:{"class":"highlighted"}},[_v("}")]),_v("\n")]),_c('span',[_v("}\n")])])])} },function anonymous( ) { with(this){return _c('p',[_c('strong',[_v("Should render correctly with heading")])])} diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts index d58f87adba..d024c274ff 100644 --- a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts @@ -32,9 +32,9 @@ export class HighlightRuleComponent { if (boundaries != null) { return new HighlightRuleComponent(lineNumber, true, boundaries); - } else { - return null; } + + return null; } // Match line-slice (character and word variant) syntax From 3ea67748fed1c8af55bd4180b2eba384853b8360 Mon Sep 17 00:00:00 2001 From: Nisemono Date: Mon, 14 Aug 2023 23:37:55 +0800 Subject: [PATCH 06/16] Clean code --- .../src/lib/markdown-it/highlight/helper.ts | 25 +++++-------------- packages/core/src/lib/markdown-it/index.ts | 5 ++-- 2 files changed, 9 insertions(+), 21 deletions(-) diff --git a/packages/core/src/lib/markdown-it/highlight/helper.ts b/packages/core/src/lib/markdown-it/highlight/helper.ts index ce7a2593b5..3f1f15658a 100644 --- a/packages/core/src/lib/markdown-it/highlight/helper.ts +++ b/packages/core/src/lib/markdown-it/highlight/helper.ts @@ -1,9 +1,9 @@ // Common helper functions to be used in HighlightRule or HighlightRuleComponent -export function splitCodeAndIndentation(codeStr: string) { - const codeStartIdx = codeStr.search(/\S|$/); - const indents = codeStr.substring(0, codeStartIdx); - const content = codeStr.substring(codeStartIdx); +export function splitCodeAndIndentation(code: string) { + const codeStartIdx = code.search(/\S|$/); + const indents = code.substring(0, codeStartIdx); + const content = code.substring(codeStartIdx); return [indents, content]; } @@ -17,12 +17,8 @@ export interface Boundary { type: BOUNDARY_TYPE; } -// Simplifies multiple bounds applied on a single line to an array of disjointed bounds -// boundaryCollection: -// e.g [{index: 1, type: BOUNDARY_TYPE.Start}, -// {index:3, type: BOUNDARY_TYPE.End}, -// {index: 5, type: BOUNDARY_TYPE.Start}, -// {index: 7, type: BOUNDARY_TYPE.End}] +// Simplifies and collates multiple bounds applied on a single line to an array of disjointed bounds +// e.g. [[0, 2], [1, 3], [8, 10]] -> [[0, 3], [8, 10]] export function collateAllIntervals(boundaryCollection: Boundary[]) { let startCount = 0; let endCount = 0; @@ -55,12 +51,3 @@ export function collateAllIntervals(boundaryCollection: Boundary[]) { } return output; } - -// console.log(collateAllIntervals([ -// { index: 1, type: BOUNDARY_TYPE.Start }, -// { index: 3, type: BOUNDARY_TYPE.End }, -// { index: 5, type: BOUNDARY_TYPE.Start }, -// { index: 8, type: BOUNDARY_TYPE.End }, -// { index: 7, type: BOUNDARY_TYPE.Start }, -// { index: 13, type: BOUNDARY_TYPE.End }, -// ])); diff --git a/packages/core/src/lib/markdown-it/index.ts b/packages/core/src/lib/markdown-it/index.ts index 9f2c376f59..fc0515656e 100644 --- a/packages/core/src/lib/markdown-it/index.ts +++ b/packages/core/src/lib/markdown-it/index.ts @@ -153,14 +153,14 @@ markdownIt.renderer.rules.fence = (tokens: Token[], // not highlighted return `${line}\n`; } - // FIXME: Reason why two rules cannot be applied on the same line + const rawBoundaries: Boundary[] = []; let lineHighlightType = HIGHLIGHT_TYPES.PartialText; // Per line WholeLine override WholeText overrides PartialText for (let i = 0; i < rules.length; i += 1) { const { highlightType, boundaries } = rules[i].getHighlightType(currentLineNumber); + if (highlightType === HIGHLIGHT_TYPES.WholeLine) { - lineHighlightType = HIGHLIGHT_TYPES.WholeLine; return Highlighter.highlightWholeLine(line); } else if (highlightType === HIGHLIGHT_TYPES.WholeText) { lineHighlightType = HIGHLIGHT_TYPES.WholeText; @@ -172,6 +172,7 @@ markdownIt.renderer.rules.fence = (tokens: Token[], if (lineHighlightType === HIGHLIGHT_TYPES.WholeText) { return Highlighter.highlightWholeText(line); } + return Highlighter.highlightPartOfText(line, rawBoundaries); }).join(''); From 25984dc4537af9e508311ce0a264edb9330d0c33 Mon Sep 17 00:00:00 2001 From: Nisemono Date: Sun, 26 Nov 2023 22:35:53 +0800 Subject: [PATCH 07/16] Use better bound merging strategy --- docs/userGuide/syntax/code.md | 8 +-- .../markdown-it/highlight/HighlightRule.ts | 15 +++--- .../highlight/HighlightRuleComponent.ts | 50 +++++++++---------- .../lib/markdown-it/highlight/Highlighter.ts | 14 +++--- .../src/lib/markdown-it/highlight/helper.ts | 50 +++++-------------- packages/core/src/lib/markdown-it/index.ts | 11 ++-- 6 files changed, 61 insertions(+), 87 deletions(-) diff --git a/docs/userGuide/syntax/code.md b/docs/userGuide/syntax/code.md index 3bd71781e5..e6f7f9ee23 100644 --- a/docs/userGuide/syntax/code.md +++ b/docs/userGuide/syntax/code.md @@ -92,7 +92,7 @@ function add(a, b) { ##### Line highlighting -You can add the `highlight-lines` attribute to add highlighting to your code block. Refer to the example code block +You can add the `highlight-lines` attribute to add highlighting to your code block. Refer to the examples below for a visual demonstration of all the possible ways of highlighting a code block. **Full text highlight** @@ -111,7 +111,7 @@ function add(a, b) { **Substring highlight** -```js {.line-numbers highlight-lines="1['function'], 4['diff']"} +```js {.line-numbers highlight-lines="1['function'], 2['a'], 2['b'], 4['diff']"} function subtract(a, b) { const diff = a - b; console.log(`${a} + ${b} = ${diff}`); @@ -159,10 +159,10 @@ function add(a, b) { -**Sample Usage** +**Sample Combined Usage** -```java {.line-numbers highlight-lines="1[:],3['Inventory'],4['It\'s designed'],5,6[8:15],6[18:],8[0::2],12[:]-14,16-18,20[12:]-22,24[1::]-26"} +```java {.line-numbers highlight-lines="1[:],3['Inventory'],3[4::6],4['It\'s designed'],5,6[8:15],6[18:],8[0::2],12[:]-14,16-18,20[12:]-22,24[1::]-26"} import java.util.List; // Inventory is a class that stores inventory items in a list. diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts b/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts index 0da475e622..11b05a46e3 100644 --- a/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts @@ -66,17 +66,20 @@ export class HighlightRule { return atLineNumber; } - getHighlightType(lineNumber: number) { + getHighlightType(lineNumber: number): { + highlightType: HIGHLIGHT_TYPES, + bounds: Array<[number, number]> | null + } { let [appliedRule] = this.ruleComponents; if (this.isLineRange()) { if (this.ruleComponents.some(comp => comp.isUnboundedSlice())) { - return { highlightType: HIGHLIGHT_TYPES.WholeLine, boundaries: null }; + return { highlightType: HIGHLIGHT_TYPES.WholeLine, bounds: null }; } const [startCompare, endCompare] = this.ruleComponents.map(comp => comp.compareLine(lineNumber)); if (startCompare < 0 && endCompare > 0) { // In-between range - return { highlightType: HIGHLIGHT_TYPES.WholeText, boundaries: null }; + return { highlightType: HIGHLIGHT_TYPES.WholeText, bounds: null }; } const [startRule, endRule] = this.ruleComponents; @@ -85,11 +88,11 @@ export class HighlightRule { if (appliedRule.isSlice) { return appliedRule.isUnboundedSlice() - ? { highlightType: HIGHLIGHT_TYPES.WholeLine, boundaries: null } - : { highlightType: HIGHLIGHT_TYPES.PartialText, boundaries: appliedRule.boundaries }; + ? { highlightType: HIGHLIGHT_TYPES.WholeLine, bounds: null } + : { highlightType: HIGHLIGHT_TYPES.PartialText, bounds: appliedRule.bounds }; } // Line number only - return { highlightType: HIGHLIGHT_TYPES.WholeText, boundaries: null }; + return { highlightType: HIGHLIGHT_TYPES.WholeText, bounds: null }; } isLineRange() { diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts index d024c274ff..8657b0f7b6 100644 --- a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts @@ -1,4 +1,4 @@ -import { splitCodeAndIndentation, Boundary, BOUNDARY_TYPE } from './helper'; +import { splitCodeAndIndentation } from './helper'; const LINESLICE_CHAR_REGEX = /(\d+)\[(\d*):(\d*)]/; const LINESLICE_WORD_REGEX = /(\d+)\[(\d*)::(\d*)]/; @@ -8,11 +8,12 @@ const UNBOUNDED = -1; export class HighlightRuleComponent { lineNumber: number; isSlice: boolean; - boundaries: Boundary[]; - constructor(lineNumber: number, isSlice: boolean = false, boundaries: Boundary[] = []) { + bounds: Array<[number, number]>; + + constructor(lineNumber: number, isSlice: boolean = false, bounds: Array<[number, number]> = []) { this.lineNumber = lineNumber; this.isSlice = isSlice; - this.boundaries = boundaries; + this.bounds = bounds; } static parseRuleComponent(compString: string, lineNumberOffset: number, lines: string[]) { @@ -28,13 +29,9 @@ export class HighlightRuleComponent { lineNumber += lineNumberOffset; const linePart = linePartWithQuotes.replace(/\\'/g, '\'').replace(/\\"/g, '"'); // unescape quotes - const boundaries = HighlightRuleComponent.computeLinePartBounds(linePart, lines[lineNumber - 1]); - - if (boundaries != null) { - return new HighlightRuleComponent(lineNumber, true, boundaries); - } + const bounds = HighlightRuleComponent.computeLinePartBounds(linePart, lines[lineNumber - 1]); - return null; + return new HighlightRuleComponent(lineNumber, true, bounds); } // Match line-slice (character and word variant) syntax @@ -56,13 +53,13 @@ export class HighlightRuleComponent { return new HighlightRuleComponent(lineNumber, true, []); } - const bound = groups.map(x => (x !== '' ? parseInt(x, 10) : UNBOUNDED)); + let bound = groups.map(x => (x !== '' ? parseInt(x, 10) : UNBOUNDED)) as [number, number]; const isCharSlice = sliceMatch === linesliceCharMatch; - const boundaries = isCharSlice + bound = isCharSlice ? HighlightRuleComponent.computeCharBounds(bound, lines[lineNumber - 1]) : HighlightRuleComponent.computeWordBounds(bound, lines[lineNumber - 1]); - return new HighlightRuleComponent(lineNumber, true, boundaries); + return new HighlightRuleComponent(lineNumber, true, [bound]); } // Match line-number syntax @@ -82,12 +79,12 @@ export class HighlightRuleComponent { * @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: number) { + compareLine(lineNumber: number): number { return this.lineNumber - lineNumber; } isUnboundedSlice() { - return this.isSlice && this.boundaries.length === 0; + return this.isSlice && this.bounds.length === 0; } /** @@ -101,7 +98,7 @@ export class HighlightRuleComponent { * @param line The given line * @returns {[number, number]} The actual bound computed */ - static computeCharBounds(bound: number[], line: string) { + static computeCharBounds(bound: [number, number], line: string): [number, number] { const [indents] = splitCodeAndIndentation(line); let [start, end] = bound; @@ -129,7 +126,7 @@ export class HighlightRuleComponent { } } - return [{ index: start, type: BOUNDARY_TYPE.Start }, { index: end, type: BOUNDARY_TYPE.End }]; + return [start, end]; } /** @@ -141,9 +138,9 @@ export class HighlightRuleComponent { * * @param bound The user-defined bound * @param line The given line - * @returns {Array} The actual bound computed + * @returns {[number, number]} The actual bound computed */ - static computeWordBounds(bound: number[], line: string) { + static computeWordBounds(bound: [number, number], line: string): [number, number] { const [indents, content] = splitCodeAndIndentation(line); const words = content.split(/\s+/); const wordPositions: number[][] = []; @@ -177,7 +174,7 @@ export class HighlightRuleComponent { end = wordEnd; } - return [{ index: start, type: BOUNDARY_TYPE.Start }, { index: end, type: BOUNDARY_TYPE.End }]; + return [start, end]; } /** @@ -185,29 +182,28 @@ export class HighlightRuleComponent { * * @param linePart The user-defined line part * @param line The given line - * @returns {Array} The bounds computed, each indicates the range of each + * @returns {Array<[number, number]>} The bounds computed, each indicates the range of each * occurrences of the line part in the line */ - static computeLinePartBounds(linePart: string, line: string) { + static computeLinePartBounds(linePart: string, line: string): Array<[number, number]> { const [indents, content] = splitCodeAndIndentation(line); let contentRemaining = content; let start = contentRemaining.indexOf(linePart); if (linePart === '' || start === -1) { - return null; + return [[0, 0]]; } - const boundaries = []; + const bounds: Array<[number, number]> = []; let curr = indents.length; while (start !== -1) { const end = start + linePart.length; - boundaries.push({ index: curr + start, type: BOUNDARY_TYPE.Start }); - boundaries.push({ index: curr + end, type: BOUNDARY_TYPE.End }); + bounds.push([curr + start, curr + end]); curr += end; contentRemaining = contentRemaining.substring(end); start = contentRemaining.indexOf(linePart); } - return boundaries; + return bounds; } } diff --git a/packages/core/src/lib/markdown-it/highlight/Highlighter.ts b/packages/core/src/lib/markdown-it/highlight/Highlighter.ts index b26eeaa346..52f9d38ea2 100644 --- a/packages/core/src/lib/markdown-it/highlight/Highlighter.ts +++ b/packages/core/src/lib/markdown-it/highlight/Highlighter.ts @@ -1,4 +1,4 @@ -import { Boundary, collateAllIntervals, splitCodeAndIndentation } from './helper'; +import { collateAllIntervals, splitCodeAndIndentation } from './helper'; export class Highlighter { static highlightWholeLine(codeStr: string) { @@ -10,13 +10,13 @@ export class Highlighter { return `${indents}${content}\n`; } - static highlightPartOfText(codeStr: string, boundaries: Boundary[]) { + static highlightPartOfText(codeStr: string, bounds: Array<[number, number]>) { /* - * Note: As part-of-text highlighting requires walking over the node of the generated - * html by highlight.js, highlighting will be applied in NodeProcessor instead. - * hl-data is used to pass over the bounds. - */ - const mergedBounds = collateAllIntervals(boundaries); + * Note: As part-of-text highlighting requires walking over the node of the generated + * html by highlight.js, highlighting will be applied in NodeProcessor instead. + * hl-data is used to pass over the bounds. + */ + const mergedBounds = collateAllIntervals(bounds); const dataStr = mergedBounds.map(bound => bound.join('-')).join(','); return `${codeStr}\n`; } diff --git a/packages/core/src/lib/markdown-it/highlight/helper.ts b/packages/core/src/lib/markdown-it/highlight/helper.ts index 3f1f15658a..1c1b2bdf29 100644 --- a/packages/core/src/lib/markdown-it/highlight/helper.ts +++ b/packages/core/src/lib/markdown-it/highlight/helper.ts @@ -7,47 +7,23 @@ export function splitCodeAndIndentation(code: string) { return [indents, content]; } -export enum BOUNDARY_TYPE { - Start, - End, -} - -export interface Boundary { - index: number; - type: BOUNDARY_TYPE; -} - // Simplifies and collates multiple bounds applied on a single line to an array of disjointed bounds // e.g. [[0, 2], [1, 3], [8, 10]] -> [[0, 3], [8, 10]] -export function collateAllIntervals(boundaryCollection: Boundary[]) { - let startCount = 0; - let endCount = 0; - let boundStart; - let boundEnd; - const output = []; - boundaryCollection.sort((boundaryA, boundaryB) => boundaryA.index - boundaryB.index); - for (let i = 0; i < boundaryCollection.length; i += 1) { - const currBoundary = boundaryCollection[i]; - - if (currBoundary.type === BOUNDARY_TYPE.Start) { - startCount += 1; - if (startCount === 1) { - // First start point that will anchor this interval - boundStart = currBoundary.index; - } +export function collateAllIntervals(bounds: Array<[number, number]>) { + const output: Array<[number, number]> = []; + bounds.sort((boundA, boundB) => boundA[0] - boundB[0]); + let currStart = bounds[0][0]; + let currEnd = bounds[0][1]; + for (let i = 1; i < bounds.length; i += 1) { + const [start, end] = bounds[i]; + if (start <= currEnd) { + currEnd = Math.max(currEnd, end); } else { - endCount += 1; - if (endCount === startCount) { - // Last end point that will conclude this interval - boundEnd = currBoundary.index; - if (boundEnd !== boundStart) { - // boundEnd should not be equal to boundStart - output.push([boundStart, boundEnd]); - } - endCount = 0; - startCount = 0; - } + output.push([currStart, currEnd]); + currStart = start; + currEnd = end; } } + output.push([currStart, currEnd]); return output; } diff --git a/packages/core/src/lib/markdown-it/index.ts b/packages/core/src/lib/markdown-it/index.ts index fc0515656e..b81272335e 100644 --- a/packages/core/src/lib/markdown-it/index.ts +++ b/packages/core/src/lib/markdown-it/index.ts @@ -9,7 +9,6 @@ import * as logger from '../../utils/logger'; import { HighlightRule, HIGHLIGHT_TYPES } from './highlight/HighlightRule'; import { Highlighter } from './highlight/Highlighter'; -import { Boundary } from './highlight/helper'; const createDoubleDelimiterInlineRule = require('./plugins/markdown-it-double-delimiter'); @@ -154,18 +153,18 @@ markdownIt.renderer.rules.fence = (tokens: Token[], return `${line}\n`; } - const rawBoundaries: Boundary[] = []; + const rawBounds: Array<[number, number]> = []; let lineHighlightType = HIGHLIGHT_TYPES.PartialText; // Per line WholeLine override WholeText overrides PartialText for (let i = 0; i < rules.length; i += 1) { - const { highlightType, boundaries } = rules[i].getHighlightType(currentLineNumber); + const { highlightType, bounds } = rules[i].getHighlightType(currentLineNumber); if (highlightType === HIGHLIGHT_TYPES.WholeLine) { return Highlighter.highlightWholeLine(line); } else if (highlightType === HIGHLIGHT_TYPES.WholeText) { lineHighlightType = HIGHLIGHT_TYPES.WholeText; - } else if (boundaries !== null) { - boundaries.forEach(boundary => rawBoundaries.push(boundary)); + } else if (bounds !== null) { + bounds.forEach(bound => rawBounds.push(bound)); } } @@ -173,7 +172,7 @@ markdownIt.renderer.rules.fence = (tokens: Token[], return Highlighter.highlightWholeText(line); } - return Highlighter.highlightPartOfText(line, rawBoundaries); + return Highlighter.highlightPartOfText(line, rawBounds); }).join(''); token.attrJoin('class', 'hljs'); From 9189f41b2616f32192aa72f92cba6a4f931d9313 Mon Sep 17 00:00:00 2001 From: Nisemono Date: Mon, 27 Nov 2023 00:43:37 +0800 Subject: [PATCH 08/16] Add unit tests and fix bugs in HighlightRuleComponent.ts --- .../highlight/HighlightRuleComponent.ts | 18 ++- .../lib/markdown-it/highlight/Highlighter.ts | 12 +- .../highlight/HighlightRuleComponent.test.ts | 151 ++++++++++++++++++ .../markdown-it/highlight/Highlighter.test.ts | 39 +++++ .../highlight/HightlightRule.test.ts | 84 ++++++++++ .../lib/markdown-it/highlight/helper.test.ts | 36 +++++ 6 files changed, 328 insertions(+), 12 deletions(-) create mode 100644 packages/core/test/unit/lib/markdown-it/highlight/HighlightRuleComponent.test.ts create mode 100644 packages/core/test/unit/lib/markdown-it/highlight/Highlighter.test.ts create mode 100644 packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts create mode 100644 packages/core/test/unit/lib/markdown-it/highlight/helper.test.ts diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts index 8657b0f7b6..0def4dd02b 100644 --- a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts @@ -27,7 +27,7 @@ export class HighlightRuleComponent { return null; } lineNumber += lineNumberOffset; - + if (lineNumber > lines.length) return null; const linePart = linePartWithQuotes.replace(/\\'/g, '\'').replace(/\\"/g, '"'); // unescape quotes const bounds = HighlightRuleComponent.computeLinePartBounds(linePart, lines[lineNumber - 1]); @@ -42,8 +42,10 @@ export class HighlightRuleComponent { // There are four capturing groups: [full match, line number, start bound, end bound] const groups = sliceMatch.slice(1); // discard full match - let lineNumber = parseInt(groups.shift() ?? '', 10); - if (Number.isNaN(lineNumber)) { + let lineNumber = Number(groups.shift() ?? ''); + if (Number.isNaN(lineNumber) + || !Number.isInteger(lineNumber) + || lineNumber < 1 || lineNumber > lines.length) { return null; } lineNumber += lineNumberOffset; @@ -63,9 +65,13 @@ export class HighlightRuleComponent { } // Match line-number syntax - if (!Number.isNaN(compString)) { // ensure the whole string can be converted to number - const lineNumber = parseInt(compString, 10) + lineNumberOffset; - return new HighlightRuleComponent(lineNumber); + const lineNumberBeforeOffset = Number(compString); + // ensure the whole string can be converted to number + if (!Number.isNaN(lineNumberBeforeOffset) && Number.isInteger(lineNumberBeforeOffset)) { + const lineNumber = lineNumberBeforeOffset + lineNumberOffset; + if (lineNumber > 0 && lineNumber <= lines.length) { + return new HighlightRuleComponent(lineNumber); + } } // the string is an improperly written rule diff --git a/packages/core/src/lib/markdown-it/highlight/Highlighter.ts b/packages/core/src/lib/markdown-it/highlight/Highlighter.ts index 52f9d38ea2..ee6cc2325b 100644 --- a/packages/core/src/lib/markdown-it/highlight/Highlighter.ts +++ b/packages/core/src/lib/markdown-it/highlight/Highlighter.ts @@ -1,16 +1,16 @@ import { collateAllIntervals, splitCodeAndIndentation } from './helper'; export class Highlighter { - static highlightWholeLine(codeStr: string) { - return `${codeStr}\n`; + static highlightWholeLine(code: string) { + return `${code}\n`; } - static highlightWholeText(codeStr: string) { - const [indents, content] = splitCodeAndIndentation(codeStr); + static highlightWholeText(code: string) { + const [indents, content] = splitCodeAndIndentation(code); return `${indents}${content}\n`; } - static highlightPartOfText(codeStr: string, bounds: Array<[number, number]>) { + static highlightPartOfText(code: string, bounds: Array<[number, number]>) { /* * Note: As part-of-text highlighting requires walking over the node of the generated * html by highlight.js, highlighting will be applied in NodeProcessor instead. @@ -18,6 +18,6 @@ export class Highlighter { */ const mergedBounds = collateAllIntervals(bounds); const dataStr = mergedBounds.map(bound => bound.join('-')).join(','); - return `${codeStr}\n`; + return `${code}\n`; } } diff --git a/packages/core/test/unit/lib/markdown-it/highlight/HighlightRuleComponent.test.ts b/packages/core/test/unit/lib/markdown-it/highlight/HighlightRuleComponent.test.ts new file mode 100644 index 0000000000..6bb2e14cee --- /dev/null +++ b/packages/core/test/unit/lib/markdown-it/highlight/HighlightRuleComponent.test.ts @@ -0,0 +1,151 @@ +import { HighlightRuleComponent } from '../../../../../src/lib/markdown-it/highlight/HighlightRuleComponent'; + +describe('parseRuleComponent', () => { + // Test for valid line-part syntax + test('parses line-part syntax correctly', () => { + const result = HighlightRuleComponent.parseRuleComponent( + '3["some text"]', 0, + ['line1', 'line2', 'some text'], + ); + expect(result).toBeInstanceOf(HighlightRuleComponent); + const castedResult = result as HighlightRuleComponent; + expect(castedResult.lineNumber).toEqual(3); + expect(castedResult.isSlice).toBe(true); + }); + + // Test for valid line-slice character syntax + test('parses line-slice character syntax correctly', () => { + const result = HighlightRuleComponent.parseRuleComponent( + '2[1:4]', 0, + ['line1', 'line2 is longer', 'line3'], + ); + expect(result).toBeInstanceOf(HighlightRuleComponent); + const castedResult = result as HighlightRuleComponent; + expect(castedResult.lineNumber).toEqual(2); + expect(castedResult.isSlice).toBe(true); + }); + + // Test for invalid line-part format + test('returns null for invalid line-part format', () => { + const result = HighlightRuleComponent.parseRuleComponent('invalid["text"]', 0, ['line1', 'line2']); + expect(result).toBeNull(); + }); + + // Test for non-existent line number in line-part syntax + test('returns null for non-existent line number in line-part syntax', () => { + const result = HighlightRuleComponent.parseRuleComponent('10["text"]', 0, ['line1', 'line2']); + expect(result).toBeNull(); + }); + + // Test for invalid line-slice format + test('returns null for invalid line-slice format', () => { + const result = HighlightRuleComponent.parseRuleComponent('2[abc:def]', 0, ['line1', 'line2']); + expect(result).toBeNull(); + }); + + // Test for invalid line number + test('returns null for invalid line number', () => { + const result = HighlightRuleComponent.parseRuleComponent('NaN', 0, ['line1', 'line2']); + expect(result).toBeNull(); + }); +}); + +describe('computeCharBounds', () => { + // Test for normal bounds + test('computes character bounds correctly', () => { + const bounds = HighlightRuleComponent.computeCharBounds([2, 5], ' some text'); + expect(bounds).toEqual([4, 7]); + }); + + // Test for unbounded start + test('handles unbounded start correctly', () => { + const bounds = HighlightRuleComponent.computeCharBounds([-1, 4], ' some text'); + expect(bounds).toEqual([2, 6]); + }); + + // Test for unbounded end + test('handles unbounded end correctly', () => { + const bounds = HighlightRuleComponent.computeCharBounds([3, -1], ' some text'); + expect(bounds).toEqual([5, ' some text'.length]); + }); + + // Test for out-of-range bounds + test('handles out-of-range bounds correctly', () => { + const bounds = HighlightRuleComponent.computeCharBounds([30, 40], ' some text'); + expect(bounds).toEqual([' some text'.length, ' some text'.length]); + }); + + // Additional tests... +}); + +describe('computeWordBounds', () => { + // Test for normal word bounds + test('computes word bounds correctly', () => { + const bounds = HighlightRuleComponent.computeWordBounds([1, 2], ' some text here'); + expect(bounds).toEqual([7, 11]); + }); + + // Test for unbounded start + test('handles unbounded start correctly', () => { + const bounds = HighlightRuleComponent.computeWordBounds([-1, 2], ' some text here'); + expect(bounds).toEqual([2, 11]); + }); + + // Test for unbounded end + test('handles unbounded end correctly', () => { + const bounds = HighlightRuleComponent.computeWordBounds([1, -1], ' some text here'); + expect(bounds).toEqual([7, ' some text here'.length]); + }); + + // Test for non-existent words + test('handles non-existent words correctly', () => { + const bounds = HighlightRuleComponent.computeWordBounds([5, 7], ' some text here'); + expect(bounds).toEqual([' some text here'.length, ' some text here'.length]); + }); +}); + +describe('computeWordBounds', () => { + // Test for normal word bounds + test('computes word bounds correctly', () => { + const bounds = HighlightRuleComponent.computeWordBounds([1, 2], ' some text here'); + expect(bounds).toEqual([7, 11]); + }); + + // Test for unbounded start + test('handles unbounded start correctly', () => { + const bounds = HighlightRuleComponent.computeWordBounds([-1, 2], ' some text here'); + expect(bounds).toEqual([2, 11]); + }); + + // Test for unbounded end + test('handles unbounded end correctly', () => { + const bounds = HighlightRuleComponent.computeWordBounds([1, -1], ' some text here'); + expect(bounds).toEqual([7, ' some text here'.length]); + }); + + // Test for non-existent words + test('handles non-existent words correctly', () => { + const bounds = HighlightRuleComponent.computeWordBounds([5, 7], ' some text here'); + expect(bounds).toEqual([' some text here'.length, ' some text here'.length]); + }); +}); + +describe('computeLinePartBounds', () => { + // Test for normal line part + test('computes line part bounds correctly', () => { + const bounds = HighlightRuleComponent.computeLinePartBounds('text', ' some text here with text'); + expect(bounds).toEqual([[7, 11], [22, 26]]); + }); + + // Test for non-existent line part + test('returns empty bounds for non-existent line part', () => { + const bounds = HighlightRuleComponent.computeLinePartBounds('nonexistent', ' some text here'); + expect(bounds).toEqual([[0, 0]]); + }); + + // Test for empty line part + test('handles empty line part correctly', () => { + const bounds = HighlightRuleComponent.computeLinePartBounds('', ' some text here'); + expect(bounds).toEqual([[0, 0]]); + }); +}); diff --git a/packages/core/test/unit/lib/markdown-it/highlight/Highlighter.test.ts b/packages/core/test/unit/lib/markdown-it/highlight/Highlighter.test.ts new file mode 100644 index 0000000000..83f742abc0 --- /dev/null +++ b/packages/core/test/unit/lib/markdown-it/highlight/Highlighter.test.ts @@ -0,0 +1,39 @@ +import { Highlighter } from '../../../../../src/lib/markdown-it/highlight/Highlighter'; + +describe('highlightWholeLine', () => { + test('wraps code in highlighted span', () => { + const code = 'const x = 10;'; + const result = Highlighter.highlightWholeLine(code); + expect(result).toBe('const x = 10;\n'); + }); +}); + +describe('highlightWholeText', () => { + test('highlights non-indented part of code', () => { + const code = ' const x = 10;'; + const result = Highlighter.highlightWholeText(code); + expect(result).toBe(' const x = 10;\n'); + }); + + test('handles code with no indentation correctly', () => { + const code = 'const x = 10;'; + const result = Highlighter.highlightWholeText(code); + expect(result).toBe('const x = 10;\n'); + }); +}); + +describe('highlightPartOfText', () => { + test('sets hl-data attribute with merged bounds', () => { + const code = 'const x = 10;'; + const bounds: Array<[number, number]> = [[0, 4], [8, 10]]; + const result = Highlighter.highlightPartOfText(code, bounds); + expect(result).toBe('const x = 10;\n'); + }); + + test('handles non-overlapping bounds correctly', () => { + const code = 'const x = 10;'; + const bounds: Array<[number, number]> = [[0, 4], [10, 14]]; + const result = Highlighter.highlightPartOfText(code, bounds); + expect(result).toBe('const x = 10;\n'); + }); +}); diff --git a/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts b/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts new file mode 100644 index 0000000000..b16d98d0dd --- /dev/null +++ b/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts @@ -0,0 +1,84 @@ +import { HighlightRule, HIGHLIGHT_TYPES } from '../../../../../src/lib/markdown-it/highlight/HighlightRule'; +import { HighlightRuleComponent } from '../../../../../src/lib/markdown-it/highlight/HighlightRuleComponent'; + +describe('parseAllRules', () => { + test('parses multiple rules correctly', () => { + const allRules = '3,4-5'; + const rules = HighlightRule.parseAllRules(allRules, 0, 'line1\nline2\nline3\nline4\nline5'); + expect(rules).toHaveLength(2); + expect(rules[0]).toBeInstanceOf(HighlightRule); + expect(rules[1]).toBeInstanceOf(HighlightRule); + }); + + test('ignores invalid rules', () => { + const allRules = 'invalid,3-4'; + const rules = HighlightRule.parseAllRules(allRules, 0, 'line1\nline2\nline3\nline4'); + expect(rules).toHaveLength(1); // Only one valid rule + }); +}); + +describe('splitByChar', () => { + test('splits string correctly', () => { + const splitResult = HighlightRule.splitByChar('3-4,5', ','); + expect(splitResult).toEqual(['3-4', '5']); + }); +}); + +describe('parseRule', () => { + test('parses a valid rule correctly', () => { + const rule = HighlightRule.parseRule('3-4', 0, ['line1', 'line2', 'line3', 'line4']); + expect(rule).toBeInstanceOf(HighlightRule); + const castedRule = rule as HighlightRule; + expect(castedRule.ruleComponents).toHaveLength(2); + }); + + test('returns null for an invalid rule', () => { + const rule = HighlightRule.parseRule('invalid', 0, ['line1', 'line2']); + expect(rule).toBeNull(); + }); +}); + +describe('shouldApplyHighlight', () => { + const rules = HighlightRule.parseAllRules('3-4', 0, 'line1\nline2\nline3\nline4\nline5'); + const rule = rules[0]; + + test('returns true within the line range', () => { + expect(rule.shouldApplyHighlight(3)).toBeTruthy(); + expect(rule.shouldApplyHighlight(4)).toBeTruthy(); + }); + + test('returns false outside the line range', () => { + expect(rule.shouldApplyHighlight(2)).toBeFalsy(); + expect(rule.shouldApplyHighlight(5)).toBeFalsy(); + }); +}); + +describe('getHighlightType', () => { + // Assuming rules are parsed correctly in these tests + const rules = HighlightRule.parseAllRules('3,4[1:5]', 0, 'line1\nline2\nline3\nline4\nline5'); + const wholeLineRule = rules[0]; + const partialTextRule = rules[1]; + + test('returns WholeText for single line', () => { + const { highlightType } = wholeLineRule.getHighlightType(3); + expect(highlightType).toBe(HIGHLIGHT_TYPES.WholeText); + }); + + test('returns PartialText for bounded slice', () => { + const { highlightType, bounds } = partialTextRule.getHighlightType(4); + expect(highlightType).toBe(HIGHLIGHT_TYPES.PartialText); + expect(bounds).toEqual([[1, 5]]); + }); +}); + +describe('isLineRange', () => { + test('returns true for range', () => { + const rule = new HighlightRule([new HighlightRuleComponent(3), new HighlightRuleComponent(4)]); + expect(rule.isLineRange()).toBeTruthy(); + }); + + test('returns false for single line', () => { + const rule = new HighlightRule([new HighlightRuleComponent(3)]); + expect(rule.isLineRange()).toBeFalsy(); + }); +}); diff --git a/packages/core/test/unit/lib/markdown-it/highlight/helper.test.ts b/packages/core/test/unit/lib/markdown-it/highlight/helper.test.ts new file mode 100644 index 0000000000..3c7e55ee1f --- /dev/null +++ b/packages/core/test/unit/lib/markdown-it/highlight/helper.test.ts @@ -0,0 +1,36 @@ +import { + collateAllIntervals, + splitCodeAndIndentation, +} from '../../../../../src/lib/markdown-it/highlight/helper'; + +test('splitCodeAndIndentation with leading spaces', () => { + const [indents, content] = splitCodeAndIndentation(' var x = 1;'); + expect(indents).toEqual(' '); + expect(content).toEqual('var x = 1;'); +}); + +test('splitCodeAndIndentation with no leading spaces', () => { + const [indents, content] = splitCodeAndIndentation('var x = 1;'); + expect(indents).toEqual(''); + expect(content).toEqual('var x = 1;'); +}); + +// Add more test cases as needed + +test('collateAllIntervals with overlapping intervals', () => { + const actual = collateAllIntervals([[0, 2], [1, 3], [8, 10]]); + const expected = [[0, 3], [8, 10]]; + expect(actual).toEqual(expected); +}); + +test('collateAllIntervals with separate intervals', () => { + const actual = collateAllIntervals([[0, 2], [3, 5], [8, 10]]); + const expected = [[0, 2], [3, 5], [8, 10]]; + expect(actual).toEqual(expected); +}); + +test('collateAllIntervals with nested intervals', () => { + const actual = collateAllIntervals([[1, 4], [2, 3]]); + const expected = [[1, 4]]; + expect(actual).toEqual(expected); +}); From 1910332fcd8315fd2ea9c1edf509d4d95b7deab9 Mon Sep 17 00:00:00 2001 From: Nisemono Date: Mon, 27 Nov 2023 19:18:49 +0800 Subject: [PATCH 09/16] formatting --- .../highlight/HighlightRuleComponent.test.ts | 23 ------------------- .../highlight/HightlightRule.test.ts | 1 - .../lib/markdown-it/highlight/helper.test.ts | 2 -- 3 files changed, 26 deletions(-) diff --git a/packages/core/test/unit/lib/markdown-it/highlight/HighlightRuleComponent.test.ts b/packages/core/test/unit/lib/markdown-it/highlight/HighlightRuleComponent.test.ts index 6bb2e14cee..7058ff4247 100644 --- a/packages/core/test/unit/lib/markdown-it/highlight/HighlightRuleComponent.test.ts +++ b/packages/core/test/unit/lib/markdown-it/highlight/HighlightRuleComponent.test.ts @@ -1,7 +1,6 @@ import { HighlightRuleComponent } from '../../../../../src/lib/markdown-it/highlight/HighlightRuleComponent'; describe('parseRuleComponent', () => { - // Test for valid line-part syntax test('parses line-part syntax correctly', () => { const result = HighlightRuleComponent.parseRuleComponent( '3["some text"]', 0, @@ -13,7 +12,6 @@ describe('parseRuleComponent', () => { expect(castedResult.isSlice).toBe(true); }); - // Test for valid line-slice character syntax test('parses line-slice character syntax correctly', () => { const result = HighlightRuleComponent.parseRuleComponent( '2[1:4]', 0, @@ -25,25 +23,21 @@ describe('parseRuleComponent', () => { expect(castedResult.isSlice).toBe(true); }); - // Test for invalid line-part format test('returns null for invalid line-part format', () => { const result = HighlightRuleComponent.parseRuleComponent('invalid["text"]', 0, ['line1', 'line2']); expect(result).toBeNull(); }); - // Test for non-existent line number in line-part syntax test('returns null for non-existent line number in line-part syntax', () => { const result = HighlightRuleComponent.parseRuleComponent('10["text"]', 0, ['line1', 'line2']); expect(result).toBeNull(); }); - // Test for invalid line-slice format test('returns null for invalid line-slice format', () => { const result = HighlightRuleComponent.parseRuleComponent('2[abc:def]', 0, ['line1', 'line2']); expect(result).toBeNull(); }); - // Test for invalid line number test('returns null for invalid line number', () => { const result = HighlightRuleComponent.parseRuleComponent('NaN', 0, ['line1', 'line2']); expect(result).toBeNull(); @@ -51,53 +45,43 @@ describe('parseRuleComponent', () => { }); describe('computeCharBounds', () => { - // Test for normal bounds test('computes character bounds correctly', () => { const bounds = HighlightRuleComponent.computeCharBounds([2, 5], ' some text'); expect(bounds).toEqual([4, 7]); }); - // Test for unbounded start test('handles unbounded start correctly', () => { const bounds = HighlightRuleComponent.computeCharBounds([-1, 4], ' some text'); expect(bounds).toEqual([2, 6]); }); - // Test for unbounded end test('handles unbounded end correctly', () => { const bounds = HighlightRuleComponent.computeCharBounds([3, -1], ' some text'); expect(bounds).toEqual([5, ' some text'.length]); }); - // Test for out-of-range bounds test('handles out-of-range bounds correctly', () => { const bounds = HighlightRuleComponent.computeCharBounds([30, 40], ' some text'); expect(bounds).toEqual([' some text'.length, ' some text'.length]); }); - - // Additional tests... }); describe('computeWordBounds', () => { - // Test for normal word bounds test('computes word bounds correctly', () => { const bounds = HighlightRuleComponent.computeWordBounds([1, 2], ' some text here'); expect(bounds).toEqual([7, 11]); }); - // Test for unbounded start test('handles unbounded start correctly', () => { const bounds = HighlightRuleComponent.computeWordBounds([-1, 2], ' some text here'); expect(bounds).toEqual([2, 11]); }); - // Test for unbounded end test('handles unbounded end correctly', () => { const bounds = HighlightRuleComponent.computeWordBounds([1, -1], ' some text here'); expect(bounds).toEqual([7, ' some text here'.length]); }); - // Test for non-existent words test('handles non-existent words correctly', () => { const bounds = HighlightRuleComponent.computeWordBounds([5, 7], ' some text here'); expect(bounds).toEqual([' some text here'.length, ' some text here'.length]); @@ -105,25 +89,21 @@ describe('computeWordBounds', () => { }); describe('computeWordBounds', () => { - // Test for normal word bounds test('computes word bounds correctly', () => { const bounds = HighlightRuleComponent.computeWordBounds([1, 2], ' some text here'); expect(bounds).toEqual([7, 11]); }); - // Test for unbounded start test('handles unbounded start correctly', () => { const bounds = HighlightRuleComponent.computeWordBounds([-1, 2], ' some text here'); expect(bounds).toEqual([2, 11]); }); - // Test for unbounded end test('handles unbounded end correctly', () => { const bounds = HighlightRuleComponent.computeWordBounds([1, -1], ' some text here'); expect(bounds).toEqual([7, ' some text here'.length]); }); - // Test for non-existent words test('handles non-existent words correctly', () => { const bounds = HighlightRuleComponent.computeWordBounds([5, 7], ' some text here'); expect(bounds).toEqual([' some text here'.length, ' some text here'.length]); @@ -131,19 +111,16 @@ describe('computeWordBounds', () => { }); describe('computeLinePartBounds', () => { - // Test for normal line part test('computes line part bounds correctly', () => { const bounds = HighlightRuleComponent.computeLinePartBounds('text', ' some text here with text'); expect(bounds).toEqual([[7, 11], [22, 26]]); }); - // Test for non-existent line part test('returns empty bounds for non-existent line part', () => { const bounds = HighlightRuleComponent.computeLinePartBounds('nonexistent', ' some text here'); expect(bounds).toEqual([[0, 0]]); }); - // Test for empty line part test('handles empty line part correctly', () => { const bounds = HighlightRuleComponent.computeLinePartBounds('', ' some text here'); expect(bounds).toEqual([[0, 0]]); diff --git a/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts b/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts index b16d98d0dd..c3a0d3b04d 100644 --- a/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts +++ b/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts @@ -54,7 +54,6 @@ describe('shouldApplyHighlight', () => { }); describe('getHighlightType', () => { - // Assuming rules are parsed correctly in these tests const rules = HighlightRule.parseAllRules('3,4[1:5]', 0, 'line1\nline2\nline3\nline4\nline5'); const wholeLineRule = rules[0]; const partialTextRule = rules[1]; diff --git a/packages/core/test/unit/lib/markdown-it/highlight/helper.test.ts b/packages/core/test/unit/lib/markdown-it/highlight/helper.test.ts index 3c7e55ee1f..4d1c13ff5b 100644 --- a/packages/core/test/unit/lib/markdown-it/highlight/helper.test.ts +++ b/packages/core/test/unit/lib/markdown-it/highlight/helper.test.ts @@ -15,8 +15,6 @@ test('splitCodeAndIndentation with no leading spaces', () => { expect(content).toEqual('var x = 1;'); }); -// Add more test cases as needed - test('collateAllIntervals with overlapping intervals', () => { const actual = collateAllIntervals([[0, 2], [1, 3], [8, 10]]); const expected = [[0, 3], [8, 10]]; From 4a11c051912136183f471d44801bf691baede82c Mon Sep 17 00:00:00 2001 From: Nisemono Date: Tue, 28 Nov 2023 08:53:16 +0800 Subject: [PATCH 10/16] restore comments --- packages/core/src/lib/markdown-it/highlight/HighlightRule.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts b/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts index 11b05a46e3..d1c5272d3a 100644 --- a/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts @@ -83,6 +83,7 @@ export class HighlightRule { } const [startRule, endRule] = this.ruleComponents; + // At the range boundaries appliedRule = startCompare === 0 ? startRule : endRule; } From 38a27f4a49a43b2b146c3bc80292957cccd94e33 Mon Sep 17 00:00:00 2001 From: Nisemono Date: Thu, 30 Nov 2023 19:16:15 +0800 Subject: [PATCH 11/16] add highlight rule priority comments as requested --- packages/core/src/lib/markdown-it/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/lib/markdown-it/index.ts b/packages/core/src/lib/markdown-it/index.ts index b81272335e..a28f373351 100644 --- a/packages/core/src/lib/markdown-it/index.ts +++ b/packages/core/src/lib/markdown-it/index.ts @@ -155,7 +155,7 @@ markdownIt.renderer.rules.fence = (tokens: Token[], const rawBounds: Array<[number, number]> = []; let lineHighlightType = HIGHLIGHT_TYPES.PartialText; - // Per line WholeLine override WholeText overrides PartialText + // Priority: WholeLine > WholeText > PartialText for (let i = 0; i < rules.length; i += 1) { const { highlightType, bounds } = rules[i].getHighlightType(currentLineNumber); From c58948660d1921dfc23ce6a7c8eac1e53af21db1 Mon Sep 17 00:00:00 2001 From: Nisemono Date: Sun, 3 Dec 2023 22:17:54 +0800 Subject: [PATCH 12/16] Add comments and extract line num check --- .../markdown-it/highlight/HighlightRule.ts | 3 ++ .../highlight/HighlightRuleComponent.ts | 39 +++++++++---------- packages/core/src/lib/markdown-it/index.ts | 6 ++- .../highlight/HightlightRule.test.ts | 5 +++ 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts b/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts index d1c5272d3a..4461de296e 100644 --- a/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts @@ -70,8 +70,11 @@ export class HighlightRule { highlightType: HIGHLIGHT_TYPES, bounds: Array<[number, number]> | null } { + // Applied rule is the first component until deduced otherwise let [appliedRule] = this.ruleComponents; if (this.isLineRange()) { + // For cases like 2[:]-3 (or 2-3[:]), the highlight would be line highlight + // across all the ranges if (this.ruleComponents.some(comp => comp.isUnboundedSlice())) { return { highlightType: HIGHLIGHT_TYPES.WholeLine, bounds: null }; } diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts index 0def4dd02b..61e16cd3a9 100644 --- a/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRuleComponent.ts @@ -16,21 +16,25 @@ export class HighlightRuleComponent { this.bounds = bounds; } + static isValidLineNumber(lineNumStr: string, min: number, max: number, offset: number) { + let lineNum = Number(lineNumStr); + if (Number.isNaN(lineNum) || !Number.isInteger(lineNum)) return null; + lineNum += offset; + return lineNum >= min && lineNum <= max ? lineNum : null; + } + static parseRuleComponent(compString: string, lineNumberOffset: number, lines: string[]) { // Match line-part syntax const linepartMatch = compString.match(LINEPART_REGEX); if (linepartMatch) { // There are four capturing groups: [full match, line number, quote type, line part] const [, lineNumberString, , linePartWithQuotes] = linepartMatch; - let lineNumber = parseInt(lineNumberString, 10); - if (Number.isNaN(lineNumber)) { - return null; - } - lineNumber += lineNumberOffset; - if (lineNumber > lines.length) return null; + const lineNumber = HighlightRuleComponent + .isValidLineNumber(lineNumberString, 1, lines.length, lineNumberOffset); + if (!lineNumber) return null; + const linePart = linePartWithQuotes.replace(/\\'/g, '\'').replace(/\\"/g, '"'); // unescape quotes const bounds = HighlightRuleComponent.computeLinePartBounds(linePart, lines[lineNumber - 1]); - return new HighlightRuleComponent(lineNumber, true, bounds); } @@ -42,13 +46,9 @@ export class HighlightRuleComponent { // There are four capturing groups: [full match, line number, start bound, end bound] const groups = sliceMatch.slice(1); // discard full match - let lineNumber = Number(groups.shift() ?? ''); - if (Number.isNaN(lineNumber) - || !Number.isInteger(lineNumber) - || lineNumber < 1 || lineNumber > lines.length) { - return null; - } - lineNumber += lineNumberOffset; + const lineNumber = HighlightRuleComponent + .isValidLineNumber(groups.shift() ?? '', 1, lines.length, lineNumberOffset); + if (!lineNumber) return null; const isUnbounded = groups.every(x => x === ''); if (isUnbounded) { @@ -65,13 +65,10 @@ export class HighlightRuleComponent { } // Match line-number syntax - const lineNumberBeforeOffset = Number(compString); - // ensure the whole string can be converted to number - if (!Number.isNaN(lineNumberBeforeOffset) && Number.isInteger(lineNumberBeforeOffset)) { - const lineNumber = lineNumberBeforeOffset + lineNumberOffset; - if (lineNumber > 0 && lineNumber <= lines.length) { - return new HighlightRuleComponent(lineNumber); - } + const lineNumber = HighlightRuleComponent + .isValidLineNumber(compString, 1, lines.length, lineNumberOffset); + if (lineNumber) { + return new HighlightRuleComponent(lineNumber); } // the string is an improperly written rule diff --git a/packages/core/src/lib/markdown-it/index.ts b/packages/core/src/lib/markdown-it/index.ts index a28f373351..565d2d0e4a 100644 --- a/packages/core/src/lib/markdown-it/index.ts +++ b/packages/core/src/lib/markdown-it/index.ts @@ -163,7 +163,11 @@ markdownIt.renderer.rules.fence = (tokens: Token[], return Highlighter.highlightWholeLine(line); } else if (highlightType === HIGHLIGHT_TYPES.WholeText) { lineHighlightType = HIGHLIGHT_TYPES.WholeText; - } else if (bounds !== null) { + } else if ( + highlightType === HIGHLIGHT_TYPES.PartialText + && lineHighlightType === HIGHLIGHT_TYPES.PartialText + && bounds !== null + ) { bounds.forEach(bound => rawBounds.push(bound)); } } diff --git a/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts b/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts index 87d7df6869..eb53ac5e5c 100644 --- a/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts +++ b/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts @@ -7,13 +7,18 @@ describe('parseAllRules', () => { const rules = HighlightRule.parseAllRules(allRules, 0, 'line1\nline2\nline3\nline4\nline5'); expect(rules).toHaveLength(2); expect(rules[0]).toBeInstanceOf(HighlightRule); + expect(rules[0].getHighlightType(3).highlightType).toBe(HIGHLIGHT_TYPES.WholeText); expect(rules[1]).toBeInstanceOf(HighlightRule); + expect(rules[1].getHighlightType(4).highlightType).toBe(HIGHLIGHT_TYPES.WholeText); + expect(rules[1].getHighlightType(5).highlightType).toBe(HIGHLIGHT_TYPES.WholeText); }); test('ignores invalid rules', () => { const allRules = 'invalid,3-4'; const rules = HighlightRule.parseAllRules(allRules, 0, 'line1\nline2\nline3\nline4'); expect(rules).toHaveLength(1); + expect(rules[0].getHighlightType(3).highlightType).toBe(HIGHLIGHT_TYPES.WholeText); + expect(rules[0].getHighlightType(4).highlightType).toBe(HIGHLIGHT_TYPES.WholeText); }); }); From 81ae091b8f0002610a53355ce3dbf7b4b9c2a384 Mon Sep 17 00:00:00 2001 From: Nisemono Date: Mon, 4 Dec 2023 15:55:30 +0800 Subject: [PATCH 13/16] Add test cases for highlightRule --- .../highlight/HightlightRule.test.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts b/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts index eb53ac5e5c..3323b4b74c 100644 --- a/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts +++ b/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts @@ -59,12 +59,25 @@ describe('shouldApplyHighlight', () => { }); describe('getHighlightType', () => { - const rules = HighlightRule.parseAllRules('3,4[1:5]', 0, 'line1\nline2\nline3\nline4\nline5'); - const wholeLineRule = rules[0]; + const rules = HighlightRule.parseAllRules( + '3,4[1:5],1[:]-2, 6-8', 0, 'line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8', + ); + const wholeTextRule = rules[0]; const partialTextRule = rules[1]; + const wholeLinesRule = rules[2]; + const wholeTextsRule = rules[3]; + + test('returns WholeLine for two lines', () => { + expect(wholeLinesRule.getHighlightType(1).highlightType).toBe(HIGHLIGHT_TYPES.WholeLine); + expect(wholeLinesRule.getHighlightType(2).highlightType).toBe(HIGHLIGHT_TYPES.WholeLine); + }); + + test('returns WholeText for in between lines', () => { + expect(wholeTextsRule.getHighlightType(7).highlightType).toBe(HIGHLIGHT_TYPES.WholeText); + }); test('returns WholeText for single line', () => { - const { highlightType } = wholeLineRule.getHighlightType(3); + const { highlightType } = wholeTextRule.getHighlightType(3); expect(highlightType).toBe(HIGHLIGHT_TYPES.WholeText); }); From e7e170aded92aa2165384e8a1f7492ea49180f40 Mon Sep 17 00:00:00 2001 From: Nisemono Date: Wed, 6 Dec 2023 00:16:00 +0800 Subject: [PATCH 14/16] One more tc --- .../unit/lib/markdown-it/highlight/HightlightRule.test.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts b/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts index 3323b4b74c..e8038c5b40 100644 --- a/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts +++ b/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts @@ -60,12 +60,13 @@ describe('shouldApplyHighlight', () => { describe('getHighlightType', () => { const rules = HighlightRule.parseAllRules( - '3,4[1:5],1[:]-2, 6-8', 0, 'line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8', + '3,4[1:5],1[:]-2,6-8,9[:]', 0, 'line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9', ); const wholeTextRule = rules[0]; const partialTextRule = rules[1]; const wholeLinesRule = rules[2]; const wholeTextsRule = rules[3]; + const wholeLineRule = rules[4]; test('returns WholeLine for two lines', () => { expect(wholeLinesRule.getHighlightType(1).highlightType).toBe(HIGHLIGHT_TYPES.WholeLine); @@ -81,6 +82,10 @@ describe('getHighlightType', () => { expect(highlightType).toBe(HIGHLIGHT_TYPES.WholeText); }); + test('returns WholeLine for single line', () => { + expect(wholeLineRule.getHighlightType(9).highlightType).toBe(HIGHLIGHT_TYPES.WholeLine); + }); + test('returns PartialText for bounded slice', () => { const { highlightType, bounds } = partialTextRule.getHighlightType(4); expect(highlightType).toBe(HIGHLIGHT_TYPES.PartialText); From 3f7008bb6c1fe05c5c2bff0f2dd0d4456b4a00c4 Mon Sep 17 00:00:00 2001 From: Nisemono Date: Sun, 10 Dec 2023 20:16:39 +0800 Subject: [PATCH 15/16] Remove empty line from token content (highlighting) + update test cases --- .../core/src/lib/markdown-it/highlight/HighlightRule.ts | 1 + .../unit/lib/markdown-it/highlight/HightlightRule.test.ts | 8 ++++---- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts b/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts index 4461de296e..4406b69ee8 100644 --- a/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts +++ b/packages/core/src/lib/markdown-it/highlight/HighlightRule.ts @@ -15,6 +15,7 @@ export class HighlightRule { static parseAllRules(allRules: string, lineOffset: number, tokenContent: string) { const highlightLines = this.splitByChar(allRules, ','); const strArray = tokenContent.split('\n'); + strArray.pop(); // removes the last empty string return highlightLines .map(ruleStr => HighlightRule.parseRule(ruleStr, lineOffset, strArray)) .filter(rule => rule) as HighlightRule[]; // discards invalid rules diff --git a/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts b/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts index e8038c5b40..154c10b385 100644 --- a/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts +++ b/packages/core/test/unit/lib/markdown-it/highlight/HightlightRule.test.ts @@ -4,7 +4,7 @@ import { HighlightRuleComponent } from '../../../../../src/lib/markdown-it/highl describe('parseAllRules', () => { test('parses multiple rules correctly', () => { const allRules = '3,4-5'; - const rules = HighlightRule.parseAllRules(allRules, 0, 'line1\nline2\nline3\nline4\nline5'); + const rules = HighlightRule.parseAllRules(allRules, 0, 'line1\nline2\nline3\nline4\nline5\n'); expect(rules).toHaveLength(2); expect(rules[0]).toBeInstanceOf(HighlightRule); expect(rules[0].getHighlightType(3).highlightType).toBe(HIGHLIGHT_TYPES.WholeText); @@ -15,7 +15,7 @@ describe('parseAllRules', () => { test('ignores invalid rules', () => { const allRules = 'invalid,3-4'; - const rules = HighlightRule.parseAllRules(allRules, 0, 'line1\nline2\nline3\nline4'); + const rules = HighlightRule.parseAllRules(allRules, 0, 'line1\nline2\nline3\nline4\n'); expect(rules).toHaveLength(1); expect(rules[0].getHighlightType(3).highlightType).toBe(HIGHLIGHT_TYPES.WholeText); expect(rules[0].getHighlightType(4).highlightType).toBe(HIGHLIGHT_TYPES.WholeText); @@ -44,7 +44,7 @@ describe('parseRule', () => { }); describe('shouldApplyHighlight', () => { - const rules = HighlightRule.parseAllRules('3-4', 0, 'line1\nline2\nline3\nline4\nline5'); + const rules = HighlightRule.parseAllRules('3-4', 0, 'line1\nline2\nline3\nline4\nline5\n'); const rule = rules[0]; test('returns true within the line range', () => { @@ -60,7 +60,7 @@ describe('shouldApplyHighlight', () => { describe('getHighlightType', () => { const rules = HighlightRule.parseAllRules( - '3,4[1:5],1[:]-2,6-8,9[:]', 0, 'line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9', + '3,4[1:5],1[:]-2,6-8,9[:]', 0, 'line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\n', ); const wholeTextRule = rules[0]; const partialTextRule = rules[1]; From 57c3f3fa92043a04bc88e7c4a99354bcce1b1a57 Mon Sep 17 00:00:00 2001 From: tlylt Date: Sun, 10 Dec 2023 21:12:06 +0800 Subject: [PATCH 16/16] add unit test --- .../highlight/HighlightRuleComponent.test.ts | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/packages/core/test/unit/lib/markdown-it/highlight/HighlightRuleComponent.test.ts b/packages/core/test/unit/lib/markdown-it/highlight/HighlightRuleComponent.test.ts index 7058ff4247..593cb30fe6 100644 --- a/packages/core/test/unit/lib/markdown-it/highlight/HighlightRuleComponent.test.ts +++ b/packages/core/test/unit/lib/markdown-it/highlight/HighlightRuleComponent.test.ts @@ -126,3 +126,35 @@ describe('computeLinePartBounds', () => { expect(bounds).toEqual([[0, 0]]); }); }); + +describe('isValidLineNumber', () => { + test('returns valid line number within range', () => { + const result = HighlightRuleComponent.isValidLineNumber('3', 1, 5, 0); + expect(result).toEqual(3); + }); + + test('returns null for NaN line number', () => { + const result = HighlightRuleComponent.isValidLineNumber('abc', 1, 5, 0); + expect(result).toBeNull(); + }); + + test('returns null for non-integer line number', () => { + const result = HighlightRuleComponent.isValidLineNumber('2.5', 1, 5, 0); + expect(result).toBeNull(); + }); + + test('returns null for line number below minimum', () => { + const result = HighlightRuleComponent.isValidLineNumber('0', 1, 5, 0); + expect(result).toBeNull(); + }); + + test('returns null for line number above maximum', () => { + const result = HighlightRuleComponent.isValidLineNumber('6', 1, 5, 0); + expect(result).toBeNull(); + }); + + test('returns valid line number with offset', () => { + const result = HighlightRuleComponent.isValidLineNumber('3', 1, 5, 2); + expect(result).toEqual(5); + }); +});