@peaceroad/markdown-it-renderer-fence 0.4.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +9 -4
  2. package/index.js +41 -17
  3. package/package.json +4 -2
package/README.md CHANGED
@@ -71,6 +71,7 @@ $ pwd
71
71
  ## Add span elements to display line number.
72
72
 
73
73
  Add `start` or `data-pre-start` attribute by adding attributes used markdown-it-attrs.
74
+ Line numbering is enabled only when `start` is a non-negative integer (`0`, `1`, `2`, ...).
74
75
 
75
76
  ~~~md
76
77
  ```js {start="1"}
@@ -89,9 +90,11 @@ md.render('Nyaan')
89
90
 
90
91
  CSS example: <https://codepen.io/peaceroad/pen/qBGpYGK>
91
92
 
93
+ If `start` is empty or not a non-negative integer (for example `start=""`, `start="abc"`, `start="1.5"`), the attribute is kept as `data-pre-start` but line-number wrapping/counter style is not enabled.
94
+
92
95
  ## Add span elements to add background color to the row ranges.
93
96
 
94
- Add `em-lines` or `emphasize-lines` attribute by by adding attributes used markdown-it-attrs.
97
+ Add `em-lines` or `emphasize-lines` attribute by adding attributes used markdown-it-attrs.
95
98
  Open-ended ranges are supported: `-5` (1..5), `3-` (3..last), `-` (all).
96
99
 
97
100
  ~~~md
@@ -151,6 +154,8 @@ echo 1
151
154
  ~~~
152
155
 
153
156
  Note: this feature relies on line splitting and is disabled when `useHighlightPre` is true and `<pre><code>` is provided by the highlighter.
157
+ If a highlighter changes logical line count (for example by injecting extra lines), comment-line marking is skipped to avoid wrong line mapping.
158
+ CRLF/LF mixed newlines are supported for logical line-count checks.
154
159
 
155
160
 
156
161
  ## Options
@@ -165,8 +170,8 @@ The following options can be specified when initializing the plugin:
165
170
  - setLineEndSpan: alias for lineEndSpanThreshold.
166
171
  - lineEndSpanClass: default 'pre-lineend-spacer' — CSS class for end-of-line span.
167
172
  - setPreWrapStyle: default true — include inline pre-wrap styles on `<pre>` when wrap is enabled.
168
- - useHighlightPre: default false — if highlight returns `<pre><code>`, keep that wrapper and skip line-splitting features; attributes are still merged.
169
-
170
- When `useHighlightPre` is true and the highlight output contains `<pre><code>`, line-splitting features are disabled (setLineNumber, setEmphasizeLines, lineEndSpanThreshold, comment-line).
171
173
  - sampLang: default 'shell,console' — comma-separated list of languages (in addition to 'samp') that will be rendered using `<samp>`.
172
174
  - langPrefix: default md.options.langPrefix || 'language-' — prefix for language class on code blocks.
175
+ - useHighlightPre: default false — if highlight returns `<pre><code>`, keep that wrapper and skip line-splitting features; attributes are still merged.
176
+
177
+ When `useHighlightPre` is true and the highlight output contains `<pre><code>`, line-splitting features are disabled (setLineNumber, setEmphasizeLines, lineEndSpanThreshold, comment-line). In this mode `<samp>` conversion is not possible, so avoid `useHighlightPre` when you need samp output.
package/index.js CHANGED
@@ -12,6 +12,8 @@ const preWrapStyle = 'white-space: pre-wrap; overflow-wrap: anywhere;'
12
12
  const preCodeWrapperReg = /^\s*<pre\b([^>]*)>\s*<code\b([^>]*)>([\s\S]*?)<\/code>\s*<\/pre>\s*$/i
13
13
  const htmlAttrReg = /([^\s=]+)(?:\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s"'=<>`]+)))?/g
14
14
  const voidTags = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'])
15
+ const nonNegativeIntReg = /^\d+$/
16
+ const lineBreakReg = /\r\n|\n|\r/
15
17
 
16
18
  const appendStyleValue = (style, addition) => {
17
19
  if (!addition) return style
@@ -69,6 +71,21 @@ const mergeAttrSets = (target, source) => {
69
71
  }
70
72
  }
71
73
 
74
+ const parseStartNumber = (val) => {
75
+ if (val == null) return null
76
+ const text = String(val).trim()
77
+ if (!nonNegativeIntReg.test(text)) return null
78
+ const n = Number(text)
79
+ if (!Number.isSafeInteger(n)) return null
80
+ return n
81
+ }
82
+
83
+ const getLogicalLineCount = (text) => {
84
+ const lines = text.split(lineBreakReg)
85
+ if (lines.length === 0) return 0
86
+ return lines[lines.length - 1] === '' ? lines.length - 1 : lines.length
87
+ }
88
+
72
89
  const getEmphasizeLines = (attrVal) => {
73
90
  const lines = []
74
91
  for (const range of attrVal.split(',')) {
@@ -97,7 +114,7 @@ const getEmphasizeLines = (attrVal) => {
97
114
  }
98
115
 
99
116
  const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmphasis, needEndSpan, threshold, lineEndSpanClass, br, commentLines, commentClass) => {
100
- const lines = content.split(br)
117
+ const lines = content.split(lineBreakReg)
101
118
  const len = lines.length
102
119
  const endSpanTag = needEndSpan ? `<span class="${lineEndSpanClass}"></span>` : ''
103
120
  const needComment = !!(commentLines && commentClass)
@@ -233,16 +250,14 @@ const orderTokenAttrs = (token, opt) => {
233
250
  const getFenceHtml = (tokens, idx, md, opt, slf) => {
234
251
  const token = tokens[idx]
235
252
  let content = token.content
236
- const info = token.info.trim()
237
- const match = info.match(infoReg)
253
+ const match = token.info.trim().match(infoReg)
238
254
  let lang = match ? match[1] : ''
239
255
 
240
256
  if (match && match[2]) {
241
257
  getInfoAttr(match[2]).forEach(([name, val]) => token.attrJoin(name, val))
242
258
  }
243
- let langClass = ''
244
259
  if (lang && lang !== 'samp') {
245
- langClass = opt.langPrefix + lang
260
+ const langClass = opt.langPrefix + lang
246
261
  const existingClass = token.attrGet('class')
247
262
  token.attrSet('class', existingClass ? langClass + ' ' + existingClass : langClass)
248
263
  }
@@ -286,14 +301,14 @@ const getFenceHtml = (tokens, idx, md, opt, slf) => {
286
301
  newAttrs.push(attr)
287
302
  break
288
303
  case 'data-pre-start':
289
- startNumber = +val
304
+ startNumber = parseStartNumber(val) ?? -1
290
305
  startValue = val
291
306
  dataPreStartIndex = newAttrs.length
292
307
  newAttrs.push(attr)
293
308
  break
294
309
  case 'start':
295
310
  case 'pre-start':
296
- startNumber = +val
311
+ startNumber = parseStartNumber(val) ?? -1
297
312
  startValue = val
298
313
  if (!sawStartAttr) {
299
314
  appendOrder.push('start')
@@ -311,7 +326,7 @@ const getFenceHtml = (tokens, idx, md, opt, slf) => {
311
326
  break
312
327
  case 'em-lines':
313
328
  case 'emphasize-lines':
314
- emphasizeLines = getEmphasizeLines(val)
329
+ if (opt.setEmphasizeLines) emphasizeLines = getEmphasizeLines(val)
315
330
  emphasisValue = val
316
331
  if (!sawEmphasisAttr) {
317
332
  appendOrder.push('emphasis')
@@ -379,15 +394,7 @@ const getFenceHtml = (tokens, idx, md, opt, slf) => {
379
394
  const isSamp = opt._sampReg.test(lang)
380
395
  let commentLines
381
396
  let needComment = false
382
- if (commentLineValue) {
383
- const rawLines = token.content.split(/\r?\n/)
384
- commentLines = new Array(rawLines.length)
385
- for (let i = 0; i < rawLines.length; i++) {
386
- const isComment = rawLines[i].trimStart().startsWith(commentLineValue)
387
- commentLines[i] = isComment
388
- if (isComment) needComment = true
389
- }
390
- }
397
+ let sourceLogicalLineCount = -1
391
398
 
392
399
  if (opt.setHighlight && md.options.highlight) {
393
400
  if (lang && lang !== 'samp' ) {
@@ -444,9 +451,26 @@ const getFenceHtml = (tokens, idx, md, opt, slf) => {
444
451
  const needEmphasis = opt.setEmphasizeLines && emphasizeLines.length > 0
445
452
  const needEndSpan = opt.lineEndSpanThreshold > 0
446
453
  const useHighlightPre = opt.useHighlightPre && hasHighlightPre
454
+ if (!useHighlightPre && commentLineValue) {
455
+ const rawLines = token.content.split(lineBreakReg)
456
+ sourceLogicalLineCount = getLogicalLineCount(token.content)
457
+ commentLines = new Array(rawLines.length)
458
+ for (let i = 0; i < rawLines.length; i++) {
459
+ const isComment = rawLines[i].trimStart().startsWith(commentLineValue)
460
+ commentLines[i] = isComment
461
+ if (isComment) needComment = true
462
+ }
463
+ }
447
464
  if (!useHighlightPre && (needLineNumber || needEmphasis || needEndSpan || needComment)) {
448
465
  const nlIndex = content.indexOf('\n')
449
466
  const br = nlIndex > 0 && content[nlIndex - 1] === '\r' ? '\r\n' : '\n'
467
+ if (needComment && sourceLogicalLineCount >= 0) {
468
+ const highlightedLogicalLineCount = getLogicalLineCount(content)
469
+ if (highlightedLogicalLineCount !== sourceLogicalLineCount) {
470
+ needComment = false
471
+ commentLines = undefined
472
+ }
473
+ }
450
474
  content = splitFenceBlockToLines(content, emphasizeLines, needLineNumber, needEmphasis, needEndSpan, opt.lineEndSpanThreshold, opt.lineEndSpanClass, br, commentLines, commentLineClass)
451
475
  }
452
476
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@peaceroad/markdown-it-renderer-fence",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "In code blocks, the `<samp>` tag is used with the language keywords `samp`, `shell`, and `console`, and provides an option to display line numbers.",
5
5
  "main": "index.js",
6
6
  "files": [
@@ -10,7 +10,9 @@
10
10
  ],
11
11
  "type": "module",
12
12
  "scripts": {
13
- "test": "node test/test.js"
13
+ "test": "node test/test.js",
14
+ "test:performance": "node test/performance/benchmark.js",
15
+ "test:performance:highlighter": "node test/performance/highlighter-benchmark.js"
14
16
  },
15
17
  "author": "peaceroad <peaceroad@gmail.com>",
16
18
  "repository": "https://github.com/peaceroad/p7d-markdown-it-renderer-fence",