@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.
- package/README.md +179 -470
- package/docs/README.md +37 -0
- package/docs/code-highlighting-design.md +321 -0
- package/docs/custom-highlight-styling-guide.md +210 -0
- package/package.json +25 -13
- package/src/custom-highlight/option-validator.js +5 -5
- package/src/custom-highlight/payload-utils.js +4 -9
- package/src/custom-highlight/{shiki-keyword-rules.js → shiki-role-rules.js} +249 -54
- package/src/custom-highlight/{shiki-keyword.js → shiki-role.js} +107 -99
- package/src/custom-highlight/shiki-theme-role.js +176 -0
- package/src/fence/line-notes.js +27 -9
- package/src/fence/render-api-constants.js +1 -1
- package/src/fence/render-api-provider.js +61 -53
- package/src/fence/render-api-renderer.js +26 -10
- package/src/fence/render-api-runtime.js +29 -10
- package/src/fence/render-api.js +20 -12
- package/src/fence/render-markup.js +34 -24
- package/src/fence/render-shared.js +150 -30
- package/src/utils/attr-utils.js +10 -0
- package/theme/_shared/rf-core.css +170 -0
- package/theme/line-notes.css +94 -0
- package/theme/line-number.css +64 -0
- package/theme/rf-basic/README.md +117 -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 +5 -0
- package/theme/rf-basic/index.css +7 -0
- package/theme/rf-basic/index.js +164 -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 +84 -0
- package/theme/rf-monochrome/base.css +5 -0
- package/theme/rf-monochrome/index.css +5 -0
- package/theme/rf-monochrome/index.js +71 -0
- package/theme/rf-monochrome/markup/highlightjs.css +150 -0
- package/theme/rf-monochrome/markup/shiki.css +38 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
} from './shiki-
|
|
2
|
+
applyRoleV4Rules,
|
|
3
|
+
applyShikiRoleLegacyPostRules,
|
|
4
|
+
} from './shiki-role-rules.js'
|
|
5
|
+
import { resolveRendererFenceShikiThemeRoleBucket } from './shiki-theme-role.js'
|
|
5
6
|
|
|
6
|
-
const
|
|
7
|
+
const shikiRoleBucketScore = {
|
|
7
8
|
comment: 210,
|
|
8
9
|
'meta-shebang': 206,
|
|
9
10
|
'tag-delimiter': 202,
|
|
@@ -37,7 +38,7 @@ const shikiGlobalLiteralTokenSet = new Set(['true', 'false', 'null', 'undefined'
|
|
|
37
38
|
const shikiShellKeywordTokenSet = new Set(['case', 'coproc', 'do', 'done', 'elif', 'else', 'esac', 'export', 'fi', 'for', 'function', 'if', 'in', 'local', 'readonly', 'select', 'then', 'time', 'typeset', 'until', 'while'])
|
|
38
39
|
const shikiHclKeywordTokenSet = new Set(['terraform', 'resource', 'data', 'module', 'provider', 'variable', 'output', 'locals', 'backend', 'dynamic', 'for_each', 'count', 'provisioner', 'connection', 'if', 'for', 'in'])
|
|
39
40
|
|
|
40
|
-
const
|
|
41
|
+
const shikiLangKeywordTokens = {
|
|
41
42
|
javascript: new Set(['await', 'async', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do', 'else', 'export', 'extends', 'finally', 'for', 'from', 'function', 'if', 'import', 'in', 'instanceof', 'let', 'new', 'of', 'return', 'static', 'super', 'switch', 'this', 'throw', 'try', 'typeof', 'var', 'void', 'while', 'with', 'yield']),
|
|
42
43
|
typescript: new Set(['abstract', 'as', 'asserts', 'async', 'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'declare', 'default', 'delete', 'do', 'else', 'enum', 'export', 'extends', 'finally', 'for', 'from', 'function', 'if', 'implements', 'import', 'in', 'infer', 'instanceof', 'interface', 'is', 'keyof', 'let', 'namespace', 'new', 'of', 'override', 'private', 'protected', 'public', 'readonly', 'return', 'satisfies', 'static', 'super', 'switch', 'this', 'throw', 'try', 'type', 'typeof', 'var', 'void', 'while', 'with', 'yield']),
|
|
43
44
|
python: new Set(['and', 'as', 'assert', 'async', 'await', 'break', 'class', 'continue', 'def', 'del', 'elif', 'else', 'except', 'finally', 'for', 'from', 'global', 'if', 'import', 'in', 'is', 'lambda', 'nonlocal', 'not', 'or', 'pass', 'raise', 'return', 'try', 'while', 'with', 'yield']),
|
|
@@ -61,26 +62,26 @@ const shikiNumberTokenReg = /^(?:0[xob][0-9a-f_]+|\d[\d_]*(?:\.\d[\d_]*)?(?:e[+-
|
|
|
61
62
|
const shikiPunctuationTokenReg = /^[()[\]{}<>.,;:!?~`'"@#$%^&*+=|/\\-]+$/
|
|
62
63
|
const shikiSimpleIdentifierReg = /^[A-Za-z_$][\w$]*$/
|
|
63
64
|
const shikiScopeLangReg = /(?:^|\.)(?:source|text)\.([A-Za-z0-9_#+-]+)/g
|
|
64
|
-
const
|
|
65
|
+
const shikiRoleLangSanitizeReg = /[^a-z0-9#+-]+/g
|
|
65
66
|
const hyphenMultiReg = /-+/g
|
|
66
|
-
const
|
|
67
|
+
const shikiScopeRoleCache = new Map()
|
|
67
68
|
|
|
68
|
-
const
|
|
69
|
+
const normalizeShikiRoleLangKey = (lang) => {
|
|
69
70
|
let key = String(lang || '').trim().toLowerCase()
|
|
70
71
|
if (!key) return ''
|
|
71
72
|
if (key.startsWith('language-')) key = key.slice('language-'.length)
|
|
72
73
|
key = key.replace(/\s+/g, '-').replace(/[./]+/g, '-').replace(/_+/g, '-')
|
|
73
|
-
key = key.replace(
|
|
74
|
+
key = key.replace(shikiRoleLangSanitizeReg, '')
|
|
74
75
|
key = key.replace(hyphenMultiReg, '-').replace(/^-+|-+$/g, '')
|
|
75
76
|
return key
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
const
|
|
79
|
+
const normalizeShikiRoleLangAliasMap = (input) => {
|
|
79
80
|
if (!input || typeof input !== 'object') return null
|
|
80
81
|
const map = {}
|
|
81
82
|
for (const key of Object.keys(input)) {
|
|
82
|
-
const from =
|
|
83
|
-
const to =
|
|
83
|
+
const from = normalizeShikiRoleLangKey(key)
|
|
84
|
+
const to = normalizeShikiRoleLangKey(input[key])
|
|
84
85
|
if (!from || !to) continue
|
|
85
86
|
map[from] = to
|
|
86
87
|
}
|
|
@@ -106,7 +107,7 @@ const getShikiRawScopeName = (tok) => {
|
|
|
106
107
|
return null
|
|
107
108
|
}
|
|
108
109
|
|
|
109
|
-
const
|
|
110
|
+
const extractShikiRoleLangCandidatesFromScope = (scope) => {
|
|
110
111
|
const out = []
|
|
111
112
|
const text = String(scope || '')
|
|
112
113
|
if (!text) return out
|
|
@@ -177,58 +178,57 @@ const createScopePatternMatcher = (lowerScopeCandidates) => {
|
|
|
177
178
|
}
|
|
178
179
|
}
|
|
179
180
|
|
|
180
|
-
const
|
|
181
|
+
const classifyShikiScopeRoleSingle = (scope) => {
|
|
181
182
|
const s = String(scope || '').toLowerCase()
|
|
182
183
|
if (!s) return 'text'
|
|
183
|
-
const cached =
|
|
184
|
+
const cached = shikiScopeRoleCache.get(s)
|
|
184
185
|
if (cached) return cached
|
|
185
186
|
let bucket = 'text'
|
|
186
|
-
if (s.includes('comment'))
|
|
187
|
-
if (s.includes('meta.shebang'))
|
|
188
|
-
if (s.includes('punctuation.definition.tag.begin') || s.includes('punctuation.definition.tag.end') || s.includes('punctuation.separator.key-value.html'))
|
|
189
|
-
if (s.includes('entity.other.attribute-name') || s.includes('attribute-name'))
|
|
190
|
-
if (s.includes('entity.name.tag'))
|
|
191
|
-
if (s.includes('string.unquoted'))
|
|
192
|
-
if (s.includes('regexp') || s.includes('regex') || s.includes('string') || s.includes('punctuation.definition.string'))
|
|
193
|
-
if (s.includes('constant.other.color')
|
|
194
|
-
if (s.includes('constant.numeric') || s.includes('number'))
|
|
195
|
-
if (s.includes('constant.language') || s.includes('boolean') || s.includes('null') || s.includes('undefined') || s.includes('none'))
|
|
196
|
-
if (s.includes('entity.name.scope-resolution') || s.includes('entity.name.namespace'))
|
|
197
|
-
if (s.includes('storage.type.function.arrow') || s.includes('keyword.operator'))
|
|
198
|
-
if (s.includes('variable.language.this'))
|
|
199
|
-
if (s.includes('constant.other.php') || s.includes('variable.other.constant') || s.includes('variable.other.enummember'))
|
|
200
|
-
if (s.includes('variable.object.property') || s.includes('variable.other.property') || s.includes('variable.other.member') || s.includes('variable.object.c') || s.includes('variable.ruby'))
|
|
201
|
-
if (s.includes('variable.parameter') || s.includes('entity.name.variable.parameter'))
|
|
202
|
-
if (s.includes('entity.name.variable.local') || s.includes('variable.other.readwrite') || s.includes('variable.other.normal') || s.includes('variable.other.php') || (s.includes('variable.other.') && !s.includes('variable.other.property') && !s.includes('variable.other.member')))
|
|
203
|
-
if (s.includes('support.function.builtin') || s.includes('support.function.kernel') || s.includes('support.function.construct'))
|
|
204
|
-
if (s.includes('entity.name.function.macro'))
|
|
205
|
-
if (s.includes('meta.function') && !s.includes('entity.name.function') && !s.includes('support.function'))
|
|
206
|
-
if (s.includes('entity.name.function') || s.includes('support.function'))
|
|
207
|
-
if (s.includes('entity.name.class') || s.includes('entity.name.type.class'))
|
|
208
|
-
if (s.includes('storage.type.built-in.primitive'))
|
|
209
|
-
if (s.includes('entity.name.type.namespace') || s.includes('entity.name.type') || s.includes('support.class'))
|
|
210
|
-
if (s.includes('support.type')
|
|
211
|
-
if (s.includes('storage') || s.includes('keyword') || s.includes('operator') || s.includes('control') || s.includes('modifier'))
|
|
212
|
-
if (s.includes('variable'))
|
|
213
|
-
if (s.includes('punctuation'))
|
|
214
|
-
if (s.includes('source.')) bucket = 'text'
|
|
187
|
+
if (s.includes('comment')) bucket = 'comment'
|
|
188
|
+
else if (s.includes('meta.shebang')) bucket = 'meta-shebang'
|
|
189
|
+
else if (s.includes('punctuation.definition.tag.begin') || s.includes('punctuation.definition.tag.end') || s.includes('punctuation.separator.key-value.html')) bucket = 'tag-delimiter'
|
|
190
|
+
else if (s.includes('entity.other.attribute-name') || s.includes('attribute-name')) bucket = 'attribute'
|
|
191
|
+
else if (s.includes('entity.name.tag')) bucket = 'tag'
|
|
192
|
+
else if (s.includes('string.unquoted')) bucket = 'string-unquoted'
|
|
193
|
+
else if (s.includes('regexp') || s.includes('regex') || s.includes('string') || s.includes('punctuation.definition.string')) bucket = 'string'
|
|
194
|
+
else if (s.includes('constant.other.color')) bucket = 'number'
|
|
195
|
+
else if (s.includes('constant.numeric') || s.includes('number')) bucket = 'number'
|
|
196
|
+
else if (s.includes('constant.language') || s.includes('boolean') || s.includes('null') || s.includes('undefined') || s.includes('none')) bucket = 'literal'
|
|
197
|
+
else if (s.includes('entity.name.scope-resolution') || s.includes('entity.name.namespace')) bucket = 'namespace'
|
|
198
|
+
else if (s.includes('storage.type.function.arrow') || s.includes('keyword.operator')) bucket = 'keyword'
|
|
199
|
+
else if (s.includes('variable.language.this')) bucket = 'variable-this'
|
|
200
|
+
else if (s.includes('constant.other.php') || s.includes('variable.other.constant') || s.includes('variable.other.enummember')) bucket = 'variable-const'
|
|
201
|
+
else if (s.includes('variable.object.property') || s.includes('variable.other.property') || s.includes('variable.other.member') || s.includes('variable.object.c') || s.includes('variable.ruby')) bucket = 'variable-member'
|
|
202
|
+
else if (s.includes('variable.parameter') || s.includes('entity.name.variable.parameter')) bucket = 'variable-parameter'
|
|
203
|
+
else if (s.includes('entity.name.variable.local') || s.includes('variable.other.readwrite') || s.includes('variable.other.normal') || s.includes('variable.other.php') || (s.includes('variable.other.') && !s.includes('variable.other.property') && !s.includes('variable.other.member'))) bucket = 'variable-plain'
|
|
204
|
+
else if (s.includes('support.function.builtin') || s.includes('support.function.kernel') || s.includes('support.function.construct')) bucket = 'title-function-builtin'
|
|
205
|
+
else if (s.includes('entity.name.function.macro')) bucket = 'title-function'
|
|
206
|
+
else if (s.includes('meta.function') && !s.includes('entity.name.function') && !s.includes('support.function')) bucket = 'meta'
|
|
207
|
+
else if (s.includes('entity.name.function') || s.includes('support.function')) bucket = 'title-function'
|
|
208
|
+
else if (s.includes('entity.name.class') || s.includes('entity.name.type.class')) bucket = 'title-class'
|
|
209
|
+
else if (s.includes('storage.type.built-in.primitive')) bucket = 'type-primitive'
|
|
210
|
+
else if (s.includes('entity.name.type.namespace') || s.includes('entity.name.type') || s.includes('support.class')) bucket = 'type-name'
|
|
211
|
+
else if (s.includes('support.type')) bucket = 'type'
|
|
212
|
+
else if (s.includes('storage') || s.includes('keyword') || s.includes('operator') || s.includes('control') || s.includes('modifier')) bucket = 'keyword'
|
|
213
|
+
else if (s.includes('variable')) bucket = 'variable'
|
|
214
|
+
else if (s.includes('punctuation')) bucket = 'punctuation'
|
|
215
215
|
else if (s.includes('meta')) bucket = 'meta'
|
|
216
|
-
|
|
217
|
-
|
|
216
|
+
if (shikiScopeRoleCache.size >= 8192) shikiScopeRoleCache.clear()
|
|
217
|
+
shikiScopeRoleCache.set(s, bucket)
|
|
218
218
|
return bucket
|
|
219
219
|
}
|
|
220
220
|
|
|
221
|
-
const hasJsonYamlPropertyNameScope = (
|
|
222
|
-
if (!Array.isArray(
|
|
223
|
-
for (let i = 0; i <
|
|
224
|
-
const s =
|
|
221
|
+
const hasJsonYamlPropertyNameScope = (lowerScopeCandidates) => {
|
|
222
|
+
if (!Array.isArray(lowerScopeCandidates)) return false
|
|
223
|
+
for (let i = 0; i < lowerScopeCandidates.length; i++) {
|
|
224
|
+
const s = lowerScopeCandidates[i]
|
|
225
225
|
if (!s.includes('property-name') && !s.includes('dictionary-key')) continue
|
|
226
226
|
if (s.includes('.json') || s.includes('-json') || s.includes('.yaml') || s.includes('-yaml')) return true
|
|
227
227
|
}
|
|
228
228
|
return false
|
|
229
229
|
}
|
|
230
230
|
|
|
231
|
-
const
|
|
231
|
+
const classifyShikiHclRoleBucket = (lowerScopeCandidates, hasAnyPattern) => {
|
|
232
232
|
const hasAny = (typeof hasAnyPattern === 'function') ? hasAnyPattern : (patterns) => hasAnyScopePattern(lowerScopeCandidates, patterns)
|
|
233
233
|
if (!Array.isArray(lowerScopeCandidates) || lowerScopeCandidates.length === 0) return null
|
|
234
234
|
if (hasAny(['entity.name.type.hcl', 'entity-name-type-hcl'])) return 'title-class'
|
|
@@ -243,10 +243,10 @@ const classifyShikiHclKeywordBucketV3 = (lowerScopeCandidates, hasAnyPattern) =>
|
|
|
243
243
|
return null
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
const
|
|
246
|
+
const resolveShikiRoleLang = (lang, scopeCandidates, tok, opt) => {
|
|
247
247
|
const queue = []
|
|
248
248
|
const seen = new Set()
|
|
249
|
-
const customAliasMap = opt && opt.
|
|
249
|
+
const customAliasMap = opt && opt.shikiRoleLangAliases
|
|
250
250
|
const shikiInternalAliasMap = opt && opt._shikiInternalLangAliasMap
|
|
251
251
|
const resolveAlias = (key) => {
|
|
252
252
|
if (customAliasMap && customAliasMap[key]) return customAliasMap[key]
|
|
@@ -254,46 +254,46 @@ const resolveShikiKeywordLang = (lang, scopeCandidates, tok, opt) => {
|
|
|
254
254
|
return ''
|
|
255
255
|
}
|
|
256
256
|
const pushCandidate = (value) => {
|
|
257
|
-
const key =
|
|
257
|
+
const key = normalizeShikiRoleLangKey(value)
|
|
258
258
|
if (!key || seen.has(key)) return
|
|
259
259
|
seen.add(key)
|
|
260
260
|
queue.push(key)
|
|
261
261
|
}
|
|
262
|
-
if (opt && typeof opt.
|
|
262
|
+
if (opt && typeof opt.shikiRoleLangResolver === 'function') {
|
|
263
263
|
try {
|
|
264
|
-
const resolved = opt.
|
|
264
|
+
const resolved = opt.shikiRoleLangResolver(lang, scopeCandidates, tok)
|
|
265
265
|
if (resolved != null && resolved !== '') pushCandidate(resolved)
|
|
266
266
|
} catch (e) {}
|
|
267
267
|
}
|
|
268
268
|
pushCandidate(lang)
|
|
269
269
|
if (Array.isArray(scopeCandidates)) {
|
|
270
270
|
for (let i = 0; i < scopeCandidates.length; i++) {
|
|
271
|
-
const candidates =
|
|
271
|
+
const candidates = extractShikiRoleLangCandidatesFromScope(scopeCandidates[i])
|
|
272
272
|
for (let j = 0; j < candidates.length; j++) pushCandidate(candidates[j])
|
|
273
273
|
}
|
|
274
274
|
}
|
|
275
275
|
for (let i = 0; i < queue.length; i++) {
|
|
276
276
|
const key = queue[i]
|
|
277
|
-
if (
|
|
277
|
+
if (shikiLangKeywordTokens[key]) return key
|
|
278
278
|
const aliased = resolveAlias(key)
|
|
279
279
|
if (aliased) {
|
|
280
|
-
if (
|
|
280
|
+
if (shikiLangKeywordTokens[aliased]) return aliased
|
|
281
281
|
pushCandidate(aliased)
|
|
282
282
|
}
|
|
283
283
|
const compact = key.replace(/-/g, '')
|
|
284
|
-
if (
|
|
284
|
+
if (shikiLangKeywordTokens[compact]) return compact
|
|
285
285
|
const compactAliased = resolveAlias(compact)
|
|
286
286
|
if (compactAliased) {
|
|
287
|
-
if (
|
|
287
|
+
if (shikiLangKeywordTokens[compactAliased]) return compactAliased
|
|
288
288
|
pushCandidate(compactAliased)
|
|
289
289
|
}
|
|
290
290
|
const parts = key.split('-')
|
|
291
291
|
for (let n = parts.length - 1; n >= 1; n--) {
|
|
292
292
|
const head = parts.slice(0, n).join('-')
|
|
293
|
-
if (
|
|
293
|
+
if (shikiLangKeywordTokens[head]) return head
|
|
294
294
|
const headAliased = resolveAlias(head)
|
|
295
295
|
if (headAliased) {
|
|
296
|
-
if (
|
|
296
|
+
if (shikiLangKeywordTokens[headAliased]) return headAliased
|
|
297
297
|
pushCandidate(headAliased)
|
|
298
298
|
}
|
|
299
299
|
}
|
|
@@ -301,30 +301,37 @@ const resolveShikiKeywordLang = (lang, scopeCandidates, tok, opt) => {
|
|
|
301
301
|
return ''
|
|
302
302
|
}
|
|
303
303
|
|
|
304
|
-
const
|
|
304
|
+
const classifyShikiRoleByToken = (content, langKey, trimmedValue, lowerValue, punctuationValue) => {
|
|
305
305
|
const text = String(content || '')
|
|
306
|
-
const trimmed = text.trim()
|
|
306
|
+
const trimmed = typeof trimmedValue === 'string' ? trimmedValue : text.trim()
|
|
307
307
|
if (!trimmed) return 'text'
|
|
308
|
-
const
|
|
308
|
+
const rawLower = typeof lowerValue === 'string' ? lowerValue : trimmed.toLowerCase()
|
|
309
|
+
const lower = rawLower.replace(/\s+/g, ' ')
|
|
309
310
|
if (lower.startsWith('#!')) return 'meta'
|
|
310
311
|
if (langKey !== 'sql' && shikiGlobalLiteralTokenSet.has(lower)) return 'literal'
|
|
311
312
|
if (shikiNumberTokenReg.test(lower)) return 'number'
|
|
312
|
-
const set =
|
|
313
|
+
const set = shikiLangKeywordTokens[langKey]
|
|
313
314
|
if (set && set.has(lower)) return 'keyword'
|
|
314
315
|
if (langKey === 'css' && lower.startsWith('@')) return 'keyword'
|
|
315
|
-
|
|
316
|
+
const isPunctuation = typeof punctuationValue === 'boolean'
|
|
317
|
+
? punctuationValue
|
|
318
|
+
: shikiPunctuationTokenReg.test(trimmed)
|
|
319
|
+
if (isPunctuation) return 'punctuation'
|
|
316
320
|
return null
|
|
317
321
|
}
|
|
318
322
|
|
|
319
|
-
const
|
|
323
|
+
const buildShikiRoleContext = (rawScope, tok, lang, opt, preResolvedLangKey = '', tokenContentArg, tokenTrimArg, tokenLowerArg) => {
|
|
320
324
|
const token = (tok && typeof tok === 'object') ? tok : {}
|
|
321
325
|
const tokenContent = (typeof tokenContentArg === 'string') ? tokenContentArg : String(token.content || '')
|
|
322
326
|
const tokenTrim = (typeof tokenTrimArg === 'string') ? tokenTrimArg : tokenContent.trim()
|
|
323
327
|
const tokenLower = (typeof tokenLowerArg === 'string') ? tokenLowerArg : tokenTrim.toLowerCase()
|
|
324
328
|
const scopeCandidates = getShikiScopeCandidates(token, rawScope)
|
|
325
|
-
const langKey = preResolvedLangKey ||
|
|
329
|
+
const langKey = preResolvedLangKey || resolveShikiRoleLang(lang, scopeCandidates, token, opt)
|
|
326
330
|
const lowerScopeCandidates = toLowerScopeCandidates(scopeCandidates)
|
|
327
331
|
const hasAnyScopePatternCached = createScopePatternMatcher(lowerScopeCandidates)
|
|
332
|
+
const isIdentifier = shikiSimpleIdentifierReg.test(tokenTrim)
|
|
333
|
+
const isPunctuation = shikiPunctuationTokenReg.test(tokenTrim)
|
|
334
|
+
const tokenBucket = classifyShikiRoleByToken(tokenContent, langKey, tokenTrim, tokenLower, isPunctuation)
|
|
328
335
|
return {
|
|
329
336
|
rawScope,
|
|
330
337
|
token,
|
|
@@ -335,18 +342,18 @@ const buildShikiKeywordContext = (rawScope, tok, lang, opt, preResolvedLangKey =
|
|
|
335
342
|
tokenContent,
|
|
336
343
|
tokenTrim,
|
|
337
344
|
tokenLower,
|
|
338
|
-
isIdentifier
|
|
339
|
-
isPunctuation
|
|
345
|
+
isIdentifier,
|
|
346
|
+
isPunctuation,
|
|
347
|
+
tokenBucket,
|
|
340
348
|
hasAnyScopePattern: hasAnyScopePatternCached,
|
|
341
349
|
hasScopePattern: (pattern) => hasAnyScopePatternCached([pattern]),
|
|
342
350
|
}
|
|
343
351
|
}
|
|
344
352
|
|
|
345
|
-
const
|
|
353
|
+
const classifyShikiScopeRoleBaseFromContext = (context) => {
|
|
346
354
|
if (!context || typeof context !== 'object') return 'text'
|
|
347
355
|
let best = 'text'
|
|
348
|
-
let bestScore =
|
|
349
|
-
const candidates = Array.isArray(context.scopeCandidates) ? context.scopeCandidates : []
|
|
356
|
+
let bestScore = shikiRoleBucketScore.text
|
|
350
357
|
const lowerScopeCandidates = Array.isArray(context.lowerScopeCandidates) ? context.lowerScopeCandidates : []
|
|
351
358
|
const hasAnyPattern = (context && typeof context.hasAnyScopePattern === 'function')
|
|
352
359
|
? context.hasAnyScopePattern
|
|
@@ -355,63 +362,64 @@ const classifyShikiScopeKeywordBaseFromContext = (context) => {
|
|
|
355
362
|
? context.hasScopePattern
|
|
356
363
|
: (pattern) => hasAnyPattern([pattern])
|
|
357
364
|
const langKey = String(context.langKey || '')
|
|
358
|
-
const tokenContent = context.tokenContent
|
|
359
365
|
if (langKey === 'hcl' || langKey === 'terraform') {
|
|
360
|
-
const hclBucket =
|
|
366
|
+
const hclBucket = classifyShikiHclRoleBucket(lowerScopeCandidates, hasAnyPattern)
|
|
361
367
|
if (hclBucket) return hclBucket
|
|
362
368
|
}
|
|
363
|
-
if (hasJsonYamlPropertyNameScope(
|
|
369
|
+
if (hasJsonYamlPropertyNameScope(lowerScopeCandidates)) return 'type'
|
|
364
370
|
if (langKey === 'yaml' && hasScope('entity.name.tag.yaml')) return 'tag'
|
|
365
371
|
for (let i = 0; i < lowerScopeCandidates.length; i++) {
|
|
366
|
-
const bucket =
|
|
367
|
-
const score =
|
|
372
|
+
const bucket = classifyShikiScopeRoleSingle(lowerScopeCandidates[i])
|
|
373
|
+
const score = shikiRoleBucketScore[bucket] || shikiRoleBucketScore.text
|
|
368
374
|
if (score > bestScore) {
|
|
369
375
|
best = bucket
|
|
370
376
|
bestScore = score
|
|
371
377
|
}
|
|
372
378
|
}
|
|
373
|
-
|
|
379
|
+
if (bestScore <= shikiRoleBucketScore.text) {
|
|
380
|
+
const themeRoleBucket = resolveRendererFenceShikiThemeRoleBucket(lowerScopeCandidates)
|
|
381
|
+
if (themeRoleBucket) {
|
|
382
|
+
best = themeRoleBucket
|
|
383
|
+
bestScore = shikiRoleBucketScore.text + 1
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
const tokenBucket = context.tokenBucket
|
|
374
387
|
if (tokenBucket) {
|
|
375
|
-
const tokenScore = (
|
|
376
|
-
if (tokenScore > bestScore) best = tokenBucket
|
|
388
|
+
const tokenScore = (shikiRoleBucketScore[tokenBucket] || shikiRoleBucketScore.text) + 1
|
|
389
|
+
if (best !== 'string' && best !== 'string-unquoted' && tokenScore > bestScore) best = tokenBucket
|
|
377
390
|
}
|
|
378
391
|
return best
|
|
379
392
|
}
|
|
380
393
|
|
|
381
|
-
const
|
|
382
|
-
return
|
|
394
|
+
const resolveShikiRoleLangForFence = (lang, opt) => {
|
|
395
|
+
return resolveShikiRoleLang(lang, null, null, opt)
|
|
383
396
|
}
|
|
384
397
|
|
|
385
|
-
const
|
|
398
|
+
const classifyShikiScopeRole = (rawScope, tok, lang, opt, preResolvedRoleLang = '') => {
|
|
386
399
|
const token = (tok && typeof tok === 'object') ? tok : {}
|
|
387
400
|
const tokenContent = String(token.content || '')
|
|
388
401
|
const tokenTrim = tokenContent.trim()
|
|
389
402
|
if (!tokenTrim) return 'text'
|
|
390
403
|
const tokenLower = tokenTrim.toLowerCase()
|
|
391
|
-
const context =
|
|
404
|
+
const context = buildShikiRoleContext(
|
|
392
405
|
rawScope,
|
|
393
406
|
token,
|
|
394
407
|
lang,
|
|
395
408
|
opt,
|
|
396
|
-
|
|
409
|
+
preResolvedRoleLang,
|
|
397
410
|
tokenContent,
|
|
398
411
|
tokenTrim,
|
|
399
412
|
tokenLower,
|
|
400
413
|
)
|
|
401
|
-
const baseBucket =
|
|
402
|
-
const
|
|
403
|
-
|
|
404
|
-
hasAnyScopePattern: context.hasAnyScopePattern,
|
|
405
|
-
hasScopePattern: context.hasScopePattern,
|
|
406
|
-
}
|
|
407
|
-
const postBucket = applyShikiKeywordLegacyPostRules(baseBucket, context, postHelpers)
|
|
408
|
-
const finalBucket = applyKeywordV4Rules(postBucket, context)
|
|
414
|
+
const baseBucket = classifyShikiScopeRoleBaseFromContext(context)
|
|
415
|
+
const postBucket = applyShikiRoleLegacyPostRules(baseBucket, context)
|
|
416
|
+
const finalBucket = applyRoleV4Rules(postBucket, context)
|
|
409
417
|
return finalBucket || postBucket || baseBucket
|
|
410
418
|
}
|
|
411
419
|
|
|
412
420
|
export {
|
|
413
|
-
|
|
421
|
+
classifyShikiScopeRole,
|
|
414
422
|
getShikiRawScopeName,
|
|
415
|
-
|
|
416
|
-
|
|
423
|
+
normalizeShikiRoleLangAliasMap,
|
|
424
|
+
resolveShikiRoleLangForFence,
|
|
417
425
|
}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
const freezeScopeGroups = (groups) => Object.freeze(Object.fromEntries(
|
|
2
|
+
Object.entries(groups).map(([key, scopes]) => [key, Object.freeze(scopes.slice())]),
|
|
3
|
+
))
|
|
4
|
+
|
|
5
|
+
const rendererFenceShikiThemeScopeGroups = freezeScopeGroups({
|
|
6
|
+
comment: [
|
|
7
|
+
'comment',
|
|
8
|
+
'punctuation.definition.comment',
|
|
9
|
+
'string.comment',
|
|
10
|
+
],
|
|
11
|
+
string: [
|
|
12
|
+
'string',
|
|
13
|
+
'constant.other.symbol',
|
|
14
|
+
'constant.other.key',
|
|
15
|
+
],
|
|
16
|
+
number: [
|
|
17
|
+
'constant.numeric',
|
|
18
|
+
'constant.numeric.integer',
|
|
19
|
+
'constant.numeric.float',
|
|
20
|
+
'constant.other.color',
|
|
21
|
+
],
|
|
22
|
+
literal: [
|
|
23
|
+
'constant.language',
|
|
24
|
+
'constant.character',
|
|
25
|
+
'support.constant',
|
|
26
|
+
'variable.language',
|
|
27
|
+
],
|
|
28
|
+
keyword: [
|
|
29
|
+
'keyword',
|
|
30
|
+
'storage.modifier',
|
|
31
|
+
'storage.type',
|
|
32
|
+
'storage.type.function',
|
|
33
|
+
],
|
|
34
|
+
punctuation: [
|
|
35
|
+
// Keep this intentionally narrower than all of keyword.operator.
|
|
36
|
+
// Some grammars classify word-like tokens under keyword.operator; mapping the
|
|
37
|
+
// whole parent scope to punctuation can demote real keywords such as "new".
|
|
38
|
+
'keyword.operator.accessor',
|
|
39
|
+
'keyword.operator.arithmetic',
|
|
40
|
+
'keyword.operator.assignment',
|
|
41
|
+
'keyword.operator.bitwise',
|
|
42
|
+
'keyword.operator.comparison',
|
|
43
|
+
'keyword.operator.expression',
|
|
44
|
+
'keyword.operator.logical',
|
|
45
|
+
'keyword.operator.nullish',
|
|
46
|
+
'keyword.operator.optional',
|
|
47
|
+
'keyword.operator.spread',
|
|
48
|
+
'keyword.operator.ternary',
|
|
49
|
+
'punctuation',
|
|
50
|
+
'meta.brace',
|
|
51
|
+
'meta.delimiter',
|
|
52
|
+
],
|
|
53
|
+
title: [
|
|
54
|
+
'entity.name.function',
|
|
55
|
+
'support.function',
|
|
56
|
+
],
|
|
57
|
+
type: [
|
|
58
|
+
'entity.name.class',
|
|
59
|
+
'entity.name.type',
|
|
60
|
+
'entity.name.namespace',
|
|
61
|
+
'support.class',
|
|
62
|
+
'support.type',
|
|
63
|
+
'support.type.primitive',
|
|
64
|
+
'meta.type',
|
|
65
|
+
],
|
|
66
|
+
variable: [
|
|
67
|
+
'variable',
|
|
68
|
+
'variable.other',
|
|
69
|
+
'variable.parameter',
|
|
70
|
+
'meta.object-literal.key',
|
|
71
|
+
],
|
|
72
|
+
tag: [
|
|
73
|
+
'entity.name.tag',
|
|
74
|
+
'meta.tag',
|
|
75
|
+
],
|
|
76
|
+
attribute: [
|
|
77
|
+
'entity.other.attribute-name',
|
|
78
|
+
'meta.attribute',
|
|
79
|
+
],
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
const rendererFenceShikiThemeRoleBuckets = Object.freeze({
|
|
83
|
+
comment: 'comment',
|
|
84
|
+
string: 'string',
|
|
85
|
+
number: 'number',
|
|
86
|
+
literal: 'literal',
|
|
87
|
+
keyword: 'keyword',
|
|
88
|
+
punctuation: 'punctuation',
|
|
89
|
+
title: 'title-function',
|
|
90
|
+
type: 'type',
|
|
91
|
+
variable: 'variable',
|
|
92
|
+
tag: 'tag',
|
|
93
|
+
attribute: 'attribute',
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
const rendererFenceShikiThemeRoleScores = Object.freeze({
|
|
97
|
+
comment: 210,
|
|
98
|
+
attribute: 200,
|
|
99
|
+
tag: 198,
|
|
100
|
+
keyword: 194,
|
|
101
|
+
type: 188,
|
|
102
|
+
number: 186,
|
|
103
|
+
literal: 184,
|
|
104
|
+
string: 182,
|
|
105
|
+
title: 172,
|
|
106
|
+
variable: 156,
|
|
107
|
+
punctuation: 140,
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
const shikiThemeRolePatternEntries = Object.freeze(
|
|
111
|
+
Object.entries(rendererFenceShikiThemeScopeGroups).flatMap(([role, patterns]) => {
|
|
112
|
+
const bucket = rendererFenceShikiThemeRoleBuckets[role] || role
|
|
113
|
+
const score = rendererFenceShikiThemeRoleScores[role] || 0
|
|
114
|
+
return patterns.map((pattern) => Object.freeze({
|
|
115
|
+
bucket,
|
|
116
|
+
pattern,
|
|
117
|
+
patternLength: pattern.length,
|
|
118
|
+
score,
|
|
119
|
+
}))
|
|
120
|
+
}),
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
const shikiThemeRoleNoHit = Object.freeze({
|
|
124
|
+
bucket: '',
|
|
125
|
+
patternLength: -1,
|
|
126
|
+
score: -1,
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
const shikiThemeRoleSingleCache = new Map()
|
|
130
|
+
|
|
131
|
+
const resolveRendererFenceShikiThemeRoleSingle = (scope) => {
|
|
132
|
+
const s = String(scope || '')
|
|
133
|
+
if (!s) return shikiThemeRoleNoHit
|
|
134
|
+
const cached = shikiThemeRoleSingleCache.get(s)
|
|
135
|
+
if (cached) return cached
|
|
136
|
+
|
|
137
|
+
let best = shikiThemeRoleNoHit
|
|
138
|
+
for (let i = 0; i < shikiThemeRolePatternEntries.length; i++) {
|
|
139
|
+
const entry = shikiThemeRolePatternEntries[i]
|
|
140
|
+
if (!s.includes(entry.pattern)) continue
|
|
141
|
+
if (
|
|
142
|
+
entry.score > best.score ||
|
|
143
|
+
(entry.score === best.score && entry.patternLength > best.patternLength)
|
|
144
|
+
) {
|
|
145
|
+
best = entry
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (shikiThemeRoleSingleCache.size >= 8192) shikiThemeRoleSingleCache.clear()
|
|
149
|
+
shikiThemeRoleSingleCache.set(s, best)
|
|
150
|
+
return best
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const resolveRendererFenceShikiThemeRoleBucket = (lowerScopeCandidates) => {
|
|
154
|
+
if (!Array.isArray(lowerScopeCandidates) || lowerScopeCandidates.length === 0) return null
|
|
155
|
+
let bestBucket = null
|
|
156
|
+
let bestScore = -1
|
|
157
|
+
let bestPatternLength = -1
|
|
158
|
+
for (let i = 0; i < lowerScopeCandidates.length; i++) {
|
|
159
|
+
const hit = resolveRendererFenceShikiThemeRoleSingle(lowerScopeCandidates[i])
|
|
160
|
+
if (!hit.bucket) continue
|
|
161
|
+
if (
|
|
162
|
+
hit.score > bestScore ||
|
|
163
|
+
(hit.score === bestScore && hit.patternLength > bestPatternLength)
|
|
164
|
+
) {
|
|
165
|
+
bestBucket = hit.bucket
|
|
166
|
+
bestScore = hit.score
|
|
167
|
+
bestPatternLength = hit.patternLength
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return bestBucket
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export {
|
|
174
|
+
rendererFenceShikiThemeScopeGroups,
|
|
175
|
+
resolveRendererFenceShikiThemeRoleBucket,
|
|
176
|
+
}
|
package/src/fence/line-notes.js
CHANGED
|
@@ -5,17 +5,36 @@ import {
|
|
|
5
5
|
const lineNotesRuleName = 'renderer_fence_line_notes'
|
|
6
6
|
const lineNotesMetaKey = '__rendererFenceLineNotes'
|
|
7
7
|
const lineNotesInstalledKey = '__rendererFenceLineNotesInstalled'
|
|
8
|
-
const fenceInfoNameReg = /^([^{\s]*)/
|
|
9
8
|
const lineNoteHeaderReg = /^\s*([1-9]\d*)(?:\s*-\s*([1-9]\d*))?\s*[::]\s*(.*)$/
|
|
10
9
|
const lineNoteContinuationReg = /^(?: {2,}|\t)/
|
|
11
|
-
const lineNoteFenceNameSet = new Set(['line-notes', 'notes'])
|
|
12
10
|
const lineNoteAttrsReg = /^(.*?)(?:\s+\{([^{}]+)\})\s*$/
|
|
13
11
|
const lineNoteAttrsOnlyReg = /^\{([^{}]+)\}\s*$/
|
|
14
12
|
const lineNoteWidthReg = /^(?:\d+(?:\.\d+)?|\.\d+)(?:px|em|rem|ch|vw|svw|lvw|dvw|%)$/
|
|
15
13
|
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
return
|
|
14
|
+
const canContainLineNotes = (state) => {
|
|
15
|
+
const src = state && state.src
|
|
16
|
+
return typeof src === 'string' && src.indexOf('notes') !== -1
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const isInfoWhitespaceCode = (code) => {
|
|
20
|
+
return code === 32 || code === 9 || code === 10 || code === 13 || code === 12
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const isInfoNameEnd = (str, pos) => {
|
|
24
|
+
if (pos >= str.length) return true
|
|
25
|
+
const code = str.charCodeAt(pos)
|
|
26
|
+
return code === 123 || isInfoWhitespaceCode(code)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const infoNameStartsWith = (info, name) => {
|
|
30
|
+
const str = String(info ?? '')
|
|
31
|
+
let pos = 0
|
|
32
|
+
while (pos < str.length && isInfoWhitespaceCode(str.charCodeAt(pos))) pos++
|
|
33
|
+
return str.startsWith(name, pos) && isInfoNameEnd(str, pos + name.length)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const isLineNoteFenceInfo = (info) => {
|
|
37
|
+
return infoNameStartsWith(info, 'line-notes') || infoNameStartsWith(info, 'notes')
|
|
19
38
|
}
|
|
20
39
|
|
|
21
40
|
const stripContinuationIndent = (line) => {
|
|
@@ -155,6 +174,7 @@ const installLineNotesCoreRule = (md) => {
|
|
|
155
174
|
if (!md || md[lineNotesInstalledKey]) return
|
|
156
175
|
md[lineNotesInstalledKey] = true
|
|
157
176
|
md.core.ruler.after('block', lineNotesRuleName, (state) => {
|
|
177
|
+
if (!canContainLineNotes(state)) return
|
|
158
178
|
const tokens = state && state.tokens
|
|
159
179
|
if (!Array.isArray(tokens) || tokens.length < 2) return
|
|
160
180
|
|
|
@@ -162,10 +182,8 @@ const installLineNotesCoreRule = (md) => {
|
|
|
162
182
|
const token = tokens[i]
|
|
163
183
|
const next = tokens[i + 1]
|
|
164
184
|
if (!token || token.type !== 'fence' || !next || next.type !== 'fence') continue
|
|
165
|
-
|
|
166
|
-
if (
|
|
167
|
-
const currentName = getFenceInfoName(token.info)
|
|
168
|
-
if (lineNoteFenceNameSet.has(currentName)) continue
|
|
185
|
+
if (!isLineNoteFenceInfo(next.info)) continue
|
|
186
|
+
if (isLineNoteFenceInfo(token.info)) continue
|
|
169
187
|
const notes = parseLineNotesContent(next.content)
|
|
170
188
|
if (!notes || !notes.length) continue
|
|
171
189
|
setTokenLineNotes(token, notes)
|
|
@@ -11,7 +11,7 @@ const customHighlightDataScriptId = 'pre-highlight-data'
|
|
|
11
11
|
const customHighlightStyleTagId = 'pre-highlight-style'
|
|
12
12
|
const customHighlightEnvInitRuleName = 'renderer_fence_custom_highlight_env_init'
|
|
13
13
|
|
|
14
|
-
const runtimeFallbackReasonSet = new Set(['api-unsupported', 'apply-error'])
|
|
14
|
+
const runtimeFallbackReasonSet = new Set(['api-unsupported', 'range-invalid', 'apply-error'])
|
|
15
15
|
|
|
16
16
|
const customHighlightPayloadSchemaVersion = 1
|
|
17
17
|
const customHighlightPayloadSupportedVersions = [customHighlightPayloadSchemaVersion]
|