Skip to content

Commit 164bd6f

Browse files
authored
Update shiki and support light themed code block (#410)
1 parent d980c80 commit 164bd6f

10 files changed

+328
-2553
lines changed

docusaurus.config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,11 @@ module.exports = {
6666
routeBasePath: "/",
6767
sidebarPath: require.resolve("./sidebars.js"),
6868
editUrl: "https://github.com/codewars/docs/edit/master/",
69-
beforeDefaultRemarkPlugins: [createHighlighter({ theme: "nord" })],
69+
beforeDefaultRemarkPlugins: [
70+
createHighlighter({
71+
themes: ["github-dark-dimmed", "github-light"],
72+
}),
73+
],
7074
},
7175
blog: false,
7276
theme: {

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@
4444
"postcss-nested": "^5.0.5",
4545
"postcss-preset-env": "^6.7.0",
4646
"prettier": "^2.2.1",
47-
"shiki": "^0.9.3",
47+
"shiki": "^0.10.1",
4848
"tailwindcss": "^2.2.2",
4949
"unist-util-visit": "^2.0.3"
5050
}

src/css/custom.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -181,3 +181,17 @@ html[data-theme="dark"] .DocSearch {
181181
var(--ifm-color-emphasis-100) 100%
182182
);
183183
}
184+
185+
html[data-theme="dark"] pre.github-light {
186+
display: none;
187+
}
188+
189+
html[data-theme="light"] pre.github-dark-dimmed {
190+
display: none;
191+
}
192+
193+
html[data-theme="light"] pre.github-light {
194+
outline-style: solid;
195+
outline-width: 1px;
196+
outline-color: #d1d5db;
197+
}

src/remark/shiki/index.js

Lines changed: 99 additions & 163 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,15 @@
1+
const fs = require("fs");
12
const path = require("path");
23

3-
const { getHighlighter, BUNDLED_LANGUAGES } = require("shiki");
4+
const { getHighlighter } = require("shiki");
45
const visit = require("unist-util-visit");
56

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+
)
7112
);
72-
});
7313

7414
// Remap language id to match Shiki.
7515
const remapLanguageId = (id) => {
@@ -86,113 +26,109 @@ const remapLanguageId = (id) => {
8626
// Use shiki for syntax highlighting.
8727
// TODO Decoration. Like Docusaurus builtin, or https://github.com/shikijs/shiki/issues/5
8828
// or https://github.com/andrewbranch/gatsby-remark-vscode#line-highlighting
89-
const createHighlighter = ({ theme = "nord" } = {}) => {
29+
const createHighlighter = ({ themes = ["nord"] } = {}) => {
9030
// 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+
);
13565

13666
return () => async (tree) => {
13767
const highlighter = await highlighterPromise;
138-
const fg = highlighter.getForegroundColor();
139-
const bg = highlighter.getBackgroundColor();
14068

14169
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 (/^No language registration for/.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("");
19175
node.type = "html";
19276
});
19377
};
19478
};
19579

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 (/^No language registration for/.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+
196132
module.exports = { createHighlighter };
197133

198134
const HTML_ESCAPES = Object.freeze({

src/remark/shiki/languages/README.md

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
11
Additional languages for shiki.
22

3+
- Factor https://github.com/DexterHaslem/vscode-factor
34
- Fortran https://github.com/krvajal/vscode-fortran-support
45
- Haxe https://github.com/vshaxe/haxe-TmLanguage
56
- Idris https://github.com/zjhmale/vscode-idris
67
- Racket https://github.com/pouyakary/vscode-racket
7-
8-
## Included in unreleased version
9-
10-
These should be removed when a new version of shiki is released.
11-
12-
- Nim https://github.com/pragmagic/vscode-nim
13-
- R https://github.com/Ikuyadeu/vscode-R
14-
- Raku https://github.com/Raku/atom-language-perl6

0 commit comments

Comments
 (0)