11import type { Element , Root } from 'hast' ;
22import { toString } from 'hast-util-to-string' ;
3- import { createHighlighter , type Highlighter , type BuiltinTheme , type BundledTheme } from 'shiki' ;
3+ import { createHighlighter , type Highlighter } from 'shiki' ;
44import type { Plugin } from 'unified' ;
55import { visit } from 'unist-util-visit' ;
66
77import {
8+ type ShikiLang ,
9+ type ShikiTheme ,
10+ shikiColorReplacements ,
811 DEFAULT_LANG_ALIASES ,
912 SHIKI_THEMES ,
1013 UNIQUE_LANGS ,
1114 DEFAULT_LANG ,
12- type ShikiLang ,
13- type ShikiTheme ,
15+ DEFAULT_DARK_THEME ,
16+ DEFAULT_LIGHT_THEME ,
17+ DEFAULT_THEMES ,
1418} from './shiki-constants.js' ;
15-
16- const shikiColorReplacements : Partial < Record < BundledTheme , string | Record < string , string > > > = {
17- 'dark-plus' : {
18- '#1e1e1e' : 'transparent' ,
19- '#569cd6' : '#9cdcfe' ,
20- '#c8c8c8' : '#f3f7f6' ,
21- '#d4d4d4' : '#f3f7f6' ,
22- } ,
23- 'github-light-default' : {
24- '#fff' : 'transparent' ,
25- '#ffffff' : 'transparent' ,
26- } ,
27- } ;
19+ import {
20+ getLanguage ,
21+ getLinesToHighlight ,
22+ lineHighlightPattern ,
23+ LINE_HIGHLIGHT_CLASS ,
24+ } from './utils.js' ;
2825
2926export type RehypeSyntaxHighlightingOptions = {
30- theme ?: BuiltinTheme ;
31- themes ?: Record < 'light' | 'dark' , BuiltinTheme > ;
27+ theme ?: ShikiTheme ;
28+ themes ?: Record < 'light' | 'dark' , ShikiTheme > ;
3229 codeStyling ?: 'dark' | 'system' ;
3330} ;
3431
35- const lineHighlightPattern = / \{ ( .* ?) \} / ;
36-
37- function classNameOrEmptyArray ( element : Element ) : string [ ] {
38- const className = element . properties . className ;
39- if ( Array . isArray ( className ) && className . every ( ( el ) => typeof el === 'string' ) ) return className ;
40- return [ ] ;
41- }
42-
4332let highlighterPromise : Promise < Highlighter > | null = null ;
4433
4534async function getHighlighter ( ) : Promise < Highlighter > {
4635 if ( ! highlighterPromise ) {
4736 highlighterPromise = createHighlighter ( {
48- themes : [ 'github-light-default' , 'dark-plus' ] ,
37+ themes : DEFAULT_THEMES ,
4938 langs : UNIQUE_LANGS ,
5039 } ) ;
5140 }
@@ -57,14 +46,18 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
5746) => {
5847 return async ( tree ) => {
5948 const highlighter = await getHighlighter ( ) ;
60- if ( options . theme ) {
49+ if ( options . theme && ! DEFAULT_THEMES . includes ( options . theme ) ) {
6150 await highlighter . loadTheme ( options . theme ) ;
6251 }
6352 if ( options . themes ) {
64- await Promise . all ( [
65- highlighter . loadTheme ( options . themes . dark ) ,
66- highlighter . loadTheme ( options . themes . light ) ,
67- ] ) ;
53+ const promises : Promise < void > [ ] = [ ] ;
54+ if ( ! DEFAULT_THEMES . includes ( options . themes . dark ) ) {
55+ promises . push ( highlighter . loadTheme ( options . themes . dark ) ) ;
56+ }
57+ if ( ! DEFAULT_THEMES . includes ( options . themes . light ) ) {
58+ promises . push ( highlighter . loadTheme ( options . themes . light ) ) ;
59+ }
60+ await Promise . all ( promises ) ;
6861 }
6962
7063 visit ( tree , 'element' , ( node , index , parent ) => {
@@ -106,8 +99,8 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
10699 light :
107100 options . themes ?. light ??
108101 options . theme ??
109- ( options . codeStyling === 'dark' ? 'dark-plus' : 'github-light-default' ) ,
110- dark : options . themes ?. dark ?? options . theme ?? 'dark-plus' ,
102+ ( options . codeStyling === 'dark' ? DEFAULT_DARK_THEME : DEFAULT_LIGHT_THEME ) ,
103+ dark : options . themes ?. dark ?? options . theme ?? DEFAULT_DARK_THEME ,
111104 } ,
112105 colorReplacements : shikiColorReplacements ,
113106 tabindex : false ,
@@ -134,9 +127,9 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
134127 lineNumber ++ ;
135128 if ( linesToHighlight . includes ( lineNumber ) ) {
136129 if ( typeof span . properties . class === 'string' ) {
137- span . properties . class += ' line-highlight' ;
130+ span . properties . class += ' ' + LINE_HIGHLIGHT_CLASS ;
138131 } else {
139- span . properties . class = [ ...span . properties . class , 'line-highlight' ] ;
132+ span . properties . class = [ ...span . properties . class , LINE_HIGHLIGHT_CLASS ] ;
140133 }
141134 }
142135 } ) ;
@@ -165,50 +158,4 @@ export const rehypeSyntaxHighlighting: Plugin<[RehypeSyntaxHighlightingOptions?]
165158 } ;
166159} ;
167160
168- function getLanguage ( node : Element , aliases : Record < string , ShikiLang > ) : ShikiLang | undefined {
169- const className = classNameOrEmptyArray ( node ) ;
170-
171- for ( const classListItem of className ) {
172- if ( classListItem . startsWith ( 'language-' ) ) {
173- const lang = classListItem . slice ( 9 ) . toLowerCase ( ) ;
174- if ( lang ) return aliases [ lang ] ;
175- }
176- }
177-
178- return undefined ;
179- }
180-
181- function getLinesToHighlight ( node : Element , maxLines : number ) : number [ ] {
182- const meta =
183- typeof node . data ?. meta === 'string'
184- ? node . data . meta
185- : classNameOrEmptyArray ( node ) . reduce ( ( acc , item ) => acc + ' ' + item , '' ) ;
186- if ( ! meta ) return [ ] ;
187-
188- const content = meta . match ( lineHighlightPattern ) ?. [ 1 ] ?. trim ( ) ;
189- if ( ! content ) return [ ] ;
190-
191- const lineNumbers = new Set < number > ( ) ;
192-
193- content . split ( ',' ) . forEach ( ( part ) => {
194- const [ start , end ] = part . split ( '-' ) . map ( ( num ) => {
195- const trimmed = num . trim ( ) ;
196- if ( ! / ^ \d + $ / . test ( trimmed ) ) return undefined ;
197- const parsed = parseInt ( trimmed , 10 ) ;
198- return parsed > maxLines ? maxLines : parsed ;
199- } ) ;
200-
201- if ( ! start ) return ;
202- const endLine = end ?? start ;
203-
204- if ( endLine < start ) return ;
205- const max = Math . min ( endLine , maxLines ) ;
206- for ( let i = start ; i <= max ; i ++ ) {
207- lineNumbers . add ( i ) ;
208- }
209- } ) ;
210-
211- return Array . from ( lineNumbers ) . sort ( ( a , b ) => a - b ) ;
212- }
213-
214161export { UNIQUE_LANGS , DEFAULT_LANG_ALIASES , SHIKI_THEMES , ShikiLang , ShikiTheme } ;
0 commit comments