1
- import type * as monaco from 'monaco-editor-core'
1
+ import type monacoNs from 'monaco-editor-core'
2
2
import type { ShikiInternal , ThemeRegistrationResolved } from '@shikijs/core'
3
3
import type { StateStack } from '@shikijs/core/textmate'
4
4
import { INITIAL , StackElementMetadata } from '@shikijs/core/textmate'
5
5
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 { }
14
7
15
8
export function textmateThemeToMonacoTheme ( theme : ThemeRegistrationResolved ) : MonacoTheme {
16
9
let rules = 'rules' in theme
@@ -48,7 +41,7 @@ export function textmateThemeToMonacoTheme(theme: ThemeRegistrationResolved): Mo
48
41
49
42
export function shikiToMonaco (
50
43
highlighter : ShikiInternal < any , any > ,
51
- monaco : MonacoInterface ,
44
+ monaco : typeof monacoNs ,
52
45
) {
53
46
// Convert themes to Monaco themes and register them
54
47
const themeMap = new Map < string , MonacoTheme > ( )
@@ -60,21 +53,41 @@ export function shikiToMonaco(
60
53
monaco . editor . defineTheme ( themeId , monacoTheme )
61
54
}
62
55
63
- let currentTheme = themeIds [ 0 ]
56
+ const colorMap : string [ ] = [ ]
57
+ const colorToScopeMap = new Map < string , string > ( )
64
58
65
59
// Because Monaco does not have the API of reading the current theme,
66
60
// We hijack it here to keep track of the current theme.
67
61
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 )
71
83
}
72
84
85
+ const monacoLanguageIds = new Set ( monaco . languages . getLanguages ( ) . map ( l => l . id ) )
73
86
for ( const lang of highlighter . getLoadedLanguages ( ) ) {
74
- if ( monaco . languages . getLanguages ( ) . some ( l => l . id === lang ) ) {
87
+ if ( monacoLanguageIds . has ( lang ) ) {
75
88
monaco . languages . setTokensProvider ( lang , {
76
89
getInitialState ( ) {
77
- return new TokenizerState ( INITIAL , highlighter )
90
+ return new TokenizerState ( INITIAL )
78
91
} ,
79
92
tokenize ( line , state : TokenizerState ) {
80
93
// Do not attempt to tokenize if a line is too long
@@ -89,26 +102,12 @@ export function shikiToMonaco(
89
102
}
90
103
}
91
104
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 )
95
106
const result = grammar . tokenizeLine2 ( line , state . ruleStack , tokenizeTimeLimit )
96
107
97
108
if ( result . stoppedEarly )
98
109
console . warn ( `Time limit reached when tokenizing line: ${ line . substring ( 0 , 100 ) } ` )
99
110
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
-
112
111
const tokensLength = result . tokens . length / 2
113
112
const tokens : any [ ] = [ ]
114
113
for ( let j = 0 ; j < tokensLength ; j ++ ) {
@@ -118,38 +117,32 @@ export function shikiToMonaco(
118
117
// Because Monaco only support one scope per token,
119
118
// we workaround this to use color to trace back the scope
120
119
const scope = findScopeByColor ( color ) || ''
121
- tokens . push ( {
122
- startIndex,
123
- scopes : scope ,
124
- } )
120
+ tokens . push ( { startIndex, scopes : scope } )
125
121
}
126
122
127
- return {
128
- endState : new TokenizerState ( result . ruleStack , state . highlighter ) ,
129
- tokens,
130
- }
123
+ return { endState : new TokenizerState ( result . ruleStack ) , tokens }
131
124
} ,
132
125
} )
133
126
}
134
127
}
135
128
}
136
129
137
- class TokenizerState implements monaco . languages . IState {
130
+ class TokenizerState implements monacoNs . languages . IState {
138
131
constructor (
139
132
private _ruleStack : StateStack ,
140
- public highlighter : ShikiInternal < any , any > ,
141
- ) { }
133
+ ) { }
142
134
143
135
public get ruleStack ( ) : StateStack {
144
136
return this . _ruleStack
145
137
}
146
138
147
139
public clone ( ) : TokenizerState {
148
- return new TokenizerState ( this . _ruleStack , this . highlighter )
140
+ return new TokenizerState ( this . _ruleStack )
149
141
}
150
142
151
- public equals ( other : monaco . languages . IState ) : boolean {
152
- if ( ! other
143
+ public equals ( other : monacoNs . languages . IState ) : boolean {
144
+ if (
145
+ ! other
153
146
|| ! ( other instanceof TokenizerState )
154
147
|| other !== this
155
148
|| other . _ruleStack !== this . _ruleStack
@@ -166,9 +159,12 @@ function normalizeColor(color: string | undefined): string | undefined
166
159
function normalizeColor ( color : string | undefined ) {
167
160
if ( ! color )
168
161
return color
169
- color = color . replace ( '#' , '' ) . toLowerCase ( )
162
+
163
+ color = ( color . charCodeAt ( 0 ) === 35 ? color . slice ( 1 ) : color ) . toLowerCase ( )
164
+
170
165
// #RGB => #RRGGBB - Monaco does not support hex color with 3 or 4 digits
171
166
if ( color . length === 3 || color . length === 4 )
172
167
color = color . split ( '' ) . map ( c => c + c ) . join ( '' )
168
+
173
169
return color
174
170
}
0 commit comments