1
+ const fs = require ( "fs" ) ;
1
2
const path = require ( "path" ) ;
2
3
3
- const { getHighlighter, BUNDLED_LANGUAGES } = require ( "shiki" ) ;
4
+ const { getHighlighter } = require ( "shiki" ) ;
4
5
const visit = require ( "unist-util-visit" ) ;
5
6
6
- const syntaxPath = ( name ) =>
7
- path . resolve ( __dirname , `./languages/${ name } .tmLanguage.json` ) ;
8
-
9
- const langs = new Set ( [
10
- "asm" ,
11
- "c" ,
12
- "clojure" ,
13
- "cobol" ,
14
- "coffee" ,
15
- "cpp" ,
16
- "crystal" ,
17
- "csharp" ,
18
- "css" ,
19
- "d" ,
20
- "dart" ,
21
- "elixir" ,
22
- "elm" ,
23
- "erlang" ,
24
- "fsharp" ,
25
- "go" ,
26
- "groovy" ,
27
- "haskell" ,
28
- "html" ,
29
- "java" ,
30
- "javascript" ,
31
- "json" ,
32
- "jsonc" ,
33
- "jsx" ,
34
- "julia" ,
35
- "kotlin" ,
36
- "latex" ,
37
- "lisp" ,
38
- "lua" ,
39
- "markdown" ,
40
- "mdx" ,
41
- "nim" ,
42
- "objc" ,
43
- "ocaml" ,
44
- "pascal" ,
45
- "perl" ,
46
- "php" ,
47
- "powershell" ,
48
- "prolog" ,
49
- "purescript" ,
50
- "python" ,
51
- "r" ,
52
- "raku" ,
53
- "ruby" ,
54
- "rust" ,
55
- "scala" ,
56
- "shell" ,
57
- "solidity" ,
58
- "sql" ,
59
- "swift" ,
60
- "toml" ,
61
- "tsx" ,
62
- "typescript" ,
63
- "vb" ,
64
- "yaml" ,
65
- ] ) ;
66
-
67
- const languages = BUNDLED_LANGUAGES . filter ( ( lang ) => {
68
- return (
69
- langs . has ( lang . id ) ||
70
- ( lang . aliases && lang . aliases . some ( ( id ) => langs . has ( id ) ) )
7
+ const grammar = ( name ) =>
8
+ JSON . parse (
9
+ fs . readFileSync (
10
+ path . resolve ( __dirname , `./languages/${ name } .tmLanguage.json` )
11
+ )
71
12
) ;
72
- } ) ;
73
13
74
14
// Remap language id to match Shiki.
75
15
const remapLanguageId = ( id ) => {
@@ -86,113 +26,109 @@ const remapLanguageId = (id) => {
86
26
// Use shiki for syntax highlighting.
87
27
// TODO Decoration. Like Docusaurus builtin, or https://github.com/shikijs/shiki/issues/5
88
28
// or https://github.com/andrewbranch/gatsby-remark-vscode#line-highlighting
89
- const createHighlighter = ( { theme = "nord" } = { } ) => {
29
+ const createHighlighter = ( { themes = [ "nord" ] } = { } ) => {
90
30
// Reuse the same instance
91
- const highlighterPromise = getHighlighter ( {
92
- theme,
93
- langs : languages . concat ( [
94
- // See languages/README.md for sources.
95
- // TODO Open PR upstream
96
- {
97
- id : "fortran" ,
98
- scopeName : "source.fortran.free" ,
99
- path : syntaxPath ( "fortran" ) ,
100
- } ,
101
- {
102
- id : "idris" ,
103
- scopeName : "source.idris" ,
104
- path : syntaxPath ( "idris" ) ,
105
- } ,
106
- {
107
- id : "haxe" ,
108
- scopeName : "source.hx" ,
109
- path : syntaxPath ( "haxe" ) ,
110
- } ,
111
- {
112
- id : "racket" ,
113
- scopeName : "source.racket" ,
114
- path : syntaxPath ( "racket" ) ,
115
- } ,
116
-
117
- // TODO Remove these after a new shiki is released
118
- {
119
- id : "nim" ,
120
- scopeName : "source.nim" ,
121
- path : syntaxPath ( "nim" ) ,
122
- } ,
123
- {
124
- id : "r" ,
125
- scopeName : "source.r" ,
126
- path : syntaxPath ( "r" ) ,
127
- } ,
128
- {
129
- id : "raku" ,
130
- scopeName : "source.perl.6" ,
131
- path : syntaxPath ( "raku" ) ,
132
- } ,
133
- ] ) ,
134
- } ) ;
31
+ const highlighterPromise = getHighlighter ( { themes } ) . then (
32
+ async ( highlighter ) => {
33
+ const langs = [
34
+ // See languages/README.md for sources.
35
+ {
36
+ id : "factor" ,
37
+ scopeName : "source.factor" ,
38
+ grammar : grammar ( "factor" ) ,
39
+ } ,
40
+ {
41
+ id : "fortran" ,
42
+ scopeName : "source.fortran.free" ,
43
+ grammar : grammar ( "fortran" ) ,
44
+ } ,
45
+ {
46
+ id : "idris" ,
47
+ scopeName : "source.idris" ,
48
+ grammar : grammar ( "idris" ) ,
49
+ } ,
50
+ {
51
+ id : "haxe" ,
52
+ scopeName : "source.hx" ,
53
+ grammar : grammar ( "haxe" ) ,
54
+ } ,
55
+ {
56
+ id : "racket" ,
57
+ scopeName : "source.racket" ,
58
+ grammar : grammar ( "racket" ) ,
59
+ } ,
60
+ ] ;
61
+ for ( const lang of langs ) await highlighter . loadLanguage ( lang ) ;
62
+ return highlighter ;
63
+ }
64
+ ) ;
135
65
136
66
return ( ) => async ( tree ) => {
137
67
const highlighter = await highlighterPromise ;
138
- const fg = highlighter . getForegroundColor ( ) ;
139
- const bg = highlighter . getBackgroundColor ( ) ;
140
68
141
69
visit ( tree , "code" , ( node ) => {
142
- // const meta = node.meta;
143
- const lang = node . lang || "text" ;
144
- const code = node . value ;
145
- let tokenLines = null ;
146
- try {
147
- tokenLines = highlighter . codeToThemedTokens (
148
- code ,
149
- remapLanguageId ( lang )
150
- ) ;
151
- } catch ( e ) {
152
- if ( / ^ N o l a n g u a g e r e g i s t r a t i o n f o r / . test ( e . message ) ) {
153
- console . warn ( `shiki: ${ e . message } ` ) ;
154
- console . warn ( `shiki: Falling back to "text"` ) ;
155
- tokenLines = highlighter . codeToThemedTokens ( code , "text" ) ;
156
- } else {
157
- throw e ;
158
- }
159
- }
160
-
161
- let html = `<pre class="${ theme } " style="background-color: ${ bg } ">` ;
162
- html += `<code class="language-${ lang } ">` ;
163
- for ( const line of tokenLines ) {
164
- html += `<span>` ;
165
- for ( const token of line ) {
166
- const css = [ `color: ${ token . color || fg } ` ] ;
167
- switch ( token . fontStyle ) {
168
- case - 1 : // NotSet
169
- case 0 : // None
170
- break ;
171
- case 1 : // Italic
172
- css . push ( `font-style: italic` ) ;
173
- break ;
174
- case 2 : // Bold
175
- css . push ( `font-weight: bold` ) ;
176
- break ;
177
- case 4 : // Underline
178
- css . push ( `text-decoration: underline` ) ;
179
- break ;
180
- }
181
- const content = escapeHtml ( token . content ) ;
182
- html += `<span style="${ css . join ( "; " ) } ">${ content } </span>` ;
183
- }
184
- html += `</span>\n` ;
185
- }
186
-
187
- html = html . replace ( / \n * $ / , "" ) ; // get rid of trailing new lines
188
- html += `</code></pre>` ;
189
-
190
- node . value = html ;
70
+ node . value = themes
71
+ . map ( ( theme ) =>
72
+ codeToHtml ( highlighter , node . value , node . lang || "text" , theme )
73
+ )
74
+ . join ( "" ) ;
191
75
node . type = "html" ;
192
76
} ) ;
193
77
} ;
194
78
} ;
195
79
80
+ const codeToHtml = ( highlighter , code , lang , theme ) => {
81
+ let tokenLines = null ;
82
+ try {
83
+ tokenLines = highlighter . codeToThemedTokens (
84
+ code ,
85
+ remapLanguageId ( lang ) ,
86
+ theme
87
+ ) ;
88
+ } catch ( e ) {
89
+ if ( / ^ N o l a n g u a g e r e g i s t r a t i o n f o r / . test ( e . message ) ) {
90
+ console . warn ( `shiki: ${ e . message } ` ) ;
91
+ console . warn ( `shiki: Falling back to "text"` ) ;
92
+ tokenLines = highlighter . codeToThemedTokens ( code , "text" , theme ) ;
93
+ } else {
94
+ throw e ;
95
+ }
96
+ }
97
+
98
+ const fg = highlighter . getForegroundColor ( theme ) ;
99
+ const bg = highlighter . getBackgroundColor ( theme ) ;
100
+
101
+ let html = `<pre class="${ theme } " style="background-color: ${ bg } ">` ;
102
+ html += `<code class="language-${ lang } ">` ;
103
+ for ( const line of tokenLines ) {
104
+ html += `<span>` ;
105
+ for ( const token of line ) {
106
+ const css = [ `color: ${ token . color || fg } ` ] ;
107
+ switch ( token . fontStyle ) {
108
+ case - 1 : // NotSet
109
+ case 0 : // None
110
+ break ;
111
+ case 1 : // Italic
112
+ css . push ( `font-style: italic` ) ;
113
+ break ;
114
+ case 2 : // Bold
115
+ css . push ( `font-weight: bold` ) ;
116
+ break ;
117
+ case 4 : // Underline
118
+ css . push ( `text-decoration: underline` ) ;
119
+ break ;
120
+ }
121
+ const content = escapeHtml ( token . content ) ;
122
+ html += `<span style="${ css . join ( "; " ) } ">${ content } </span>` ;
123
+ }
124
+ html += `</span>\n` ;
125
+ }
126
+
127
+ html = html . replace ( / \n * $ / , "" ) ; // get rid of trailing new lines
128
+ html += `</code></pre>` ;
129
+ return html ;
130
+ } ;
131
+
196
132
module . exports = { createHighlighter } ;
197
133
198
134
const HTML_ESCAPES = Object . freeze ( {
0 commit comments