Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/quarto-core/text-highlighting.ts
3562 views
1
/*
2
* text-highlighting.ts
3
*
4
* Copyright (C) 2020-2022 Posit Software, PBC
5
*/
6
import { join } from "../deno_ral/path.ts";
7
8
import { kDefaultHighlightStyle } from "../command/render/constants.ts";
9
import { kHighlightStyle } from "../config/constants.ts";
10
import { FormatPandoc } from "../config/types.ts";
11
12
import { existsSync } from "../deno_ral/fs.ts";
13
import { resourcePath } from "../core/resources.ts";
14
import { normalizePath } from "../core/path.ts";
15
import { warnOnce } from "../core/log.ts";
16
17
export interface ThemeDescriptor {
18
json: Record<string, unknown>;
19
isAdaptive: boolean;
20
}
21
22
const kDarkSuffix = "dark";
23
const kLightSuffix = "light";
24
25
export function textHighlightThemePath(
26
inputDir: string,
27
theme: string | Record<string, string>,
28
style?: "dark" | "light",
29
) {
30
let resolvedTheme: string;
31
if (typeof theme === "object") {
32
if (style && theme[style]) {
33
resolvedTheme = theme[style] as string;
34
} else {
35
resolvedTheme = theme[Object.keys(theme)[0]] as string;
36
}
37
} else {
38
resolvedTheme = theme as string;
39
}
40
41
// First try the style specific version of the theme, otherwise
42
// fall back to the plain name
43
const names = [
44
`${resolvedTheme}-${style === "dark" ? kDarkSuffix : kLightSuffix}`,
45
resolvedTheme,
46
];
47
48
const themePath = names.map((name) => {
49
return resourcePath(join("pandoc", "highlight-styles", `${name}.theme`));
50
}).find((path) => existsSync(path));
51
52
if (themePath) {
53
// first see if this matches a built in name
54
return themePath;
55
} else {
56
// see if this is a path to a user theme
57
const userThemePath = join(inputDir, resolvedTheme);
58
if (existsSync(userThemePath)) {
59
return normalizePath(userThemePath);
60
}
61
}
62
63
// Could find a path
64
return undefined;
65
}
66
67
export function readHighlightingTheme(
68
inputDir: string,
69
pandoc: FormatPandoc,
70
style: "dark" | "light" | "default",
71
): ThemeDescriptor | undefined {
72
const theme = pandoc[kHighlightStyle] || kDefaultHighlightStyle;
73
if (theme) {
74
const themeRaw = readTheme(inputDir, theme, style);
75
if (themeRaw) {
76
return {
77
json: JSON.parse(themeRaw),
78
isAdaptive: isAdaptiveTheme(theme),
79
};
80
} else {
81
return undefined;
82
}
83
} else {
84
return undefined;
85
}
86
}
87
88
export function hasAdaptiveTheme(pandoc: FormatPandoc) {
89
const theme = pandoc[kHighlightStyle] || kDefaultHighlightStyle;
90
return theme && isAdaptiveTheme(theme);
91
}
92
93
export function hasTextHighlighting(pandoc: FormatPandoc): boolean {
94
const theme = pandoc[kHighlightStyle];
95
return theme !== null;
96
}
97
98
export function isAdaptiveTheme(theme: string | Record<string, string>) {
99
if (typeof theme === "string") {
100
return [
101
"a11y",
102
"arrow",
103
"atom-one",
104
"ayu",
105
"breeze",
106
"github",
107
"gruvbox",
108
"monochrome",
109
].includes(
110
theme,
111
);
112
} else {
113
const keys = Object.keys(theme);
114
return keys.includes("dark") && keys.includes("light");
115
}
116
}
117
118
// Reads the contents of a theme file, falling back if the style specific version isn't available
119
export function readTheme(
120
inputDir: string,
121
theme: string | Record<string, string>,
122
style: "light" | "dark" | "default",
123
) {
124
const themeFile = textHighlightThemePath(
125
inputDir,
126
theme,
127
style === "default" ? undefined : style,
128
);
129
if (!themeFile) {
130
return undefined;
131
}
132
133
if (!existsSync(themeFile)) {
134
warnOnce(`The text highlighting theme ${themeFile} does not exist.`);
135
return undefined;
136
}
137
138
if (Deno.statSync(themeFile).isDirectory) {
139
throw new Error(
140
`The text highlighting theme ${themeFile} is a directory. Please provide a valid theme name or path to a .theme file.`,
141
);
142
}
143
return Deno.readTextFileSync(themeFile);
144
}
145
146
// From https://github.com/jgm/skylighting/blob/a1d02a0db6260c73aaf04aae2e6e18b569caacdc/skylighting-core/src/Skylighting/Format/HTML.hs#L117-L147
147
export const kAbbrevs: Record<string, string> = {
148
"Keyword": "kw",
149
"DataType": "dt",
150
"DecVal": "dv",
151
"BaseN": "bn",
152
"Float": "fl",
153
"Char": "ch",
154
"String": "st",
155
"Comment": "co",
156
"Other": "ot",
157
"Alert": "al",
158
"Function": "fu",
159
"RegionMarker": "re",
160
"Error": "er",
161
"Constant": "cn",
162
"SpecialChar": "sc",
163
"VerbatimString": "vs",
164
"SpecialString": "ss",
165
"Import": "im",
166
"Documentation": "do",
167
"Annotation": "an",
168
"CommentVar": "cv",
169
"Variable": "va",
170
"ControlFlow": "cf",
171
"Operator": "op",
172
"BuiltIn": "bu",
173
"Extension": "ex",
174
"Preprocessor": "pp",
175
"Attribute": "at",
176
"Information": "in",
177
"Warning": "wa",
178
"Normal": "",
179
};
180
181