@peaceroad/markdown-it-renderer-fence 0.7.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.
Files changed (36) hide show
  1. package/README.md +179 -470
  2. package/docs/README.md +37 -0
  3. package/docs/code-highlighting-design.md +321 -0
  4. package/docs/custom-highlight-styling-guide.md +210 -0
  5. package/package.json +25 -13
  6. package/src/custom-highlight/option-validator.js +5 -5
  7. package/src/custom-highlight/payload-utils.js +4 -9
  8. package/src/custom-highlight/{shiki-keyword-rules.js → shiki-role-rules.js} +249 -54
  9. package/src/custom-highlight/{shiki-keyword.js → shiki-role.js} +107 -99
  10. package/src/custom-highlight/shiki-theme-role.js +176 -0
  11. package/src/fence/line-notes.js +27 -9
  12. package/src/fence/render-api-constants.js +1 -1
  13. package/src/fence/render-api-provider.js +61 -53
  14. package/src/fence/render-api-renderer.js +26 -10
  15. package/src/fence/render-api-runtime.js +29 -10
  16. package/src/fence/render-api.js +20 -12
  17. package/src/fence/render-markup.js +34 -24
  18. package/src/fence/render-shared.js +150 -30
  19. package/src/utils/attr-utils.js +10 -0
  20. package/theme/_shared/rf-core.css +170 -0
  21. package/theme/line-notes.css +94 -0
  22. package/theme/line-number.css +64 -0
  23. package/theme/rf-basic/README.md +117 -0
  24. package/theme/rf-basic/api/highlightjs.css +71 -0
  25. package/theme/rf-basic/api/shiki.css +67 -0
  26. package/theme/rf-basic/base.css +5 -0
  27. package/theme/rf-basic/index.css +7 -0
  28. package/theme/rf-basic/index.js +164 -0
  29. package/theme/rf-basic/markup/highlightjs.css +77 -0
  30. package/theme/rf-basic/markup/shiki.css +11 -0
  31. package/theme/rf-monochrome/README.md +84 -0
  32. package/theme/rf-monochrome/base.css +5 -0
  33. package/theme/rf-monochrome/index.css +5 -0
  34. package/theme/rf-monochrome/index.js +71 -0
  35. package/theme/rf-monochrome/markup/highlightjs.css +150 -0
  36. package/theme/rf-monochrome/markup/shiki.css +38 -0
@@ -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
@@ -103,9 +105,16 @@ const createCommonFenceOptionDefaults = (md) => {
103
105
  }
104
106
 
105
107
  const finalizeCommonFenceOption = (opt) => {
106
- opt._sampReg = new RegExp('^(?:samp|' + opt.sampLang.split(',').join('|') + ')$')
108
+ const sampLangs = ['samp']
109
+ const extraSampLangs = String(opt.sampLang ?? '').split(',')
110
+ for (let i = 0; i < extraSampLangs.length; i++) {
111
+ const lang = extraSampLangs[i].trim()
112
+ if (lang) sampLangs.push(lang)
113
+ }
114
+ opt._sampLangSet = new Set(sampLangs)
107
115
  opt._attrOrderIndex = createAttrOrderIndexGetter(opt.attrsOrder || [])
108
116
  opt._attrOrderRankCache = new Map()
117
+ opt.lineEndSpanClass = escapeHtmlAttr(opt.lineEndSpanClass)
109
118
  return opt
110
119
  }
111
120
 
@@ -132,6 +141,8 @@ const parsePositiveLineIndex = (val) => {
132
141
  return num
133
142
  }
134
143
 
144
+ const isTruthyAttrValue = (val) => val !== 'false' && val !== '0'
145
+
135
146
  const escapeHtmlText = (value) => {
136
147
  return String(value ?? '')
137
148
  .replace(/&/g, '&amp;')
@@ -154,6 +165,62 @@ const getLogicalLineCount = (text) => {
154
165
  return count
155
166
  }
156
167
 
168
+ const isLineStartWhitespaceCode = (code) => {
169
+ return code <= 32 || code === 160 || code === 0xFEFF
170
+ }
171
+
172
+ const lineStartsWithAfterIndent = (str, start, end, marker) => {
173
+ let pos = start
174
+ while (pos < end && isLineStartWhitespaceCode(str.charCodeAt(pos))) pos++
175
+ return str.startsWith(marker, pos)
176
+ }
177
+
178
+ const scanLogicalLineBounds = (str, onLine) => {
179
+ let lineStart = 0
180
+ let lineIndex = 0
181
+
182
+ for (let i = 0; i <= str.length; i++) {
183
+ const code = i < str.length ? str.charCodeAt(i) : 10
184
+ if (code !== 10 && code !== 13) continue
185
+ onLine(lineStart, i, lineIndex)
186
+ if (code === 13 && i + 1 < str.length && str.charCodeAt(i + 1) === 10) i++
187
+ lineStart = i + 1
188
+ lineIndex++
189
+ }
190
+ }
191
+
192
+ const getCommentLineFlags = (text, commentMarkValue) => {
193
+ const str = String(text ?? '')
194
+ const marker = String(commentMarkValue ?? '')
195
+ if (!str || !marker) return null
196
+ const flags = []
197
+ let hasComment = false
198
+
199
+ scanLogicalLineBounds(str, (lineStart, lineEnd, lineIndex) => {
200
+ if (lineStartsWithAfterIndent(str, lineStart, lineEnd, marker)) {
201
+ flags[lineIndex] = true
202
+ hasComment = true
203
+ }
204
+ })
205
+
206
+ return hasComment ? flags : null
207
+ }
208
+
209
+ const getCommentLineRanges = (text, commentMarkValue) => {
210
+ const str = String(text ?? '')
211
+ const marker = String(commentMarkValue ?? '')
212
+ if (!str || !marker) return null
213
+ const ranges = []
214
+
215
+ scanLogicalLineBounds(str, (lineStart, lineEnd) => {
216
+ if (lineEnd > lineStart && lineStartsWithAfterIndent(str, lineStart, lineEnd, marker)) {
217
+ ranges.push([lineStart, lineEnd])
218
+ }
219
+ })
220
+
221
+ return ranges.length ? ranges : null
222
+ }
223
+
157
224
  const parseLineRangeList = (attrVal, parseValue) => {
158
225
  const str = String(attrVal ?? '')
159
226
  if (!str) return []
@@ -179,14 +246,8 @@ const parseLineRangeList = (attrVal, parseValue) => {
179
246
  return result
180
247
  }
181
248
 
182
- const parsePositiveLineNumber = (val) => {
183
- const num = Number(val)
184
- if (!Number.isFinite(num) || num <= 0) return null
185
- return num
186
- }
187
-
188
249
  const getEmphasizeLines = (attrVal) => {
189
- return parseLineRangeList(attrVal, parsePositiveLineNumber)
250
+ return parseLineRangeList(attrVal, parsePositiveLineIndex)
190
251
  }
191
252
 
192
253
  const getLineNumberSkipRanges = (attrVal) => {
@@ -196,6 +257,8 @@ const getLineNumberSkipRanges = (attrVal) => {
196
257
  const normalizeEmphasisRanges = (emphasizeLines, maxLine) => {
197
258
  const normalized = []
198
259
  if (!emphasizeLines || !emphasizeLines.length || maxLine <= 0) return normalized
260
+ let previousStart = -1
261
+ let needsSort = false
199
262
  for (const range of emphasizeLines) {
200
263
  let s = range[0]
201
264
  let e = range[1]
@@ -209,8 +272,25 @@ const normalizeEmphasisRanges = (emphasizeLines, maxLine) => {
209
272
  }
210
273
  if (s > maxLine || e < 1) continue
211
274
  if (e > maxLine) e = maxLine
275
+ if (previousStart > s) needsSort = true
212
276
  normalized.push([s, e])
277
+ previousStart = s
213
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++
292
+ }
293
+ normalized.length = writeIndex
214
294
  return normalized
215
295
  }
216
296
 
@@ -370,9 +450,10 @@ const buildPreLineTags = (hidden, setNumber, lineNote) => {
370
450
  }
371
451
  }
372
452
 
373
- 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) => {
374
454
  const lines = content.split(br)
375
455
  const max = lines.length
456
+ const logicalLineMax = wrapLastLine ? max : Math.max(max - 1, 0)
376
457
  const lineNumberHidden = lineNumberPlan ? lineNumberPlan.hidden : null
377
458
  const lineNumberSets = lineNumberPlan ? lineNumberPlan.sets : null
378
459
  const lineNoteAnchors = lineNotePlan ? lineNotePlan.anchors : null
@@ -381,6 +462,7 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
381
462
  let emIdx = 0
382
463
  let emStart = -1
383
464
  let emEnd = -1
465
+ let emphasisOpen = false
384
466
  if (needEmphasis && emphasizeLines && emphasizeLines.length) {
385
467
  emStart = emphasizeLines[0][0]
386
468
  emEnd = emphasizeLines[0][1]
@@ -392,7 +474,8 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
392
474
 
393
475
  for (let n = 0; n < max; n++) {
394
476
  let line = lines[n]
395
- const notLastLine = n < max - 1
477
+ const hasNextLine = n < max - 1
478
+ const isLogicalLine = n < logicalLineMax
396
479
  const doComment = commentLines && commentLines[n]
397
480
  const hidden = !!(lineNumberHidden && lineNumberHidden[n])
398
481
  const setNumber = lineNumberSets ? lineNumberSets[n] : undefined
@@ -420,7 +503,7 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
420
503
  }
421
504
  }
422
505
 
423
- if (needLineWrap && notLastLine) {
506
+ if (needLineWrap && isLogicalLine && hasNextLine) {
424
507
  if (!hasLtChecked) {
425
508
  hasLt = line.indexOf('<') !== -1
426
509
  hasLtChecked = true
@@ -450,24 +533,31 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
450
533
  line = `<span class="${commentClass}">` + line + closeTag
451
534
  }
452
535
 
453
- if (needLineWrap && notLastLine) {
536
+ if (needLineWrap && isLogicalLine) {
454
537
  const lineTags = buildPreLineTags(hidden, setNumber, lineNote)
455
538
  line = lineTags.open + line + lineTags.close
456
539
  }
457
540
 
458
- 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) {
459
544
  line = emphOpenTag + line
460
545
  }
461
- if (needEmphasis && emIdx < emphasizeLines.length && emEnd === n) {
546
+ if (closesEmphasis) {
462
547
  line = closeTag + line
463
548
  emIdx++
464
549
  const nextEmphasis = emphasizeLines[emIdx] || []
465
550
  emStart = nextEmphasis[0]
466
551
  emEnd = nextEmphasis[1]
467
552
  }
553
+ if (opensEmphasis) emphasisOpen = true
554
+ else if (closesEmphasis) emphasisOpen = false
468
555
 
469
556
  lines[n] = line
470
557
  }
558
+ if (emphasisOpen && logicalLineMax > 0) {
559
+ lines[logicalLineMax - 1] += closeTag
560
+ }
471
561
  return lines.join(br)
472
562
  }
473
563
 
@@ -502,6 +592,7 @@ const orderTokenAttrs = (token, opt) => {
502
592
  let rank = rankCache.get(name)
503
593
  if (rank === undefined) {
504
594
  rank = opt._attrOrderIndex(name)
595
+ if (rankCache.size >= attrOrderRankCacheMaxSize) rankCache.clear()
505
596
  rankCache.set(name, rank)
506
597
  }
507
598
  return rank
@@ -537,11 +628,22 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
537
628
  let wrapEnabled = false
538
629
  let preWrapValue
539
630
  let commentMarkValue
631
+ let tagOverride = ''
540
632
  const lineNotes = getTokenLineNotes(token)
541
633
  const lineNoteIdPrefix = lineNotes && lineNotes.length ? getLineNoteIdPrefix(token, idx) : ''
542
634
  const attrNormalizeStartedAt = timingEnabled ? getNowMs() : 0
543
635
 
544
- 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) {
545
647
  const newAttrs = []
546
648
  let dataPreStartIndex = -1
547
649
  let dataPreEmphasisIndex = -1
@@ -557,7 +659,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
557
659
  let sawEmphasisAttr = false
558
660
  let sawLineNumberSkipAttr = false
559
661
  let sawLineNumberSetAttr = false
560
- const appendOrder = []
662
+ let appendOrder = null
561
663
 
562
664
  for (const attr of token.attrs) {
563
665
  const name = attr[0]
@@ -576,7 +678,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
576
678
  newAttrs.push(attr)
577
679
  break
578
680
  case 'data-pre-start':
579
- startNumber = parseStartNumber(val) ?? -1
681
+ if (opt.setLineNumber) startNumber = parseStartNumber(val) ?? -1
580
682
  startValue = val
581
683
  dataPreStartIndex = newAttrs.length
582
684
  newAttrs.push(attr)
@@ -584,14 +686,16 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
584
686
  case 'line-number-start':
585
687
  case 'start':
586
688
  case 'pre-start':
587
- startNumber = parseStartNumber(val) ?? -1
689
+ if (opt.setLineNumber) startNumber = parseStartNumber(val) ?? -1
588
690
  startValue = val
589
691
  if (!sawStartAttr) {
692
+ if (!appendOrder) appendOrder = []
590
693
  appendOrder.push('start')
591
694
  sawStartAttr = true
592
695
  }
593
696
  break
594
697
  case 'data-pre-emphasis':
698
+ if (opt.setEmphasizeLines) emphasizeLines = getEmphasizeLines(val)
595
699
  dataPreEmphasisIndex = newAttrs.length
596
700
  newAttrs.push(attr)
597
701
  break
@@ -617,6 +721,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
617
721
  if (opt.setEmphasizeLines) emphasizeLines = getEmphasizeLines(val)
618
722
  emphasisValue = val
619
723
  if (!sawEmphasisAttr) {
724
+ if (!appendOrder) appendOrder = []
620
725
  appendOrder.push('emphasis')
621
726
  sawEmphasisAttr = true
622
727
  }
@@ -624,14 +729,22 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
624
729
  case 'comment-mark':
625
730
  commentMarkValue = val
626
731
  if (!sawCommentMark) {
732
+ if (!appendOrder) appendOrder = []
627
733
  appendOrder.push('comment')
628
734
  sawCommentMark = true
629
735
  }
630
736
  break
737
+ case 'samp':
738
+ tagOverride = isTruthyAttrValue(val) ? 'samp' : ''
739
+ break
740
+ case 'code':
741
+ tagOverride = isTruthyAttrValue(val) ? 'code' : ''
742
+ break
631
743
  case 'line-number-skip':
632
744
  case 'pre-line-number-skip':
633
745
  lineNumberSkipValue = val
634
746
  if (!sawLineNumberSkipAttr) {
747
+ if (!appendOrder) appendOrder = []
635
748
  appendOrder.push('line-number-skip')
636
749
  sawLineNumberSkipAttr = true
637
750
  }
@@ -640,6 +753,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
640
753
  case 'pre-line-number-set':
641
754
  lineNumberSetValue = val
642
755
  if (!sawLineNumberSetAttr) {
756
+ if (!appendOrder) appendOrder = []
643
757
  appendOrder.push('line-number-set')
644
758
  sawLineNumberSetAttr = true
645
759
  }
@@ -666,21 +780,23 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
666
780
  if (lineNumberSkipValue !== undefined && dataPreLineNumberSkipIndex >= 0) newAttrs[dataPreLineNumberSkipIndex][1] = lineNumberSkipValue
667
781
  if (lineNumberSetValue !== undefined && dataPreLineNumberSetIndex >= 0) newAttrs[dataPreLineNumberSetIndex][1] = lineNumberSetValue
668
782
 
669
- for (const kind of appendOrder) {
670
- if (kind === 'start' && dataPreStartIndex === -1 && startValue !== undefined) {
671
- newAttrs.push(['data-pre-start', startValue])
672
- } else if (kind === 'emphasis' && dataPreEmphasisIndex === -1 && emphasisValue !== undefined) {
673
- newAttrs.push(['data-pre-emphasis', emphasisValue])
674
- } else if (kind === 'comment' && dataPreCommentIndex === -1 && commentMarkValue !== undefined) {
675
- newAttrs.push(['data-pre-comment-mark', commentMarkValue])
676
- } else if (kind === 'line-number-skip' && dataPreLineNumberSkipIndex === -1 && lineNumberSkipValue !== undefined) {
677
- newAttrs.push(['data-pre-line-number-skip', lineNumberSkipValue])
678
- } else if (kind === 'line-number-set' && dataPreLineNumberSetIndex === -1 && lineNumberSetValue !== undefined) {
679
- 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
+ }
680
796
  }
681
797
  }
682
798
 
683
- if (startNumber !== -1) {
799
+ if (startNumber !== -1 && opt.setLineNumber) {
684
800
  styleValue = appendStyleValue(styleValue, 'counter-set:pre-line-number ' + startNumber + ';')
685
801
  }
686
802
  if (styleIndex >= 0) {
@@ -695,10 +811,12 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
695
811
 
696
812
  if (timingEnabled) addTimingMs(timings, 'attrNormalizeMs', getNowMs() - attrNormalizeStartedAt)
697
813
  if (lineNotes && lineNotes.length) token.attrSet('data-pre-line-notes', 'true')
814
+ const isSamp = tagOverride ? tagOverride === 'samp' : opt._sampLangSet.has(lang)
698
815
 
699
816
  return {
700
817
  token,
701
818
  lang,
819
+ isSamp,
702
820
  startNumber,
703
821
  emphasizeLines,
704
822
  lineNumberSkipValue,
@@ -722,6 +840,8 @@ export {
722
840
  emitFenceDecision,
723
841
  finalizeCommonFenceOption,
724
842
  finalizeFenceTimings,
843
+ getCommentLineFlags,
844
+ getCommentLineRanges,
725
845
  getLogicalLineCount,
726
846
  getNowMs,
727
847
  getEmphasizeLines,
@@ -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,
@@ -0,0 +1,170 @@
1
+ /*! Renderer Fence shared base | MIT License */
2
+
3
+ :root {
4
+ --rf-code-color-scheme: light;
5
+ --rf-samp-color-scheme: dark;
6
+
7
+ --rf-code-bg: oklch(99.4% 0 0);
8
+ --rf-code-fg: oklch(4% 0 0);
9
+ --rf-code-muted: oklch(42% 0 0);
10
+ --rf-code-border: oklch(86% 0 0);
11
+
12
+ --rf-samp-bg: oklch(18% 0 0);
13
+ --rf-samp-fg: oklch(95% 0 0);
14
+ --rf-samp-muted: oklch(76% 0 0);
15
+ --rf-samp-border: oklch(41% 0 0);
16
+ --rf-samp-prompt: oklch(78% 0.13 285);
17
+
18
+ --rf-accent: oklch(50% 0.18 285);
19
+ --rf-accent-soft: oklch(94% 0.035 285);
20
+ --rf-accent-border: oklch(82% 0.08 285);
21
+
22
+ --rf-syntax-keyword: oklch(38% 0.255 25);
23
+ --rf-syntax-string: oklch(34% 0.235 250);
24
+ --rf-syntax-number: oklch(36% 0.245 260);
25
+ --rf-syntax-comment: oklch(39% 0.026 255);
26
+ --rf-syntax-type: oklch(38% 0.235 295);
27
+ --rf-syntax-title: oklch(39% 0.27 305);
28
+ --rf-syntax-variable: oklch(37% 0.205 80);
29
+ --rf-syntax-literal: oklch(36% 0.245 260);
30
+ --rf-syntax-tag: oklch(34% 0.18 150);
31
+ --rf-syntax-attribute: oklch(36% 0.23 260);
32
+ --rf-syntax-punctuation: oklch(12% 0.018 255);
33
+ --rf-syntax-meta: oklch(12% 0.018 255);
34
+
35
+ --rf-samp-syntax-keyword: oklch(78% 0.08 25);
36
+ --rf-samp-syntax-string: oklch(82% 0.055 250);
37
+ --rf-samp-syntax-number: oklch(80% 0.07 255);
38
+ --rf-samp-syntax-comment: oklch(75% 0.01 255);
39
+ --rf-samp-syntax-type: oklch(81% 0.06 295);
40
+ --rf-samp-syntax-title: oklch(82% 0.07 305);
41
+ --rf-samp-syntax-variable: oklch(82% 0.075 90);
42
+ --rf-samp-syntax-literal: oklch(80% 0.07 255);
43
+ --rf-samp-syntax-tag: oklch(81% 0.065 150);
44
+ --rf-samp-syntax-attribute: oklch(80% 0.07 255);
45
+ --rf-samp-syntax-punctuation: oklch(86% 0.012 255);
46
+
47
+ --rf-line-emphasis-bg: color-mix(in oklch, var(--rf-accent-soft) 70%, transparent);
48
+ --rf-line-number-fg: var(--rf-code-muted);
49
+ --rf-line-number-rule: color-mix(in oklch, var(--rf-code-border) 85%, transparent);
50
+ --rf-line-comment-bg: color-mix(in oklch, var(--rf-syntax-comment) 12%, transparent);
51
+ --rf-line-note-bg: oklch(100% 0 0);
52
+ --rf-line-note-border: var(--rf-accent-border);
53
+ --rf-line-note-fg: var(--rf-code-muted);
54
+ }
55
+
56
+ @media (prefers-color-scheme: dark) {
57
+ :root {
58
+ --rf-code-color-scheme: dark;
59
+ --rf-samp-color-scheme: light;
60
+
61
+ --rf-code-bg: oklch(18% 0 0);
62
+ --rf-code-fg: oklch(95% 0 0);
63
+ --rf-code-muted: oklch(76% 0 0);
64
+ --rf-code-border: oklch(38% 0 0);
65
+
66
+ --rf-samp-bg: oklch(99.4% 0 0);
67
+ --rf-samp-fg: oklch(4% 0 0);
68
+ --rf-samp-muted: oklch(42% 0 0);
69
+ --rf-samp-border: oklch(86% 0 0);
70
+ --rf-samp-prompt: oklch(50% 0.18 285);
71
+
72
+ --rf-accent: oklch(78% 0.13 285);
73
+ --rf-accent-soft: oklch(27% 0.05 285);
74
+ --rf-accent-border: oklch(48% 0.11 285);
75
+
76
+ --rf-syntax-keyword: oklch(78% 0.08 25);
77
+ --rf-syntax-string: oklch(82% 0.055 250);
78
+ --rf-syntax-number: oklch(80% 0.07 255);
79
+ --rf-syntax-comment: oklch(75% 0.01 255);
80
+ --rf-syntax-type: oklch(81% 0.06 295);
81
+ --rf-syntax-title: oklch(82% 0.07 305);
82
+ --rf-syntax-variable: oklch(82% 0.075 90);
83
+ --rf-syntax-literal: oklch(80% 0.07 255);
84
+ --rf-syntax-tag: oklch(81% 0.065 150);
85
+ --rf-syntax-attribute: oklch(80% 0.07 255);
86
+ --rf-syntax-punctuation: oklch(86% 0.012 255);
87
+ --rf-syntax-meta: oklch(86% 0.012 255);
88
+
89
+ --rf-samp-syntax-keyword: oklch(38% 0.255 25);
90
+ --rf-samp-syntax-string: oklch(34% 0.235 250);
91
+ --rf-samp-syntax-number: oklch(36% 0.245 260);
92
+ --rf-samp-syntax-comment: oklch(39% 0.026 255);
93
+ --rf-samp-syntax-type: oklch(38% 0.235 295);
94
+ --rf-samp-syntax-title: oklch(39% 0.27 305);
95
+ --rf-samp-syntax-variable: oklch(37% 0.205 80);
96
+ --rf-samp-syntax-literal: oklch(36% 0.245 260);
97
+ --rf-samp-syntax-tag: oklch(34% 0.18 150);
98
+ --rf-samp-syntax-attribute: oklch(36% 0.23 260);
99
+ --rf-samp-syntax-punctuation: oklch(12% 0.018 255);
100
+
101
+ --rf-line-emphasis-bg: color-mix(in oklch, var(--rf-accent-soft) 70%, transparent);
102
+ --rf-line-number-fg: var(--rf-code-muted);
103
+ --rf-line-number-rule: color-mix(in oklch, var(--rf-code-border) 85%, transparent);
104
+ --rf-line-comment-bg: color-mix(in oklch, var(--rf-syntax-comment) 16%, transparent);
105
+ --rf-line-note-bg: oklch(24% 0.01 255);
106
+ --rf-line-note-border: var(--rf-accent-border);
107
+ --rf-line-note-fg: var(--rf-code-muted);
108
+ }
109
+ }
110
+
111
+ pre {
112
+ color-scheme: var(--rf-code-color-scheme);
113
+ margin: 0;
114
+ padding: 1rem;
115
+ overflow: auto;
116
+ color: var(--rf-code-fg);
117
+ background: var(--rf-code-bg);
118
+ border: 1px solid var(--rf-code-border);
119
+ border-radius: 0.5rem;
120
+ }
121
+
122
+ pre code,
123
+ pre samp {
124
+ color: inherit;
125
+ background: transparent;
126
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
127
+ }
128
+
129
+ .pre-lines-emphasis {
130
+ background: var(--rf-line-emphasis-bg);
131
+ }
132
+
133
+ .pre-line-comment {
134
+ color: var(--rf-syntax-comment);
135
+ background: var(--rf-line-comment-bg);
136
+ padding-block: 0.04em;
137
+ line-height: 1.45;
138
+ -webkit-box-decoration-break: clone;
139
+ box-decoration-break: clone;
140
+ }
141
+
142
+ pre:has(> samp) {
143
+ color-scheme: var(--rf-samp-color-scheme);
144
+ --rf-syntax-keyword: var(--rf-samp-syntax-keyword);
145
+ --rf-syntax-string: var(--rf-samp-syntax-string);
146
+ --rf-syntax-number: var(--rf-samp-syntax-number);
147
+ --rf-syntax-comment: var(--rf-samp-syntax-comment);
148
+ --rf-syntax-type: var(--rf-samp-syntax-type);
149
+ --rf-syntax-title: var(--rf-samp-syntax-title);
150
+ --rf-syntax-variable: var(--rf-samp-syntax-variable);
151
+ --rf-syntax-literal: var(--rf-samp-syntax-literal);
152
+ --rf-syntax-tag: var(--rf-samp-syntax-tag);
153
+ --rf-syntax-attribute: var(--rf-samp-syntax-attribute);
154
+ --rf-syntax-punctuation: var(--rf-samp-syntax-punctuation);
155
+ --rf-syntax-meta: var(--rf-samp-syntax-punctuation);
156
+ --line-number-color: var(--rf-samp-muted);
157
+ --line-number-rule-color: var(--rf-samp-border);
158
+ --line-note-color: var(--rf-samp-muted);
159
+ --line-note-label-color: var(--rf-samp-muted);
160
+ --line-note-border-color: var(--rf-samp-border);
161
+ --line-note-background: color-mix(in oklch, var(--rf-samp-bg) 92%, var(--rf-samp-fg));
162
+ color: var(--rf-samp-fg);
163
+ background: var(--rf-samp-bg);
164
+ border-color: var(--rf-samp-border);
165
+ }
166
+
167
+ pre:has(> samp) .pre-line-comment {
168
+ color: var(--rf-samp-muted);
169
+ background: color-mix(in oklch, var(--rf-samp-muted) 18%, transparent);
170
+ }
@@ -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
+ }