@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.
@@ -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
+ }