Skip to content

Commit 966e7e4

Browse files
authoredJun 15, 2024··
perf: improve performance for the includeExplanation option (#701)
1 parent 2469296 commit 966e7e4

File tree

1 file changed

+46
-40
lines changed

1 file changed

+46
-40
lines changed
 

‎packages/core/src/code-to-tokens-base.ts

+46-40
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/* ---------------------------------------------------------
22
* Copyright (C) Microsoft Corporation. All rights reserved.
33
*-------------------------------------------------------- */
4-
import type { IGrammar } from './textmate'
4+
import type { IGrammar, IRawThemeSetting } from './textmate'
55
import { INITIAL } from './textmate'
66
import type { CodeToTokensBaseOptions, FontStyle, ShikiInternal, ThemeRegistrationResolved, ThemedToken, ThemedTokenScopeExplanation, TokenizeWithThemeOptions } from './types'
77
import { StackElementMetadata } from './stack-element-metadata'
@@ -33,6 +33,12 @@ export function codeToTokensBase(
3333
return tokenizeWithTheme(code, _grammar, theme, colorMap, options)
3434
}
3535

36+
/** for explanations */
37+
interface ThemeSettingsSelectors {
38+
settings: IRawThemeSetting
39+
selectors: string[][]
40+
}
41+
3642
export function tokenizeWithTheme(
3743
code: string,
3844
grammar: IGrammar,
@@ -53,6 +59,28 @@ export function tokenizeWithTheme(
5359
let actual: ThemedToken[] = []
5460
const final: ThemedToken[][] = []
5561

62+
const themeSettingsSelectors: ThemeSettingsSelectors[] = []
63+
if (options.includeExplanation) {
64+
for (const setting of theme.settings) {
65+
let selectors: string[]
66+
switch (typeof setting.scope) {
67+
case 'string':
68+
selectors = setting.scope.split(/,/).map(scope => scope.trim())
69+
break
70+
case 'object':
71+
selectors = setting.scope
72+
break
73+
default:
74+
continue
75+
}
76+
77+
themeSettingsSelectors.push({
78+
settings: setting,
79+
selectors: selectors.map(selector => selector.split(/ /)),
80+
})
81+
}
82+
}
83+
5684
for (let i = 0, len = lines.length; i < len; i++) {
5785
const [line, lineOffset] = lines[i]
5886
if (line === '') {
@@ -119,7 +147,7 @@ export function tokenizeWithTheme(
119147
offset += tokenWithScopesText.length
120148
token.explanation.push({
121149
content: tokenWithScopesText,
122-
scopes: explainThemeScopes(theme, tokenWithScopes.scopes),
150+
scopes: explainThemeScopes(themeSettingsSelectors, tokenWithScopes.scopes),
123151
})
124152

125153
tokensWithScopesIndex! += 1
@@ -137,7 +165,7 @@ export function tokenizeWithTheme(
137165
}
138166

139167
function explainThemeScopes(
140-
theme: ThemeRegistrationResolved,
168+
themeSelectors: ThemeSettingsSelectors[],
141169
scopes: string[],
142170
): ThemedTokenScopeExplanation[] {
143171
const result: ThemedTokenScopeExplanation[] = []
@@ -146,33 +174,29 @@ function explainThemeScopes(
146174
const scope = scopes[i]
147175
result[i] = {
148176
scopeName: scope,
149-
themeMatches: explainThemeScope(theme, scope, parentScopes),
177+
themeMatches: explainThemeScope(themeSelectors, scope, parentScopes),
150178
}
151179
}
152180
return result
153181
}
154182

155183
function matchesOne(selector: string, scope: string): boolean {
156-
const selectorPrefix = `${selector}.`
157-
if (selector === scope || scope.substring(0, selectorPrefix.length) === selectorPrefix)
158-
return true
159-
160-
return false
184+
return selector === scope
185+
|| (scope.substring(0, selector.length) === selector && scope[selector.length] === '.')
161186
}
162187

163188
function matches(
164-
selector: string,
165-
selectorParentScopes: string[],
189+
selectors: string[],
166190
scope: string,
167191
parentScopes: string[],
168192
): boolean {
169-
if (!matchesOne(selector, scope))
193+
if (!matchesOne(selectors[selectors.length - 1], scope))
170194
return false
171195

172-
let selectorParentIndex = selectorParentScopes.length - 1
196+
let selectorParentIndex = selectors.length - 2
173197
let parentIndex = parentScopes.length - 1
174198
while (selectorParentIndex >= 0 && parentIndex >= 0) {
175-
if (matchesOne(selectorParentScopes[selectorParentIndex], parentScopes[parentIndex]))
199+
if (matchesOne(selectors[selectorParentIndex], parentScopes[parentIndex]))
176200
selectorParentIndex -= 1
177201
parentIndex -= 1
178202
}
@@ -184,34 +208,16 @@ function matches(
184208
}
185209

186210
function explainThemeScope(
187-
theme: ThemeRegistrationResolved,
211+
themeSettingsSelectors: ThemeSettingsSelectors[],
188212
scope: string,
189213
parentScopes: string[],
190-
): any[] {
191-
const result: any[] = []
192-
let resultLen = 0
193-
for (let i = 0, len = theme.settings.length; i < len; i++) {
194-
const setting = theme.settings[i]
195-
let selectors: string[]
196-
if (typeof setting.scope === 'string')
197-
selectors = setting.scope.split(/,/).map(scope => scope.trim())
198-
else if (Array.isArray(setting.scope))
199-
selectors = setting.scope
200-
else
201-
continue
202-
203-
for (let j = 0, lenJ = selectors.length; j < lenJ; j++) {
204-
const rawSelector = selectors[j]
205-
const rawSelectorPieces = rawSelector.split(/ /)
206-
207-
const selector = rawSelectorPieces[rawSelectorPieces.length - 1]
208-
const selectorParentScopes = rawSelectorPieces.slice(0, rawSelectorPieces.length - 1)
209-
210-
if (matches(selector, selectorParentScopes, scope, parentScopes)) {
211-
// match!
212-
result[resultLen++] = setting
213-
// break the loop
214-
j = lenJ
214+
): IRawThemeSetting[] {
215+
const result: IRawThemeSetting[] = []
216+
for (const { selectors, settings } of themeSettingsSelectors) {
217+
for (const selectorPieces of selectors) {
218+
if (matches(selectorPieces, scope, parentScopes)) {
219+
result.push(settings)
220+
break // continue to the next theme settings
215221
}
216222
}
217223
}

0 commit comments

Comments
 (0)
Please sign in to comment.