11import type { ShikiInternal , ThemeRegistrationResolved } from '@shikijs/types'
22import type * as monacoNs from 'monaco-editor-core'
33import type { MonacoLineToken } from './types'
4- import { EncodedTokenMetadata , INITIAL } from '@shikijs/vscode-textmate'
4+ import { EncodedTokenMetadata , FontStyle , INITIAL } from '@shikijs/vscode-textmate'
55import { TokenizerState } from './tokenizer'
66import { normalizeColor } from './utils'
77
@@ -36,11 +36,15 @@ export function textmateThemeToMonacoTheme(theme: ThemeRegistrationResolved): Mo
3636 continue
3737 const scopes = Array . isArray ( scope ) ? scope : scope ? [ scope ] : [ ]
3838
39+ const normalizedFontStyle = normalizeFontStyleString ( fontStyle )
40+ const normalizedForeground = normalizeColor ( foreground )
41+ const normalizedBackground = normalizeColor ( background )
42+
3943 rules . push ( ...scopes . map ( s => ( {
4044 token : s ,
41- foreground : normalizeColor ( foreground ) ,
42- background : normalizeColor ( background ) ,
43- fontStyle,
45+ foreground : normalizedForeground ,
46+ background : normalizedBackground ,
47+ fontStyle : normalizedFontStyle ,
4448 } ) ) )
4549 }
4650 }
@@ -74,7 +78,7 @@ export function shikiToMonaco(
7478 }
7579
7680 const colorMap : string [ ] = [ ]
77- const colorToScopeMap = new Map < string , string > ( )
81+ const colorStyleToScopeMap = new Map < string , string > ( )
7882
7983 // Because Monaco does not have the API of reading the current theme,
8084 // We hijack it here to keep track of the current theme.
@@ -86,20 +90,25 @@ export function shikiToMonaco(
8690 ret . colorMap . forEach ( ( color , i ) => {
8791 colorMap [ i ] = color
8892 } )
89- colorToScopeMap . clear ( )
93+ colorStyleToScopeMap . clear ( )
9094 theme ?. rules . forEach ( ( rule ) => {
9195 const c = normalizeColor ( rule . foreground )
92- if ( c && ! colorToScopeMap . has ( c ) )
93- colorToScopeMap . set ( c , rule . token )
96+ if ( ! c )
97+ return
98+
99+ const key = getColorStyleKey ( c , normalizeFontStyleString ( rule . fontStyle ) )
100+ if ( ! colorStyleToScopeMap . has ( key ) )
101+ colorStyleToScopeMap . set ( key , rule . token )
94102 } )
95103 _setTheme ( themeName )
96104 }
97105
98106 // Set the first theme as the default theme
99107 monaco . editor . setTheme ( themeIds [ 0 ] )
100108
101- function findScopeByColor ( color : string ) : string | undefined {
102- return colorToScopeMap . get ( color )
109+ function findScopeByColorAndStyle ( color : string , fontStyle : FontStyle ) : string | undefined {
110+ const key = getColorStyleKey ( color , normalizeFontStyleBits ( fontStyle ) )
111+ return colorStyleToScopeMap . get ( key )
103112 }
104113
105114 // Do not attempt to tokenize if a line is too long
@@ -139,10 +148,11 @@ export function shikiToMonaco(
139148 const startIndex = result . tokens [ 2 * j ]
140149 const metadata = result . tokens [ 2 * j + 1 ]
141150 const color = normalizeColor ( colorMap [ EncodedTokenMetadata . getForeground ( metadata ) ] || '' )
151+ const fontStyle = EncodedTokenMetadata . getFontStyle ( metadata )
142152
143153 // Because Monaco only support one scope per token,
144- // we workaround this to use color to trace back the scope
145- const scope = findScopeByColor ( color ) || ''
154+ // we workaround this to use color (and font style when available) to trace back the scope
155+ const scope = color ? ( findScopeByColorAndStyle ( color , fontStyle ) || '' ) : ''
146156 tokens . push ( { startIndex, scopes : scope } )
147157 }
148158
@@ -152,3 +162,55 @@ export function shikiToMonaco(
152162 }
153163 }
154164}
165+
166+ function normalizeFontStyleBits ( fontStyle : FontStyle ) : string {
167+ if ( fontStyle <= FontStyle . None )
168+ return ''
169+
170+ const styles : string [ ] = [ ]
171+
172+ if ( fontStyle & FontStyle . Italic )
173+ styles . push ( 'italic' )
174+ if ( fontStyle & FontStyle . Bold )
175+ styles . push ( 'bold' )
176+ if ( fontStyle & FontStyle . Underline )
177+ styles . push ( 'underline' )
178+ if ( fontStyle & FontStyle . Strikethrough )
179+ styles . push ( 'strikethrough' )
180+
181+ return styles . join ( ' ' )
182+ }
183+
184+ const VALID_FONT_STYLES = [
185+ 'italic' ,
186+ 'bold' ,
187+ 'underline' ,
188+ 'strikethrough' ,
189+ ] as const
190+
191+ const VALID_FONT_ALIASES : Record < string , typeof VALID_FONT_STYLES [ number ] > = {
192+ 'line-through' : 'strikethrough' ,
193+ }
194+
195+ function normalizeFontStyleString ( fontStyle ?: string ) : string {
196+ if ( ! fontStyle )
197+ return ''
198+
199+ const styles = new Set (
200+ fontStyle
201+ . split ( / [ \s , ] + / )
202+ . map ( style => style . trim ( ) . toLowerCase ( ) )
203+ . map ( style => VALID_FONT_ALIASES [ style ] || style )
204+ . filter ( Boolean ) ,
205+ )
206+
207+ return VALID_FONT_STYLES
208+ . filter ( style => styles . has ( style ) )
209+ . join ( ' ' )
210+ }
211+
212+ function getColorStyleKey ( color : string , fontStyle : string ) : string {
213+ if ( ! fontStyle )
214+ return color
215+ return `${ color } |${ fontStyle } `
216+ }
0 commit comments