@peaceroad/markdown-it-renderer-fence 0.4.1 → 0.6.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 +491 -114
- package/THIRD_PARTY_NOTICES.md +56 -0
- package/index.js +33 -503
- package/package.json +28 -4
- package/src/custom-highlight/option-validator.js +152 -0
- package/src/custom-highlight/payload-utils.js +43 -0
- package/src/custom-highlight/runtime-utils.js +83 -0
- package/src/custom-highlight/shiki-keyword-rules.js +407 -0
- package/src/custom-highlight/shiki-keyword.js +417 -0
- package/src/entry/custom-highlight-runtime.js +11 -0
- package/src/entry/custom-highlight.js +25 -0
- package/src/entry/markup-highlight.js +27 -0
- package/src/fence/render-api-constants.js +34 -0
- package/src/fence/render-api-provider.js +810 -0
- package/src/fence/render-api-renderer.js +145 -0
- package/src/fence/render-api-runtime.js +712 -0
- package/src/fence/render-api.js +107 -0
- package/src/fence/render-markup.js +177 -0
- package/src/fence/render-shared.js +590 -0
- package/src/utils/attr-utils.js +120 -0
- package/src/utils/pre-code-wrapper-parser.js +114 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getCustomHighlightPayloadMap,
|
|
3
|
+
} from '../custom-highlight/payload-utils.js'
|
|
4
|
+
import {
|
|
5
|
+
applyLineEndAlias,
|
|
6
|
+
createCommonFenceOptionDefaults,
|
|
7
|
+
finalizeCommonFenceOption,
|
|
8
|
+
prepareFenceRenderContext,
|
|
9
|
+
} from './render-shared.js'
|
|
10
|
+
import {
|
|
11
|
+
customHighlightDataEnvKey,
|
|
12
|
+
customHighlightEnvInitRuleName,
|
|
13
|
+
customHighlightPayloadSchemaVersion,
|
|
14
|
+
customHighlightPayloadSupportedVersions,
|
|
15
|
+
runtimeFallbackReasonSet,
|
|
16
|
+
customHighlightSeqEnvKey,
|
|
17
|
+
} from './render-api-constants.js'
|
|
18
|
+
import {
|
|
19
|
+
isNormalizedCustomHighlightOpt,
|
|
20
|
+
normalizeCustomHighlightOpt,
|
|
21
|
+
renderCustomHighlightPayloadScript,
|
|
22
|
+
renderCustomHighlightScopeStyleTag,
|
|
23
|
+
shouldApplyApiFallbackForReason,
|
|
24
|
+
} from './render-api-provider.js'
|
|
25
|
+
import {
|
|
26
|
+
getFenceHtml,
|
|
27
|
+
} from './render-api-renderer.js'
|
|
28
|
+
import {
|
|
29
|
+
applyCustomHighlights,
|
|
30
|
+
clearCustomHighlights,
|
|
31
|
+
observeCustomHighlights,
|
|
32
|
+
} from './render-api-runtime.js'
|
|
33
|
+
|
|
34
|
+
const shouldRuntimeFallback = (reason, opt = {}) => {
|
|
35
|
+
const chOpt = isNormalizedCustomHighlightOpt(opt) ? opt : normalizeCustomHighlightOpt(opt)
|
|
36
|
+
if (!runtimeFallbackReasonSet.has(reason)) return false
|
|
37
|
+
return shouldApplyApiFallbackForReason(chOpt, reason)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const mditRendererFenceCustomHighlight = (md, option) => {
|
|
41
|
+
const opt = {
|
|
42
|
+
...createCommonFenceOptionDefaults(md),
|
|
43
|
+
customHighlight: null,
|
|
44
|
+
}
|
|
45
|
+
if (option) {
|
|
46
|
+
Object.assign(opt, option)
|
|
47
|
+
applyLineEndAlias(opt, option)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const rawCustomHighlightOpt = (opt.customHighlight && typeof opt.customHighlight === 'object')
|
|
51
|
+
? opt.customHighlight
|
|
52
|
+
: {}
|
|
53
|
+
opt.customHighlight = normalizeCustomHighlightOpt(rawCustomHighlightOpt)
|
|
54
|
+
if (opt.customHighlight.provider === 'custom') {
|
|
55
|
+
opt.customHighlight._customGetRanges = (typeof opt.customHighlight.getRanges === 'function')
|
|
56
|
+
? opt.customHighlight.getRanges
|
|
57
|
+
: null
|
|
58
|
+
} else if (opt.customHighlight.provider === 'hljs') {
|
|
59
|
+
opt.customHighlight._hljsHighlightFn =
|
|
60
|
+
(typeof opt.customHighlight.hljsHighlight === 'function')
|
|
61
|
+
? opt.customHighlight.hljsHighlight
|
|
62
|
+
: ((typeof opt.customHighlight.highlight === 'function')
|
|
63
|
+
? opt.customHighlight.highlight
|
|
64
|
+
: ((md && md.options && typeof md.options.highlight === 'function') ? md.options.highlight : null))
|
|
65
|
+
} else if (opt.customHighlight.provider === 'shiki') {
|
|
66
|
+
opt.customHighlight._hasShikiHighlighter = !!(opt.customHighlight.highlighter && typeof opt.customHighlight.highlighter.codeToTokens === 'function')
|
|
67
|
+
const tokenOptionBase = {}
|
|
68
|
+
if (opt.customHighlight._singleTheme) tokenOptionBase.theme = opt.customHighlight._singleTheme
|
|
69
|
+
if (opt.customHighlight.shikiScopeMode === 'semantic' || opt.customHighlight.shikiScopeMode === 'keyword') {
|
|
70
|
+
tokenOptionBase.includeExplanation = 'scopeName'
|
|
71
|
+
}
|
|
72
|
+
opt.customHighlight._shikiTokenOptionBase = Object.keys(tokenOptionBase).length ? tokenOptionBase : null
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
finalizeCommonFenceOption(opt)
|
|
76
|
+
|
|
77
|
+
md.core.ruler.before('block', customHighlightEnvInitRuleName, (state) => {
|
|
78
|
+
const env = state && state.env
|
|
79
|
+
if (!env || typeof env !== 'object') return
|
|
80
|
+
if (opt.customHighlight.transport === 'env') {
|
|
81
|
+
env[customHighlightDataEnvKey] = {}
|
|
82
|
+
env[customHighlightSeqEnvKey] = 0
|
|
83
|
+
return
|
|
84
|
+
}
|
|
85
|
+
delete env[customHighlightDataEnvKey]
|
|
86
|
+
delete env[customHighlightSeqEnvKey]
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
md.renderer.rules.fence = (tokens, idx, options, env, slf) => {
|
|
90
|
+
const context = prepareFenceRenderContext(tokens, idx, opt)
|
|
91
|
+
return getFenceHtml(context, md, opt, slf, env)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export {
|
|
96
|
+
applyCustomHighlights,
|
|
97
|
+
clearCustomHighlights,
|
|
98
|
+
customHighlightPayloadSchemaVersion,
|
|
99
|
+
customHighlightPayloadSupportedVersions,
|
|
100
|
+
getCustomHighlightPayloadMap,
|
|
101
|
+
observeCustomHighlights,
|
|
102
|
+
renderCustomHighlightPayloadScript,
|
|
103
|
+
renderCustomHighlightScopeStyleTag,
|
|
104
|
+
shouldRuntimeFallback,
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export default mditRendererFenceCustomHighlight
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import {
|
|
2
|
+
appendStyleValue,
|
|
3
|
+
mergeAttrSets,
|
|
4
|
+
parseHtmlAttrs,
|
|
5
|
+
} from '../utils/attr-utils.js'
|
|
6
|
+
import {
|
|
7
|
+
addTimingMs,
|
|
8
|
+
commentLineClass,
|
|
9
|
+
emitFenceDecision,
|
|
10
|
+
finalizeFenceTimings,
|
|
11
|
+
getLogicalLineCount,
|
|
12
|
+
getNowMs,
|
|
13
|
+
normalizeEmphasisRanges,
|
|
14
|
+
orderTokenAttrs,
|
|
15
|
+
preWrapStyle,
|
|
16
|
+
resolveAdvancedLineNumberPlan,
|
|
17
|
+
splitFenceBlockToLines,
|
|
18
|
+
} from './render-shared.js'
|
|
19
|
+
import {
|
|
20
|
+
parsePreCodeWrapper,
|
|
21
|
+
} from '../utils/pre-code-wrapper-parser.js'
|
|
22
|
+
|
|
23
|
+
const renderFenceMarkup = (context, md, opt, slf) => {
|
|
24
|
+
const token = context.token
|
|
25
|
+
const lang = context.lang
|
|
26
|
+
const timingEnabled = context.timingEnabled
|
|
27
|
+
const timings = context.timings
|
|
28
|
+
const fenceStartedAt = context.fenceStartedAt
|
|
29
|
+
const startNumber = context.startNumber
|
|
30
|
+
const emphasizeLines = context.emphasizeLines
|
|
31
|
+
const lineNumberSkipValue = context.lineNumberSkipValue
|
|
32
|
+
const lineNumberResetValue = context.lineNumberResetValue
|
|
33
|
+
const wrapEnabled = context.wrapEnabled
|
|
34
|
+
const preWrapValue = context.preWrapValue
|
|
35
|
+
const commentMarkValue = context.commentMarkValue
|
|
36
|
+
|
|
37
|
+
const isSamp = opt._sampReg.test(lang)
|
|
38
|
+
const sourceContent = token.content
|
|
39
|
+
let content = sourceContent
|
|
40
|
+
let commentLines
|
|
41
|
+
let needComment = false
|
|
42
|
+
|
|
43
|
+
if (opt.setHighlight && md.options.highlight) {
|
|
44
|
+
const highlightStartedAt = timingEnabled ? getNowMs() : 0
|
|
45
|
+
if (lang && lang !== 'samp') {
|
|
46
|
+
content = md.options.highlight(content, lang)
|
|
47
|
+
} else {
|
|
48
|
+
content = md.utils.escapeHtml(sourceContent)
|
|
49
|
+
}
|
|
50
|
+
if (timingEnabled) addTimingMs(timings, 'highlightMs', getNowMs() - highlightStartedAt)
|
|
51
|
+
} else {
|
|
52
|
+
content = md.utils.escapeHtml(sourceContent)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let preAttrsFromHighlight
|
|
56
|
+
let hasHighlightPre = false
|
|
57
|
+
const hasPreTag = (content.indexOf('<pre') !== -1 || content.indexOf('<PRE') !== -1)
|
|
58
|
+
if (hasPreTag) {
|
|
59
|
+
const preMatch = parsePreCodeWrapper(content)
|
|
60
|
+
if (preMatch) {
|
|
61
|
+
hasHighlightPre = true
|
|
62
|
+
preAttrsFromHighlight = parseHtmlAttrs(preMatch.preAttrsText)
|
|
63
|
+
const codeAttrsFromHighlight = parseHtmlAttrs(preMatch.codeAttrsText)
|
|
64
|
+
content = preMatch.content
|
|
65
|
+
if (codeAttrsFromHighlight.length) {
|
|
66
|
+
if (!token.attrs) token.attrs = []
|
|
67
|
+
mergeAttrSets(token.attrs, codeAttrsFromHighlight)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (opt.useHighlightPre && hasPreTag && !hasHighlightPre) {
|
|
73
|
+
const decision = {
|
|
74
|
+
renderer: 'markup',
|
|
75
|
+
useHighlightPre: true,
|
|
76
|
+
passthrough: true,
|
|
77
|
+
passthroughReason: 'pre-code-parse-failed',
|
|
78
|
+
hasHighlightPre: false,
|
|
79
|
+
disabledFeatures: ['setLineNumber', 'setEmphasizeLines', 'lineEndSpanThreshold', 'comment-mark', 'samp'],
|
|
80
|
+
}
|
|
81
|
+
if (timingEnabled) decision.timings = finalizeFenceTimings(timings, fenceStartedAt)
|
|
82
|
+
emitFenceDecision(opt, decision)
|
|
83
|
+
return content.endsWith('\n') ? content : content + '\n'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
orderTokenAttrs(token, opt)
|
|
87
|
+
|
|
88
|
+
const preAttrs = preAttrsFromHighlight ? preAttrsFromHighlight.slice() : []
|
|
89
|
+
if (preWrapValue !== undefined) {
|
|
90
|
+
const idx = preAttrs.findIndex((attr) => attr[0] === 'data-pre-wrap')
|
|
91
|
+
if (idx === -1) {
|
|
92
|
+
preAttrs.push(['data-pre-wrap', preWrapValue])
|
|
93
|
+
} else {
|
|
94
|
+
preAttrs[idx][1] = preWrapValue
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (wrapEnabled && opt.setPreWrapStyle !== false) {
|
|
98
|
+
const idx = preAttrs.findIndex((attr) => attr[0] === 'style')
|
|
99
|
+
if (idx === -1) {
|
|
100
|
+
preAttrs.push(['style', preWrapStyle])
|
|
101
|
+
} else {
|
|
102
|
+
preAttrs[idx][1] = appendStyleValue(preAttrs[idx][1], preWrapStyle)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (preAttrs.length) orderTokenAttrs({ attrs: preAttrs }, opt)
|
|
106
|
+
const preAttrsText = preAttrs.length ? slf.renderAttrs({ attrs: preAttrs }) : ''
|
|
107
|
+
|
|
108
|
+
const needLineNumber = opt.setLineNumber && startNumber >= 0
|
|
109
|
+
let sourceLogicalLineCount = -1
|
|
110
|
+
let highlightedLogicalLineCount = -1
|
|
111
|
+
const ensureLogicalLineCounts = () => {
|
|
112
|
+
if (sourceLogicalLineCount === -1) sourceLogicalLineCount = getLogicalLineCount(sourceContent)
|
|
113
|
+
if (highlightedLogicalLineCount === -1) highlightedLogicalLineCount = getLogicalLineCount(content)
|
|
114
|
+
}
|
|
115
|
+
let normalizedEmphasis = []
|
|
116
|
+
if (opt.setEmphasizeLines && emphasizeLines.length > 0) {
|
|
117
|
+
ensureLogicalLineCounts()
|
|
118
|
+
normalizedEmphasis = normalizeEmphasisRanges(emphasizeLines, highlightedLogicalLineCount)
|
|
119
|
+
}
|
|
120
|
+
let lineNumberPlan = null
|
|
121
|
+
if (needLineNumber && (lineNumberSkipValue !== undefined || lineNumberResetValue !== undefined)) {
|
|
122
|
+
ensureLogicalLineCounts()
|
|
123
|
+
lineNumberPlan = resolveAdvancedLineNumberPlan(lineNumberSkipValue, lineNumberResetValue, sourceLogicalLineCount, highlightedLogicalLineCount)
|
|
124
|
+
}
|
|
125
|
+
const needEmphasis = normalizedEmphasis.length > 0
|
|
126
|
+
const needEndSpan = opt.lineEndSpanThreshold > 0
|
|
127
|
+
const useHighlightPre = opt.useHighlightPre && hasHighlightPre
|
|
128
|
+
|
|
129
|
+
if (!useHighlightPre && commentMarkValue && sourceContent.indexOf(commentMarkValue) !== -1) {
|
|
130
|
+
ensureLogicalLineCounts()
|
|
131
|
+
if (highlightedLogicalLineCount === sourceLogicalLineCount) {
|
|
132
|
+
const rawLines = sourceContent.split('\n')
|
|
133
|
+
for (let i = 0; i < rawLines.length; i++) {
|
|
134
|
+
if (rawLines[i].trimStart().startsWith(commentMarkValue)) {
|
|
135
|
+
if (!commentLines) commentLines = []
|
|
136
|
+
commentLines[i] = true
|
|
137
|
+
needComment = true
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!useHighlightPre && (needLineNumber || needEmphasis || needEndSpan || needComment)) {
|
|
144
|
+
const splitStartedAt = timingEnabled ? getNowMs() : 0
|
|
145
|
+
const nlIndex = content.indexOf('\n')
|
|
146
|
+
const br = nlIndex > 0 && content[nlIndex - 1] === '\r' ? '\r\n' : '\n'
|
|
147
|
+
content = splitFenceBlockToLines(
|
|
148
|
+
content,
|
|
149
|
+
normalizedEmphasis,
|
|
150
|
+
needLineNumber,
|
|
151
|
+
needEmphasis,
|
|
152
|
+
needEndSpan,
|
|
153
|
+
opt.lineEndSpanThreshold,
|
|
154
|
+
opt.lineEndSpanClass,
|
|
155
|
+
br,
|
|
156
|
+
commentLines,
|
|
157
|
+
commentLineClass,
|
|
158
|
+
lineNumberPlan,
|
|
159
|
+
)
|
|
160
|
+
if (timingEnabled) addTimingMs(timings, 'lineSplitMs', getNowMs() - splitStartedAt)
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const tag = isSamp ? 'samp' : 'code'
|
|
164
|
+
const decision = {
|
|
165
|
+
renderer: 'markup',
|
|
166
|
+
useHighlightPre,
|
|
167
|
+
hasHighlightPre,
|
|
168
|
+
disabledFeatures: useHighlightPre ? ['setLineNumber', 'line-number-skip', 'line-number-reset', 'setEmphasizeLines', 'lineEndSpanThreshold', 'comment-mark', 'samp'] : [],
|
|
169
|
+
}
|
|
170
|
+
if (timingEnabled) decision.timings = finalizeFenceTimings(timings, fenceStartedAt)
|
|
171
|
+
emitFenceDecision(opt, decision)
|
|
172
|
+
return `<pre${preAttrsText}><${tag}${slf.renderAttrs(token)}>${content}</${tag}></pre>\n`
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
export {
|
|
176
|
+
renderFenceMarkup,
|
|
177
|
+
}
|