@shikijs/transformers 3.23.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 -745
- package/package.json +8 -5
package/dist/index.mjs
CHANGED
|
@@ -1,831 +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
|
-
classMap,
|
|
376
|
-
classActivePre,
|
|
377
|
-
classActiveCode,
|
|
378
|
-
matchAlgorithm: options.matchAlgorithm
|
|
379
|
-
},
|
|
380
|
-
"@shikijs/transformers:notation-error-level"
|
|
381
|
-
);
|
|
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");
|
|
382
337
|
}
|
|
383
338
|
|
|
339
|
+
//#endregion
|
|
340
|
+
//#region src/transformers/notation-focus.ts
|
|
341
|
+
/**
|
|
342
|
+
* Allow using `[!code focus]` notation in code to mark focused lines.
|
|
343
|
+
*/
|
|
384
344
|
function transformerNotationFocus(options = {}) {
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
classMap: {
|
|
393
|
-
focus: classActiveLine
|
|
394
|
-
},
|
|
395
|
-
classActivePre,
|
|
396
|
-
classActiveCode,
|
|
397
|
-
matchAlgorithm: options.matchAlgorithm
|
|
398
|
-
},
|
|
399
|
-
"@shikijs/transformers:notation-focus"
|
|
400
|
-
);
|
|
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");
|
|
401
352
|
}
|
|
402
353
|
|
|
354
|
+
//#endregion
|
|
355
|
+
//#region src/transformers/notation-highlight.ts
|
|
356
|
+
/**
|
|
357
|
+
* Allow using `[!code highlight]` notation in code to mark highlighted lines.
|
|
358
|
+
*/
|
|
403
359
|
function transformerNotationHighlight(options = {}) {
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
},
|
|
415
|
-
classActivePre,
|
|
416
|
-
classActiveCode,
|
|
417
|
-
matchAlgorithm: options.matchAlgorithm
|
|
418
|
-
},
|
|
419
|
-
"@shikijs/transformers:notation-highlight"
|
|
420
|
-
);
|
|
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");
|
|
421
370
|
}
|
|
422
371
|
|
|
372
|
+
//#endregion
|
|
373
|
+
//#region src/shared/highlight-word.ts
|
|
423
374
|
function highlightWordInLine(line, ignoredElement, word, className) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
+
}
|
|
430
381
|
}
|
|
431
382
|
function getTextContent(element) {
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
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
|
+
*/
|
|
438
394
|
function highlightRange(elements, ignoredElement, index, len, className) {
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
}
|
|
458
|
-
currentIdx += textNode.value.length;
|
|
459
|
-
}
|
|
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
|
+
}
|
|
460
413
|
}
|
|
461
414
|
function hasOverlap(range1, range2) {
|
|
462
|
-
|
|
415
|
+
return range1[0] <= range2[1] && range1[1] >= range2[0];
|
|
463
416
|
}
|
|
464
417
|
function separateToken(span, textNode, index, len) {
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
index > 0 ? createNode(text.slice(0, index)) : void 0,
|
|
476
|
-
createNode(text.slice(index, index + len)),
|
|
477
|
-
index + len < text.length ? createNode(text.slice(index + len)) : void 0
|
|
478
|
-
];
|
|
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
|
+
];
|
|
479
428
|
}
|
|
480
429
|
function inheritElement(original, overrides) {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
...overrides
|
|
487
|
-
};
|
|
430
|
+
return {
|
|
431
|
+
...original,
|
|
432
|
+
properties: { ...original.properties },
|
|
433
|
+
...overrides
|
|
434
|
+
};
|
|
488
435
|
}
|
|
489
436
|
|
|
437
|
+
//#endregion
|
|
438
|
+
//#region src/transformers/notation-highlight-word.ts
|
|
490
439
|
function transformerNotationWordHighlight(options = {}) {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
const lineNum = range ? Number.parseInt(range.slice(1), 10) : lines.length;
|
|
500
|
-
word = word.replace(/\\(.)/g, "$1");
|
|
501
|
-
for (let i = index; i < Math.min(index + lineNum, lines.length); i++) {
|
|
502
|
-
highlightWordInLine.call(this, lines[i], comment, word, classActiveWord);
|
|
503
|
-
}
|
|
504
|
-
if (classActivePre)
|
|
505
|
-
this.addClassToHast(this.pre, classActivePre);
|
|
506
|
-
return true;
|
|
507
|
-
},
|
|
508
|
-
options.matchAlgorithm
|
|
509
|
-
);
|
|
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);
|
|
510
448
|
}
|
|
511
449
|
|
|
450
|
+
//#endregion
|
|
451
|
+
//#region src/transformers/remove-comments.ts
|
|
452
|
+
/**
|
|
453
|
+
* Remove comments from the code.
|
|
454
|
+
*/
|
|
512
455
|
function transformerRemoveComments(options = {}) {
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
}
|
|
535
|
-
if (removeEmptyLines && hasComment) {
|
|
536
|
-
const isAllWhitespace = filteredLine.every((token) => !token.content.trim());
|
|
537
|
-
if (isAllWhitespace)
|
|
538
|
-
continue;
|
|
539
|
-
}
|
|
540
|
-
result.push(filteredLine);
|
|
541
|
-
}
|
|
542
|
-
return result;
|
|
543
|
-
}
|
|
544
|
-
};
|
|
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
|
+
};
|
|
545
477
|
}
|
|
546
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
|
+
*/
|
|
547
485
|
function transformerRemoveLineBreak() {
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
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
|
+
};
|
|
554
492
|
}
|
|
555
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
|
+
*/
|
|
556
501
|
function transformerRemoveNotationEscape() {
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
}
|
|
569
|
-
replace(hast);
|
|
570
|
-
return hast;
|
|
571
|
-
}
|
|
572
|
-
};
|
|
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
|
+
};
|
|
573
513
|
}
|
|
574
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
|
+
*/
|
|
575
521
|
function transformerRenderIndentGuides(options = {}) {
|
|
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
|
-
|
|
626
|
-
children: [{
|
|
627
|
-
type: "text",
|
|
628
|
-
value: text.value.slice(start, end)
|
|
629
|
-
}]
|
|
630
|
-
}))
|
|
631
|
-
);
|
|
632
|
-
text.value = text.value.slice(ranges.at(-1)[1]);
|
|
633
|
-
}
|
|
634
|
-
}
|
|
635
|
-
return hast;
|
|
636
|
-
}
|
|
637
|
-
};
|
|
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
|
+
};
|
|
638
572
|
}
|
|
639
573
|
|
|
574
|
+
//#endregion
|
|
575
|
+
//#region src/shared/utils.ts
|
|
640
576
|
function isTab(part) {
|
|
641
|
-
|
|
577
|
+
return part === " ";
|
|
642
578
|
}
|
|
643
579
|
function isSpace(part) {
|
|
644
|
-
|
|
580
|
+
return part === " " || part === " ";
|
|
645
581
|
}
|
|
646
582
|
function separateContinuousSpaces(inputs) {
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
});
|
|
665
|
-
bump();
|
|
666
|
-
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;
|
|
667
600
|
}
|
|
668
601
|
function splitSpaces(parts, type, renderContinuousSpaces = true) {
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
for (let i = parts.length - 1; i >= 0; i--) {
|
|
683
|
-
if (isSpace(parts[i]))
|
|
684
|
-
rightCount++;
|
|
685
|
-
else
|
|
686
|
-
break;
|
|
687
|
-
}
|
|
688
|
-
}
|
|
689
|
-
const middle = parts.slice(leftCount, parts.length - rightCount);
|
|
690
|
-
return [
|
|
691
|
-
...parts.slice(0, leftCount),
|
|
692
|
-
...renderContinuousSpaces ? separateContinuousSpaces(middle) : [middle.join("")],
|
|
693
|
-
...parts.slice(parts.length - rightCount)
|
|
694
|
-
];
|
|
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
|
+
];
|
|
695
615
|
}
|
|
696
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
|
+
*/
|
|
697
623
|
function transformerRenderWhitespace(options = {}) {
|
|
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
|
-
|
|
741
|
-
clone.children = [{ type: "text", value: part }];
|
|
742
|
-
if (keys.includes(part)) {
|
|
743
|
-
this.addClassToHast(clone, classMap[part]);
|
|
744
|
-
delete clone.properties.style;
|
|
745
|
-
}
|
|
746
|
-
return clone;
|
|
747
|
-
});
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
);
|
|
751
|
-
}
|
|
752
|
-
};
|
|
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
|
+
};
|
|
753
667
|
}
|
|
754
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
|
+
*/
|
|
755
675
|
function transformerStyleToClass(options = {}) {
|
|
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
|
-
|
|
802
|
-
|
|
803
|
-
},
|
|
804
|
-
getCSS() {
|
|
805
|
-
let css = "";
|
|
806
|
-
for (const [className, style] of classToStyle.entries()) {
|
|
807
|
-
css += `.${className}{${typeof style === "string" ? style : stringifyStyle(style)}}`;
|
|
808
|
-
}
|
|
809
|
-
return css;
|
|
810
|
-
},
|
|
811
|
-
clearRegistry() {
|
|
812
|
-
classToStyle.clear();
|
|
813
|
-
}
|
|
814
|
-
};
|
|
815
|
-
}
|
|
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
|
+
*/
|
|
816
723
|
function cyrb53(str, seed = 0) {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
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);
|
|
829
736
|
}
|
|
830
737
|
|
|
831
|
-
|
|
738
|
+
//#endregion
|
|
739
|
+
export { createCommentNotationTransformer, findAllSubstringIndexes, parseMetaHighlightString, parseMetaHighlightWords, transformerCompactLineOptions, transformerMetaHighlight, transformerMetaWordHighlight, transformerNotationDiff, transformerNotationErrorLevel, transformerNotationFocus, transformerNotationHighlight, transformerNotationMap, transformerNotationWordHighlight, transformerRemoveComments, transformerRemoveLineBreak, transformerRemoveNotationEscape, transformerRenderIndentGuides, transformerRenderWhitespace, transformerStyleToClass };
|