Skip to content

Commit 8ceee96

Browse files
committed
Add terminal highlighting (fix #28)
1 parent 89402fb commit 8ceee96

18 files changed

+375
-17
lines changed

.changeset/moody-trainers-confess.md

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@code-hike/lighter": patch
3+
---
4+
5+
Add terminal highlighting

lib/dist/browser.esm.mjs

+1-1
Large diffs are not rendered by default.

lib/dist/index.cjs.js

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

lib/dist/index.esm.mjs

+1-1
Large diffs are not rendered by default.

lib/dist/worker.esm.mjs

+1-1
Large diffs are not rendered by default.

lib/package.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -52,5 +52,8 @@
5252
},
5353
"repository": "https://github.com/code-hike/lighter",
5454
"homepage": "https://lighter.codehike.org",
55-
"funding": "https://github.com/code-hike/lighter?sponsor=1"
55+
"funding": "https://github.com/code-hike/lighter?sponsor=1",
56+
"dependencies": {
57+
"ansi-sequence-parser": "^1.1.0"
58+
}
5659
}

lib/src/highlighter.ts

+7-3
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@ import onig from "./wasm";
1515

1616
let registry: Registry | null = null;
1717

18+
function isGramarless(lang: LanguageAlias) {
19+
return lang == "text" || lang == "terminal";
20+
}
21+
1822
export function preloadGrammars(languages: LanguageAlias[]) {
1923
// initialize the registry the first time
2024
if (!registry) {
@@ -29,7 +33,7 @@ export function preloadGrammars(languages: LanguageAlias[]) {
2933
}
3034

3135
const promises = languages
32-
.filter((alias) => alias != "text")
36+
.filter((alias) => !isGramarless(alias))
3337
.map((alias) => {
3438
const langData = aliasToLangData(alias);
3539
if (!langData) {
@@ -45,9 +49,9 @@ export function getGrammar(alias: LanguageAlias): {
4549
langId: string;
4650
grammar: IGrammar | null;
4751
} {
48-
if (alias == "text") {
52+
if (isGramarless(alias)) {
4953
return {
50-
langId: "text",
54+
langId: alias,
5155
grammar: null,
5256
};
5357
}

lib/src/index.ts

+13-8
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
Tokens,
2828
Token,
2929
} from "./annotations";
30+
import { getTerminalStyle, highlightTerminal } from "./terminal";
3031

3132
type Config = { scopes?: boolean };
3233
type AnnotatedConfig = { annotations: Annotation[] } & Config;
@@ -140,28 +141,32 @@ export function highlightSync(
140141
const lines =
141142
langId == "text"
142143
? highlightText(theCode)
144+
: langId == "terminal"
145+
? highlightTerminal(theCode, theme)
143146
: config?.scopes
144147
? highlightTokensWithScopes(theCode, grammar, theme)
145148
: highlightTokens(theCode, grammar, theme);
146149

150+
const style =
151+
langId == "terminal"
152+
? getTerminalStyle(theme)
153+
: {
154+
color: theme.foreground,
155+
background: theme.background,
156+
};
157+
147158
if (isAnnotatedConfig(config)) {
148159
const annotations = config?.annotations || [];
149160
return {
150161
lines: applyAnnotations(lines, annotations),
151162
lang: langId,
152-
style: {
153-
color: theme.foreground,
154-
background: theme.background,
155-
},
163+
style,
156164
};
157165
} else {
158166
return {
159167
lines: lines,
160168
lang: langId,
161-
style: {
162-
color: theme.foreground,
163-
background: theme.background,
164-
},
169+
style,
165170
};
166171
}
167172
}

lib/src/language-data.ts

+1
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,7 @@ export const LANG_NAMES = [
180180
"system-verilog",
181181
"tasl",
182182
"tcl",
183+
"terminal",
183184
"tex",
184185
"text",
185186
"toml",

lib/src/terminal.ts

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { FinalTheme } from "./theme";
2+
import { Token } from "./annotations";
3+
import { Color, createAnsiSequenceParser } from "ansi-sequence-parser";
4+
import { getColor } from "./theme-colors";
5+
6+
export function highlightTerminal(code: string, theme: FinalTheme): Token[][] {
7+
const parser = createAnsiSequenceParser();
8+
const lines = code
9+
.split(/\r?\n|\r/g)
10+
.map((line) => highlightLine(parser, line, theme));
11+
12+
return lines;
13+
}
14+
15+
function highlightLine(
16+
parser: ReturnType<typeof createAnsiSequenceParser>,
17+
line: string,
18+
theme: FinalTheme
19+
) {
20+
const ansiLine = parser.parse(line);
21+
const tokens = ansiLine.map(
22+
({ value, foreground, background, decorations }) => {
23+
const color = getAnsiColor(foreground, theme);
24+
const style = {};
25+
if (color) {
26+
style["color"] = color;
27+
}
28+
const backgroundColor = getAnsiColor(background, theme);
29+
if (backgroundColor) {
30+
style["background"] = backgroundColor;
31+
}
32+
if (decorations.has("bold")) {
33+
style["fontWeight"] = "bold";
34+
}
35+
if (decorations.has("italic")) {
36+
style["fontStyle"] = "italic";
37+
}
38+
if (decorations.has("underline")) {
39+
style["textDecoration"] = "underline";
40+
}
41+
if (decorations.has("strikethrough")) {
42+
style["textDecoration"] = "line-through";
43+
}
44+
if (decorations.has("reverse")) {
45+
style["color"] = backgroundColor;
46+
style["background"] = color;
47+
}
48+
if (decorations.has("dim")) {
49+
style["opacity"] = 0.5;
50+
}
51+
52+
return {
53+
content: value,
54+
style,
55+
};
56+
}
57+
);
58+
return tokens;
59+
}
60+
61+
function getAnsiColor(color: Color | null, theme: FinalTheme) {
62+
if (!color) return undefined;
63+
if (color.type === "named") {
64+
// capitalize name
65+
const name = color.name[0].toUpperCase() + color.name.slice(1);
66+
return getColor(theme, "terminal.ansi" + name);
67+
}
68+
if (color.type === "rgb") {
69+
const [r, g, b] = color.rgb;
70+
return `rgb(${r}, ${g}, ${b})`;
71+
}
72+
return undefined;
73+
}
74+
75+
export function getTerminalStyle(theme: FinalTheme) {
76+
return {
77+
color: getColor(theme, "terminal.foreground"),
78+
background: getColor(theme, "terminal.background"),
79+
};
80+
}

lib/src/theme-colors.ts

+84
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,88 @@ const defaults = {
112112
dark: [transparent, "editor.background", 0.9],
113113
light: [transparent, "editor.background", 0.9],
114114
},
115+
116+
// terminal colors
117+
"terminal.background": "editor.background",
118+
"terminal.foreground": "editor.foreground",
119+
"terminal.ansiBlack": {
120+
dark: "#000000",
121+
light: "#000000",
122+
hc: "#000000",
123+
},
124+
"terminal.ansiRed": {
125+
dark: "#cd3131",
126+
light: "#cd3131",
127+
hc: "#cd0000",
128+
},
129+
"terminal.ansiGreen": {
130+
dark: "#0DBC79",
131+
light: "#00BC00",
132+
hc: "#00cd00",
133+
},
134+
"terminal.ansiYellow": {
135+
dark: "#e5e510",
136+
light: "#949800",
137+
hc: "#cdcd00",
138+
},
139+
"terminal.ansiBlue": {
140+
dark: "#2472c8",
141+
light: "#0451a5",
142+
hc: "#0000ee",
143+
},
144+
"terminal.ansiMagenta": {
145+
dark: "#bc3fbc",
146+
light: "#bc05bc",
147+
hc: "#cd00cd",
148+
},
149+
"terminal.ansiCyan": {
150+
dark: "#11a8cd",
151+
light: "#0598bc",
152+
hc: "#00cdcd",
153+
},
154+
"terminal.ansiWhite": {
155+
dark: "#e5e5e5",
156+
light: "#555555",
157+
hc: "#e5e5e5",
158+
},
159+
"terminal.ansiBrightBlack": {
160+
dark: "#666666",
161+
light: "#666666",
162+
hc: "#7f7f7f",
163+
},
164+
"terminal.ansiBrightRed": {
165+
dark: "#f14c4c",
166+
light: "#cd3131",
167+
hc: "#ff0000",
168+
},
169+
"terminal.ansiBrightGreen": {
170+
dark: "#23d18b",
171+
light: "#14CE14",
172+
hc: "#00ff00",
173+
},
174+
"terminal.ansiBrightYellow": {
175+
dark: "#f5f543",
176+
light: "#b5ba00",
177+
hc: "#ffff00",
178+
},
179+
"terminal.ansiBrightBlue": {
180+
dark: "#3b8eea",
181+
light: "#0451a5",
182+
hc: "#5c5cff",
183+
},
184+
"terminal.ansiBrightMagenta": {
185+
dark: "#d670d6",
186+
light: "#bc05bc",
187+
hc: "#ff00ff",
188+
},
189+
"terminal.ansiBrightCyan": {
190+
dark: "#29b8db",
191+
light: "#0598bc",
192+
hc: "#00ffff",
193+
},
194+
"terminal.ansiBrightWhite": {
195+
dark: "#e5e5e5",
196+
light: "#a5a5a5",
197+
hc: "#ffffff",
198+
},
115199
};

lib/test/__snapshots__/browser.test.ts.snap

+36
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,42 @@ exports[`highlight js 1`] = `
426426
}
427427
`;
428428

429+
exports[`highlight terminal 1`] = `
430+
{
431+
"lang": "terminal",
432+
"lines": [
433+
[
434+
{
435+
"content": "Foo",
436+
"style": {},
437+
},
438+
],
439+
],
440+
"style": {
441+
"background": "#0d1117",
442+
"color": "#c9d1d9",
443+
},
444+
}
445+
`;
446+
447+
exports[`highlight terminal ansi codes 1`] = `
448+
{
449+
"lang": "terminal",
450+
"lines": [
451+
[
452+
{
453+
"content": "Foo",
454+
"style": {},
455+
},
456+
],
457+
],
458+
"style": {
459+
"background": "#0d1117",
460+
"color": "#c9d1d9",
461+
},
462+
}
463+
`;
464+
429465
exports[`highlight text 1`] = `
430466
{
431467
"lang": "text",

lib/test/__snapshots__/cjs.test.ts.snap

+36
Original file line numberDiff line numberDiff line change
@@ -426,6 +426,42 @@ exports[`highlight js 1`] = `
426426
}
427427
`;
428428

429+
exports[`highlight terminal 1`] = `
430+
{
431+
"lang": "terminal",
432+
"lines": [
433+
[
434+
{
435+
"content": "Foo",
436+
"style": {},
437+
},
438+
],
439+
],
440+
"style": {
441+
"background": "#0d1117",
442+
"color": "#c9d1d9",
443+
},
444+
}
445+
`;
446+
447+
exports[`highlight terminal ansi codes 1`] = `
448+
{
449+
"lang": "terminal",
450+
"lines": [
451+
[
452+
{
453+
"content": "Foo",
454+
"style": {},
455+
},
456+
],
457+
],
458+
"style": {
459+
"background": "#0d1117",
460+
"color": "#c9d1d9",
461+
},
462+
}
463+
`;
464+
429465
exports[`highlight text 1`] = `
430466
{
431467
"lang": "text",

0 commit comments

Comments
 (0)