@peaceroad/markdown-it-renderer-fence 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +175 -471
- package/docs/README.md +37 -0
- package/docs/code-highlighting-design.md +317 -0
- package/docs/custom-highlight-styling-guide.md +204 -0
- package/package.json +21 -11
- package/src/custom-highlight/option-validator.js +5 -5
- package/src/custom-highlight/{shiki-keyword-rules.js → shiki-role-rules.js} +229 -44
- package/src/custom-highlight/{shiki-keyword.js → shiki-role.js} +64 -56
- package/src/custom-highlight/shiki-theme-role.js +176 -0
- package/src/fence/line-notes.js +27 -9
- package/src/fence/render-api-provider.js +59 -50
- package/src/fence/render-api-renderer.js +7 -6
- package/src/fence/render-api.js +20 -12
- package/src/fence/render-markup.js +4 -9
- package/src/fence/render-shared.js +78 -8
- package/theme/_shared/rf-core.css +164 -0
- package/theme/rf-basic/README.md +91 -0
- package/theme/rf-basic/api/highlightjs.css +71 -0
- package/theme/rf-basic/api/shiki.css +67 -0
- package/theme/rf-basic/base.css +3 -0
- package/theme/rf-basic/index.css +7 -0
- package/theme/rf-basic/index.js +155 -0
- package/theme/rf-basic/markup/highlightjs.css +77 -0
- package/theme/rf-basic/markup/shiki.css +11 -0
- package/theme/rf-monochrome/README.md +71 -0
- package/theme/rf-monochrome/base.css +3 -0
- package/theme/rf-monochrome/index.css +5 -0
- package/theme/rf-monochrome/index.js +72 -0
- package/theme/rf-monochrome/markup/highlightjs.css +134 -0
- package/theme/rf-monochrome/markup/shiki.css +27 -0
|
@@ -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,10 +178,10 @@ 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
187
|
if (s.includes('comment')) return 'comment'
|
|
@@ -190,7 +191,7 @@ const classifyShikiScopeKeywordSingleV3 = (scope) => {
|
|
|
190
191
|
if (s.includes('entity.name.tag')) return 'tag'
|
|
191
192
|
if (s.includes('string.unquoted')) return 'string-unquoted'
|
|
192
193
|
if (s.includes('regexp') || s.includes('regex') || s.includes('string') || s.includes('punctuation.definition.string')) return 'string'
|
|
193
|
-
if (s.includes('constant.other.color')
|
|
194
|
+
if (s.includes('constant.other.color')) return 'number'
|
|
194
195
|
if (s.includes('constant.numeric') || s.includes('number')) return 'number'
|
|
195
196
|
if (s.includes('constant.language') || s.includes('boolean') || s.includes('null') || s.includes('undefined') || s.includes('none')) return 'literal'
|
|
196
197
|
if (s.includes('entity.name.scope-resolution') || s.includes('entity.name.namespace')) return 'namespace'
|
|
@@ -207,14 +208,14 @@ const classifyShikiScopeKeywordSingleV3 = (scope) => {
|
|
|
207
208
|
if (s.includes('entity.name.class') || s.includes('entity.name.type.class')) return 'title-class'
|
|
208
209
|
if (s.includes('storage.type.built-in.primitive')) return 'type-primitive'
|
|
209
210
|
if (s.includes('entity.name.type.namespace') || s.includes('entity.name.type') || s.includes('support.class')) return 'type-name'
|
|
210
|
-
if (s.includes('support.type')
|
|
211
|
+
if (s.includes('support.type')) return 'type'
|
|
211
212
|
if (s.includes('storage') || s.includes('keyword') || s.includes('operator') || s.includes('control') || s.includes('modifier')) return 'keyword'
|
|
212
213
|
if (s.includes('variable')) return 'variable'
|
|
213
214
|
if (s.includes('punctuation')) return 'punctuation'
|
|
214
215
|
if (s.includes('source.')) bucket = 'text'
|
|
215
216
|
else if (s.includes('meta')) bucket = 'meta'
|
|
216
|
-
|
|
217
|
-
if (
|
|
217
|
+
shikiScopeRoleCache.set(s, bucket)
|
|
218
|
+
if (shikiScopeRoleCache.size > 8192) shikiScopeRoleCache.clear()
|
|
218
219
|
return bucket
|
|
219
220
|
}
|
|
220
221
|
|
|
@@ -228,7 +229,7 @@ const hasJsonYamlPropertyNameScope = (scopeCandidates) => {
|
|
|
228
229
|
return false
|
|
229
230
|
}
|
|
230
231
|
|
|
231
|
-
const
|
|
232
|
+
const classifyShikiHclRoleBucket = (lowerScopeCandidates, hasAnyPattern) => {
|
|
232
233
|
const hasAny = (typeof hasAnyPattern === 'function') ? hasAnyPattern : (patterns) => hasAnyScopePattern(lowerScopeCandidates, patterns)
|
|
233
234
|
if (!Array.isArray(lowerScopeCandidates) || lowerScopeCandidates.length === 0) return null
|
|
234
235
|
if (hasAny(['entity.name.type.hcl', 'entity-name-type-hcl'])) return 'title-class'
|
|
@@ -243,10 +244,10 @@ const classifyShikiHclKeywordBucketV3 = (lowerScopeCandidates, hasAnyPattern) =>
|
|
|
243
244
|
return null
|
|
244
245
|
}
|
|
245
246
|
|
|
246
|
-
const
|
|
247
|
+
const resolveShikiRoleLang = (lang, scopeCandidates, tok, opt) => {
|
|
247
248
|
const queue = []
|
|
248
249
|
const seen = new Set()
|
|
249
|
-
const customAliasMap = opt && opt.
|
|
250
|
+
const customAliasMap = opt && opt.shikiRoleLangAliases
|
|
250
251
|
const shikiInternalAliasMap = opt && opt._shikiInternalLangAliasMap
|
|
251
252
|
const resolveAlias = (key) => {
|
|
252
253
|
if (customAliasMap && customAliasMap[key]) return customAliasMap[key]
|
|
@@ -254,46 +255,46 @@ const resolveShikiKeywordLang = (lang, scopeCandidates, tok, opt) => {
|
|
|
254
255
|
return ''
|
|
255
256
|
}
|
|
256
257
|
const pushCandidate = (value) => {
|
|
257
|
-
const key =
|
|
258
|
+
const key = normalizeShikiRoleLangKey(value)
|
|
258
259
|
if (!key || seen.has(key)) return
|
|
259
260
|
seen.add(key)
|
|
260
261
|
queue.push(key)
|
|
261
262
|
}
|
|
262
|
-
if (opt && typeof opt.
|
|
263
|
+
if (opt && typeof opt.shikiRoleLangResolver === 'function') {
|
|
263
264
|
try {
|
|
264
|
-
const resolved = opt.
|
|
265
|
+
const resolved = opt.shikiRoleLangResolver(lang, scopeCandidates, tok)
|
|
265
266
|
if (resolved != null && resolved !== '') pushCandidate(resolved)
|
|
266
267
|
} catch (e) {}
|
|
267
268
|
}
|
|
268
269
|
pushCandidate(lang)
|
|
269
270
|
if (Array.isArray(scopeCandidates)) {
|
|
270
271
|
for (let i = 0; i < scopeCandidates.length; i++) {
|
|
271
|
-
const candidates =
|
|
272
|
+
const candidates = extractShikiRoleLangCandidatesFromScope(scopeCandidates[i])
|
|
272
273
|
for (let j = 0; j < candidates.length; j++) pushCandidate(candidates[j])
|
|
273
274
|
}
|
|
274
275
|
}
|
|
275
276
|
for (let i = 0; i < queue.length; i++) {
|
|
276
277
|
const key = queue[i]
|
|
277
|
-
if (
|
|
278
|
+
if (shikiLangKeywordTokens[key]) return key
|
|
278
279
|
const aliased = resolveAlias(key)
|
|
279
280
|
if (aliased) {
|
|
280
|
-
if (
|
|
281
|
+
if (shikiLangKeywordTokens[aliased]) return aliased
|
|
281
282
|
pushCandidate(aliased)
|
|
282
283
|
}
|
|
283
284
|
const compact = key.replace(/-/g, '')
|
|
284
|
-
if (
|
|
285
|
+
if (shikiLangKeywordTokens[compact]) return compact
|
|
285
286
|
const compactAliased = resolveAlias(compact)
|
|
286
287
|
if (compactAliased) {
|
|
287
|
-
if (
|
|
288
|
+
if (shikiLangKeywordTokens[compactAliased]) return compactAliased
|
|
288
289
|
pushCandidate(compactAliased)
|
|
289
290
|
}
|
|
290
291
|
const parts = key.split('-')
|
|
291
292
|
for (let n = parts.length - 1; n >= 1; n--) {
|
|
292
293
|
const head = parts.slice(0, n).join('-')
|
|
293
|
-
if (
|
|
294
|
+
if (shikiLangKeywordTokens[head]) return head
|
|
294
295
|
const headAliased = resolveAlias(head)
|
|
295
296
|
if (headAliased) {
|
|
296
|
-
if (
|
|
297
|
+
if (shikiLangKeywordTokens[headAliased]) return headAliased
|
|
297
298
|
pushCandidate(headAliased)
|
|
298
299
|
}
|
|
299
300
|
}
|
|
@@ -301,7 +302,7 @@ const resolveShikiKeywordLang = (lang, scopeCandidates, tok, opt) => {
|
|
|
301
302
|
return ''
|
|
302
303
|
}
|
|
303
304
|
|
|
304
|
-
const
|
|
305
|
+
const classifyShikiRoleByToken = (content, langKey) => {
|
|
305
306
|
const text = String(content || '')
|
|
306
307
|
const trimmed = text.trim()
|
|
307
308
|
if (!trimmed) return 'text'
|
|
@@ -309,20 +310,20 @@ const classifyShikiKeywordByToken = (content, langKey) => {
|
|
|
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
|
if (shikiPunctuationTokenReg.test(trimmed)) return 'punctuation'
|
|
316
317
|
return null
|
|
317
318
|
}
|
|
318
319
|
|
|
319
|
-
const
|
|
320
|
+
const buildShikiRoleContext = (rawScope, tok, lang, opt, preResolvedLangKey = '', tokenContentArg, tokenTrimArg, tokenLowerArg) => {
|
|
320
321
|
const token = (tok && typeof tok === 'object') ? tok : {}
|
|
321
322
|
const tokenContent = (typeof tokenContentArg === 'string') ? tokenContentArg : String(token.content || '')
|
|
322
323
|
const tokenTrim = (typeof tokenTrimArg === 'string') ? tokenTrimArg : tokenContent.trim()
|
|
323
324
|
const tokenLower = (typeof tokenLowerArg === 'string') ? tokenLowerArg : tokenTrim.toLowerCase()
|
|
324
325
|
const scopeCandidates = getShikiScopeCandidates(token, rawScope)
|
|
325
|
-
const langKey = preResolvedLangKey ||
|
|
326
|
+
const langKey = preResolvedLangKey || resolveShikiRoleLang(lang, scopeCandidates, token, opt)
|
|
326
327
|
const lowerScopeCandidates = toLowerScopeCandidates(scopeCandidates)
|
|
327
328
|
const hasAnyScopePatternCached = createScopePatternMatcher(lowerScopeCandidates)
|
|
328
329
|
return {
|
|
@@ -342,10 +343,10 @@ const buildShikiKeywordContext = (rawScope, tok, lang, opt, preResolvedLangKey =
|
|
|
342
343
|
}
|
|
343
344
|
}
|
|
344
345
|
|
|
345
|
-
const
|
|
346
|
+
const classifyShikiScopeRoleBaseFromContext = (context) => {
|
|
346
347
|
if (!context || typeof context !== 'object') return 'text'
|
|
347
348
|
let best = 'text'
|
|
348
|
-
let bestScore =
|
|
349
|
+
let bestScore = shikiRoleBucketScore.text
|
|
349
350
|
const candidates = Array.isArray(context.scopeCandidates) ? context.scopeCandidates : []
|
|
350
351
|
const lowerScopeCandidates = Array.isArray(context.lowerScopeCandidates) ? context.lowerScopeCandidates : []
|
|
351
352
|
const hasAnyPattern = (context && typeof context.hasAnyScopePattern === 'function')
|
|
@@ -357,61 +358,68 @@ const classifyShikiScopeKeywordBaseFromContext = (context) => {
|
|
|
357
358
|
const langKey = String(context.langKey || '')
|
|
358
359
|
const tokenContent = context.tokenContent
|
|
359
360
|
if (langKey === 'hcl' || langKey === 'terraform') {
|
|
360
|
-
const hclBucket =
|
|
361
|
+
const hclBucket = classifyShikiHclRoleBucket(lowerScopeCandidates, hasAnyPattern)
|
|
361
362
|
if (hclBucket) return hclBucket
|
|
362
363
|
}
|
|
363
364
|
if (hasJsonYamlPropertyNameScope(candidates)) return 'type'
|
|
364
365
|
if (langKey === 'yaml' && hasScope('entity.name.tag.yaml')) return 'tag'
|
|
365
366
|
for (let i = 0; i < lowerScopeCandidates.length; i++) {
|
|
366
|
-
const bucket =
|
|
367
|
-
const score =
|
|
367
|
+
const bucket = classifyShikiScopeRoleSingle(lowerScopeCandidates[i])
|
|
368
|
+
const score = shikiRoleBucketScore[bucket] || shikiRoleBucketScore.text
|
|
368
369
|
if (score > bestScore) {
|
|
369
370
|
best = bucket
|
|
370
371
|
bestScore = score
|
|
371
372
|
}
|
|
372
373
|
}
|
|
373
|
-
|
|
374
|
+
if (bestScore <= shikiRoleBucketScore.text) {
|
|
375
|
+
const themeRoleBucket = resolveRendererFenceShikiThemeRoleBucket(lowerScopeCandidates)
|
|
376
|
+
if (themeRoleBucket) {
|
|
377
|
+
best = themeRoleBucket
|
|
378
|
+
bestScore = shikiRoleBucketScore.text + 1
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
const tokenBucket = classifyShikiRoleByToken(tokenContent, langKey)
|
|
374
382
|
if (tokenBucket) {
|
|
375
|
-
const tokenScore = (
|
|
376
|
-
if (tokenScore > bestScore) best = tokenBucket
|
|
383
|
+
const tokenScore = (shikiRoleBucketScore[tokenBucket] || shikiRoleBucketScore.text) + 1
|
|
384
|
+
if (best !== 'string' && best !== 'string-unquoted' && tokenScore > bestScore) best = tokenBucket
|
|
377
385
|
}
|
|
378
386
|
return best
|
|
379
387
|
}
|
|
380
388
|
|
|
381
|
-
const
|
|
382
|
-
return
|
|
389
|
+
const resolveShikiRoleLangForFence = (lang, opt) => {
|
|
390
|
+
return resolveShikiRoleLang(lang, null, null, opt)
|
|
383
391
|
}
|
|
384
392
|
|
|
385
|
-
const
|
|
393
|
+
const classifyShikiScopeRole = (rawScope, tok, lang, opt, preResolvedRoleLang = '') => {
|
|
386
394
|
const token = (tok && typeof tok === 'object') ? tok : {}
|
|
387
395
|
const tokenContent = String(token.content || '')
|
|
388
396
|
const tokenTrim = tokenContent.trim()
|
|
389
397
|
if (!tokenTrim) return 'text'
|
|
390
398
|
const tokenLower = tokenTrim.toLowerCase()
|
|
391
|
-
const context =
|
|
399
|
+
const context = buildShikiRoleContext(
|
|
392
400
|
rawScope,
|
|
393
401
|
token,
|
|
394
402
|
lang,
|
|
395
403
|
opt,
|
|
396
|
-
|
|
404
|
+
preResolvedRoleLang,
|
|
397
405
|
tokenContent,
|
|
398
406
|
tokenTrim,
|
|
399
407
|
tokenLower,
|
|
400
408
|
)
|
|
401
|
-
const baseBucket =
|
|
409
|
+
const baseBucket = classifyShikiScopeRoleBaseFromContext(context)
|
|
402
410
|
const postHelpers = {
|
|
403
|
-
classifyToken:
|
|
411
|
+
classifyToken: classifyShikiRoleByToken,
|
|
404
412
|
hasAnyScopePattern: context.hasAnyScopePattern,
|
|
405
413
|
hasScopePattern: context.hasScopePattern,
|
|
406
414
|
}
|
|
407
|
-
const postBucket =
|
|
408
|
-
const finalBucket =
|
|
415
|
+
const postBucket = applyShikiRoleLegacyPostRules(baseBucket, context, postHelpers)
|
|
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)
|