@peaceroad/markdown-it-renderer-fence 0.4.1 → 0.5.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 +440 -117
- 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 +40 -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 +136 -0
- package/src/fence/render-api-runtime.js +712 -0
- package/src/fence/render-api.js +117 -0
- package/src/fence/render-markup.js +161 -0
- package/src/fence/render-shared.js +428 -0
- package/src/utils/attr-utils.js +120 -0
- package/src/utils/pre-code-wrapper-parser.js +114 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
const warnedOptionIssues = new Set()
|
|
2
|
+
|
|
3
|
+
const isWarnEnabled = () => {
|
|
4
|
+
if (typeof process === 'undefined' || !process || !process.env) return true
|
|
5
|
+
const mode = process.env.NODE_ENV
|
|
6
|
+
return mode === 'development'
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const warnOptionIssueOnce = (code, message) => {
|
|
10
|
+
if (!isWarnEnabled()) return
|
|
11
|
+
const key = `${code}:${message}`
|
|
12
|
+
if (warnedOptionIssues.has(key)) return
|
|
13
|
+
warnedOptionIssues.add(key)
|
|
14
|
+
if (typeof console !== 'undefined' && console && typeof console.warn === 'function') {
|
|
15
|
+
console.warn(`[renderer-fence] ${message}`)
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const validProviderSet = new Set(['shiki', 'hljs', 'custom'])
|
|
20
|
+
const validFallbackSet = new Set(['plain', 'markup'])
|
|
21
|
+
const validTransportSet = new Set(['env', 'inline-script'])
|
|
22
|
+
const validLineFeatureStrategySet = new Set(['hybrid', 'disable'])
|
|
23
|
+
const validShikiScopeModeSet = new Set(['auto', 'color', 'semantic', 'keyword'])
|
|
24
|
+
const knownCustomHighlightKeys = new Set([
|
|
25
|
+
'provider',
|
|
26
|
+
'getRanges',
|
|
27
|
+
'fallback',
|
|
28
|
+
'fallbackOn',
|
|
29
|
+
'transport',
|
|
30
|
+
'idPrefix',
|
|
31
|
+
'lineFeatureStrategy',
|
|
32
|
+
'scopePrefix',
|
|
33
|
+
'includeScopeStyles',
|
|
34
|
+
'shikiScopeMode',
|
|
35
|
+
'shikiKeywordClassifier',
|
|
36
|
+
'shikiKeywordLangResolver',
|
|
37
|
+
'shikiKeywordLangAliases',
|
|
38
|
+
'highlighter',
|
|
39
|
+
'hljsHighlight',
|
|
40
|
+
'highlight',
|
|
41
|
+
'defaultLang',
|
|
42
|
+
'theme',
|
|
43
|
+
])
|
|
44
|
+
|
|
45
|
+
const validateCustomHighlightOptions = (rawOpt, normalizedOpt) => {
|
|
46
|
+
if (!rawOpt || typeof rawOpt !== 'object') return
|
|
47
|
+
|
|
48
|
+
for (const key of Object.keys(rawOpt)) {
|
|
49
|
+
if (!knownCustomHighlightKeys.has(key)) {
|
|
50
|
+
warnOptionIssueOnce(
|
|
51
|
+
'unknown-option',
|
|
52
|
+
`Unknown customHighlight option "${key}" was ignored.`,
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (rawOpt.provider != null && !validProviderSet.has(normalizedOpt.provider)) {
|
|
58
|
+
warnOptionIssueOnce(
|
|
59
|
+
'provider',
|
|
60
|
+
`Invalid customHighlight.provider "${String(rawOpt.provider)}". Using "${normalizedOpt.provider}".`,
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
if (rawOpt.fallback != null && !validFallbackSet.has(normalizedOpt.fallback)) {
|
|
64
|
+
warnOptionIssueOnce(
|
|
65
|
+
'fallback',
|
|
66
|
+
`Invalid customHighlight.fallback "${String(rawOpt.fallback)}". Using "${normalizedOpt.fallback}".`,
|
|
67
|
+
)
|
|
68
|
+
}
|
|
69
|
+
if (rawOpt.transport != null && !validTransportSet.has(normalizedOpt.transport)) {
|
|
70
|
+
warnOptionIssueOnce(
|
|
71
|
+
'transport',
|
|
72
|
+
`Invalid customHighlight.transport "${String(rawOpt.transport)}". Using "${normalizedOpt.transport}".`,
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
if (rawOpt.lineFeatureStrategy != null && !validLineFeatureStrategySet.has(normalizedOpt.lineFeatureStrategy)) {
|
|
76
|
+
warnOptionIssueOnce(
|
|
77
|
+
'line-feature-strategy',
|
|
78
|
+
`Invalid customHighlight.lineFeatureStrategy "${String(rawOpt.lineFeatureStrategy)}". Using "${normalizedOpt.lineFeatureStrategy}".`,
|
|
79
|
+
)
|
|
80
|
+
}
|
|
81
|
+
if (rawOpt.shikiScopeMode != null && !validShikiScopeModeSet.has(normalizedOpt.shikiScopeMode)) {
|
|
82
|
+
warnOptionIssueOnce(
|
|
83
|
+
'shiki-scope-mode',
|
|
84
|
+
`Invalid customHighlight.shikiScopeMode "${String(rawOpt.shikiScopeMode)}". Using "${normalizedOpt.shikiScopeMode}".`,
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
if (rawOpt.fallbackOn != null && !Array.isArray(rawOpt.fallbackOn)) {
|
|
88
|
+
warnOptionIssueOnce(
|
|
89
|
+
'fallback-on',
|
|
90
|
+
'customHighlight.fallbackOn should be an array; using default fallback reasons.',
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (rawOpt.theme != null) {
|
|
95
|
+
if (typeof rawOpt.theme !== 'string' && (typeof rawOpt.theme !== 'object' || Array.isArray(rawOpt.theme))) {
|
|
96
|
+
warnOptionIssueOnce(
|
|
97
|
+
'theme-type',
|
|
98
|
+
'customHighlight.theme should be a string or an object { light, dark, default? }.',
|
|
99
|
+
)
|
|
100
|
+
} else if (rawOpt.theme && typeof rawOpt.theme === 'object' && !Array.isArray(rawOpt.theme)) {
|
|
101
|
+
const hasLight = (typeof rawOpt.theme.light === 'string' && rawOpt.theme.light.trim().length > 0)
|
|
102
|
+
const hasDark = (typeof rawOpt.theme.dark === 'string' && rawOpt.theme.dark.trim().length > 0)
|
|
103
|
+
if (rawOpt.theme.light != null && typeof rawOpt.theme.light !== 'string') {
|
|
104
|
+
warnOptionIssueOnce(
|
|
105
|
+
'theme-light-type',
|
|
106
|
+
'customHighlight.theme.light should be a non-empty string when provided.',
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
if (rawOpt.theme.dark != null && typeof rawOpt.theme.dark !== 'string') {
|
|
110
|
+
warnOptionIssueOnce(
|
|
111
|
+
'theme-dark-type',
|
|
112
|
+
'customHighlight.theme.dark should be a non-empty string when provided.',
|
|
113
|
+
)
|
|
114
|
+
}
|
|
115
|
+
if (!hasLight && !hasDark) {
|
|
116
|
+
warnOptionIssueOnce(
|
|
117
|
+
'theme-missing-variants',
|
|
118
|
+
'customHighlight.theme object should include at least one of { light, dark }.',
|
|
119
|
+
)
|
|
120
|
+
}
|
|
121
|
+
if (rawOpt.theme.default != null) {
|
|
122
|
+
const key = String(rawOpt.theme.default).trim().toLowerCase()
|
|
123
|
+
if (key !== 'light' && key !== 'dark') {
|
|
124
|
+
warnOptionIssueOnce(
|
|
125
|
+
'theme-default',
|
|
126
|
+
'customHighlight.theme.default should be "light" or "dark".',
|
|
127
|
+
)
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (normalizedOpt.provider === 'custom' && typeof normalizedOpt.getRanges !== 'function') {
|
|
134
|
+
warnOptionIssueOnce(
|
|
135
|
+
'custom-provider-ranges',
|
|
136
|
+
'customHighlight.provider="custom" requires customHighlight.getRanges(code, lang, context).',
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
if (normalizedOpt.provider === 'shiki') {
|
|
140
|
+
const ok = !!(normalizedOpt.highlighter && typeof normalizedOpt.highlighter.codeToTokens === 'function')
|
|
141
|
+
if (!ok) {
|
|
142
|
+
warnOptionIssueOnce(
|
|
143
|
+
'shiki-provider-highlighter',
|
|
144
|
+
'customHighlight.provider="shiki" requires customHighlight.highlighter.codeToTokens().',
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export {
|
|
151
|
+
validateCustomHighlightOptions,
|
|
152
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const escapeJsonForScript = (data) => {
|
|
2
|
+
return JSON.stringify(data)
|
|
3
|
+
.replace(/</g, '\\u003C')
|
|
4
|
+
.replace(/>/g, '\\u003E')
|
|
5
|
+
.replace(/&/g, '\\u0026')
|
|
6
|
+
.replace(/\u2028/g, '\\u2028')
|
|
7
|
+
.replace(/\u2029/g, '\\u2029')
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const escapeHtmlAttr = (value) => {
|
|
11
|
+
return String(value)
|
|
12
|
+
.replace(/&/g, '&')
|
|
13
|
+
.replace(/"/g, '"')
|
|
14
|
+
.replace(/'/g, ''')
|
|
15
|
+
.replace(/</g, '<')
|
|
16
|
+
.replace(/>/g, '>')
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const getCustomHighlightPayloadMap = (env, envKey = 'rendererFenceCustomHighlights') => {
|
|
20
|
+
if (!env || typeof env !== 'object') return {}
|
|
21
|
+
const map = env[envKey]
|
|
22
|
+
if (!map || typeof map !== 'object') return {}
|
|
23
|
+
return map
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const renderCustomHighlightPayloadScript = (
|
|
27
|
+
env,
|
|
28
|
+
scriptId = 'pre-highlight-data',
|
|
29
|
+
envKey = 'rendererFenceCustomHighlights',
|
|
30
|
+
) => {
|
|
31
|
+
const map = getCustomHighlightPayloadMap(env, envKey)
|
|
32
|
+
const keys = Object.keys(map)
|
|
33
|
+
if (!keys.length) return ''
|
|
34
|
+
const id = escapeHtmlAttr(scriptId || 'pre-highlight-data')
|
|
35
|
+
return `<script type="application/json" id="${id}">${escapeJsonForScript(map)}</script>`
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export {
|
|
39
|
+
escapeHtmlAttr,
|
|
40
|
+
escapeJsonForScript,
|
|
41
|
+
getCustomHighlightPayloadMap,
|
|
42
|
+
renderCustomHighlightPayloadScript,
|
|
43
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
const styleValueSafeReg = /^[#(),.%\w\s-]+$/
|
|
2
|
+
|
|
3
|
+
const getPayloadMapFromScript = (root, scriptId = 'pre-highlight-data', inlineAttrName = 'data-pre-highlight') => {
|
|
4
|
+
if (!root || typeof root.querySelector !== 'function') return {}
|
|
5
|
+
const map = {}
|
|
6
|
+
const safeScriptId = scriptId || 'pre-highlight-data'
|
|
7
|
+
const script = root.querySelector(`#${safeScriptId}`)
|
|
8
|
+
if (script && script.textContent) {
|
|
9
|
+
try {
|
|
10
|
+
const parsed = JSON.parse(script.textContent)
|
|
11
|
+
if (parsed && typeof parsed === 'object') Object.assign(map, parsed)
|
|
12
|
+
} catch (e) {}
|
|
13
|
+
}
|
|
14
|
+
const inlineScripts = root.querySelectorAll(`script[type="application/json"][${inlineAttrName}]`)
|
|
15
|
+
for (const item of inlineScripts) {
|
|
16
|
+
const id = item && typeof item.getAttribute === 'function' ? item.getAttribute(inlineAttrName) : null
|
|
17
|
+
if (!id || !item.textContent) continue
|
|
18
|
+
try {
|
|
19
|
+
map[id] = JSON.parse(item.textContent)
|
|
20
|
+
} catch (e) {}
|
|
21
|
+
}
|
|
22
|
+
return map
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const collectTextSegments = (node) => {
|
|
26
|
+
const segments = []
|
|
27
|
+
const doc = node && (node.ownerDocument || (typeof document !== 'undefined' ? document : null))
|
|
28
|
+
if (!doc || typeof doc.createTreeWalker !== 'function') return segments
|
|
29
|
+
const textMask = typeof NodeFilter !== 'undefined' ? NodeFilter.SHOW_TEXT : 4
|
|
30
|
+
const walker = doc.createTreeWalker(node, textMask)
|
|
31
|
+
let current = walker.nextNode()
|
|
32
|
+
let cursor = 0
|
|
33
|
+
while (current) {
|
|
34
|
+
const len = current.nodeValue ? current.nodeValue.length : 0
|
|
35
|
+
segments.push({ node: current, start: cursor, end: cursor + len })
|
|
36
|
+
cursor += len
|
|
37
|
+
current = walker.nextNode()
|
|
38
|
+
}
|
|
39
|
+
return segments
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const findTextPosition = (segments, offset) => {
|
|
43
|
+
let lo = 0
|
|
44
|
+
let hi = segments.length - 1
|
|
45
|
+
while (lo <= hi) {
|
|
46
|
+
const mid = (lo + hi) >> 1
|
|
47
|
+
const seg = segments[mid]
|
|
48
|
+
if (offset < seg.start) {
|
|
49
|
+
hi = mid - 1
|
|
50
|
+
continue
|
|
51
|
+
}
|
|
52
|
+
if (offset > seg.end) {
|
|
53
|
+
lo = mid + 1
|
|
54
|
+
continue
|
|
55
|
+
}
|
|
56
|
+
return { node: seg.node, offset: offset - seg.start }
|
|
57
|
+
}
|
|
58
|
+
if (segments.length === 0) return null
|
|
59
|
+
const last = segments[segments.length - 1]
|
|
60
|
+
if (offset === last.end) return { node: last.node, offset: last.end - last.start }
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const styleValueSafe = (val) => {
|
|
65
|
+
return typeof val === 'string' && styleValueSafeReg.test(val)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const styleToHighlightCss = (style) => {
|
|
69
|
+
if (!style || typeof style !== 'object') return ''
|
|
70
|
+
const parts = []
|
|
71
|
+
if (styleValueSafe(style.color)) parts.push(`color:${style.color}`)
|
|
72
|
+
if (styleValueSafe(style.backgroundColor)) parts.push(`background-color:${style.backgroundColor}`)
|
|
73
|
+
if (styleValueSafe(style.textDecoration)) parts.push(`text-decoration:${style.textDecoration}`)
|
|
74
|
+
if (styleValueSafe(style.textShadow)) parts.push(`text-shadow:${style.textShadow}`)
|
|
75
|
+
return parts.join(';')
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export {
|
|
79
|
+
collectTextSegments,
|
|
80
|
+
findTextPosition,
|
|
81
|
+
getPayloadMapFromScript,
|
|
82
|
+
styleToHighlightCss,
|
|
83
|
+
}
|
|
@@ -0,0 +1,407 @@
|
|
|
1
|
+
const shikiSimpleIdentifierReg = /^[A-Za-z_$][\w$]*$/
|
|
2
|
+
const shikiPunctuationTokenReg = /^[()[\]{}<>.,;:!?~`'"@#$%^&*+=|/\\-]+$/
|
|
3
|
+
const shikiFunctionLikeIdentifierReg = /^[A-Za-z_$][\w$]*[!?]?$/
|
|
4
|
+
const shikiShellOptionTokenReg = /^-[A-Za-z0-9][A-Za-z0-9-]*$/
|
|
5
|
+
const shikiShellTestOperatorSet = new Set(['-n', '-z', '-gt', '-lt', '-ge', '-le', '-eq', '-ne'])
|
|
6
|
+
|
|
7
|
+
const keywordV4GlobalRules = [
|
|
8
|
+
{
|
|
9
|
+
id: 'css-color-name-to-number',
|
|
10
|
+
priority: 2100,
|
|
11
|
+
scopeIncludesAny: ['support.constant.color.w3c-standard-color-name.css'],
|
|
12
|
+
setBucket: 'number',
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
id: 'rust-lifetime-to-type-name',
|
|
16
|
+
priority: 2050,
|
|
17
|
+
lang: ['rust'],
|
|
18
|
+
scopeIncludesAny: ['entity.name.type.lifetime.rust'],
|
|
19
|
+
setBucket: 'type-name',
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
id: 'regex-alternative-operator-keyword',
|
|
23
|
+
priority: 2040,
|
|
24
|
+
scopeIncludesAny: ['keyword.operator.or.regexp'],
|
|
25
|
+
tokenEquals: ['|'],
|
|
26
|
+
setBucket: 'keyword',
|
|
27
|
+
},
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
const keywordV4LangRules = {
|
|
31
|
+
sql: [
|
|
32
|
+
{
|
|
33
|
+
id: 'sql-window-function-blue',
|
|
34
|
+
priority: 2000,
|
|
35
|
+
scopeIncludesAny: ['support.function.ranking.sql'],
|
|
36
|
+
setBucket: 'type',
|
|
37
|
+
},
|
|
38
|
+
{
|
|
39
|
+
id: 'sql-table-database-name-blue',
|
|
40
|
+
priority: 1980,
|
|
41
|
+
scopeIncludesAny: ['constant.other.table-name.sql', 'constant.other.database-name.sql'],
|
|
42
|
+
setBucket: 'type',
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
java: [
|
|
46
|
+
{
|
|
47
|
+
id: 'java-record-signature-neutral',
|
|
48
|
+
priority: 1995,
|
|
49
|
+
scopeIncludesAny: ['meta.record.identifier.java'],
|
|
50
|
+
tokenRegex: /[(),]/,
|
|
51
|
+
setBucket: 'text',
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
id: 'java-package-separator-neutral',
|
|
55
|
+
priority: 1980,
|
|
56
|
+
scopeIncludesAny: ['punctuation.separator.java'],
|
|
57
|
+
setBucket: 'text',
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
id: 'java-generic-bracket-neutral',
|
|
61
|
+
priority: 1970,
|
|
62
|
+
scopeIncludesAny: ['punctuation.bracket.angle.java'],
|
|
63
|
+
setBucket: 'text',
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
id: 'java-record-keyword',
|
|
67
|
+
priority: 1960,
|
|
68
|
+
scopeIncludesAny: ['storage.modifier.java'],
|
|
69
|
+
tokenKind: 'identifier',
|
|
70
|
+
tokenEquals: ['record'],
|
|
71
|
+
setBucket: 'keyword',
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
id: 'java-record-type-name',
|
|
75
|
+
priority: 1950,
|
|
76
|
+
scopeIncludesAny: ['entity.name.type.record.java'],
|
|
77
|
+
tokenKind: 'identifier',
|
|
78
|
+
setBucket: 'type-name',
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
id: 'java-primitive-keyword',
|
|
82
|
+
priority: 1940,
|
|
83
|
+
scopeIncludesAny: ['storage.type.primitive.java'],
|
|
84
|
+
tokenKind: 'identifier',
|
|
85
|
+
setBucket: 'keyword',
|
|
86
|
+
},
|
|
87
|
+
],
|
|
88
|
+
php: [
|
|
89
|
+
{
|
|
90
|
+
id: 'php-array-func-builtin',
|
|
91
|
+
priority: 2000,
|
|
92
|
+
scopeIncludesAny: ['support.function.array.php'],
|
|
93
|
+
setBucket: 'title-function-builtin',
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
id: 'php-core-const-blue',
|
|
97
|
+
priority: 1980,
|
|
98
|
+
scopeIncludesAny: ['support.constant.core.php', 'constant.other.php'],
|
|
99
|
+
setBucket: 'variable-const',
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
id: 'php-enum-const-blue',
|
|
103
|
+
priority: 1970,
|
|
104
|
+
scopeIncludesAny: ['constant.enum.php', 'constant.other.class.php'],
|
|
105
|
+
setBucket: 'variable-const',
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
ruby: [
|
|
109
|
+
{
|
|
110
|
+
id: 'ruby-entity-function-title',
|
|
111
|
+
priority: 2000,
|
|
112
|
+
scopeIncludesAny: ['entity.name.function.ruby'],
|
|
113
|
+
setBucket: 'title-function',
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
id: 'ruby-variable-accent',
|
|
117
|
+
priority: 1980,
|
|
118
|
+
scopeIncludesAny: ['variable.ruby'],
|
|
119
|
+
baseIn: ['variable-member'],
|
|
120
|
+
tokenKind: 'identifier',
|
|
121
|
+
setBucket: 'variable',
|
|
122
|
+
},
|
|
123
|
+
],
|
|
124
|
+
typescript: [
|
|
125
|
+
{
|
|
126
|
+
id: 'ts-support-property-blue',
|
|
127
|
+
priority: 1950,
|
|
128
|
+
scopeIncludesAny: ['support.variable.property.ts'],
|
|
129
|
+
setBucket: 'variable-const',
|
|
130
|
+
},
|
|
131
|
+
],
|
|
132
|
+
javascript: [
|
|
133
|
+
{
|
|
134
|
+
id: 'js-support-class-blue',
|
|
135
|
+
priority: 1960,
|
|
136
|
+
scopeIncludesAny: ['support.class.'],
|
|
137
|
+
setBucket: 'type',
|
|
138
|
+
},
|
|
139
|
+
],
|
|
140
|
+
go: [
|
|
141
|
+
{
|
|
142
|
+
id: 'go-storage-type-keyword',
|
|
143
|
+
priority: 1960,
|
|
144
|
+
scopeIncludesAny: ['storage.type.string.go', 'storage.type.boolean.go'],
|
|
145
|
+
setBucket: 'keyword',
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
id: 'go-format-placeholder-blue',
|
|
149
|
+
priority: 1940,
|
|
150
|
+
scopeIncludesAny: ['constant.other.placeholder.go'],
|
|
151
|
+
setBucket: 'literal',
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
csharp: [
|
|
155
|
+
{
|
|
156
|
+
id: 'csharp-keyword-type-keyword',
|
|
157
|
+
priority: 1960,
|
|
158
|
+
scopeIncludesAny: ['keyword.type.string.cs'],
|
|
159
|
+
setBucket: 'keyword',
|
|
160
|
+
},
|
|
161
|
+
],
|
|
162
|
+
c: [
|
|
163
|
+
{
|
|
164
|
+
id: 'c-array-bracket-keyword',
|
|
165
|
+
priority: 1960,
|
|
166
|
+
scopeIncludesAny: ['storage.modifier.array.bracket.square.c', 'storage-modifier-array-bracket-square-c'],
|
|
167
|
+
setBucket: 'keyword',
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
id: 'c-array-bracket-token-keyword',
|
|
171
|
+
priority: 1955,
|
|
172
|
+
baseIn: ['type'],
|
|
173
|
+
tokenEquals: ['[]'],
|
|
174
|
+
setBucket: 'keyword',
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: 'c-placeholder-blue',
|
|
178
|
+
priority: 1950,
|
|
179
|
+
scopeIncludesAny: ['constant.other.placeholder.c'],
|
|
180
|
+
setBucket: 'literal',
|
|
181
|
+
},
|
|
182
|
+
],
|
|
183
|
+
cpp: [
|
|
184
|
+
{
|
|
185
|
+
id: 'cpp-reference-modifier-keyword',
|
|
186
|
+
priority: 1960,
|
|
187
|
+
scopeIncludesAny: ['storage.modifier.reference.cpp'],
|
|
188
|
+
setBucket: 'keyword',
|
|
189
|
+
},
|
|
190
|
+
],
|
|
191
|
+
python: [
|
|
192
|
+
{
|
|
193
|
+
id: 'python-fstring-conversion-neutral',
|
|
194
|
+
priority: 1900,
|
|
195
|
+
baseIn: ['string'],
|
|
196
|
+
scopeIncludesAny: ['meta.fstring.python'],
|
|
197
|
+
tokenEquals: ['s', 'r', 'a'],
|
|
198
|
+
setBucket: 'text',
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
id: 'python-fstring-identifier-neutral',
|
|
202
|
+
priority: 1890,
|
|
203
|
+
baseIn: ['string'],
|
|
204
|
+
scopeIncludesAny: ['meta.fstring.python'],
|
|
205
|
+
tokenKind: 'identifier',
|
|
206
|
+
setBucket: 'text',
|
|
207
|
+
},
|
|
208
|
+
],
|
|
209
|
+
css: [
|
|
210
|
+
{
|
|
211
|
+
id: 'css-function-type-blue',
|
|
212
|
+
priority: 1940,
|
|
213
|
+
scopeIncludesAny: ['support.function.calc.css', 'support.function.gradient.css'],
|
|
214
|
+
setBucket: 'type',
|
|
215
|
+
},
|
|
216
|
+
],
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const keywordV4SortedGlobalRules = keywordV4GlobalRules.slice().sort((a, b) => (b.priority || 0) - (a.priority || 0))
|
|
220
|
+
const keywordV4SortedLangRules = new Map(
|
|
221
|
+
Object.keys(keywordV4LangRules).map((lang) => [
|
|
222
|
+
lang,
|
|
223
|
+
keywordV4LangRules[lang].slice().sort((a, b) => (b.priority || 0) - (a.priority || 0)),
|
|
224
|
+
]),
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
const applyShikiKeywordLegacyPostRules = (currentBucket, ctx, helper) => {
|
|
228
|
+
const postHelper = helper && typeof helper === 'object' ? helper : {}
|
|
229
|
+
const hasAnyScopePattern = typeof postHelper.hasAnyScopePattern === 'function'
|
|
230
|
+
? postHelper.hasAnyScopePattern
|
|
231
|
+
: () => false
|
|
232
|
+
const hasScopePattern = typeof postHelper.hasScopePattern === 'function'
|
|
233
|
+
? postHelper.hasScopePattern
|
|
234
|
+
: (pattern) => hasAnyScopePattern([pattern])
|
|
235
|
+
const classifyToken = typeof postHelper.classifyToken === 'function'
|
|
236
|
+
? postHelper.classifyToken
|
|
237
|
+
: () => null
|
|
238
|
+
|
|
239
|
+
const bucket = String(currentBucket || 'text')
|
|
240
|
+
const langKey = String(ctx && ctx.langKey || '')
|
|
241
|
+
const lowerScopeCandidates = Array.isArray(ctx && ctx.lowerScopeCandidates) ? ctx.lowerScopeCandidates : []
|
|
242
|
+
const tokenContent = String(ctx && ctx.tokenContent || '')
|
|
243
|
+
const trimmed = String(ctx && ctx.tokenTrim || '')
|
|
244
|
+
const tokenLower = (ctx && typeof ctx.tokenLower === 'string') ? ctx.tokenLower : trimmed.toLowerCase()
|
|
245
|
+
if (!trimmed) return 'text'
|
|
246
|
+
|
|
247
|
+
if ((langKey === 'bash' || langKey === 'shellscript') && shikiShellOptionTokenReg.test(trimmed)) {
|
|
248
|
+
return shikiShellTestOperatorSet.has(tokenLower) ? 'keyword' : 'literal'
|
|
249
|
+
}
|
|
250
|
+
if ((langKey === 'hcl' || langKey === 'terraform') && trimmed === '=') return 'keyword'
|
|
251
|
+
if (bucket === 'comment' && trimmed.startsWith('#!')) return 'meta-shebang'
|
|
252
|
+
if (bucket === 'string') {
|
|
253
|
+
if (
|
|
254
|
+
(langKey === 'bash' || langKey === 'shellscript') &&
|
|
255
|
+
hasAnyScopePattern(['entity.name.function.shell', 'entity.name.command.shell'])
|
|
256
|
+
) return 'title-function'
|
|
257
|
+
if (
|
|
258
|
+
(langKey === 'bash' || langKey === 'shellscript') &&
|
|
259
|
+
(
|
|
260
|
+
hasAnyScopePattern(['variable.parameter.positional.shell', 'variable.language.special.shell']) ||
|
|
261
|
+
shikiShellOptionTokenReg.test(trimmed) ||
|
|
262
|
+
trimmed.startsWith('${') ||
|
|
263
|
+
trimmed === '}'
|
|
264
|
+
)
|
|
265
|
+
) return 'literal'
|
|
266
|
+
if (
|
|
267
|
+
(langKey === 'bash' || langKey === 'shellscript') &&
|
|
268
|
+
hasAnyScopePattern(['constant.other.option'])
|
|
269
|
+
) return shikiShellTestOperatorSet.has(tokenLower) ? 'keyword' : 'literal'
|
|
270
|
+
if (langKey === 'python' && hasAnyScopePattern(['storage.type.string.python'])) return 'keyword'
|
|
271
|
+
if (langKey === 'go' && hasAnyScopePattern(['entity.name.import.go'])) return 'type-name'
|
|
272
|
+
if (hasAnyScopePattern(['keyword.operator.string'])) return 'keyword'
|
|
273
|
+
if (hasAnyScopePattern(['constant.character.escape', 'constant.character.format.placeholder'])) return 'number'
|
|
274
|
+
if (hasAnyScopePattern(['variable.other.', 'variable.ruby', 'meta.attribute.python'])) return 'variable-plain'
|
|
275
|
+
}
|
|
276
|
+
if (bucket === 'string-unquoted') {
|
|
277
|
+
if (
|
|
278
|
+
(langKey === 'bash' || langKey === 'shellscript') &&
|
|
279
|
+
hasAnyScopePattern(['entity.name.function.shell', 'entity.name.command.shell'])
|
|
280
|
+
) return 'title-function'
|
|
281
|
+
if (
|
|
282
|
+
(langKey === 'bash' || langKey === 'shellscript') &&
|
|
283
|
+
(
|
|
284
|
+
hasAnyScopePattern(['variable.parameter.positional.shell', 'variable.language.special.shell']) ||
|
|
285
|
+
shikiShellOptionTokenReg.test(trimmed) ||
|
|
286
|
+
trimmed.startsWith('${') ||
|
|
287
|
+
trimmed === '}'
|
|
288
|
+
)
|
|
289
|
+
) return 'literal'
|
|
290
|
+
if (
|
|
291
|
+
(langKey === 'bash' || langKey === 'shellscript') &&
|
|
292
|
+
hasAnyScopePattern(['constant.other.option'])
|
|
293
|
+
) return shikiShellTestOperatorSet.has(tokenLower) ? 'keyword' : 'literal'
|
|
294
|
+
if (hasAnyScopePattern(['entity.name.tag.yaml'])) return 'tag'
|
|
295
|
+
return 'string'
|
|
296
|
+
}
|
|
297
|
+
if (bucket === 'title-function') {
|
|
298
|
+
if (langKey === 'css' && hasAnyScopePattern(['support.function.misc.css'])) return 'type'
|
|
299
|
+
if (hasAnyScopePattern(['support.function.builtin', 'support.function.kernel', 'support.function.construct'])) return 'title-function-builtin'
|
|
300
|
+
if (hasAnyScopePattern(['storage.type.function.arrow', 'keyword.operator'])) return 'keyword'
|
|
301
|
+
if (hasAnyScopePattern(['punctuation.section.function', 'punctuation.definition.arguments', 'meta.function-call.arguments'])) return 'punctuation'
|
|
302
|
+
if (hasAnyScopePattern(['meta.function']) && !hasAnyScopePattern(['entity.name.function', 'support.function'])) return 'meta'
|
|
303
|
+
if (hasAnyScopePattern(['punctuation.accessor'])) return 'text'
|
|
304
|
+
if (!shikiFunctionLikeIdentifierReg.test(trimmed)) {
|
|
305
|
+
if (shikiPunctuationTokenReg.test(trimmed)) return 'punctuation'
|
|
306
|
+
if (/[.\[\](){}:]/.test(trimmed)) return 'text'
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
if (bucket === 'title-function-builtin' && hasAnyScopePattern(['entity.name.function.macro'])) return 'title-function'
|
|
310
|
+
if (bucket === 'type' && langKey === 'csharp' && hasScopePattern('keyword.type.')) return 'keyword'
|
|
311
|
+
if (bucket === 'type' && langKey === 'rust' && hasAnyScopePattern(['entity.name.type.numeric.rust'])) return 'type-name'
|
|
312
|
+
if (bucket === 'type-name' && langKey === 'php' && hasScopePattern('support.class.php')) return 'type'
|
|
313
|
+
if (bucket === 'type-name' && langKey === 'ruby' && hasScopePattern('support.class.ruby')) return 'type'
|
|
314
|
+
if (bucket === 'meta') {
|
|
315
|
+
if (trimmed.startsWith('#!') || hasAnyScopePattern(['meta.shebang'])) return 'meta-shebang'
|
|
316
|
+
if (hasAnyScopePattern(['entity.name.scope-resolution', 'entity.name.namespace'])) return 'namespace'
|
|
317
|
+
if (hasAnyScopePattern(['source.sql', 'source.python', 'source.ruby', 'source.rust'])) return 'text'
|
|
318
|
+
if (hasAnyScopePattern(['punctuation.terminator.statement', 'punctuation.section.block', 'punctuation.brackets.curly', 'meta.body.function.definition'])) return 'punctuation'
|
|
319
|
+
}
|
|
320
|
+
if (bucket === 'variable') {
|
|
321
|
+
if (
|
|
322
|
+
hasAnyScopePattern(['meta.type.annotation']) &&
|
|
323
|
+
shikiPunctuationTokenReg.test(trimmed)
|
|
324
|
+
) return 'punctuation'
|
|
325
|
+
if (hasAnyScopePattern(['variable.language.this'])) return 'variable-this'
|
|
326
|
+
if (hasAnyScopePattern(['variable.other.constant', 'constant.other.php', 'variable.other.enummember'])) return 'variable-const'
|
|
327
|
+
if (hasAnyScopePattern(['variable.object.property', 'variable.other.property', 'variable.other.member', 'variable.object.c', 'variable.ruby'])) return 'variable-member'
|
|
328
|
+
if (!hasAnyScopePattern(['variable.', 'entity.name.variable']) && hasAnyScopePattern(['source.'])) return 'text'
|
|
329
|
+
if (hasAnyScopePattern(['variable.other.readwrite', 'variable.other.normal', 'variable.other.php', 'entity.name.variable.local'])) return 'variable-plain'
|
|
330
|
+
}
|
|
331
|
+
if (bucket === 'variable-plain') {
|
|
332
|
+
if (hasAnyScopePattern(['variable.other.constant', 'constant.other.php', 'variable.other.enummember'])) return 'variable-const'
|
|
333
|
+
if (hasAnyScopePattern(['entity.name.variable.local.cs'])) return 'type-name'
|
|
334
|
+
if (hasAnyScopePattern(['source.sql', 'source.python', 'source.ruby'])) return 'text'
|
|
335
|
+
if (!shikiSimpleIdentifierReg.test(trimmed) && hasAnyScopePattern(['punctuation.'])) return 'punctuation'
|
|
336
|
+
}
|
|
337
|
+
if (bucket === 'variable-member') {
|
|
338
|
+
if (hasAnyScopePattern(['variable.object.property.ts', 'variable-object-property-ts', 'variable.object.property.js', 'variable-object-property-js'])) return 'variable-property'
|
|
339
|
+
if (hasAnyScopePattern(['variable.object.c'])) return 'variable-parameter'
|
|
340
|
+
}
|
|
341
|
+
if (bucket === 'variable-parameter') {
|
|
342
|
+
if (langKey === 'csharp' && hasAnyScopePattern(['entity.name.variable.parameter.cs'])) return 'type-name'
|
|
343
|
+
if (hasAnyScopePattern(['variable.other.constant', 'constant.other.php', 'variable.other.enummember'])) return 'variable-const'
|
|
344
|
+
if (hasAnyScopePattern(['variable.object.property', 'variable.other.property', 'variable.other.member', 'variable.object.c', 'variable.ruby'])) return 'variable-member'
|
|
345
|
+
if (hasAnyScopePattern(['variable.other.readwrite.hcl', 'variable.other.normal.shell', 'meta.block.hcl'])) return 'variable-plain'
|
|
346
|
+
if (hasAnyScopePattern(['storage.type.built-in.primitive', 'keyword.operator.type.annotation', 'keyword.operator.type'])) return 'keyword'
|
|
347
|
+
if (hasAnyScopePattern(['entity.name.type', 'support.type'])) return 'type-name'
|
|
348
|
+
if (!shikiSimpleIdentifierReg.test(trimmed)) return 'punctuation'
|
|
349
|
+
}
|
|
350
|
+
if (bucket === 'variable-property') {
|
|
351
|
+
if (hasAnyScopePattern(['variable.object.property', 'variable.other.property'])) return 'variable-parameter'
|
|
352
|
+
}
|
|
353
|
+
if (bucket === 'keyword' && langKey === 'php' && hasAnyScopePattern(['support.function.construct.output.php'])) return 'title-function-builtin'
|
|
354
|
+
if (bucket === 'keyword' && hasAnyScopePattern(['variable.language.this'])) return 'variable-this'
|
|
355
|
+
if (bucket === 'literal' && langKey === 'sql') return 'text'
|
|
356
|
+
if (bucket === 'text') {
|
|
357
|
+
const tokenBucket = classifyToken(tokenContent, langKey)
|
|
358
|
+
if (tokenBucket) return tokenBucket
|
|
359
|
+
if (shikiSimpleIdentifierReg.test(trimmed)) return 'variable-plain'
|
|
360
|
+
}
|
|
361
|
+
if (!shikiSimpleIdentifierReg.test(trimmed) && (bucket === 'text' || bucket === 'meta')) {
|
|
362
|
+
const tokenBucket = classifyToken(tokenContent, langKey)
|
|
363
|
+
if (tokenBucket) return tokenBucket
|
|
364
|
+
}
|
|
365
|
+
return bucket
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const matchKeywordV4Rule = (rule, currentBucket, ctx) => {
|
|
369
|
+
const hasAnyScopePattern = (ctx && typeof ctx.hasAnyScopePattern === 'function')
|
|
370
|
+
? ctx.hasAnyScopePattern
|
|
371
|
+
: (() => false)
|
|
372
|
+
if (!rule) return false
|
|
373
|
+
if (Array.isArray(rule.baseIn) && rule.baseIn.length > 0 && !rule.baseIn.includes(currentBucket)) return false
|
|
374
|
+
if (Array.isArray(rule.lang) && rule.lang.length > 0 && !rule.lang.includes(ctx.langKey)) return false
|
|
375
|
+
if (Array.isArray(rule.scopeIncludesAny) && rule.scopeIncludesAny.length > 0 && !hasAnyScopePattern(rule.scopeIncludesAny)) return false
|
|
376
|
+
if (Array.isArray(rule.scopeExcludesAny) && rule.scopeExcludesAny.length > 0 && hasAnyScopePattern(rule.scopeExcludesAny)) return false
|
|
377
|
+
if (Array.isArray(rule.tokenEquals) && rule.tokenEquals.length > 0 && !rule.tokenEquals.includes(ctx.tokenLower)) return false
|
|
378
|
+
if (rule.tokenRegex instanceof RegExp && !rule.tokenRegex.test(ctx.tokenTrim)) return false
|
|
379
|
+
if (rule.tokenKind === 'identifier' && !ctx.isIdentifier) return false
|
|
380
|
+
if (rule.tokenKind === 'punctuation' && !ctx.isPunctuation) return false
|
|
381
|
+
return true
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const applyRuleSet = (currentBucket, rules, ctx) => {
|
|
385
|
+
if (!Array.isArray(rules) || rules.length === 0) return currentBucket
|
|
386
|
+
let next = currentBucket
|
|
387
|
+
for (let i = 0; i < rules.length; i++) {
|
|
388
|
+
const rule = rules[i]
|
|
389
|
+
if (!matchKeywordV4Rule(rule, next, ctx)) continue
|
|
390
|
+
next = rule.setBucket || next
|
|
391
|
+
if (rule.stop !== false) break
|
|
392
|
+
}
|
|
393
|
+
return next
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const applyKeywordV4Rules = (currentBucket, ctx) => {
|
|
397
|
+
let next = applyRuleSet(currentBucket, keywordV4SortedGlobalRules, ctx)
|
|
398
|
+
const langRules = keywordV4SortedLangRules.get(ctx.langKey)
|
|
399
|
+
next = applyRuleSet(next, langRules, ctx)
|
|
400
|
+
return next
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
export {
|
|
404
|
+
applyKeywordV4Rules,
|
|
405
|
+
applyShikiKeywordLegacyPostRules,
|
|
406
|
+
}
|
|
407
|
+
|