Skip to content

Commit 069e0be

Browse files
authoredMar 30, 2024··
perf(shiki-monaco): improve tokenizer performance (#645)
1 parent 5ed274e commit 069e0be

File tree

1 file changed

+42
-46
lines changed

1 file changed

+42
-46
lines changed
 

‎packages/monaco/src/index.ts

+42-46
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,9 @@
1-
import type * as monaco from 'monaco-editor-core'
1+
import type monacoNs from 'monaco-editor-core'
22
import type { ShikiInternal, ThemeRegistrationResolved } from '@shikijs/core'
33
import type { StateStack } from '@shikijs/core/textmate'
44
import { INITIAL, StackElementMetadata } from '@shikijs/core/textmate'
55

6-
type Monaco = typeof monaco
7-
8-
export interface MonacoInterface {
9-
editor: Monaco['editor']
10-
languages: Monaco['languages']
11-
}
12-
13-
export interface MonacoTheme extends monaco.editor.IStandaloneThemeData { }
6+
export interface MonacoTheme extends monacoNs.editor.IStandaloneThemeData {}
147

158
export function textmateThemeToMonacoTheme(theme: ThemeRegistrationResolved): MonacoTheme {
169
let rules = 'rules' in theme
@@ -48,7 +41,7 @@ export function textmateThemeToMonacoTheme(theme: ThemeRegistrationResolved): Mo
4841

4942
export function shikiToMonaco(
5043
highlighter: ShikiInternal<any, any>,
51-
monaco: MonacoInterface,
44+
monaco: typeof monacoNs,
5245
) {
5346
// Convert themes to Monaco themes and register them
5447
const themeMap = new Map<string, MonacoTheme>()
@@ -60,21 +53,41 @@ export function shikiToMonaco(
6053
monaco.editor.defineTheme(themeId, monacoTheme)
6154
}
6255

63-
let currentTheme = themeIds[0]
56+
const colorMap: string[] = []
57+
const colorToScopeMap = new Map<string, string>()
6458

6559
// Because Monaco does not have the API of reading the current theme,
6660
// We hijack it here to keep track of the current theme.
6761
const _setTheme = monaco.editor.setTheme.bind(monaco.editor)
68-
monaco.editor.setTheme = (theme: string) => {
69-
_setTheme(theme)
70-
currentTheme = theme
62+
monaco.editor.setTheme = (themeName: string) => {
63+
const ret = highlighter.setTheme(themeName)
64+
const theme = themeMap.get(themeName)
65+
colorMap.length = ret.colorMap.length
66+
ret.colorMap.forEach((color, i) => {
67+
colorMap[i] = color
68+
})
69+
colorToScopeMap.clear()
70+
theme?.rules.forEach((rule) => {
71+
const c = normalizeColor(rule.foreground)
72+
if (c && !colorToScopeMap.has(c))
73+
colorToScopeMap.set(c, rule.token)
74+
})
75+
_setTheme(themeName)
76+
}
77+
78+
// Set the first theme as the default theme
79+
monaco.editor.setTheme(themeIds[0])
80+
81+
function findScopeByColor(color: string) {
82+
return colorToScopeMap.get(color)
7183
}
7284

85+
const monacoLanguageIds = new Set(monaco.languages.getLanguages().map(l => l.id))
7386
for (const lang of highlighter.getLoadedLanguages()) {
74-
if (monaco.languages.getLanguages().some(l => l.id === lang)) {
87+
if (monacoLanguageIds.has(lang)) {
7588
monaco.languages.setTokensProvider(lang, {
7689
getInitialState() {
77-
return new TokenizerState(INITIAL, highlighter)
90+
return new TokenizerState(INITIAL)
7891
},
7992
tokenize(line, state: TokenizerState) {
8093
// Do not attempt to tokenize if a line is too long
@@ -89,26 +102,12 @@ export function shikiToMonaco(
89102
}
90103
}
91104

92-
const grammar = state.highlighter.getLanguage(lang)
93-
const { colorMap } = state.highlighter.setTheme(currentTheme)
94-
const theme = themeMap.get(currentTheme)
105+
const grammar = highlighter.getLanguage(lang)
95106
const result = grammar.tokenizeLine2(line, state.ruleStack, tokenizeTimeLimit)
96107

97108
if (result.stoppedEarly)
98109
console.warn(`Time limit reached when tokenizing line: ${line.substring(0, 100)}`)
99110

100-
const colorToScopeMap = new Map<string, string>()
101-
102-
theme!.rules.forEach((rule) => {
103-
const c = normalizeColor(rule.foreground)
104-
if (c && !colorToScopeMap.has(c))
105-
colorToScopeMap.set(c, rule.token)
106-
})
107-
108-
function findScopeByColor(color: string) {
109-
return colorToScopeMap.get(color)
110-
}
111-
112111
const tokensLength = result.tokens.length / 2
113112
const tokens: any[] = []
114113
for (let j = 0; j < tokensLength; j++) {
@@ -118,38 +117,32 @@ export function shikiToMonaco(
118117
// Because Monaco only support one scope per token,
119118
// we workaround this to use color to trace back the scope
120119
const scope = findScopeByColor(color) || ''
121-
tokens.push({
122-
startIndex,
123-
scopes: scope,
124-
})
120+
tokens.push({ startIndex, scopes: scope })
125121
}
126122

127-
return {
128-
endState: new TokenizerState(result.ruleStack, state.highlighter),
129-
tokens,
130-
}
123+
return { endState: new TokenizerState(result.ruleStack), tokens }
131124
},
132125
})
133126
}
134127
}
135128
}
136129

137-
class TokenizerState implements monaco.languages.IState {
130+
class TokenizerState implements monacoNs.languages.IState {
138131
constructor(
139132
private _ruleStack: StateStack,
140-
public highlighter: ShikiInternal<any, any>,
141-
) { }
133+
) {}
142134

143135
public get ruleStack(): StateStack {
144136
return this._ruleStack
145137
}
146138

147139
public clone(): TokenizerState {
148-
return new TokenizerState(this._ruleStack, this.highlighter)
140+
return new TokenizerState(this._ruleStack)
149141
}
150142

151-
public equals(other: monaco.languages.IState): boolean {
152-
if (!other
143+
public equals(other: monacoNs.languages.IState): boolean {
144+
if (
145+
!other
153146
|| !(other instanceof TokenizerState)
154147
|| other !== this
155148
|| other._ruleStack !== this._ruleStack
@@ -166,9 +159,12 @@ function normalizeColor(color: string | undefined): string | undefined
166159
function normalizeColor(color: string | undefined) {
167160
if (!color)
168161
return color
169-
color = color.replace('#', '').toLowerCase()
162+
163+
color = (color.charCodeAt(0) === 35 ? color.slice(1) : color).toLowerCase()
164+
170165
// #RGB => #RRGGBB - Monaco does not support hex color with 3 or 4 digits
171166
if (color.length === 3 || color.length === 4)
172167
color = color.split('').map(c => c + c).join('')
168+
173169
return color
174170
}

0 commit comments

Comments
 (0)
Please sign in to comment.