@peaceroad/markdown-it-renderer-fence 0.6.1 → 0.8.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.
- package/README.md +183 -430
- package/docs/README.md +37 -0
- package/docs/code-highlighting-design.md +317 -0
- package/docs/custom-highlight-styling-guide.md +204 -0
- package/package.json +22 -13
- package/src/custom-highlight/option-validator.js +19 -10
- package/src/custom-highlight/scope-name.js +23 -0
- package/src/custom-highlight/{shiki-keyword-rules.js → shiki-role-rules.js} +229 -44
- package/src/custom-highlight/{shiki-keyword.js → shiki-role.js} +64 -56
- package/src/custom-highlight/shiki-theme-role.js +176 -0
- package/src/entry/markup-highlight.js +4 -0
- package/src/fence/line-notes.js +199 -0
- package/src/fence/render-api-provider.js +62 -62
- package/src/fence/render-api-renderer.js +26 -11
- package/src/fence/render-api-runtime.js +5 -13
- package/src/fence/render-api.js +24 -12
- package/src/fence/render-markup.js +24 -16
- package/src/fence/render-shared.js +232 -20
- package/theme/_shared/rf-core.css +164 -0
- package/theme/rf-basic/README.md +91 -0
- package/theme/rf-basic/api/highlightjs.css +71 -0
- package/theme/rf-basic/api/shiki.css +67 -0
- package/theme/rf-basic/base.css +3 -0
- package/theme/rf-basic/index.css +7 -0
- package/theme/rf-basic/index.js +155 -0
- package/theme/rf-basic/markup/highlightjs.css +77 -0
- package/theme/rf-basic/markup/shiki.css +11 -0
- package/theme/rf-monochrome/README.md +71 -0
- package/theme/rf-monochrome/base.css +3 -0
- package/theme/rf-monochrome/index.css +5 -0
- package/theme/rf-monochrome/index.js +72 -0
- package/theme/rf-monochrome/markup/highlightjs.css +134 -0
- package/theme/rf-monochrome/markup/shiki.css +27 -0
- package/THIRD_PARTY_NOTICES.md +0 -56
|
@@ -8,18 +8,23 @@ import {
|
|
|
8
8
|
commentLineClass,
|
|
9
9
|
emitFenceDecision,
|
|
10
10
|
finalizeFenceTimings,
|
|
11
|
+
getCommentLineFlags,
|
|
11
12
|
getLogicalLineCount,
|
|
12
13
|
getNowMs,
|
|
13
14
|
normalizeEmphasisRanges,
|
|
14
15
|
orderTokenAttrs,
|
|
15
16
|
preWrapStyle,
|
|
16
17
|
resolveAdvancedLineNumberPlan,
|
|
18
|
+
resolveLineNotesPlan,
|
|
17
19
|
splitFenceBlockToLines,
|
|
20
|
+
wrapFencePreWithLineNotes,
|
|
18
21
|
} from './render-shared.js'
|
|
19
22
|
import {
|
|
20
23
|
parsePreCodeWrapper,
|
|
21
24
|
} from '../utils/pre-code-wrapper-parser.js'
|
|
22
25
|
|
|
26
|
+
const markupPassthroughDisabledFeatures = Object.freeze(['setLineNumber', 'line-number-skip', 'line-number-set', 'setEmphasizeLines', 'lineEndSpanThreshold', 'comment-mark', 'line-notes', 'samp'])
|
|
27
|
+
|
|
23
28
|
const renderFenceMarkup = (context, md, opt, slf) => {
|
|
24
29
|
const token = context.token
|
|
25
30
|
const lang = context.lang
|
|
@@ -33,8 +38,10 @@ const renderFenceMarkup = (context, md, opt, slf) => {
|
|
|
33
38
|
const wrapEnabled = context.wrapEnabled
|
|
34
39
|
const preWrapValue = context.preWrapValue
|
|
35
40
|
const commentMarkValue = context.commentMarkValue
|
|
41
|
+
const lineNotes = context.lineNotes
|
|
42
|
+
const lineNoteIdPrefix = context.lineNoteIdPrefix
|
|
36
43
|
|
|
37
|
-
const isSamp =
|
|
44
|
+
const isSamp = context.isSamp
|
|
38
45
|
const sourceContent = token.content
|
|
39
46
|
let content = sourceContent
|
|
40
47
|
let commentLines
|
|
@@ -76,7 +83,7 @@ const renderFenceMarkup = (context, md, opt, slf) => {
|
|
|
76
83
|
passthrough: true,
|
|
77
84
|
passthroughReason: 'pre-code-parse-failed',
|
|
78
85
|
hasHighlightPre: false,
|
|
79
|
-
disabledFeatures:
|
|
86
|
+
disabledFeatures: markupPassthroughDisabledFeatures,
|
|
80
87
|
}
|
|
81
88
|
if (timingEnabled) decision.timings = finalizeFenceTimings(timings, fenceStartedAt)
|
|
82
89
|
emitFenceDecision(opt, decision)
|
|
@@ -105,7 +112,8 @@ const renderFenceMarkup = (context, md, opt, slf) => {
|
|
|
105
112
|
if (preAttrs.length) orderTokenAttrs({ attrs: preAttrs }, opt)
|
|
106
113
|
const preAttrsText = preAttrs.length ? slf.renderAttrs({ attrs: preAttrs }) : ''
|
|
107
114
|
|
|
108
|
-
const
|
|
115
|
+
const useHighlightPre = opt.useHighlightPre && hasHighlightPre
|
|
116
|
+
const needLineNumber = !useHighlightPre && opt.setLineNumber && startNumber >= 0
|
|
109
117
|
let sourceLogicalLineCount = -1
|
|
110
118
|
let highlightedLogicalLineCount = -1
|
|
111
119
|
const ensureLogicalLineCounts = () => {
|
|
@@ -113,7 +121,7 @@ const renderFenceMarkup = (context, md, opt, slf) => {
|
|
|
113
121
|
if (highlightedLogicalLineCount === -1) highlightedLogicalLineCount = getLogicalLineCount(content)
|
|
114
122
|
}
|
|
115
123
|
let normalizedEmphasis = []
|
|
116
|
-
if (opt.setEmphasizeLines && emphasizeLines.length > 0) {
|
|
124
|
+
if (!useHighlightPre && opt.setEmphasizeLines && emphasizeLines.length > 0) {
|
|
117
125
|
ensureLogicalLineCounts()
|
|
118
126
|
normalizedEmphasis = normalizeEmphasisRanges(emphasizeLines, highlightedLogicalLineCount)
|
|
119
127
|
}
|
|
@@ -122,25 +130,23 @@ const renderFenceMarkup = (context, md, opt, slf) => {
|
|
|
122
130
|
ensureLogicalLineCounts()
|
|
123
131
|
lineNumberPlan = resolveAdvancedLineNumberPlan(lineNumberSkipValue, lineNumberSetValue, sourceLogicalLineCount, highlightedLogicalLineCount)
|
|
124
132
|
}
|
|
133
|
+
let lineNotePlan = null
|
|
134
|
+
if (!useHighlightPre && lineNotes && lineNotes.length) {
|
|
135
|
+
ensureLogicalLineCounts()
|
|
136
|
+
lineNotePlan = resolveLineNotesPlan(lineNotes, sourceLogicalLineCount, highlightedLogicalLineCount, lineNoteIdPrefix)
|
|
137
|
+
}
|
|
125
138
|
const needEmphasis = normalizedEmphasis.length > 0
|
|
126
139
|
const needEndSpan = opt.lineEndSpanThreshold > 0
|
|
127
|
-
const useHighlightPre = opt.useHighlightPre && hasHighlightPre
|
|
128
140
|
|
|
129
141
|
if (!useHighlightPre && commentMarkValue && sourceContent.indexOf(commentMarkValue) !== -1) {
|
|
130
142
|
ensureLogicalLineCounts()
|
|
131
143
|
if (highlightedLogicalLineCount === sourceLogicalLineCount) {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (rawLines[i].trimStart().startsWith(commentMarkValue)) {
|
|
135
|
-
if (!commentLines) commentLines = []
|
|
136
|
-
commentLines[i] = true
|
|
137
|
-
needComment = true
|
|
138
|
-
}
|
|
139
|
-
}
|
|
144
|
+
commentLines = getCommentLineFlags(sourceContent, commentMarkValue)
|
|
145
|
+
needComment = !!commentLines
|
|
140
146
|
}
|
|
141
147
|
}
|
|
142
148
|
|
|
143
|
-
if (!useHighlightPre && (needLineNumber || needEmphasis || needEndSpan || needComment)) {
|
|
149
|
+
if (!useHighlightPre && (needLineNumber || needEmphasis || needEndSpan || needComment || lineNotePlan)) {
|
|
144
150
|
const splitStartedAt = timingEnabled ? getNowMs() : 0
|
|
145
151
|
const nlIndex = content.indexOf('\n')
|
|
146
152
|
const br = nlIndex > 0 && content[nlIndex - 1] === '\r' ? '\r\n' : '\n'
|
|
@@ -156,6 +162,7 @@ const renderFenceMarkup = (context, md, opt, slf) => {
|
|
|
156
162
|
commentLines,
|
|
157
163
|
commentLineClass,
|
|
158
164
|
lineNumberPlan,
|
|
165
|
+
lineNotePlan,
|
|
159
166
|
)
|
|
160
167
|
if (timingEnabled) addTimingMs(timings, 'lineSplitMs', getNowMs() - splitStartedAt)
|
|
161
168
|
}
|
|
@@ -165,11 +172,12 @@ const renderFenceMarkup = (context, md, opt, slf) => {
|
|
|
165
172
|
renderer: 'markup',
|
|
166
173
|
useHighlightPre,
|
|
167
174
|
hasHighlightPre,
|
|
168
|
-
disabledFeatures: useHighlightPre ?
|
|
175
|
+
disabledFeatures: useHighlightPre ? markupPassthroughDisabledFeatures : [],
|
|
169
176
|
}
|
|
170
177
|
if (timingEnabled) decision.timings = finalizeFenceTimings(timings, fenceStartedAt)
|
|
171
178
|
emitFenceDecision(opt, decision)
|
|
172
|
-
|
|
179
|
+
const preHtml = `<pre${preAttrsText}><${tag}${slf.renderAttrs(token)}>${content}</${tag}></pre>`
|
|
180
|
+
return wrapFencePreWithLineNotes(preHtml, lineNotePlan)
|
|
173
181
|
}
|
|
174
182
|
|
|
175
183
|
export {
|
|
@@ -4,20 +4,36 @@ import {
|
|
|
4
4
|
getInfoAttr,
|
|
5
5
|
getLangFromClassAttr,
|
|
6
6
|
} from '../utils/attr-utils.js'
|
|
7
|
+
import {
|
|
8
|
+
getTokenLineNotes,
|
|
9
|
+
} from './line-notes.js'
|
|
7
10
|
|
|
8
11
|
const infoReg = /^([^{\s]*)(?:\s*\{(.*)\})?$/
|
|
9
12
|
const tagReg = /<\/?([A-Za-z][A-Za-z0-9-]*)(?:\s+[^>]*?)?\/?\s*>/g
|
|
10
13
|
const preLineTag = '<span class="pre-line">'
|
|
11
14
|
const preLineNoNumberClass = 'pre-line-no-number'
|
|
15
|
+
const preLineHasEndNoteClass = 'pre-line-has-end-note'
|
|
16
|
+
const preLineContentClass = 'pre-line-content'
|
|
12
17
|
const emphOpenTag = '<span class="pre-lines-emphasis">'
|
|
13
18
|
const commentLineClass = 'pre-line-comment'
|
|
19
|
+
const preWithLineNotesClass = 'pre-wrapper-line-notes'
|
|
20
|
+
const lineNoteLayerClass = 'pre-line-note-layer'
|
|
21
|
+
const lineNoteClass = 'pre-line-note'
|
|
14
22
|
const closeTag = '</span>'
|
|
15
23
|
const closeTagLen = closeTag.length
|
|
24
|
+
const defaultPreLineTags = { open: preLineTag, close: closeTag }
|
|
16
25
|
const preWrapStyle = 'white-space: pre-wrap; overflow-wrap: anywhere;'
|
|
17
26
|
const voidTags = new Set(['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'])
|
|
18
27
|
const nonNegativeIntReg = /^\d+$/
|
|
19
28
|
const positiveIntReg = /^[1-9]\d*$/
|
|
20
29
|
|
|
30
|
+
const getLineNoteIdPrefix = (token, idx) => {
|
|
31
|
+
const tokenMap = token && Array.isArray(token.map) ? token.map : null
|
|
32
|
+
const mapStart = tokenMap && Number.isSafeInteger(tokenMap[0]) ? tokenMap[0] + 1 : 0
|
|
33
|
+
const tokenIndex = Number.isSafeInteger(idx) ? idx + 1 : 0
|
|
34
|
+
return `pre-line-note-${mapStart}-${tokenIndex}`
|
|
35
|
+
}
|
|
36
|
+
|
|
21
37
|
const getLineVisualLengthIgnoringTags = (line, threshold) => {
|
|
22
38
|
let len = 0
|
|
23
39
|
let inTag = false
|
|
@@ -87,8 +103,15 @@ const createCommonFenceOptionDefaults = (md) => {
|
|
|
87
103
|
}
|
|
88
104
|
|
|
89
105
|
const finalizeCommonFenceOption = (opt) => {
|
|
90
|
-
|
|
106
|
+
const sampLangs = ['samp']
|
|
107
|
+
const extraSampLangs = String(opt.sampLang ?? '').split(',')
|
|
108
|
+
for (let i = 0; i < extraSampLangs.length; i++) {
|
|
109
|
+
const lang = extraSampLangs[i].trim()
|
|
110
|
+
if (lang) sampLangs.push(lang)
|
|
111
|
+
}
|
|
112
|
+
opt._sampLangSet = new Set(sampLangs)
|
|
91
113
|
opt._attrOrderIndex = createAttrOrderIndexGetter(opt.attrsOrder || [])
|
|
114
|
+
opt._attrOrderRankCache = new Map()
|
|
92
115
|
return opt
|
|
93
116
|
}
|
|
94
117
|
|
|
@@ -115,6 +138,15 @@ const parsePositiveLineIndex = (val) => {
|
|
|
115
138
|
return num
|
|
116
139
|
}
|
|
117
140
|
|
|
141
|
+
const isTruthyAttrValue = (val) => val !== 'false' && val !== '0'
|
|
142
|
+
|
|
143
|
+
const escapeHtmlText = (value) => {
|
|
144
|
+
return String(value ?? '')
|
|
145
|
+
.replace(/&/g, '&')
|
|
146
|
+
.replace(/</g, '<')
|
|
147
|
+
.replace(/>/g, '>')
|
|
148
|
+
}
|
|
149
|
+
|
|
118
150
|
const getLogicalLineCount = (text) => {
|
|
119
151
|
const str = String(text ?? '')
|
|
120
152
|
if (!str) return 0
|
|
@@ -130,6 +162,62 @@ const getLogicalLineCount = (text) => {
|
|
|
130
162
|
return count
|
|
131
163
|
}
|
|
132
164
|
|
|
165
|
+
const isLineStartWhitespaceCode = (code) => {
|
|
166
|
+
return code <= 32 || code === 160 || code === 0xFEFF
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const lineStartsWithAfterIndent = (str, start, end, marker) => {
|
|
170
|
+
let pos = start
|
|
171
|
+
while (pos < end && isLineStartWhitespaceCode(str.charCodeAt(pos))) pos++
|
|
172
|
+
return str.startsWith(marker, pos)
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const scanLogicalLineBounds = (str, onLine) => {
|
|
176
|
+
let lineStart = 0
|
|
177
|
+
let lineIndex = 0
|
|
178
|
+
|
|
179
|
+
for (let i = 0; i <= str.length; i++) {
|
|
180
|
+
const code = i < str.length ? str.charCodeAt(i) : 10
|
|
181
|
+
if (code !== 10 && code !== 13) continue
|
|
182
|
+
onLine(lineStart, i, lineIndex)
|
|
183
|
+
if (code === 13 && i + 1 < str.length && str.charCodeAt(i + 1) === 10) i++
|
|
184
|
+
lineStart = i + 1
|
|
185
|
+
lineIndex++
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
const getCommentLineFlags = (text, commentMarkValue) => {
|
|
190
|
+
const str = String(text ?? '')
|
|
191
|
+
const marker = String(commentMarkValue ?? '')
|
|
192
|
+
if (!str || !marker) return null
|
|
193
|
+
const flags = []
|
|
194
|
+
let hasComment = false
|
|
195
|
+
|
|
196
|
+
scanLogicalLineBounds(str, (lineStart, lineEnd, lineIndex) => {
|
|
197
|
+
if (lineStartsWithAfterIndent(str, lineStart, lineEnd, marker)) {
|
|
198
|
+
flags[lineIndex] = true
|
|
199
|
+
hasComment = true
|
|
200
|
+
}
|
|
201
|
+
})
|
|
202
|
+
|
|
203
|
+
return hasComment ? flags : null
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const getCommentLineRanges = (text, commentMarkValue) => {
|
|
207
|
+
const str = String(text ?? '')
|
|
208
|
+
const marker = String(commentMarkValue ?? '')
|
|
209
|
+
if (!str || !marker) return null
|
|
210
|
+
const ranges = []
|
|
211
|
+
|
|
212
|
+
scanLogicalLineBounds(str, (lineStart, lineEnd) => {
|
|
213
|
+
if (lineEnd > lineStart && lineStartsWithAfterIndent(str, lineStart, lineEnd, marker)) {
|
|
214
|
+
ranges.push([lineStart, lineEnd])
|
|
215
|
+
}
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
return ranges.length ? ranges : null
|
|
219
|
+
}
|
|
220
|
+
|
|
133
221
|
const parseLineRangeList = (attrVal, parseValue) => {
|
|
134
222
|
const str = String(attrVal ?? '')
|
|
135
223
|
if (!str) return []
|
|
@@ -155,14 +243,8 @@ const parseLineRangeList = (attrVal, parseValue) => {
|
|
|
155
243
|
return result
|
|
156
244
|
}
|
|
157
245
|
|
|
158
|
-
const parsePositiveLineNumber = (val) => {
|
|
159
|
-
const num = Number(val)
|
|
160
|
-
if (!Number.isFinite(num) || num <= 0) return null
|
|
161
|
-
return num
|
|
162
|
-
}
|
|
163
|
-
|
|
164
246
|
const getEmphasizeLines = (attrVal) => {
|
|
165
|
-
return parseLineRangeList(attrVal,
|
|
247
|
+
return parseLineRangeList(attrVal, parsePositiveLineIndex)
|
|
166
248
|
}
|
|
167
249
|
|
|
168
250
|
const getLineNumberSkipRanges = (attrVal) => {
|
|
@@ -254,22 +336,106 @@ const resolveAdvancedLineNumberPlan = (lineNumberSkipValue, lineNumberSetValue,
|
|
|
254
336
|
return buildAdvancedLineNumberPlan(skipRanges, setEntries, sourceLineCount)
|
|
255
337
|
}
|
|
256
338
|
|
|
257
|
-
const
|
|
258
|
-
if (!
|
|
259
|
-
const
|
|
260
|
-
const
|
|
261
|
-
|
|
339
|
+
const createLineNotePlan = (lineNotes, maxLine, idPrefix) => {
|
|
340
|
+
if (!Array.isArray(lineNotes) || !lineNotes.length || maxLine <= 0) return null
|
|
341
|
+
const anchors = []
|
|
342
|
+
const items = []
|
|
343
|
+
const noteIdPrefix = String(idPrefix || 'pre-line-note')
|
|
344
|
+
let prevFrom = 0
|
|
345
|
+
let prevTo = 0
|
|
346
|
+
let needsSort = false
|
|
347
|
+
for (let i = 0; i < lineNotes.length; i++) {
|
|
348
|
+
const note = lineNotes[i]
|
|
349
|
+
if (!note || !Number.isSafeInteger(note.from) || !Number.isSafeInteger(note.to)) continue
|
|
350
|
+
let from = note.from
|
|
351
|
+
let to = note.to
|
|
352
|
+
if (from > to) {
|
|
353
|
+
const swap = from
|
|
354
|
+
from = to
|
|
355
|
+
to = swap
|
|
356
|
+
}
|
|
357
|
+
if (to < 1 || from > maxLine) continue
|
|
358
|
+
if (from < 1) from = 1
|
|
359
|
+
if (to > maxLine) to = maxLine
|
|
360
|
+
if (anchors[from - 1] !== undefined) return null
|
|
361
|
+
const lineCount = Number.isSafeInteger(note.lineCount) && note.lineCount > 0
|
|
362
|
+
? note.lineCount
|
|
363
|
+
: Math.max(getLogicalLineCount(note.text), 1)
|
|
364
|
+
const item = {
|
|
365
|
+
from,
|
|
366
|
+
to,
|
|
367
|
+
label: from === to ? String(from) : `${from}-${to}`,
|
|
368
|
+
html: escapeHtmlText(note.text),
|
|
369
|
+
anchorName: `--pre-line-note-${from}`,
|
|
370
|
+
id: `${noteIdPrefix}-${from}`,
|
|
371
|
+
lineCount,
|
|
372
|
+
width: note.width || '',
|
|
373
|
+
}
|
|
374
|
+
anchors[from - 1] = item
|
|
375
|
+
items.push(item)
|
|
376
|
+
if (!needsSort && items.length > 1 && (from < prevFrom || (from === prevFrom && to < prevTo))) {
|
|
377
|
+
needsSort = true
|
|
378
|
+
}
|
|
379
|
+
prevFrom = from
|
|
380
|
+
prevTo = to
|
|
381
|
+
}
|
|
382
|
+
if (!items.length) return null
|
|
383
|
+
|
|
384
|
+
if (needsSort) items.sort((a, b) => a.from - b.from || a.to - b.to)
|
|
385
|
+
let canAnchor = true
|
|
386
|
+
let previousVisualEnd = 0
|
|
387
|
+
let overflowLines = 0
|
|
388
|
+
for (let i = 0; i < items.length; i++) {
|
|
389
|
+
const item = items[i]
|
|
390
|
+
const visualEnd = item.from + item.lineCount - 1
|
|
391
|
+
if (item.from <= previousVisualEnd) canAnchor = false
|
|
392
|
+
if (visualEnd > maxLine) overflowLines = Math.max(overflowLines, visualEnd - maxLine)
|
|
393
|
+
previousVisualEnd = visualEnd
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
return {
|
|
397
|
+
anchors,
|
|
398
|
+
items,
|
|
399
|
+
canAnchor,
|
|
400
|
+
overflowLines,
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
const resolveLineNotesPlan = (lineNotes, sourceLineCount, renderedLineCount, idPrefix) => {
|
|
405
|
+
if (!Array.isArray(lineNotes) || !lineNotes.length) return null
|
|
406
|
+
if (!Number.isSafeInteger(sourceLineCount) || sourceLineCount <= 0) return null
|
|
407
|
+
if (sourceLineCount !== renderedLineCount) return null
|
|
408
|
+
return createLineNotePlan(lineNotes, sourceLineCount, idPrefix)
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
const buildPreLineTags = (hidden, setNumber, lineNote) => {
|
|
412
|
+
if (!hidden && setNumber == null && !lineNote) return defaultPreLineTags
|
|
262
413
|
let tag = '<span class="pre-line'
|
|
263
414
|
if (hidden) tag += ' ' + preLineNoNumberClass
|
|
415
|
+
if (lineNote) tag += ' ' + preLineHasEndNoteClass
|
|
264
416
|
tag += '"'
|
|
265
417
|
if (setNumber != null) tag += ` style="counter-set:pre-line-number ${setNumber};"`
|
|
266
|
-
|
|
418
|
+
if (lineNote) {
|
|
419
|
+
tag += ` data-pre-line-note-from="${lineNote.from}"`
|
|
420
|
+
tag += ` data-pre-line-note-to="${lineNote.to}"`
|
|
421
|
+
}
|
|
422
|
+
tag += '>'
|
|
423
|
+
if (!lineNote) return { open: tag, close: closeTag }
|
|
424
|
+
const describedByAttr = lineNote.id ? ` aria-describedby="${lineNote.id}"` : ''
|
|
425
|
+
return {
|
|
426
|
+
open: `${tag}<span class="${preLineContentClass}" style="anchor-name:${lineNote.anchorName};"${describedByAttr}>`,
|
|
427
|
+
close: `${closeTag}${closeTag}`,
|
|
428
|
+
}
|
|
267
429
|
}
|
|
268
430
|
|
|
269
|
-
const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmphasis, needEndSpan, threshold, lineEndSpanClass, br, commentLines, commentClass, lineNumberPlan) => {
|
|
431
|
+
const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmphasis, needEndSpan, threshold, lineEndSpanClass, br, commentLines, commentClass, lineNumberPlan, lineNotePlan) => {
|
|
270
432
|
const lines = content.split(br)
|
|
271
433
|
const max = lines.length
|
|
272
|
-
const
|
|
434
|
+
const lineNumberHidden = lineNumberPlan ? lineNumberPlan.hidden : null
|
|
435
|
+
const lineNumberSets = lineNumberPlan ? lineNumberPlan.sets : null
|
|
436
|
+
const lineNoteAnchors = lineNotePlan ? lineNotePlan.anchors : null
|
|
437
|
+
const hasLineNotes = !!lineNoteAnchors
|
|
438
|
+
const needLineWrap = needLineNumber || hasLineNotes
|
|
273
439
|
let emIdx = 0
|
|
274
440
|
let emStart = -1
|
|
275
441
|
let emEnd = -1
|
|
@@ -286,6 +452,9 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
|
|
|
286
452
|
let line = lines[n]
|
|
287
453
|
const notLastLine = n < max - 1
|
|
288
454
|
const doComment = commentLines && commentLines[n]
|
|
455
|
+
const hidden = !!(lineNumberHidden && lineNumberHidden[n])
|
|
456
|
+
const setNumber = lineNumberSets ? lineNumberSets[n] : undefined
|
|
457
|
+
const lineNote = hasLineNotes ? lineNoteAnchors[n] : null
|
|
289
458
|
let hasLt = false
|
|
290
459
|
let hasLtChecked = false
|
|
291
460
|
|
|
@@ -309,7 +478,7 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
|
|
|
309
478
|
}
|
|
310
479
|
}
|
|
311
480
|
|
|
312
|
-
if (
|
|
481
|
+
if (needLineWrap && notLastLine) {
|
|
313
482
|
if (!hasLtChecked) {
|
|
314
483
|
hasLt = line.indexOf('<') !== -1
|
|
315
484
|
hasLtChecked = true
|
|
@@ -339,8 +508,9 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
|
|
|
339
508
|
line = `<span class="${commentClass}">` + line + closeTag
|
|
340
509
|
}
|
|
341
510
|
|
|
342
|
-
if (
|
|
343
|
-
|
|
511
|
+
if (needLineWrap && notLastLine) {
|
|
512
|
+
const lineTags = buildPreLineTags(hidden, setNumber, lineNote)
|
|
513
|
+
line = lineTags.open + line + lineTags.close
|
|
344
514
|
}
|
|
345
515
|
|
|
346
516
|
if (needEmphasis && emIdx < emphasizeLines.length && emStart === n + 1) {
|
|
@@ -359,10 +529,33 @@ const splitFenceBlockToLines = (content, emphasizeLines, needLineNumber, needEmp
|
|
|
359
529
|
return lines.join(br)
|
|
360
530
|
}
|
|
361
531
|
|
|
532
|
+
const renderLineNotesLayer = (lineNotePlan) => {
|
|
533
|
+
const items = lineNotePlan && lineNotePlan.items
|
|
534
|
+
if (!items || !items.length) return ''
|
|
535
|
+
let html = `<div class="${lineNoteLayerClass}">`
|
|
536
|
+
for (let i = 0; i < items.length; i++) {
|
|
537
|
+
const item = items[i]
|
|
538
|
+
let style = `position-anchor:${item.anchorName};`
|
|
539
|
+
if (item.width) style += ` --line-note-width:${item.width};`
|
|
540
|
+
html += `<div id="${item.id}" class="${lineNoteClass}" role="note" data-pre-line-note-from="${item.from}" data-pre-line-note-to="${item.to}" data-pre-line-note-label="${item.label}" style="${style}">${item.html}</div>`
|
|
541
|
+
}
|
|
542
|
+
html += '</div>'
|
|
543
|
+
return html
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const wrapFencePreWithLineNotes = (preHtml, lineNotePlan) => {
|
|
547
|
+
const items = lineNotePlan && lineNotePlan.items
|
|
548
|
+
if (!items || !items.length) return preHtml + '\n'
|
|
549
|
+
let attrs = ` class="${preWithLineNotesClass}"`
|
|
550
|
+
if (lineNotePlan.canAnchor) attrs += ' data-pre-line-notes-layout="anchor"'
|
|
551
|
+
if (lineNotePlan.overflowLines > 0) attrs += ` style="--pre-line-note-overflow-lines:${lineNotePlan.overflowLines};"`
|
|
552
|
+
return `<div${attrs}>${preHtml}${renderLineNotesLayer(lineNotePlan)}</div>\n`
|
|
553
|
+
}
|
|
554
|
+
|
|
362
555
|
const orderTokenAttrs = (token, opt) => {
|
|
363
556
|
const attrs = token.attrs
|
|
364
557
|
if (!attrs || attrs.length < 2) return
|
|
365
|
-
const rankCache = new Map()
|
|
558
|
+
const rankCache = opt._attrOrderRankCache || new Map()
|
|
366
559
|
const getRank = (name) => {
|
|
367
560
|
let rank = rankCache.get(name)
|
|
368
561
|
if (rank === undefined) {
|
|
@@ -402,6 +595,9 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
|
|
|
402
595
|
let wrapEnabled = false
|
|
403
596
|
let preWrapValue
|
|
404
597
|
let commentMarkValue
|
|
598
|
+
let tagOverride = ''
|
|
599
|
+
const lineNotes = getTokenLineNotes(token)
|
|
600
|
+
const lineNoteIdPrefix = lineNotes && lineNotes.length ? getLineNoteIdPrefix(token, idx) : ''
|
|
405
601
|
const attrNormalizeStartedAt = timingEnabled ? getNowMs() : 0
|
|
406
602
|
|
|
407
603
|
if (token.attrs) {
|
|
@@ -455,6 +651,7 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
|
|
|
455
651
|
}
|
|
456
652
|
break
|
|
457
653
|
case 'data-pre-emphasis':
|
|
654
|
+
if (opt.setEmphasizeLines) emphasizeLines = getEmphasizeLines(val)
|
|
458
655
|
dataPreEmphasisIndex = newAttrs.length
|
|
459
656
|
newAttrs.push(attr)
|
|
460
657
|
break
|
|
@@ -491,6 +688,12 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
|
|
|
491
688
|
sawCommentMark = true
|
|
492
689
|
}
|
|
493
690
|
break
|
|
691
|
+
case 'samp':
|
|
692
|
+
tagOverride = isTruthyAttrValue(val) ? 'samp' : ''
|
|
693
|
+
break
|
|
694
|
+
case 'code':
|
|
695
|
+
tagOverride = isTruthyAttrValue(val) ? 'code' : ''
|
|
696
|
+
break
|
|
494
697
|
case 'line-number-skip':
|
|
495
698
|
case 'pre-line-number-skip':
|
|
496
699
|
lineNumberSkipValue = val
|
|
@@ -557,10 +760,13 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
|
|
|
557
760
|
}
|
|
558
761
|
|
|
559
762
|
if (timingEnabled) addTimingMs(timings, 'attrNormalizeMs', getNowMs() - attrNormalizeStartedAt)
|
|
763
|
+
if (lineNotes && lineNotes.length) token.attrSet('data-pre-line-notes', 'true')
|
|
764
|
+
const isSamp = tagOverride ? tagOverride === 'samp' : opt._sampLangSet.has(lang)
|
|
560
765
|
|
|
561
766
|
return {
|
|
562
767
|
token,
|
|
563
768
|
lang,
|
|
769
|
+
isSamp,
|
|
564
770
|
startNumber,
|
|
565
771
|
emphasizeLines,
|
|
566
772
|
lineNumberSkipValue,
|
|
@@ -568,6 +774,8 @@ const prepareFenceRenderContext = (tokens, idx, opt) => {
|
|
|
568
774
|
wrapEnabled,
|
|
569
775
|
preWrapValue,
|
|
570
776
|
commentMarkValue,
|
|
777
|
+
lineNotes,
|
|
778
|
+
lineNoteIdPrefix,
|
|
571
779
|
timingEnabled,
|
|
572
780
|
timings,
|
|
573
781
|
fenceStartedAt,
|
|
@@ -582,6 +790,8 @@ export {
|
|
|
582
790
|
emitFenceDecision,
|
|
583
791
|
finalizeCommonFenceOption,
|
|
584
792
|
finalizeFenceTimings,
|
|
793
|
+
getCommentLineFlags,
|
|
794
|
+
getCommentLineRanges,
|
|
585
795
|
getLogicalLineCount,
|
|
586
796
|
getNowMs,
|
|
587
797
|
getEmphasizeLines,
|
|
@@ -590,5 +800,7 @@ export {
|
|
|
590
800
|
preWrapStyle,
|
|
591
801
|
prepareFenceRenderContext,
|
|
592
802
|
resolveAdvancedLineNumberPlan,
|
|
803
|
+
resolveLineNotesPlan,
|
|
593
804
|
splitFenceBlockToLines,
|
|
805
|
+
wrapFencePreWithLineNotes,
|
|
594
806
|
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
/*! Renderer Fence shared base | MIT License */
|
|
2
|
+
|
|
3
|
+
:root {
|
|
4
|
+
color-scheme: light dark;
|
|
5
|
+
|
|
6
|
+
--rf-code-bg: oklch(99.4% 0 0);
|
|
7
|
+
--rf-code-fg: oklch(4% 0 0);
|
|
8
|
+
--rf-code-muted: oklch(42% 0 0);
|
|
9
|
+
--rf-code-border: oklch(86% 0 0);
|
|
10
|
+
|
|
11
|
+
--rf-samp-bg: oklch(18% 0 0);
|
|
12
|
+
--rf-samp-fg: oklch(95% 0 0);
|
|
13
|
+
--rf-samp-muted: oklch(76% 0 0);
|
|
14
|
+
--rf-samp-border: oklch(41% 0 0);
|
|
15
|
+
--rf-samp-prompt: oklch(78% 0.13 285);
|
|
16
|
+
|
|
17
|
+
--rf-accent: oklch(50% 0.18 285);
|
|
18
|
+
--rf-accent-soft: oklch(94% 0.035 285);
|
|
19
|
+
--rf-accent-border: oklch(82% 0.08 285);
|
|
20
|
+
|
|
21
|
+
--rf-syntax-keyword: oklch(38% 0.255 25);
|
|
22
|
+
--rf-syntax-string: oklch(34% 0.235 250);
|
|
23
|
+
--rf-syntax-number: oklch(36% 0.245 260);
|
|
24
|
+
--rf-syntax-comment: oklch(39% 0.026 255);
|
|
25
|
+
--rf-syntax-type: oklch(38% 0.235 295);
|
|
26
|
+
--rf-syntax-title: oklch(39% 0.27 305);
|
|
27
|
+
--rf-syntax-variable: oklch(37% 0.205 80);
|
|
28
|
+
--rf-syntax-literal: oklch(36% 0.245 260);
|
|
29
|
+
--rf-syntax-tag: oklch(34% 0.18 150);
|
|
30
|
+
--rf-syntax-attribute: oklch(36% 0.23 260);
|
|
31
|
+
--rf-syntax-punctuation: oklch(12% 0.018 255);
|
|
32
|
+
--rf-syntax-meta: oklch(12% 0.018 255);
|
|
33
|
+
|
|
34
|
+
--rf-samp-syntax-keyword: oklch(78% 0.08 25);
|
|
35
|
+
--rf-samp-syntax-string: oklch(82% 0.055 250);
|
|
36
|
+
--rf-samp-syntax-number: oklch(80% 0.07 255);
|
|
37
|
+
--rf-samp-syntax-comment: oklch(75% 0.01 255);
|
|
38
|
+
--rf-samp-syntax-type: oklch(81% 0.06 295);
|
|
39
|
+
--rf-samp-syntax-title: oklch(82% 0.07 305);
|
|
40
|
+
--rf-samp-syntax-variable: oklch(82% 0.075 90);
|
|
41
|
+
--rf-samp-syntax-literal: oklch(80% 0.07 255);
|
|
42
|
+
--rf-samp-syntax-tag: oklch(81% 0.065 150);
|
|
43
|
+
--rf-samp-syntax-attribute: oklch(80% 0.07 255);
|
|
44
|
+
--rf-samp-syntax-punctuation: oklch(86% 0.012 255);
|
|
45
|
+
|
|
46
|
+
--rf-line-emphasis-bg: color-mix(in oklch, var(--rf-accent-soft) 70%, transparent);
|
|
47
|
+
--rf-line-number-fg: var(--rf-code-muted);
|
|
48
|
+
--rf-line-number-rule: color-mix(in oklch, var(--rf-code-border) 85%, transparent);
|
|
49
|
+
--rf-line-comment-bg: color-mix(in oklch, var(--rf-syntax-comment) 12%, transparent);
|
|
50
|
+
--rf-line-note-bg: oklch(100% 0 0);
|
|
51
|
+
--rf-line-note-border: var(--rf-accent-border);
|
|
52
|
+
--rf-line-note-fg: var(--rf-code-muted);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
@media (prefers-color-scheme: dark) {
|
|
56
|
+
:root {
|
|
57
|
+
--rf-code-bg: oklch(18% 0 0);
|
|
58
|
+
--rf-code-fg: oklch(95% 0 0);
|
|
59
|
+
--rf-code-muted: oklch(76% 0 0);
|
|
60
|
+
--rf-code-border: oklch(38% 0 0);
|
|
61
|
+
|
|
62
|
+
--rf-samp-bg: oklch(99.4% 0 0);
|
|
63
|
+
--rf-samp-fg: oklch(4% 0 0);
|
|
64
|
+
--rf-samp-muted: oklch(42% 0 0);
|
|
65
|
+
--rf-samp-border: oklch(86% 0 0);
|
|
66
|
+
--rf-samp-prompt: oklch(50% 0.18 285);
|
|
67
|
+
|
|
68
|
+
--rf-accent: oklch(78% 0.13 285);
|
|
69
|
+
--rf-accent-soft: oklch(27% 0.05 285);
|
|
70
|
+
--rf-accent-border: oklch(48% 0.11 285);
|
|
71
|
+
|
|
72
|
+
--rf-syntax-keyword: oklch(78% 0.08 25);
|
|
73
|
+
--rf-syntax-string: oklch(82% 0.055 250);
|
|
74
|
+
--rf-syntax-number: oklch(80% 0.07 255);
|
|
75
|
+
--rf-syntax-comment: oklch(75% 0.01 255);
|
|
76
|
+
--rf-syntax-type: oklch(81% 0.06 295);
|
|
77
|
+
--rf-syntax-title: oklch(82% 0.07 305);
|
|
78
|
+
--rf-syntax-variable: oklch(82% 0.075 90);
|
|
79
|
+
--rf-syntax-literal: oklch(80% 0.07 255);
|
|
80
|
+
--rf-syntax-tag: oklch(81% 0.065 150);
|
|
81
|
+
--rf-syntax-attribute: oklch(80% 0.07 255);
|
|
82
|
+
--rf-syntax-punctuation: oklch(86% 0.012 255);
|
|
83
|
+
--rf-syntax-meta: oklch(86% 0.012 255);
|
|
84
|
+
|
|
85
|
+
--rf-samp-syntax-keyword: oklch(38% 0.255 25);
|
|
86
|
+
--rf-samp-syntax-string: oklch(34% 0.235 250);
|
|
87
|
+
--rf-samp-syntax-number: oklch(36% 0.245 260);
|
|
88
|
+
--rf-samp-syntax-comment: oklch(39% 0.026 255);
|
|
89
|
+
--rf-samp-syntax-type: oklch(38% 0.235 295);
|
|
90
|
+
--rf-samp-syntax-title: oklch(39% 0.27 305);
|
|
91
|
+
--rf-samp-syntax-variable: oklch(37% 0.205 80);
|
|
92
|
+
--rf-samp-syntax-literal: oklch(36% 0.245 260);
|
|
93
|
+
--rf-samp-syntax-tag: oklch(34% 0.18 150);
|
|
94
|
+
--rf-samp-syntax-attribute: oklch(36% 0.23 260);
|
|
95
|
+
--rf-samp-syntax-punctuation: oklch(12% 0.018 255);
|
|
96
|
+
|
|
97
|
+
--rf-line-emphasis-bg: color-mix(in oklch, var(--rf-accent-soft) 70%, transparent);
|
|
98
|
+
--rf-line-number-fg: var(--rf-code-muted);
|
|
99
|
+
--rf-line-number-rule: color-mix(in oklch, var(--rf-code-border) 85%, transparent);
|
|
100
|
+
--rf-line-comment-bg: color-mix(in oklch, var(--rf-syntax-comment) 16%, transparent);
|
|
101
|
+
--rf-line-note-bg: oklch(24% 0.01 255);
|
|
102
|
+
--rf-line-note-border: var(--rf-accent-border);
|
|
103
|
+
--rf-line-note-fg: var(--rf-code-muted);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
pre {
|
|
108
|
+
margin: 0;
|
|
109
|
+
padding: 1rem;
|
|
110
|
+
overflow: auto;
|
|
111
|
+
color: var(--rf-code-fg);
|
|
112
|
+
background: var(--rf-code-bg);
|
|
113
|
+
border: 1px solid var(--rf-code-border);
|
|
114
|
+
border-radius: 0.5rem;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
pre code,
|
|
118
|
+
pre samp {
|
|
119
|
+
color: inherit;
|
|
120
|
+
background: transparent;
|
|
121
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
.pre-lines-emphasis {
|
|
125
|
+
background: var(--rf-line-emphasis-bg);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
.pre-line-comment {
|
|
129
|
+
color: var(--rf-syntax-comment);
|
|
130
|
+
background: var(--rf-line-comment-bg);
|
|
131
|
+
padding-block: 0.04em;
|
|
132
|
+
line-height: 1.45;
|
|
133
|
+
-webkit-box-decoration-break: clone;
|
|
134
|
+
box-decoration-break: clone;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
pre:has(> samp) {
|
|
138
|
+
--rf-syntax-keyword: var(--rf-samp-syntax-keyword);
|
|
139
|
+
--rf-syntax-string: var(--rf-samp-syntax-string);
|
|
140
|
+
--rf-syntax-number: var(--rf-samp-syntax-number);
|
|
141
|
+
--rf-syntax-comment: var(--rf-samp-syntax-comment);
|
|
142
|
+
--rf-syntax-type: var(--rf-samp-syntax-type);
|
|
143
|
+
--rf-syntax-title: var(--rf-samp-syntax-title);
|
|
144
|
+
--rf-syntax-variable: var(--rf-samp-syntax-variable);
|
|
145
|
+
--rf-syntax-literal: var(--rf-samp-syntax-literal);
|
|
146
|
+
--rf-syntax-tag: var(--rf-samp-syntax-tag);
|
|
147
|
+
--rf-syntax-attribute: var(--rf-samp-syntax-attribute);
|
|
148
|
+
--rf-syntax-punctuation: var(--rf-samp-syntax-punctuation);
|
|
149
|
+
--rf-syntax-meta: var(--rf-samp-syntax-punctuation);
|
|
150
|
+
--line-number-color: var(--rf-samp-muted);
|
|
151
|
+
--line-number-rule-color: var(--rf-samp-border);
|
|
152
|
+
--line-note-color: var(--rf-samp-muted);
|
|
153
|
+
--line-note-label-color: var(--rf-samp-muted);
|
|
154
|
+
--line-note-border-color: var(--rf-samp-border);
|
|
155
|
+
--line-note-background: color-mix(in oklch, var(--rf-samp-bg) 92%, var(--rf-samp-fg));
|
|
156
|
+
color: var(--rf-samp-fg);
|
|
157
|
+
background: var(--rf-samp-bg);
|
|
158
|
+
border-color: var(--rf-samp-border);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
pre:has(> samp) .pre-line-comment {
|
|
162
|
+
color: var(--rf-samp-muted);
|
|
163
|
+
background: color-mix(in oklch, var(--rf-samp-muted) 18%, transparent);
|
|
164
|
+
}
|