@shikijs/transformers 3.22.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +173 -158
- package/dist/index.mjs +653 -742
- package/package.json +8 -5
package/dist/index.mjs
CHANGED
|
@@ -1,828 +1,739 @@
|
|
|
1
|
+
//#region src/shared/parse-comments.ts
|
|
2
|
+
/**
|
|
3
|
+
* some comment formats have to be located at the end of line
|
|
4
|
+
* hence we can skip matching them for other tokens
|
|
5
|
+
*/
|
|
1
6
|
const matchers = [
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
* for multi-line comments like this
|
|
7
|
-
*/
|
|
8
|
-
[/^(\*)(.+)$/, true]
|
|
7
|
+
[/^(<!--)(.+)(-->)$/, false],
|
|
8
|
+
[/^(\/\*)(.+)(\*\/)$/, false],
|
|
9
|
+
[/^(\/\/|["'#]|;{1,2}|%{1,2}|--)(.*)$/, true],
|
|
10
|
+
[/^(\*)(.+)$/, true]
|
|
9
11
|
];
|
|
12
|
+
/**
|
|
13
|
+
* @param lines line tokens
|
|
14
|
+
* @param jsx enable JSX parsing
|
|
15
|
+
* @param matchAlgorithm matching algorithm
|
|
16
|
+
*/
|
|
10
17
|
function parseComments(lines, jsx, matchAlgorithm) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
out.push({
|
|
94
|
-
info: match,
|
|
95
|
-
line,
|
|
96
|
-
token,
|
|
97
|
-
isLineCommentOnly: elements.length === 3 && token.children.length === 1,
|
|
98
|
-
isJsxStyle,
|
|
99
|
-
additionalTokens
|
|
100
|
-
});
|
|
101
|
-
} else {
|
|
102
|
-
out.push({
|
|
103
|
-
info: match,
|
|
104
|
-
line,
|
|
105
|
-
token,
|
|
106
|
-
isLineCommentOnly: elements.length === 1 && token.children.length === 1,
|
|
107
|
-
isJsxStyle: false,
|
|
108
|
-
additionalTokens
|
|
109
|
-
});
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
return out;
|
|
18
|
+
const out = [];
|
|
19
|
+
for (const line of lines) {
|
|
20
|
+
if (matchAlgorithm === "v3") {
|
|
21
|
+
const splittedElements = line.children.flatMap((element, idx) => {
|
|
22
|
+
if (element.type !== "element") return element;
|
|
23
|
+
const token = element.children[0];
|
|
24
|
+
if (token.type !== "text") return element;
|
|
25
|
+
const isLast = idx === line.children.length - 1;
|
|
26
|
+
if (!matchToken(token.value, isLast)) return element;
|
|
27
|
+
const rawSplits = token.value.split(/(\s+\/\/)/);
|
|
28
|
+
if (rawSplits.length <= 1) return element;
|
|
29
|
+
let splits = [rawSplits[0]];
|
|
30
|
+
for (let i = 1; i < rawSplits.length; i += 2) splits.push(rawSplits[i] + (rawSplits[i + 1] || ""));
|
|
31
|
+
splits = splits.filter(Boolean);
|
|
32
|
+
if (splits.length <= 1) return element;
|
|
33
|
+
return splits.map((split) => {
|
|
34
|
+
return {
|
|
35
|
+
...element,
|
|
36
|
+
children: [{
|
|
37
|
+
type: "text",
|
|
38
|
+
value: split
|
|
39
|
+
}]
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
if (splittedElements.length !== line.children.length) line.children = splittedElements;
|
|
44
|
+
}
|
|
45
|
+
const elements = line.children;
|
|
46
|
+
let start = elements.length - 1;
|
|
47
|
+
if (matchAlgorithm === "v1") start = 0;
|
|
48
|
+
else if (jsx) start = elements.length - 2;
|
|
49
|
+
for (let i = Math.max(start, 0); i < elements.length; i++) {
|
|
50
|
+
const token = elements[i];
|
|
51
|
+
if (token.type !== "element") continue;
|
|
52
|
+
const head = token.children.at(0);
|
|
53
|
+
if (head?.type !== "text") continue;
|
|
54
|
+
const isLast = i === elements.length - 1;
|
|
55
|
+
let match = matchToken(head.value, isLast);
|
|
56
|
+
let additionalTokens;
|
|
57
|
+
if (!match && i > 0 && head.value.trim().startsWith("[!code")) {
|
|
58
|
+
const prevToken = elements[i - 1];
|
|
59
|
+
if (prevToken?.type === "element") {
|
|
60
|
+
const prevHead = prevToken.children.at(0);
|
|
61
|
+
if (prevHead?.type === "text" && prevHead.value.includes("//")) {
|
|
62
|
+
const combinedMatch = matchToken(prevHead.value + head.value, isLast);
|
|
63
|
+
if (combinedMatch) {
|
|
64
|
+
match = combinedMatch;
|
|
65
|
+
out.push({
|
|
66
|
+
info: combinedMatch,
|
|
67
|
+
line,
|
|
68
|
+
token: prevToken,
|
|
69
|
+
isLineCommentOnly: elements.length === 2 && prevToken.children.length === 1 && token.children.length === 1,
|
|
70
|
+
isJsxStyle: false,
|
|
71
|
+
additionalTokens: [token]
|
|
72
|
+
});
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
if (!match) continue;
|
|
79
|
+
if (jsx && !isLast && i !== 0) {
|
|
80
|
+
const isJsxStyle = isValue(elements[i - 1], "{") && isValue(elements[i + 1], "}");
|
|
81
|
+
out.push({
|
|
82
|
+
info: match,
|
|
83
|
+
line,
|
|
84
|
+
token,
|
|
85
|
+
isLineCommentOnly: elements.length === 3 && token.children.length === 1,
|
|
86
|
+
isJsxStyle,
|
|
87
|
+
additionalTokens
|
|
88
|
+
});
|
|
89
|
+
} else out.push({
|
|
90
|
+
info: match,
|
|
91
|
+
line,
|
|
92
|
+
token,
|
|
93
|
+
isLineCommentOnly: elements.length === 1 && token.children.length === 1,
|
|
94
|
+
isJsxStyle: false,
|
|
95
|
+
additionalTokens
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
114
100
|
}
|
|
115
101
|
function isValue(element, value) {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
102
|
+
if (element.type !== "element") return false;
|
|
103
|
+
const text = element.children[0];
|
|
104
|
+
if (text.type !== "text") return false;
|
|
105
|
+
return text.value.trim() === value;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* @param text text value of comment node
|
|
109
|
+
* @param isLast whether the token is located at the end of line
|
|
110
|
+
*/
|
|
123
111
|
function matchToken(text, isLast) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
112
|
+
let trimmed = text.trimStart();
|
|
113
|
+
const spaceFront = text.length - trimmed.length;
|
|
114
|
+
trimmed = trimmed.trimEnd();
|
|
115
|
+
const spaceEnd = text.length - trimmed.length - spaceFront;
|
|
116
|
+
for (const [matcher, endOfLine] of matchers) {
|
|
117
|
+
if (endOfLine && !isLast) continue;
|
|
118
|
+
const result = matcher.exec(trimmed);
|
|
119
|
+
if (!result) continue;
|
|
120
|
+
return [
|
|
121
|
+
" ".repeat(spaceFront) + result[1],
|
|
122
|
+
result[2],
|
|
123
|
+
result[3] ? result[3] + " ".repeat(spaceEnd) : void 0
|
|
124
|
+
];
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Remove empty comment prefixes at line end, e.g. `// `
|
|
129
|
+
*
|
|
130
|
+
* For matchAlgorithm v1
|
|
131
|
+
*/
|
|
141
132
|
function v1ClearEndCommentPrefix(text) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
}
|
|
146
|
-
return text;
|
|
133
|
+
const match = text.match(/(?:\/\/|["'#]|;{1,2}|%{1,2}|--)(\s*)$/);
|
|
134
|
+
if (match && match[1].trim().length === 0) return text.slice(0, match.index);
|
|
135
|
+
return text;
|
|
147
136
|
}
|
|
148
137
|
|
|
138
|
+
//#endregion
|
|
139
|
+
//#region src/shared/notation-transformer.ts
|
|
149
140
|
function createCommentNotationTransformer(name, regex, onMatch, matchAlgorithm) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (additionalHead?.type === "text") {
|
|
206
|
-
additionalHead.value = "";
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
for (const line of linesToRemove) {
|
|
214
|
-
const index = code.children.indexOf(line);
|
|
215
|
-
const nextLine = code.children[index + 1];
|
|
216
|
-
let removeLength = 1;
|
|
217
|
-
if (nextLine?.type === "text" && nextLine?.value === "\n")
|
|
218
|
-
removeLength = 2;
|
|
219
|
-
code.children.splice(index, removeLength);
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
};
|
|
141
|
+
if (matchAlgorithm == null) matchAlgorithm = "v3";
|
|
142
|
+
return {
|
|
143
|
+
name,
|
|
144
|
+
code(code) {
|
|
145
|
+
const lines = code.children.filter((i) => i.type === "element");
|
|
146
|
+
const linesToRemove = [];
|
|
147
|
+
code.data ??= {};
|
|
148
|
+
const data = code.data;
|
|
149
|
+
data._shiki_notation ??= parseComments(lines, ["jsx", "tsx"].includes(this.options.lang), matchAlgorithm);
|
|
150
|
+
const parsed = data._shiki_notation;
|
|
151
|
+
for (const comment of parsed) {
|
|
152
|
+
if (comment.info[1].length === 0) continue;
|
|
153
|
+
let lineIdx = lines.indexOf(comment.line);
|
|
154
|
+
if (comment.isLineCommentOnly && matchAlgorithm !== "v1") lineIdx++;
|
|
155
|
+
let replaced = false;
|
|
156
|
+
comment.info[1] = comment.info[1].replace(regex, (...match) => {
|
|
157
|
+
if (onMatch.call(this, match, comment.line, comment.token, lines, lineIdx)) {
|
|
158
|
+
replaced = true;
|
|
159
|
+
return "";
|
|
160
|
+
}
|
|
161
|
+
return match[0];
|
|
162
|
+
});
|
|
163
|
+
if (!replaced) continue;
|
|
164
|
+
if (matchAlgorithm === "v1") comment.info[1] = v1ClearEndCommentPrefix(comment.info[1]);
|
|
165
|
+
const isEmpty = comment.info[1].trim().length === 0;
|
|
166
|
+
if (isEmpty) comment.info[1] = "";
|
|
167
|
+
if (isEmpty && comment.isLineCommentOnly) linesToRemove.push(comment.line);
|
|
168
|
+
else if (isEmpty && comment.isJsxStyle) comment.line.children.splice(comment.line.children.indexOf(comment.token) - 1, 3);
|
|
169
|
+
else if (isEmpty) {
|
|
170
|
+
if (comment.additionalTokens) for (let j = comment.additionalTokens.length - 1; j >= 0; j--) {
|
|
171
|
+
const additionalToken = comment.additionalTokens[j];
|
|
172
|
+
const tokenIndex = comment.line.children.indexOf(additionalToken);
|
|
173
|
+
if (tokenIndex !== -1) comment.line.children.splice(tokenIndex, 1);
|
|
174
|
+
}
|
|
175
|
+
comment.line.children.splice(comment.line.children.indexOf(comment.token), 1);
|
|
176
|
+
} else {
|
|
177
|
+
const head = comment.token.children[0];
|
|
178
|
+
if (head.type === "text") {
|
|
179
|
+
head.value = comment.info.join("");
|
|
180
|
+
if (comment.additionalTokens) for (const additionalToken of comment.additionalTokens) {
|
|
181
|
+
const additionalHead = additionalToken.children[0];
|
|
182
|
+
if (additionalHead?.type === "text") additionalHead.value = "";
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
for (const line of linesToRemove) {
|
|
188
|
+
const index = code.children.indexOf(line);
|
|
189
|
+
const nextLine = code.children[index + 1];
|
|
190
|
+
let removeLength = 1;
|
|
191
|
+
if (nextLine?.type === "text" && nextLine?.value === "\n") removeLength = 2;
|
|
192
|
+
code.children.splice(index, removeLength);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
223
196
|
}
|
|
224
197
|
|
|
198
|
+
//#endregion
|
|
199
|
+
//#region src/transformers/compact-line-options.ts
|
|
200
|
+
/**
|
|
201
|
+
* Transformer for `shiki`'s legacy `lineOptions`
|
|
202
|
+
*/
|
|
225
203
|
function transformerCompactLineOptions(lineOptions = []) {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
};
|
|
204
|
+
return {
|
|
205
|
+
name: "@shikijs/transformers:compact-line-options",
|
|
206
|
+
line(node, line) {
|
|
207
|
+
const lineOption = lineOptions.find((o) => o.line === line);
|
|
208
|
+
if (lineOption?.classes) this.addClassToHast(node, lineOption.classes);
|
|
209
|
+
return node;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
235
212
|
}
|
|
236
213
|
|
|
214
|
+
//#endregion
|
|
215
|
+
//#region src/transformers/meta-highlight.ts
|
|
237
216
|
function parseMetaHighlightString(meta) {
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
|
|
217
|
+
if (!meta) return null;
|
|
218
|
+
const match = meta.match(/\{([\d,-]+)\}/);
|
|
219
|
+
if (!match) return null;
|
|
220
|
+
return match[1].split(",").flatMap((v) => {
|
|
221
|
+
const range = v.split("-").map((n) => Number.parseInt(n, 10));
|
|
222
|
+
return range.length === 1 ? [range[0]] : Array.from({ length: range[1] - range[0] + 1 }, (_, i) => range[0] + i);
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
const symbol = Symbol("highlighted-lines");
|
|
226
|
+
/**
|
|
227
|
+
* Allow using `{1,3-5}` in the code snippet meta to mark highlighted lines.
|
|
228
|
+
*/
|
|
250
229
|
function transformerMetaHighlight(options = {}) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
}
|
|
265
|
-
};
|
|
230
|
+
const { className = "highlighted", zeroIndexed = false } = options;
|
|
231
|
+
return {
|
|
232
|
+
name: "@shikijs/transformers:meta-highlight",
|
|
233
|
+
line(node, lineNumber) {
|
|
234
|
+
if (!this.options.meta?.__raw) return;
|
|
235
|
+
const meta = this.meta;
|
|
236
|
+
meta[symbol] ??= parseMetaHighlightString(this.options.meta.__raw);
|
|
237
|
+
const highlightedLines = meta[symbol] ?? [];
|
|
238
|
+
const effectiveLine = zeroIndexed ? lineNumber - 1 : lineNumber;
|
|
239
|
+
if (highlightedLines.includes(effectiveLine)) this.addClassToHast(node, className);
|
|
240
|
+
return node;
|
|
241
|
+
}
|
|
242
|
+
};
|
|
266
243
|
}
|
|
267
244
|
|
|
245
|
+
//#endregion
|
|
246
|
+
//#region src/transformers/meta-highlight-word.ts
|
|
268
247
|
function parseMetaHighlightWords(meta) {
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const match = Array.from(meta.matchAll(/\/((?:\\.|[^/])+)\//g));
|
|
272
|
-
return match.map((v) => v[1].replace(/\\(.)/g, "$1"));
|
|
248
|
+
if (!meta) return [];
|
|
249
|
+
return Array.from(meta.matchAll(/\/((?:\\.|[^/])+)\//g)).map((v) => v[1].replace(/\\(.)/g, "$1"));
|
|
273
250
|
}
|
|
251
|
+
/**
|
|
252
|
+
* Allow using `/word/` in the code snippet meta to mark highlighted words.
|
|
253
|
+
*/
|
|
274
254
|
function transformerMetaWordHighlight(options = {}) {
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
class: className
|
|
293
|
-
}
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
};
|
|
255
|
+
const { className = "highlighted-word" } = options;
|
|
256
|
+
return {
|
|
257
|
+
name: "@shikijs/transformers:meta-word-highlight",
|
|
258
|
+
preprocess(code, options) {
|
|
259
|
+
if (!this.options.meta?.__raw) return;
|
|
260
|
+
const words = parseMetaHighlightWords(this.options.meta.__raw);
|
|
261
|
+
options.decorations ||= [];
|
|
262
|
+
for (const word of words) {
|
|
263
|
+
const indexes = findAllSubstringIndexes(code, word);
|
|
264
|
+
for (const index of indexes) options.decorations.push({
|
|
265
|
+
start: index,
|
|
266
|
+
end: index + word.length,
|
|
267
|
+
properties: { class: className }
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
};
|
|
299
272
|
}
|
|
300
273
|
function findAllSubstringIndexes(str, substr) {
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}
|
|
312
|
-
return indexes;
|
|
274
|
+
const indexes = [];
|
|
275
|
+
let cursor = 0;
|
|
276
|
+
while (true) {
|
|
277
|
+
const index = str.indexOf(substr, cursor);
|
|
278
|
+
if (index === -1 || index >= str.length) break;
|
|
279
|
+
if (index < cursor) break;
|
|
280
|
+
indexes.push(index);
|
|
281
|
+
cursor = index + substr.length;
|
|
282
|
+
}
|
|
283
|
+
return indexes;
|
|
313
284
|
}
|
|
314
285
|
|
|
286
|
+
//#endregion
|
|
287
|
+
//#region src/transformers/notation-map.ts
|
|
315
288
|
function escapeRegExp(str) {
|
|
316
|
-
|
|
289
|
+
return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
317
290
|
}
|
|
318
291
|
function transformerNotationMap(options = {}, name = "@shikijs/transformers:notation-map") {
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
function([_, match, range = ":1"], _line, _comment, lines, index) {
|
|
328
|
-
const lineNum = Number.parseInt(range.slice(1), 10);
|
|
329
|
-
for (let i = index; i < Math.min(index + lineNum, lines.length); i++) {
|
|
330
|
-
this.addClassToHast(lines[i], classMap[match]);
|
|
331
|
-
}
|
|
332
|
-
if (classActivePre)
|
|
333
|
-
this.addClassToHast(this.pre, classActivePre);
|
|
334
|
-
if (classActiveCode)
|
|
335
|
-
this.addClassToHast(this.code, classActiveCode);
|
|
336
|
-
return true;
|
|
337
|
-
},
|
|
338
|
-
options.matchAlgorithm
|
|
339
|
-
);
|
|
292
|
+
const { classMap = {}, classActivePre = void 0, classActiveCode = void 0 } = options;
|
|
293
|
+
return createCommentNotationTransformer(name, new RegExp(`#?\\s*\\[!code (${Object.keys(classMap).map(escapeRegExp).join("|")})(:\\d+)?\\]`, "gi"), function([_, match, range = ":1"], _line, _comment, lines, index) {
|
|
294
|
+
const lineNum = Number.parseInt(range.slice(1), 10);
|
|
295
|
+
for (let i = index; i < Math.min(index + lineNum, lines.length); i++) this.addClassToHast(lines[i], classMap[match]);
|
|
296
|
+
if (classActivePre) this.addClassToHast(this.pre, classActivePre);
|
|
297
|
+
if (classActiveCode) this.addClassToHast(this.code, classActiveCode);
|
|
298
|
+
return true;
|
|
299
|
+
}, options.matchAlgorithm);
|
|
340
300
|
}
|
|
341
301
|
|
|
302
|
+
//#endregion
|
|
303
|
+
//#region src/transformers/notation-diff.ts
|
|
304
|
+
/**
|
|
305
|
+
* Use `[!code ++]` and `[!code --]` to mark added and removed lines.
|
|
306
|
+
*/
|
|
342
307
|
function transformerNotationDiff(options = {}) {
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
"--": classLineRemove
|
|
354
|
-
},
|
|
355
|
-
classActivePre,
|
|
356
|
-
classActiveCode,
|
|
357
|
-
matchAlgorithm: options.matchAlgorithm
|
|
358
|
-
},
|
|
359
|
-
"@shikijs/transformers:notation-diff"
|
|
360
|
-
);
|
|
308
|
+
const { classLineAdd = "diff add", classLineRemove = "diff remove", classActivePre = "has-diff", classActiveCode } = options;
|
|
309
|
+
return transformerNotationMap({
|
|
310
|
+
classMap: {
|
|
311
|
+
"++": classLineAdd,
|
|
312
|
+
"--": classLineRemove
|
|
313
|
+
},
|
|
314
|
+
classActivePre,
|
|
315
|
+
classActiveCode,
|
|
316
|
+
matchAlgorithm: options.matchAlgorithm
|
|
317
|
+
}, "@shikijs/transformers:notation-diff");
|
|
361
318
|
}
|
|
362
319
|
|
|
320
|
+
//#endregion
|
|
321
|
+
//#region src/transformers/notation-error-level.ts
|
|
322
|
+
/**
|
|
323
|
+
* Allow using `[!code error]` `[!code warning]` notation in code to mark highlighted lines.
|
|
324
|
+
*/
|
|
363
325
|
function transformerNotationErrorLevel(options = {}) {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
classActivePre,
|
|
376
|
-
classActiveCode,
|
|
377
|
-
matchAlgorithm: options.matchAlgorithm
|
|
378
|
-
},
|
|
379
|
-
"@shikijs/transformers:notation-error-level"
|
|
380
|
-
);
|
|
326
|
+
const { classMap = {
|
|
327
|
+
error: ["highlighted", "error"],
|
|
328
|
+
warning: ["highlighted", "warning"],
|
|
329
|
+
info: ["highlighted", "info"]
|
|
330
|
+
}, classActivePre = "has-highlighted", classActiveCode } = options;
|
|
331
|
+
return transformerNotationMap({
|
|
332
|
+
classMap,
|
|
333
|
+
classActivePre,
|
|
334
|
+
classActiveCode,
|
|
335
|
+
matchAlgorithm: options.matchAlgorithm
|
|
336
|
+
}, "@shikijs/transformers:notation-error-level");
|
|
381
337
|
}
|
|
382
338
|
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/transformers/notation-focus.ts
|
|
341
|
+
/**
|
|
342
|
+
* Allow using `[!code focus]` notation in code to mark focused lines.
|
|
343
|
+
*/
|
|
383
344
|
function transformerNotationFocus(options = {}) {
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
classMap: {
|
|
392
|
-
focus: classActiveLine
|
|
393
|
-
},
|
|
394
|
-
classActivePre,
|
|
395
|
-
classActiveCode,
|
|
396
|
-
matchAlgorithm: options.matchAlgorithm
|
|
397
|
-
},
|
|
398
|
-
"@shikijs/transformers:notation-focus"
|
|
399
|
-
);
|
|
345
|
+
const { classActiveLine = "focused", classActivePre = "has-focused", classActiveCode } = options;
|
|
346
|
+
return transformerNotationMap({
|
|
347
|
+
classMap: { focus: classActiveLine },
|
|
348
|
+
classActivePre,
|
|
349
|
+
classActiveCode,
|
|
350
|
+
matchAlgorithm: options.matchAlgorithm
|
|
351
|
+
}, "@shikijs/transformers:notation-focus");
|
|
400
352
|
}
|
|
401
353
|
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/transformers/notation-highlight.ts
|
|
356
|
+
/**
|
|
357
|
+
* Allow using `[!code highlight]` notation in code to mark highlighted lines.
|
|
358
|
+
*/
|
|
402
359
|
function transformerNotationHighlight(options = {}) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
},
|
|
414
|
-
classActivePre,
|
|
415
|
-
classActiveCode,
|
|
416
|
-
matchAlgorithm: options.matchAlgorithm
|
|
417
|
-
},
|
|
418
|
-
"@shikijs/transformers:notation-highlight"
|
|
419
|
-
);
|
|
360
|
+
const { classActiveLine = "highlighted", classActivePre = "has-highlighted", classActiveCode } = options;
|
|
361
|
+
return transformerNotationMap({
|
|
362
|
+
classMap: {
|
|
363
|
+
highlight: classActiveLine,
|
|
364
|
+
hl: classActiveLine
|
|
365
|
+
},
|
|
366
|
+
classActivePre,
|
|
367
|
+
classActiveCode,
|
|
368
|
+
matchAlgorithm: options.matchAlgorithm
|
|
369
|
+
}, "@shikijs/transformers:notation-highlight");
|
|
420
370
|
}
|
|
421
371
|
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region src/shared/highlight-word.ts
|
|
422
374
|
function highlightWordInLine(line, ignoredElement, word, className) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
375
|
+
const content = getTextContent(line);
|
|
376
|
+
let index = content.indexOf(word);
|
|
377
|
+
while (index !== -1) {
|
|
378
|
+
highlightRange.call(this, line.children, ignoredElement, index, word.length, className);
|
|
379
|
+
index = content.indexOf(word, index + 1);
|
|
380
|
+
}
|
|
429
381
|
}
|
|
430
382
|
function getTextContent(element) {
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
383
|
+
if (element.type === "text") return element.value;
|
|
384
|
+
if (element.type === "element" && element.tagName === "span") return element.children.map(getTextContent).join("");
|
|
385
|
+
return "";
|
|
386
|
+
}
|
|
387
|
+
/**
|
|
388
|
+
* @param elements
|
|
389
|
+
* @param ignoredElement
|
|
390
|
+
* @param index highlight beginning index
|
|
391
|
+
* @param len highlight length
|
|
392
|
+
* @param className class name to add to highlighted nodes
|
|
393
|
+
*/
|
|
437
394
|
function highlightRange(elements, ignoredElement, index, len, className) {
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
}
|
|
457
|
-
currentIdx += textNode.value.length;
|
|
458
|
-
}
|
|
395
|
+
let currentIdx = 0;
|
|
396
|
+
for (let i = 0; i < elements.length; i++) {
|
|
397
|
+
const element = elements[i];
|
|
398
|
+
if (element.type !== "element" || element.tagName !== "span" || element === ignoredElement) continue;
|
|
399
|
+
const textNode = element.children[0];
|
|
400
|
+
if (textNode.type !== "text") continue;
|
|
401
|
+
if (hasOverlap([currentIdx, currentIdx + textNode.value.length - 1], [index, index + len])) {
|
|
402
|
+
const start = Math.max(0, index - currentIdx);
|
|
403
|
+
const length = len - Math.max(0, currentIdx - index);
|
|
404
|
+
if (length === 0) continue;
|
|
405
|
+
const separated = separateToken(element, textNode, start, length);
|
|
406
|
+
this.addClassToHast(separated[1], className);
|
|
407
|
+
const output = separated.filter(Boolean);
|
|
408
|
+
elements.splice(i, 1, ...output);
|
|
409
|
+
i += output.length - 1;
|
|
410
|
+
}
|
|
411
|
+
currentIdx += textNode.value.length;
|
|
412
|
+
}
|
|
459
413
|
}
|
|
460
414
|
function hasOverlap(range1, range2) {
|
|
461
|
-
|
|
415
|
+
return range1[0] <= range2[1] && range1[1] >= range2[0];
|
|
462
416
|
}
|
|
463
417
|
function separateToken(span, textNode, index, len) {
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
index > 0 ? createNode(text.slice(0, index)) : void 0,
|
|
475
|
-
createNode(text.slice(index, index + len)),
|
|
476
|
-
index + len < text.length ? createNode(text.slice(index + len)) : void 0
|
|
477
|
-
];
|
|
418
|
+
const text = textNode.value;
|
|
419
|
+
const createNode = (value) => inheritElement(span, { children: [{
|
|
420
|
+
type: "text",
|
|
421
|
+
value
|
|
422
|
+
}] });
|
|
423
|
+
return [
|
|
424
|
+
index > 0 ? createNode(text.slice(0, index)) : void 0,
|
|
425
|
+
createNode(text.slice(index, index + len)),
|
|
426
|
+
index + len < text.length ? createNode(text.slice(index + len)) : void 0
|
|
427
|
+
];
|
|
478
428
|
}
|
|
479
429
|
function inheritElement(original, overrides) {
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
...overrides
|
|
486
|
-
};
|
|
430
|
+
return {
|
|
431
|
+
...original,
|
|
432
|
+
properties: { ...original.properties },
|
|
433
|
+
...overrides
|
|
434
|
+
};
|
|
487
435
|
}
|
|
488
436
|
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region src/transformers/notation-highlight-word.ts
|
|
489
439
|
function transformerNotationWordHighlight(options = {}) {
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
const lineNum = range ? Number.parseInt(range.slice(1), 10) : lines.length;
|
|
499
|
-
word = word.replace(/\\(.)/g, "$1");
|
|
500
|
-
for (let i = index; i < Math.min(index + lineNum, lines.length); i++) {
|
|
501
|
-
highlightWordInLine.call(this, lines[i], comment, word, classActiveWord);
|
|
502
|
-
}
|
|
503
|
-
if (classActivePre)
|
|
504
|
-
this.addClassToHast(this.pre, classActivePre);
|
|
505
|
-
return true;
|
|
506
|
-
},
|
|
507
|
-
options.matchAlgorithm
|
|
508
|
-
);
|
|
440
|
+
const { classActiveWord = "highlighted-word", classActivePre = void 0 } = options;
|
|
441
|
+
return createCommentNotationTransformer("@shikijs/transformers:notation-highlight-word", /\s*\[!code word:((?:\\.|[^:\]])+)(:\d+)?\]/, function([_, word, range], _line, comment, lines, index) {
|
|
442
|
+
const lineNum = range ? Number.parseInt(range.slice(1), 10) : lines.length;
|
|
443
|
+
word = word.replace(/\\(.)/g, "$1");
|
|
444
|
+
for (let i = index; i < Math.min(index + lineNum, lines.length); i++) highlightWordInLine.call(this, lines[i], comment, word, classActiveWord);
|
|
445
|
+
if (classActivePre) this.addClassToHast(this.pre, classActivePre);
|
|
446
|
+
return true;
|
|
447
|
+
}, options.matchAlgorithm);
|
|
509
448
|
}
|
|
510
449
|
|
|
450
|
+
//#endregion
|
|
451
|
+
//#region src/transformers/remove-comments.ts
|
|
452
|
+
/**
|
|
453
|
+
* Remove comments from the code.
|
|
454
|
+
*/
|
|
511
455
|
function transformerRemoveComments(options = {}) {
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
}
|
|
534
|
-
if (removeEmptyLines && hasComment) {
|
|
535
|
-
const isAllWhitespace = filteredLine.every((token) => !token.content.trim());
|
|
536
|
-
if (isAllWhitespace)
|
|
537
|
-
continue;
|
|
538
|
-
}
|
|
539
|
-
result.push(filteredLine);
|
|
540
|
-
}
|
|
541
|
-
return result;
|
|
542
|
-
}
|
|
543
|
-
};
|
|
456
|
+
const { removeEmptyLines = true } = options;
|
|
457
|
+
return {
|
|
458
|
+
name: "@shikijs/transformers:remove-comments",
|
|
459
|
+
preprocess(_code, options) {
|
|
460
|
+
if (options.includeExplanation !== true && options.includeExplanation !== "scopeName") throw new Error("`transformerRemoveComments` requires `includeExplanation` to be set to `true` or `'scopeName'`");
|
|
461
|
+
},
|
|
462
|
+
tokens(tokens) {
|
|
463
|
+
const result = [];
|
|
464
|
+
for (const line of tokens) {
|
|
465
|
+
const filteredLine = [];
|
|
466
|
+
let hasComment = false;
|
|
467
|
+
for (const token of line) if (token.explanation?.some((exp) => exp.scopes.some((s) => s.scopeName.startsWith("comment")))) hasComment = true;
|
|
468
|
+
else filteredLine.push(token);
|
|
469
|
+
if (removeEmptyLines && hasComment) {
|
|
470
|
+
if (filteredLine.every((token) => !token.content.trim())) continue;
|
|
471
|
+
}
|
|
472
|
+
result.push(filteredLine);
|
|
473
|
+
}
|
|
474
|
+
return result;
|
|
475
|
+
}
|
|
476
|
+
};
|
|
544
477
|
}
|
|
545
478
|
|
|
479
|
+
//#endregion
|
|
480
|
+
//#region src/transformers/remove-line-breaks.ts
|
|
481
|
+
/**
|
|
482
|
+
* Remove line breaks between lines.
|
|
483
|
+
* Useful when you override `display: block` to `.line` in CSS.
|
|
484
|
+
*/
|
|
546
485
|
function transformerRemoveLineBreak() {
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
486
|
+
return {
|
|
487
|
+
name: "@shikijs/transformers:remove-line-break",
|
|
488
|
+
code(code) {
|
|
489
|
+
code.children = code.children.filter((line) => !(line.type === "text" && line.value === "\n"));
|
|
490
|
+
}
|
|
491
|
+
};
|
|
553
492
|
}
|
|
554
493
|
|
|
494
|
+
//#endregion
|
|
495
|
+
//#region src/transformers/remove-notation-escape.ts
|
|
496
|
+
/**
|
|
497
|
+
* Remove notation escapes.
|
|
498
|
+
* Useful when you want to write `// [!code` in markdown.
|
|
499
|
+
* If you process `// [\!code ...]` expression, you can get `// [!code ...]` in the output.
|
|
500
|
+
*/
|
|
555
501
|
function transformerRemoveNotationEscape() {
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
}
|
|
568
|
-
replace(hast);
|
|
569
|
-
return hast;
|
|
570
|
-
}
|
|
571
|
-
};
|
|
502
|
+
return {
|
|
503
|
+
name: "@shikijs/transformers:remove-notation-escape",
|
|
504
|
+
code(hast) {
|
|
505
|
+
function replace(node) {
|
|
506
|
+
if (node.type === "text") node.value = node.value.replace("[\\!code", "[!code");
|
|
507
|
+
else if ("children" in node) for (const child of node.children) replace(child);
|
|
508
|
+
}
|
|
509
|
+
replace(hast);
|
|
510
|
+
return hast;
|
|
511
|
+
}
|
|
512
|
+
};
|
|
572
513
|
}
|
|
573
514
|
|
|
515
|
+
//#endregion
|
|
516
|
+
//#region src/transformers/render-indent-guides.ts
|
|
517
|
+
/**
|
|
518
|
+
* Render indentations as separate tokens.
|
|
519
|
+
* Apply with CSS, it can be used to render indent guides visually.
|
|
520
|
+
*/
|
|
574
521
|
function transformerRenderIndentGuides(options = {}) {
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
children: [{
|
|
626
|
-
type: "text",
|
|
627
|
-
value: text.value.slice(start, end)
|
|
628
|
-
}]
|
|
629
|
-
}))
|
|
630
|
-
);
|
|
631
|
-
text.value = text.value.slice(ranges.at(-1)[1]);
|
|
632
|
-
}
|
|
633
|
-
}
|
|
634
|
-
return hast;
|
|
635
|
-
}
|
|
636
|
-
};
|
|
522
|
+
return {
|
|
523
|
+
name: "@shikijs/transformers:render-indent-guides",
|
|
524
|
+
code(hast) {
|
|
525
|
+
const indent = Number(this.options.meta?.indent ?? this.options.meta?.__raw?.match(/\{indent:(\d+|false)\}/)?.[1] ?? options.indent ?? 2);
|
|
526
|
+
if (Number.isNaN(indent) || indent <= 0) return hast;
|
|
527
|
+
const indentRegex = new RegExp(` {${indent}}| {0,${indent - 1}}\t| {1,}$`, "g");
|
|
528
|
+
const emptyLines = [];
|
|
529
|
+
let level = 0;
|
|
530
|
+
for (const line of hast.children) {
|
|
531
|
+
if (line.type !== "element") continue;
|
|
532
|
+
const first = line.children[0];
|
|
533
|
+
if (first?.type !== "element" || first?.children[0]?.type !== "text") {
|
|
534
|
+
emptyLines.push([line, level]);
|
|
535
|
+
continue;
|
|
536
|
+
}
|
|
537
|
+
const text = first.children[0];
|
|
538
|
+
const blanks = text.value.split(/[^ \t]/, 1)[0];
|
|
539
|
+
const ranges = [];
|
|
540
|
+
for (const match of blanks.matchAll(indentRegex)) {
|
|
541
|
+
const start = match.index;
|
|
542
|
+
const end = start + match[0].length;
|
|
543
|
+
ranges.push([start, end]);
|
|
544
|
+
}
|
|
545
|
+
for (const [line, level] of emptyLines) line.children.unshift(...Array.from({ length: Math.min(ranges.length, level + 1) }, (_, i) => ({
|
|
546
|
+
type: "element",
|
|
547
|
+
tagName: "span",
|
|
548
|
+
properties: {
|
|
549
|
+
class: "indent",
|
|
550
|
+
style: `--indent-offset: ${i * indent}ch;`
|
|
551
|
+
},
|
|
552
|
+
children: []
|
|
553
|
+
})));
|
|
554
|
+
emptyLines.length = 0;
|
|
555
|
+
level = ranges.length;
|
|
556
|
+
if (ranges.length) {
|
|
557
|
+
line.children.unshift(...ranges.map(([start, end]) => ({
|
|
558
|
+
type: "element",
|
|
559
|
+
tagName: "span",
|
|
560
|
+
properties: { class: "indent" },
|
|
561
|
+
children: [{
|
|
562
|
+
type: "text",
|
|
563
|
+
value: text.value.slice(start, end)
|
|
564
|
+
}]
|
|
565
|
+
})));
|
|
566
|
+
text.value = text.value.slice(ranges.at(-1)[1]);
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
return hast;
|
|
570
|
+
}
|
|
571
|
+
};
|
|
637
572
|
}
|
|
638
573
|
|
|
574
|
+
//#endregion
|
|
575
|
+
//#region src/shared/utils.ts
|
|
639
576
|
function isTab(part) {
|
|
640
|
-
|
|
577
|
+
return part === " ";
|
|
641
578
|
}
|
|
642
579
|
function isSpace(part) {
|
|
643
|
-
|
|
580
|
+
return part === " " || part === " ";
|
|
644
581
|
}
|
|
645
582
|
function separateContinuousSpaces(inputs) {
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
});
|
|
664
|
-
bump();
|
|
665
|
-
return result;
|
|
583
|
+
const result = [];
|
|
584
|
+
let current = "";
|
|
585
|
+
function bump() {
|
|
586
|
+
if (current.length) result.push(current);
|
|
587
|
+
current = "";
|
|
588
|
+
}
|
|
589
|
+
inputs.forEach((part, idx) => {
|
|
590
|
+
if (isTab(part)) {
|
|
591
|
+
bump();
|
|
592
|
+
result.push(part);
|
|
593
|
+
} else if (isSpace(part) && (isSpace(inputs[idx - 1]) || isSpace(inputs[idx + 1]))) {
|
|
594
|
+
bump();
|
|
595
|
+
result.push(part);
|
|
596
|
+
} else current += part;
|
|
597
|
+
});
|
|
598
|
+
bump();
|
|
599
|
+
return result;
|
|
666
600
|
}
|
|
667
601
|
function splitSpaces(parts, type, renderContinuousSpaces = true) {
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
682
|
-
if (isSpace(parts[i]))
|
|
683
|
-
rightCount++;
|
|
684
|
-
else
|
|
685
|
-
break;
|
|
686
|
-
}
|
|
687
|
-
}
|
|
688
|
-
const middle = parts.slice(leftCount, parts.length - rightCount);
|
|
689
|
-
return [
|
|
690
|
-
...parts.slice(0, leftCount),
|
|
691
|
-
...renderContinuousSpaces ? separateContinuousSpaces(middle) : [middle.join("")],
|
|
692
|
-
...parts.slice(parts.length - rightCount)
|
|
693
|
-
];
|
|
602
|
+
if (type === "all") return parts;
|
|
603
|
+
let leftCount = 0;
|
|
604
|
+
let rightCount = 0;
|
|
605
|
+
if (type === "boundary" || type === "leading") for (let i = 0; i < parts.length; i++) if (isSpace(parts[i])) leftCount++;
|
|
606
|
+
else break;
|
|
607
|
+
if (type === "boundary" || type === "trailing") for (let i = parts.length - 1; i >= 0; i--) if (isSpace(parts[i])) rightCount++;
|
|
608
|
+
else break;
|
|
609
|
+
const middle = parts.slice(leftCount, parts.length - rightCount);
|
|
610
|
+
return [
|
|
611
|
+
...parts.slice(0, leftCount),
|
|
612
|
+
...renderContinuousSpaces ? separateContinuousSpaces(middle) : [middle.join("")],
|
|
613
|
+
...parts.slice(parts.length - rightCount)
|
|
614
|
+
];
|
|
694
615
|
}
|
|
695
616
|
|
|
617
|
+
//#endregion
|
|
618
|
+
//#region src/transformers/render-whitespace.ts
|
|
619
|
+
/**
|
|
620
|
+
* Render whitespaces as separate tokens.
|
|
621
|
+
* Apply with CSS, it can be used to render tabs and spaces visually.
|
|
622
|
+
*/
|
|
696
623
|
function transformerRenderWhitespace(options = {}) {
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
this.addClassToHast(clone, classMap[part]);
|
|
741
|
-
delete clone.properties.style;
|
|
742
|
-
}
|
|
743
|
-
return clone;
|
|
744
|
-
});
|
|
745
|
-
});
|
|
746
|
-
}
|
|
747
|
-
);
|
|
748
|
-
}
|
|
749
|
-
};
|
|
624
|
+
const classMap = {
|
|
625
|
+
" ": options.classSpace ?? "space",
|
|
626
|
+
" ": options.classTab ?? "tab"
|
|
627
|
+
};
|
|
628
|
+
const position = options.position ?? "all";
|
|
629
|
+
const keys = Object.keys(classMap);
|
|
630
|
+
return {
|
|
631
|
+
name: "@shikijs/transformers:render-whitespace",
|
|
632
|
+
root(root) {
|
|
633
|
+
const pre = root.children[0];
|
|
634
|
+
(pre.tagName === "pre" ? pre.children[0] : { children: [root] }).children.forEach((line) => {
|
|
635
|
+
if (line.type !== "element" && line.type !== "root") return;
|
|
636
|
+
const elements = line.children.filter((token) => token.type === "element");
|
|
637
|
+
const last = elements.length - 1;
|
|
638
|
+
line.children = line.children.flatMap((token) => {
|
|
639
|
+
if (token.type !== "element") return token;
|
|
640
|
+
const index = elements.indexOf(token);
|
|
641
|
+
if (position === "boundary" && index !== 0 && index !== last) return token;
|
|
642
|
+
if (position === "trailing" && index !== last) return token;
|
|
643
|
+
if (position === "leading" && index !== 0) return token;
|
|
644
|
+
const node = token.children[0];
|
|
645
|
+
if (node.type !== "text" || !node.value) return token;
|
|
646
|
+
const parts = splitSpaces(node.value.split(/([ \t])/).filter((i) => i.length), position === "boundary" && index === last && last !== 0 ? "trailing" : position, position !== "trailing" && position !== "leading");
|
|
647
|
+
if (parts.length <= 1) return token;
|
|
648
|
+
return parts.map((part) => {
|
|
649
|
+
const clone = {
|
|
650
|
+
...token,
|
|
651
|
+
properties: { ...token.properties }
|
|
652
|
+
};
|
|
653
|
+
clone.children = [{
|
|
654
|
+
type: "text",
|
|
655
|
+
value: part
|
|
656
|
+
}];
|
|
657
|
+
if (keys.includes(part)) {
|
|
658
|
+
this.addClassToHast(clone, classMap[part]);
|
|
659
|
+
delete clone.properties.style;
|
|
660
|
+
}
|
|
661
|
+
return clone;
|
|
662
|
+
});
|
|
663
|
+
});
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
};
|
|
750
667
|
}
|
|
751
668
|
|
|
669
|
+
//#endregion
|
|
670
|
+
//#region src/transformers/style-to-class.ts
|
|
671
|
+
/**
|
|
672
|
+
* Remove line breaks between lines.
|
|
673
|
+
* Useful when you override `display: block` to `.line` in CSS.
|
|
674
|
+
*/
|
|
752
675
|
function transformerStyleToClass(options = {}) {
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
},
|
|
801
|
-
getCSS() {
|
|
802
|
-
let css = "";
|
|
803
|
-
for (const [className, style] of classToStyle.entries()) {
|
|
804
|
-
css += `.${className}{${typeof style === "string" ? style : stringifyStyle(style)}}`;
|
|
805
|
-
}
|
|
806
|
-
return css;
|
|
807
|
-
},
|
|
808
|
-
clearRegistry() {
|
|
809
|
-
classToStyle.clear();
|
|
810
|
-
}
|
|
811
|
-
};
|
|
812
|
-
}
|
|
676
|
+
const { classPrefix = "__shiki_", classSuffix = "", classReplacer = (className) => className } = options;
|
|
677
|
+
const classToStyle = /* @__PURE__ */ new Map();
|
|
678
|
+
function stringifyStyle(style) {
|
|
679
|
+
return Object.entries(style).map(([key, value]) => `${key}:${value}`).join(";");
|
|
680
|
+
}
|
|
681
|
+
function registerStyle(style) {
|
|
682
|
+
let className = classPrefix + cyrb53(typeof style === "string" ? style : stringifyStyle(style)) + classSuffix;
|
|
683
|
+
className = classReplacer(className);
|
|
684
|
+
if (!classToStyle.has(className)) classToStyle.set(className, typeof style === "string" ? style : { ...style });
|
|
685
|
+
return className;
|
|
686
|
+
}
|
|
687
|
+
return {
|
|
688
|
+
name: "@shikijs/transformers:style-to-class",
|
|
689
|
+
pre(t) {
|
|
690
|
+
if (!t.properties.style) return;
|
|
691
|
+
const className = registerStyle(t.properties.style);
|
|
692
|
+
delete t.properties.style;
|
|
693
|
+
this.addClassToHast(t, className);
|
|
694
|
+
},
|
|
695
|
+
tokens(lines) {
|
|
696
|
+
for (const line of lines) for (const token of line) {
|
|
697
|
+
if (!token.htmlStyle) continue;
|
|
698
|
+
const className = registerStyle(token.htmlStyle);
|
|
699
|
+
token.htmlStyle = {};
|
|
700
|
+
token.htmlAttrs ||= {};
|
|
701
|
+
if (!token.htmlAttrs.class) token.htmlAttrs.class = className;
|
|
702
|
+
else token.htmlAttrs.class += ` ${className}`;
|
|
703
|
+
}
|
|
704
|
+
},
|
|
705
|
+
getClassRegistry() {
|
|
706
|
+
return classToStyle;
|
|
707
|
+
},
|
|
708
|
+
getCSS() {
|
|
709
|
+
let css = "";
|
|
710
|
+
for (const [className, style] of classToStyle.entries()) css += `.${className}{${typeof style === "string" ? style : stringifyStyle(style)}}`;
|
|
711
|
+
return css;
|
|
712
|
+
},
|
|
713
|
+
clearRegistry() {
|
|
714
|
+
classToStyle.clear();
|
|
715
|
+
}
|
|
716
|
+
};
|
|
717
|
+
}
|
|
718
|
+
/**
|
|
719
|
+
* A simple hash function.
|
|
720
|
+
*
|
|
721
|
+
* @see https://stackoverflow.com/a/52171480
|
|
722
|
+
*/
|
|
813
723
|
function cyrb53(str, seed = 0) {
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
724
|
+
let h1 = 3735928559 ^ seed;
|
|
725
|
+
let h2 = 1103547991 ^ seed;
|
|
726
|
+
for (let i = 0, ch; i < str.length; i++) {
|
|
727
|
+
ch = str.charCodeAt(i);
|
|
728
|
+
h1 = Math.imul(h1 ^ ch, 2654435761);
|
|
729
|
+
h2 = Math.imul(h2 ^ ch, 1597334677);
|
|
730
|
+
}
|
|
731
|
+
h1 = Math.imul(h1 ^ h1 >>> 16, 2246822507);
|
|
732
|
+
h1 ^= Math.imul(h2 ^ h2 >>> 13, 3266489909);
|
|
733
|
+
h2 = Math.imul(h2 ^ h2 >>> 16, 2246822507);
|
|
734
|
+
h2 ^= Math.imul(h1 ^ h1 >>> 13, 3266489909);
|
|
735
|
+
return (4294967296 * (2097151 & h2) + (h1 >>> 0)).toString(36).slice(0, 6);
|
|
826
736
|
}
|
|
827
737
|
|
|
828
|
-
|
|
738
|
+
//#endregion
|
|
739
|
+
export { createCommentNotationTransformer, findAllSubstringIndexes, parseMetaHighlightString, parseMetaHighlightWords, transformerCompactLineOptions, transformerMetaHighlight, transformerMetaWordHighlight, transformerNotationDiff, transformerNotationErrorLevel, transformerNotationFocus, transformerNotationHighlight, transformerNotationMap, transformerNotationWordHighlight, transformerRemoveComments, transformerRemoveLineBreak, transformerRemoveNotationEscape, transformerRenderIndentGuides, transformerRenderWhitespace, transformerStyleToClass };
|