@peaceroad/markdown-it-renderer-fence 0.8.0 → 0.9.0

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.
@@ -297,6 +297,20 @@ const applyCustomHighlights = (root, options = {}) => {
297
297
  break
298
298
  }
299
299
  }
300
+ if (sameRefs && prevState.blockCache instanceof Map) {
301
+ for (const [blockId, codeEl] of blockRefs) {
302
+ const cached = prevState.blockCache.get(blockId)
303
+ const textSnapshot = typeof codeEl.textContent === 'string' ? codeEl.textContent : null
304
+ const boundaryRefs = getCodeBoundaryRefs(codeEl)
305
+ if (!cached ||
306
+ cached.textSnapshot !== textSnapshot ||
307
+ cached.firstRef !== boundaryRefs.first ||
308
+ cached.lastRef !== boundaryRefs.last) {
309
+ sameRefs = false
310
+ break
311
+ }
312
+ }
313
+ }
300
314
  if (sameRefs) {
301
315
  if (emitDiag) emitDiag({ type: 'runtime-skip', reason: 'unchanged' })
302
316
  return { appliedBlocks: 0, appliedRanges: 0, skipped: true, reason: 'unchanged' }
@@ -308,6 +322,7 @@ const applyCustomHighlights = (root, options = {}) => {
308
322
  let insertedScopeStyleMap = null
309
323
  if (!incremental) clearAppliedHighlightNames(queryRoot)
310
324
  const allScopeRanges = new Map()
325
+ const runtimeScopeNamesByVariant = new Map()
311
326
  let appliedBlocks = 0
312
327
  let appliedRanges = 0
313
328
  let pendingCss = ''
@@ -342,17 +357,12 @@ const applyCustomHighlights = (root, options = {}) => {
342
357
  if (payloadDigestByBlock) {
343
358
  blockPayloadDigest = payloadDigestByBlock.get(blockId)
344
359
  if (blockPayloadDigest === undefined) {
345
- const prevCached = prevBlockCache ? prevBlockCache.get(blockId) : null
346
- if (prevCached && prevCached.payloadRef === payload && prevCached.variantKey === activePayload.variantKey) {
347
- blockPayloadDigest = prevCached.payloadDigest || ''
348
- } else {
349
- blockPayloadDigest = getCachedPayloadString(payload, payloadStringifyCache) + `|variant=${activePayload.variantKey || ''}`
350
- }
360
+ blockPayloadDigest = getCachedPayloadString(payload, payloadStringifyCache) + `|variant=${activePayload.variantKey || ''}`
351
361
  payloadDigestByBlock.set(blockId, blockPayloadDigest)
352
362
  }
353
363
  }
354
- const textSnapshot = typeof codeEl.textContent === 'string' ? codeEl.textContent : null
355
- const boundaryRefs = getCodeBoundaryRefs(codeEl)
364
+ const textSnapshot = incremental && typeof codeEl.textContent === 'string' ? codeEl.textContent : null
365
+ const boundaryRefs = incremental ? getCodeBoundaryRefs(codeEl) : null
356
366
  const cached = prevBlockCache ? prevBlockCache.get(blockId) : null
357
367
  if (cached &&
358
368
  cached.codeEl === codeEl &&
@@ -404,6 +414,11 @@ const applyCustomHighlights = (root, options = {}) => {
404
414
  const appliedNames = new Set()
405
415
  const appliedNameList = []
406
416
  const scopeRuntimeNames = new Array(activePayload.scopes.length)
417
+ let runtimeScopeNameCache = runtimeScopeNamesByVariant.get(activePayload.variantKey)
418
+ if (!runtimeScopeNameCache) {
419
+ runtimeScopeNameCache = new Map()
420
+ runtimeScopeNamesByVariant.set(activePayload.variantKey, runtimeScopeNameCache)
421
+ }
407
422
  const scopeStyles = Array.isArray(activePayload.scopeStyles) ? activePayload.scopeStyles : null
408
423
  const blockScopeRanges = nextBlockCache ? new Map() : null
409
424
  const blockScopeMetaParts = nextBlockCache ? new Map() : null
@@ -448,7 +463,12 @@ const applyCustomHighlights = (root, options = {}) => {
448
463
  }
449
464
  let runtimeName = scopeRuntimeNames[scopeIdx]
450
465
  if (!runtimeName) {
451
- runtimeName = getRuntimeScopeName(scopeName, activePayload.variantKey)
466
+ const scopeCacheKey = String(scopeName)
467
+ runtimeName = runtimeScopeNameCache.get(scopeCacheKey)
468
+ if (!runtimeName) {
469
+ runtimeName = getRuntimeScopeName(scopeCacheKey, activePayload.variantKey)
470
+ runtimeScopeNameCache.set(scopeCacheKey, runtimeName)
471
+ }
452
472
  scopeRuntimeNames[scopeIdx] = runtimeName
453
473
  if (scopeStyles) {
454
474
  const css = styleToHighlightCss(scopeStyles[scopeIdx])
@@ -514,7 +534,6 @@ const applyCustomHighlights = (root, options = {}) => {
514
534
  if (nextBlockCache) {
515
535
  nextBlockCache.set(blockId, {
516
536
  codeEl,
517
- payloadRef: payload,
518
537
  variantKey: activePayload.variantKey || '',
519
538
  payloadDigest: blockPayloadDigest,
520
539
  textSnapshot,
@@ -23,7 +23,8 @@ import {
23
23
  parsePreCodeWrapper,
24
24
  } from '../utils/pre-code-wrapper-parser.js'
25
25
 
26
- const markupPassthroughDisabledFeatures = Object.freeze(['setLineNumber', 'line-number-skip', 'line-number-set', 'setEmphasizeLines', 'lineEndSpanThreshold', 'comment-mark', 'line-notes', 'samp'])
26
+ const markupHighlightPreDisabledFeatures = Object.freeze(['setLineNumber', 'line-number-skip', 'line-number-set', 'setEmphasizeLines', 'lineEndSpanThreshold', 'comment-mark', 'line-notes', 'samp'])
27
+ const markupPassthroughDisabledFeatures = Object.freeze([...markupHighlightPreDisabledFeatures, 'wrap', 'setPreWrapStyle'])
27
28
 
28
29
  const renderFenceMarkup = (context, md, opt, slf) => {
29
30
  const token = context.token
@@ -114,32 +115,45 @@ const renderFenceMarkup = (context, md, opt, slf) => {
114
115
 
115
116
  const useHighlightPre = opt.useHighlightPre && hasHighlightPre
116
117
  const needLineNumber = !useHighlightPre && opt.setLineNumber && startNumber >= 0
118
+ const wantsEmphasis = !useHighlightPre && opt.setEmphasizeLines && emphasizeLines.length > 0
119
+ const wantsAdvancedLineNumbers = needLineNumber && (lineNumberSkipValue !== undefined || lineNumberSetValue !== undefined)
120
+ const wantsLineNotes = !useHighlightPre && !!(lineNotes && lineNotes.length)
121
+ const hasCommentCandidate = !!(
122
+ !useHighlightPre &&
123
+ commentMarkValue &&
124
+ sourceContent.indexOf(commentMarkValue) !== -1
125
+ )
126
+ const needsMappedLineCounts = wantsAdvancedLineNumbers || wantsLineNotes || hasCommentCandidate
117
127
  let sourceLogicalLineCount = -1
118
128
  let highlightedLogicalLineCount = -1
119
- const ensureLogicalLineCounts = () => {
120
- if (sourceLogicalLineCount === -1) sourceLogicalLineCount = getLogicalLineCount(sourceContent)
121
- if (highlightedLogicalLineCount === -1) highlightedLogicalLineCount = getLogicalLineCount(content)
122
- }
129
+ if (wantsEmphasis || needsMappedLineCounts) highlightedLogicalLineCount = getLogicalLineCount(content)
130
+ if (needsMappedLineCounts) sourceLogicalLineCount = getLogicalLineCount(sourceContent)
131
+ const highlightedHasTrailingBreak = content.endsWith('\n') || content.endsWith('\r')
132
+ const needsLastLineWrapCheck = !highlightedHasTrailingBreak && (needLineNumber || wantsEmphasis || wantsLineNotes)
133
+ if (needsLastLineWrapCheck && highlightedLogicalLineCount < 0) highlightedLogicalLineCount = getLogicalLineCount(content)
134
+ if (needsLastLineWrapCheck && sourceLogicalLineCount < 0) sourceLogicalLineCount = getLogicalLineCount(sourceContent)
135
+ const wrapLastLine = !!(
136
+ needsLastLineWrapCheck &&
137
+ highlightedLogicalLineCount > 0 &&
138
+ highlightedLogicalLineCount === sourceLogicalLineCount
139
+ )
140
+
123
141
  let normalizedEmphasis = []
124
- if (!useHighlightPre && opt.setEmphasizeLines && emphasizeLines.length > 0) {
125
- ensureLogicalLineCounts()
142
+ if (wantsEmphasis) {
126
143
  normalizedEmphasis = normalizeEmphasisRanges(emphasizeLines, highlightedLogicalLineCount)
127
144
  }
128
145
  let lineNumberPlan = null
129
- if (needLineNumber && (lineNumberSkipValue !== undefined || lineNumberSetValue !== undefined)) {
130
- ensureLogicalLineCounts()
146
+ if (wantsAdvancedLineNumbers) {
131
147
  lineNumberPlan = resolveAdvancedLineNumberPlan(lineNumberSkipValue, lineNumberSetValue, sourceLogicalLineCount, highlightedLogicalLineCount)
132
148
  }
133
149
  let lineNotePlan = null
134
- if (!useHighlightPre && lineNotes && lineNotes.length) {
135
- ensureLogicalLineCounts()
150
+ if (wantsLineNotes) {
136
151
  lineNotePlan = resolveLineNotesPlan(lineNotes, sourceLogicalLineCount, highlightedLogicalLineCount, lineNoteIdPrefix)
137
152
  }
138
153
  const needEmphasis = normalizedEmphasis.length > 0
139
154
  const needEndSpan = opt.lineEndSpanThreshold > 0
140
155
 
141
- if (!useHighlightPre && commentMarkValue && sourceContent.indexOf(commentMarkValue) !== -1) {
142
- ensureLogicalLineCounts()
156
+ if (hasCommentCandidate) {
143
157
  if (highlightedLogicalLineCount === sourceLogicalLineCount) {
144
158
  commentLines = getCommentLineFlags(sourceContent, commentMarkValue)
145
159
  needComment = !!commentLines
@@ -163,16 +177,17 @@ const renderFenceMarkup = (context, md, opt, slf) => {
163
177
  commentLineClass,
164
178
  lineNumberPlan,
165
179
  lineNotePlan,
180
+ wrapLastLine,
166
181
  )
167
182
  if (timingEnabled) addTimingMs(timings, 'lineSplitMs', getNowMs() - splitStartedAt)
168
183
  }
169
184
 
170
- const tag = isSamp ? 'samp' : 'code'
185
+ const tag = useHighlightPre ? 'code' : (isSamp ? 'samp' : 'code')
171
186
  const decision = {
172
187
  renderer: 'markup',
173
188
  useHighlightPre,
174
189
  hasHighlightPre,
175
- disabledFeatures: useHighlightPre ? markupPassthroughDisabledFeatures : [],
190
+ disabledFeatures: useHighlightPre ? markupHighlightPreDisabledFeatures : [],
176
191
  }
177
192
  if (timingEnabled) decision.timings = finalizeFenceTimings(timings, fenceStartedAt)
178
193
  emitFenceDecision(opt, decision)
@@ -1,6 +1,7 @@
1
1
  import {
2
2
  appendStyleValue,
3
3
  createAttrOrderIndexGetter,
4
+ escapeHtmlAttr,
4
5
  getInfoAttr,
5
6
  getLangFromClassAttr,
6
7
  } from '../utils/attr-utils.js'
@@ -26,6 +27,7 @@ const preWrapStyle = 'white-space: pre-wrap; overflow-wrap: anywhere;'
26
27
  const voidTags = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'])
27
28
  const nonNegativeIntReg = /^\d+$/
28
29
  const positiveIntReg = /^[1-9]\d*$/
30
+ const attrOrderRankCacheMaxSize = 256
29
31
 
30
32
  const getLineNoteIdPrefix = (token, idx) => {
31
33
  const tokenMap = token && Array.isArray(token.map) ? token.map : null
@@ -112,6 +114,7 @@ const finalizeCommonFenceOption = (opt) => {
112
114
  opt._sampLangSet = new Set(sampLangs)
113
115
  opt._attrOrderIndex = createAttrOrderIndexGetter(opt.attrsOrder || [])
114
116
  opt._attrOrderRankCache = new Map()
117
+ opt.lineEndSpanClass = escapeHtmlAttr(opt.lineEndSpanClass)
115
118
  return opt
116
119
  }
117
120
 
@@ -254,6 +257,8 @@ const getLineNumberSkipRanges = (attrVal) => {
254
257
  const normalizeEmphasisRanges = (emphasizeLines, maxLine) => {
255
258
  const normalized = []
256
259
  if (!emphasizeLines || !emphasizeLines.length || maxLine <= 0) return normalized
260
+ let previousStart = -1
261
+ let needsSort = false
257
262
  for (const range of emphasizeLines) {
258
263
  let s = range[0]
259
264
  let e = range[1]
@@ -267,8 +272,25 @@ const normalizeEmphasisRanges = (emphasizeLines, maxLine) => {
267
272
  }
268
273
  if (s > maxLine || e < 1) continue
269
274
  if (e > maxLine) e = maxLine
275
+ if (previousStart > s) needsSort = true
270
276
  normalized.push([s, e])
277
+ previousStart = s
278
+ }
279
+ if (normalized.length < 2) return normalized
280
+ if (needsSort) normalized.sort((a, b) => a[0] - b[0] || a[1] - b[1])
281
+
282
+ let writeIndex = 1
283
+ for (let i = 1; i < normalized.length; i++) {
284
+ const previous = normalized[writeIndex - 1]
285
+ const current = normalized[i]
286
+ if (current[0] <= previous[1] + 1) {
287
+ if (current[1] > previous[1]) previous[1] = current[1]
288
+ continue
289
+ }
290
+ normalized[writeIndex] = current
291
+ writeIndex++
271
292
  }
293
+ normalized.length = writeIndex
272
294
  return normalized
273
295
  }
274
296
 
@@ -428,9 +450,10 @@ const buildPreLineTags = (hidden, setNumber, lineNote) => {
428
450
  }
429
451
  }
430
452
 
431
- const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmphasis, needEndSpan, threshold, lineEndSpanClass, br, commentLines, commentClass, lineNumberPlan, lineNotePlan) => {
453
+ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmphasis, needEndSpan, threshold, lineEndSpanClass, br, commentLines, commentClass, lineNumberPlan, lineNotePlan, wrapLastLine) => {
432
454
  const lines = content.split(br)
433
455
  const max = lines.length
456
+ const logicalLineMax = wrapLastLine ? max : Math.max(max - 1, 0)
434
457
  const lineNumberHidden = lineNumberPlan ? lineNumberPlan.hidden : null
435
458
  const lineNumberSets = lineNumberPlan ? lineNumberPlan.sets : null
436
459
  const lineNoteAnchors = lineNotePlan ? lineNotePlan.anchors : null
@@ -439,6 +462,7 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
439
462
  let emIdx = 0
440
463
  let emStart = -1
441
464
  let emEnd = -1
465
+ let emphasisOpen = false
442
466
  if (needEmphasis && emphasizeLines && emphasizeLines.length) {
443
467
  emStart = emphasizeLines[0][0]
444
468
  emEnd = emphasizeLines[0][1]
@@ -450,7 +474,8 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
450
474
 
451
475
  for (let n = 0; n < max; n++) {
452
476
  let line = lines[n]
453
- const notLastLine = n < max - 1
477
+ const hasNextLine = n < max - 1
478
+ const isLogicalLine = n < logicalLineMax
454
479
  const doComment = commentLines && commentLines[n]
455
480
  const hidden = !!(lineNumberHidden && lineNumberHidden[n])
456
481
  const setNumber = lineNumberSets ? lineNumberSets[n] : undefined
@@ -478,7 +503,7 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
478
503
  }
479
504
  }
480
505
 
481
- if (needLineWrap && notLastLine) {
506
+ if (needLineWrap && isLogicalLine && hasNextLine) {
482
507
  if (!hasLtChecked) {
483
508
  hasLt = line.indexOf('<') !== -1
484
509
  hasLtChecked = true
@@ -508,24 +533,31 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
508
533
  line = `<span class="${commentClass}">` + line + closeTag
509
534
  }
510
535
 
511
- if (needLineWrap && notLastLine) {
536
+ if (needLineWrap && isLogicalLine) {
512
537
  const lineTags = buildPreLineTags(hidden, setNumber, lineNote)
513
538
  line = lineTags.open + line + lineTags.close
514
539
  }
515
540
 
516
- if (needEmphasis && emIdx < emphasizeLines.length && emStart === n + 1) {
541
+ const opensEmphasis = needEmphasis && emIdx < emphasizeLines.length && emStart === n + 1
542
+ const closesEmphasis = needEmphasis && emIdx < emphasizeLines.length && emEnd === n
543
+ if (opensEmphasis) {
517
544
  line = emphOpenTag + line
518
545
  }
519
- if (needEmphasis && emIdx < emphasizeLines.length && emEnd === n) {
546
+ if (closesEmphasis) {
520
547
  line = closeTag + line
521
548
  emIdx++
522
549
  const nextEmphasis = emphasizeLines[emIdx] || []
523
550
  emStart = nextEmphasis[0]
524
551
  emEnd = nextEmphasis[1]
525
552
  }
553
+ if (opensEmphasis) emphasisOpen = true
554
+ else if (closesEmphasis) emphasisOpen = false
526
555
 
527
556
  lines[n] = line
528
557
  }
558
+ if (emphasisOpen && logicalLineMax > 0) {
559
+ lines[logicalLineMax - 1] += closeTag
560
+ }
529
561
  return lines.join(br)
530
562
  }
531
563
 
@@ -560,6 +592,7 @@ const orderTokenAttrs = (token, opt) => {
560
592
  let rank = rankCache.get(name)
561
593
  if (rank === undefined) {
562
594
  rank = opt._attrOrderIndex(name)
595
+ if (rankCache.size >= attrOrderRankCacheMaxSize) rankCache.clear()
563
596
  rankCache.set(name, rank)
564
597
  }
565
598
  return rank
@@ -600,7 +633,17 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
600
633
  const lineNoteIdPrefix = lineNotes && lineNotes.length ? getLineNoteIdPrefix(token, idx) : ''
601
634
  const attrNormalizeStartedAt = timingEnabled ? getNowMs() : 0
602
635
 
603
- if (token.attrs) {
636
+ const classOnlyAttr = !!(
637
+ token.attrs &&
638
+ token.attrs.length === 1 &&
639
+ token.attrs[0][0] === 'class'
640
+ )
641
+ if (classOnlyAttr) {
642
+ const classLang = getLangFromClassAttr(token.attrs[0][1], opt.langPrefix)
643
+ if (classLang) lang = classLang
644
+ }
645
+
646
+ if (token.attrs && !classOnlyAttr) {
604
647
  const newAttrs = []
605
648
  let dataPreStartIndex = -1
606
649
  let dataPreEmphasisIndex = -1
@@ -616,7 +659,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
616
659
  let sawEmphasisAttr = false
617
660
  let sawLineNumberSkipAttr = false
618
661
  let sawLineNumberSetAttr = false
619
- const appendOrder = []
662
+ let appendOrder = null
620
663
 
621
664
  for (const attr of token.attrs) {
622
665
  const name = attr[0]
@@ -635,7 +678,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
635
678
  newAttrs.push(attr)
636
679
  break
637
680
  case 'data-pre-start':
638
- startNumber = parseStartNumber(val) ?? -1
681
+ if (opt.setLineNumber) startNumber = parseStartNumber(val) ?? -1
639
682
  startValue = val
640
683
  dataPreStartIndex = newAttrs.length
641
684
  newAttrs.push(attr)
@@ -643,9 +686,10 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
643
686
  case 'line-number-start':
644
687
  case 'start':
645
688
  case 'pre-start':
646
- startNumber = parseStartNumber(val) ?? -1
689
+ if (opt.setLineNumber) startNumber = parseStartNumber(val) ?? -1
647
690
  startValue = val
648
691
  if (!sawStartAttr) {
692
+ if (!appendOrder) appendOrder = []
649
693
  appendOrder.push('start')
650
694
  sawStartAttr = true
651
695
  }
@@ -677,6 +721,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
677
721
  if (opt.setEmphasizeLines) emphasizeLines = getEmphasizeLines(val)
678
722
  emphasisValue = val
679
723
  if (!sawEmphasisAttr) {
724
+ if (!appendOrder) appendOrder = []
680
725
  appendOrder.push('emphasis')
681
726
  sawEmphasisAttr = true
682
727
  }
@@ -684,6 +729,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
684
729
  case 'comment-mark':
685
730
  commentMarkValue = val
686
731
  if (!sawCommentMark) {
732
+ if (!appendOrder) appendOrder = []
687
733
  appendOrder.push('comment')
688
734
  sawCommentMark = true
689
735
  }
@@ -698,6 +744,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
698
744
  case 'pre-line-number-skip':
699
745
  lineNumberSkipValue = val
700
746
  if (!sawLineNumberSkipAttr) {
747
+ if (!appendOrder) appendOrder = []
701
748
  appendOrder.push('line-number-skip')
702
749
  sawLineNumberSkipAttr = true
703
750
  }
@@ -706,6 +753,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
706
753
  case 'pre-line-number-set':
707
754
  lineNumberSetValue = val
708
755
  if (!sawLineNumberSetAttr) {
756
+ if (!appendOrder) appendOrder = []
709
757
  appendOrder.push('line-number-set')
710
758
  sawLineNumberSetAttr = true
711
759
  }
@@ -732,21 +780,23 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
732
780
  if (lineNumberSkipValue !== undefined && dataPreLineNumberSkipIndex >= 0) newAttrs[dataPreLineNumberSkipIndex][1] = lineNumberSkipValue
733
781
  if (lineNumberSetValue !== undefined && dataPreLineNumberSetIndex >= 0) newAttrs[dataPreLineNumberSetIndex][1] = lineNumberSetValue
734
782
 
735
- for (const kind of appendOrder) {
736
- if (kind === 'start' && dataPreStartIndex === -1 && startValue !== undefined) {
737
- newAttrs.push(['data-pre-start', startValue])
738
- } else if (kind === 'emphasis' && dataPreEmphasisIndex === -1 && emphasisValue !== undefined) {
739
- newAttrs.push(['data-pre-emphasis', emphasisValue])
740
- } else if (kind === 'comment' && dataPreCommentIndex === -1 && commentMarkValue !== undefined) {
741
- newAttrs.push(['data-pre-comment-mark', commentMarkValue])
742
- } else if (kind === 'line-number-skip' && dataPreLineNumberSkipIndex === -1 && lineNumberSkipValue !== undefined) {
743
- newAttrs.push(['data-pre-line-number-skip', lineNumberSkipValue])
744
- } else if (kind === 'line-number-set' && dataPreLineNumberSetIndex === -1 && lineNumberSetValue !== undefined) {
745
- newAttrs.push(['data-pre-line-number-set', lineNumberSetValue])
783
+ if (appendOrder) {
784
+ for (const kind of appendOrder) {
785
+ if (kind === 'start' && dataPreStartIndex === -1 && startValue !== undefined) {
786
+ newAttrs.push(['data-pre-start', startValue])
787
+ } else if (kind === 'emphasis' && dataPreEmphasisIndex === -1 && emphasisValue !== undefined) {
788
+ newAttrs.push(['data-pre-emphasis', emphasisValue])
789
+ } else if (kind === 'comment' && dataPreCommentIndex === -1 && commentMarkValue !== undefined) {
790
+ newAttrs.push(['data-pre-comment-mark', commentMarkValue])
791
+ } else if (kind === 'line-number-skip' && dataPreLineNumberSkipIndex === -1 && lineNumberSkipValue !== undefined) {
792
+ newAttrs.push(['data-pre-line-number-skip', lineNumberSkipValue])
793
+ } else if (kind === 'line-number-set' && dataPreLineNumberSetIndex === -1 && lineNumberSetValue !== undefined) {
794
+ newAttrs.push(['data-pre-line-number-set', lineNumberSetValue])
795
+ }
746
796
  }
747
797
  }
748
798
 
749
- if (startNumber !== -1) {
799
+ if (startNumber !== -1 && opt.setLineNumber) {
750
800
  styleValue = appendStyleValue(styleValue, 'counter-set:pre-line-number ' + startNumber + ';')
751
801
  }
752
802
  if (styleIndex >= 0) {
@@ -35,6 +35,15 @@ const appendStyleValue = (style, addition) => {
35
35
  return base + separator + next
36
36
  }
37
37
 
38
+ const escapeHtmlAttr = (value) => {
39
+ return String(value ?? '')
40
+ .replace(/&/g, '&amp;')
41
+ .replace(/"/g, '&quot;')
42
+ .replace(/'/g, '&#39;')
43
+ .replace(/</g, '&lt;')
44
+ .replace(/>/g, '&gt;')
45
+ }
46
+
38
47
  const parseHtmlAttrs = (attrText) => {
39
48
  const attrs = []
40
49
  if (!attrText) return attrs
@@ -113,6 +122,7 @@ const getLangFromClassAttr = (classText, langPrefix) => {
113
122
  export {
114
123
  appendStyleValue,
115
124
  createAttrOrderIndexGetter,
125
+ escapeHtmlAttr,
116
126
  getInfoAttr,
117
127
  getLangFromClassAttr,
118
128
  mergeAttrSets,
@@ -1,7 +1,8 @@
1
1
  /*! Renderer Fence shared base | MIT License */
2
2
 
3
3
  :root {
4
- color-scheme: light dark;
4
+ --rf-code-color-scheme: light;
5
+ --rf-samp-color-scheme: dark;
5
6
 
6
7
  --rf-code-bg: oklch(99.4% 0 0);
7
8
  --rf-code-fg: oklch(4% 0 0);
@@ -54,6 +55,9 @@
54
55
 
55
56
  @media (prefers-color-scheme: dark) {
56
57
  :root {
58
+ --rf-code-color-scheme: dark;
59
+ --rf-samp-color-scheme: light;
60
+
57
61
  --rf-code-bg: oklch(18% 0 0);
58
62
  --rf-code-fg: oklch(95% 0 0);
59
63
  --rf-code-muted: oklch(76% 0 0);
@@ -105,6 +109,7 @@
105
109
  }
106
110
 
107
111
  pre {
112
+ color-scheme: var(--rf-code-color-scheme);
108
113
  margin: 0;
109
114
  padding: 1rem;
110
115
  overflow: auto;
@@ -135,6 +140,7 @@ pre samp {
135
140
  }
136
141
 
137
142
  pre:has(> samp) {
143
+ color-scheme: var(--rf-samp-color-scheme);
138
144
  --rf-syntax-keyword: var(--rf-samp-syntax-keyword);
139
145
  --rf-syntax-string: var(--rf-samp-syntax-string);
140
146
  --rf-syntax-number: var(--rf-samp-syntax-number);
@@ -0,0 +1,94 @@
1
+ /*! Renderer Fence line-notes layout | MIT License */
2
+
3
+ :root {
4
+ --line-note-gap: 0.35rem;
5
+ --line-note-inline-gap: 1.25ch;
6
+ --line-note-width: 14rem;
7
+ --line-note-color: var(--rf-line-note-fg, #666);
8
+ --line-note-border-color: var(--rf-line-note-border, #cfcfcf);
9
+ --line-note-background: var(--rf-line-note-bg, rgba(255, 255, 255, 0.72));
10
+ --line-note-font-family: inherit;
11
+ --line-note-font-size: inherit;
12
+ --line-note-line-height: inherit;
13
+ --line-note-padding-block: 0.05rem;
14
+ --line-note-padding-inline: 0.4rem;
15
+ --line-note-anchor-font-size: 0.92em;
16
+ --line-note-anchor-line-height: 1.15;
17
+ --line-note-anchor-padding-block: 0;
18
+ --line-note-anchor-padding-inline: 0.35rem;
19
+ --line-note-radius: 0.25rem;
20
+ --line-note-label-color: var(--line-note-color);
21
+ --line-note-label-prefix: "line ";
22
+ --line-note-label-suffix: ":";
23
+ --line-note-anchor-padding-block-end: 1.25em;
24
+ }
25
+
26
+ .pre-wrapper-line-notes pre {
27
+ margin-bottom: 0;
28
+ }
29
+
30
+ .pre-wrapper-line-notes .pre-line-note-layer {
31
+ display: grid;
32
+ gap: var(--line-note-gap);
33
+ margin-top: 0.65rem;
34
+ }
35
+
36
+ .pre-wrapper-line-notes .pre-line-note {
37
+ display: grid;
38
+ grid-template-columns: max-content minmax(0, 1fr);
39
+ align-items: start;
40
+ column-gap: 0.5rem;
41
+ box-sizing: border-box;
42
+ color: var(--line-note-color);
43
+ font-family: var(--line-note-font-family);
44
+ font-size: var(--line-note-font-size);
45
+ line-height: var(--line-note-line-height);
46
+ vertical-align: baseline;
47
+ white-space: pre-wrap;
48
+ }
49
+
50
+ .pre-wrapper-line-notes .pre-line-note::before {
51
+ content: var(--line-note-label-prefix) attr(data-pre-line-note-label) var(--line-note-label-suffix);
52
+ display: inline-block;
53
+ color: var(--line-note-label-color);
54
+ font-weight: 700;
55
+ font-variant-numeric: tabular-nums;
56
+ white-space: nowrap;
57
+ }
58
+
59
+ .pre-wrapper-line-notes .pre-line-content {
60
+ display: inline-block;
61
+ }
62
+
63
+ @supports ((anchor-name: --x) and (position-anchor: --x)) {
64
+ .pre-wrapper-line-notes[data-pre-line-notes-layout="anchor"] {
65
+ position: relative;
66
+ anchor-scope: all;
67
+ padding-block-end: calc(var(--line-note-anchor-padding-block-end) + (var(--pre-line-note-overflow-lines, 0) * 1.5em));
68
+ }
69
+
70
+ .pre-wrapper-line-notes[data-pre-line-notes-layout="anchor"] .pre-line-note-layer {
71
+ margin-top: 0;
72
+ padding-top: 0;
73
+ border-top: 0;
74
+ }
75
+
76
+ .pre-wrapper-line-notes[data-pre-line-notes-layout="anchor"] .pre-line-note {
77
+ display: block;
78
+ position: absolute;
79
+ inset-block-start: anchor(top);
80
+ inset-inline-start: calc(anchor(right) + var(--line-note-inline-gap));
81
+ inline-size: clamp(0px, calc(100% - anchor(right) - var(--line-note-inline-gap)), var(--line-note-width));
82
+ padding: var(--line-note-anchor-padding-block) var(--line-note-anchor-padding-inline);
83
+ border: 1px solid var(--line-note-border-color);
84
+ border-radius: var(--line-note-radius);
85
+ background: var(--line-note-background);
86
+ font-size: var(--line-note-anchor-font-size);
87
+ line-height: var(--line-note-anchor-line-height);
88
+ transform: translateY(0.08em);
89
+ }
90
+
91
+ .pre-wrapper-line-notes[data-pre-line-notes-layout="anchor"] .pre-line-note::before {
92
+ content: none;
93
+ }
94
+ }
@@ -0,0 +1,64 @@
1
+ /*! Renderer Fence line-number layout | MIT License */
2
+
3
+ :root {
4
+ --line-number-width: 2em;
5
+ --line-number-gap: 0.5em;
6
+ --line-number-rule-gap: 0.25em;
7
+ --line-number-color: var(--rf-line-number-fg, #666);
8
+ --line-number-rule-color: var(--rf-line-number-rule, #ccc);
9
+ }
10
+
11
+ pre :is(code, samp)[data-pre-start] .pre-line {
12
+ display: block;
13
+ position: relative;
14
+ padding-inline-start: calc(var(--line-number-width) + var(--line-number-rule-gap) + var(--line-number-gap));
15
+ background-image: linear-gradient(var(--line-number-rule-color), var(--line-number-rule-color));
16
+ background-repeat: no-repeat;
17
+ background-size: 1px 100%;
18
+ background-position: calc(var(--line-number-width) + var(--line-number-rule-gap)) 0;
19
+ }
20
+
21
+ pre :is(code, samp)[data-pre-start] {
22
+ white-space: normal;
23
+ }
24
+
25
+ pre:not([data-pre-wrap]) :is(code, samp)[data-pre-start] .pre-line {
26
+ white-space: pre;
27
+ }
28
+
29
+ pre[data-pre-wrap] :is(code, samp)[data-pre-start] .pre-line {
30
+ white-space: break-spaces;
31
+ overflow-wrap: anywhere;
32
+ }
33
+
34
+ pre :is(code, samp)[data-pre-start] .pre-line::before {
35
+ content: counter(pre-line-number);
36
+ position: absolute;
37
+ inset-inline-start: 0;
38
+ top: 0;
39
+ width: var(--line-number-width);
40
+ padding-inline-end: var(--line-number-rule-gap);
41
+ color: var(--line-number-color);
42
+ text-align: right;
43
+ }
44
+
45
+ pre :is(code, samp)[data-pre-start] .pre-line[data-pre-line-label]::before {
46
+ content: attr(data-pre-line-label);
47
+ }
48
+
49
+ pre :is(code, samp)[data-pre-start] .pre-line[data-pre-line-label-bold]::before {
50
+ font-weight: bold;
51
+ }
52
+
53
+ pre :is(code, samp)[data-pre-start] .pre-line::after {
54
+ content: "";
55
+ counter-increment: pre-line-number;
56
+ }
57
+
58
+ pre :is(code, samp)[data-pre-start] .pre-line.pre-line-no-number::before {
59
+ content: "";
60
+ }
61
+
62
+ pre :is(code, samp)[data-pre-start] .pre-line.pre-line-no-number::after {
63
+ counter-increment: none;
64
+ }