reslop 0.2.4 → 0.2.6
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/CHANGELOG.md +25 -1
- package/LICENSE +1 -1
- package/README.md +12 -13
- package/bin/reslop.js +3 -8
- package/lib/ansi.js +32 -19
- package/lib/capabilities.js +8 -12
- package/lib/cli.js +40 -74
- package/lib/clipboard.js +48 -18
- package/lib/dashboard/git.js +128 -0
- package/lib/dashboard/model.js +332 -0
- package/lib/dashboard/npm.js +193 -0
- package/lib/dashboard/tree.js +278 -0
- package/lib/deps-diff.js +196 -404
- package/lib/deps.js +240 -399
- package/lib/detect.js +8 -18
- package/lib/diff/align.js +41 -113
- package/lib/diff/diff.js +12 -43
- package/lib/diff/display.js +25 -36
- package/lib/diff/edit.js +6 -20
- package/lib/diff/inline.js +138 -141
- package/lib/diff/items.js +33 -47
- package/lib/diff/parse.js +28 -56
- package/lib/diff/patch.js +12 -33
- package/lib/diff/unit.js +90 -206
- package/lib/editor.js +98 -55
- package/lib/files.js +86 -103
- package/lib/git-branches.js +254 -416
- package/lib/git-deps.js +182 -344
- package/lib/git-reword-todo.js +5 -13
- package/lib/git-worktree.js +141 -212
- package/lib/git.js +21 -49
- package/lib/github.js +67 -181
- package/lib/gitlab.js +40 -90
- package/lib/highlight/bash.js +55 -177
- package/lib/highlight/core.js +157 -132
- package/lib/highlight/css.js +25 -58
- package/lib/highlight/csv.js +19 -37
- package/lib/highlight/dart.js +72 -213
- package/lib/highlight/dot.js +16 -26
- package/lib/highlight/html.js +42 -63
- package/lib/highlight/js.js +177 -506
- package/lib/highlight/json.js +17 -37
- package/lib/highlight/log.js +22 -35
- package/lib/highlight/txt.js +9 -22
- package/lib/highlight.js +17 -46
- package/lib/keys.js +62 -85
- package/lib/manifest.js +67 -130
- package/lib/npm-commands.js +119 -189
- package/lib/remote.js +141 -197
- package/lib/render/branches.js +66 -73
- package/lib/render/chrome.js +264 -323
- package/lib/render/commits.js +187 -500
- package/lib/render/dash-activity.js +212 -0
- package/lib/render/dash-blocks.js +245 -0
- package/lib/render/dash-table.js +290 -0
- package/lib/render/dashboard.js +62 -0
- package/lib/render/diff.js +123 -228
- package/lib/render/files.js +45 -99
- package/lib/render/frame.js +129 -183
- package/lib/render/notes.js +144 -160
- package/lib/render/npm.js +234 -112
- package/lib/render/present.js +4 -11
- package/lib/render/primitives.js +131 -170
- package/lib/render/render.js +5 -14
- package/lib/render/report.js +43 -0
- package/lib/render/tiles.js +260 -0
- package/lib/render/unit.js +15 -28
- package/lib/report-model.js +110 -33
- package/lib/report-parse.js +56 -38
- package/lib/report-render.js +102 -59
- package/lib/report-run.js +87 -78
- package/lib/report-test.js +256 -141
- package/lib/report-tools.js +96 -161
- package/lib/review-merge.js +114 -152
- package/lib/review.js +115 -189
- package/lib/runs.js +225 -0
- package/lib/select.js +89 -75
- package/lib/session/accessors.js +14 -21
- package/lib/session/actions.js +82 -63
- package/lib/session/branches.js +193 -247
- package/lib/session/change-actions.js +171 -214
- package/lib/session/commit.js +280 -342
- package/lib/session/compose.js +559 -674
- package/lib/session/dashboard.js +385 -0
- package/lib/session/dispatch.js +164 -157
- package/lib/session/edit-click.js +57 -0
- package/lib/session/files-pane.js +85 -53
- package/lib/session/items.js +73 -104
- package/lib/session/lifecycle.js +403 -541
- package/lib/session/load.js +48 -68
- package/lib/session/navigation.js +96 -87
- package/lib/session/npm.js +409 -345
- package/lib/session/ops.js +196 -63
- package/lib/session/pointer.js +23 -15
- package/lib/session/progress.js +33 -21
- package/lib/session/review.js +33 -54
- package/lib/session/tasks.js +333 -0
- package/lib/session/templates.js +51 -59
- package/lib/session/terminal.js +85 -136
- package/lib/session/unit.js +3 -4
- package/lib/session/update.js +76 -97
- package/lib/session/view.js +52 -83
- package/lib/session/watch.js +208 -204
- package/lib/session.js +112 -237
- package/lib/update.js +45 -116
- package/lib/utilities.js +67 -89
- package/lib/wrap.js +33 -44
- package/package.json +1 -1
- package/lib/session/todos.js +0 -293
package/lib/highlight/js.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const { QUOTES,
|
|
5
|
-
const {
|
|
6
|
-
const {
|
|
7
|
-
const {
|
|
3
|
+
const core = require('./core.js');
|
|
4
|
+
const { QUOTES, IDENT_START, IDENT_CHAR, EXPECT_STYLE } = core;
|
|
5
|
+
const { words, emit, wordStyles, appendNode, scanWhile } = core;
|
|
6
|
+
const { lineEnd, blockCommentEnd, stringEnd, numberEnd, operatorEnd } = core;
|
|
7
|
+
const { pushInterpolation, highlightInterpolated, nameStyle } = core;
|
|
8
8
|
|
|
9
9
|
const OPTIONS = {
|
|
10
10
|
js: {},
|
|
@@ -14,210 +14,94 @@ const OPTIONS = {
|
|
|
14
14
|
tsx: { ts: true, jsx: true },
|
|
15
15
|
};
|
|
16
16
|
|
|
17
|
+
const OP_CHARS = '=!+-*%>&|^~?:/<';
|
|
18
|
+
const PUNCT_CHARS = '{}()[];,.';
|
|
17
19
|
const DIGITS = '0123456789';
|
|
18
|
-
const
|
|
19
|
-
const
|
|
20
|
-
const
|
|
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
|
-
'true',
|
|
84
|
-
'false',
|
|
85
|
-
'null',
|
|
86
|
-
'undefined',
|
|
87
|
-
'NaN',
|
|
88
|
-
'Infinity',
|
|
89
|
-
'this',
|
|
90
|
-
'super',
|
|
91
|
-
];
|
|
92
|
-
|
|
93
|
-
const TYPES = [
|
|
94
|
-
'string',
|
|
95
|
-
'number',
|
|
96
|
-
'boolean',
|
|
97
|
-
'symbol',
|
|
98
|
-
'bigint',
|
|
99
|
-
'object',
|
|
100
|
-
'any',
|
|
101
|
-
'unknown',
|
|
102
|
-
'never',
|
|
103
|
-
'void',
|
|
104
|
-
'keyof',
|
|
105
|
-
'infer',
|
|
106
|
-
'unique',
|
|
107
|
-
'asserts',
|
|
108
|
-
'is',
|
|
109
|
-
];
|
|
110
|
-
|
|
111
|
-
const BUILTINS = [
|
|
112
|
-
'console',
|
|
113
|
-
'Math',
|
|
114
|
-
'JSON',
|
|
115
|
-
'Date',
|
|
116
|
-
'Array',
|
|
117
|
-
'Object',
|
|
118
|
-
'String',
|
|
119
|
-
'Number',
|
|
120
|
-
'Boolean',
|
|
121
|
-
'Map',
|
|
122
|
-
'Set',
|
|
123
|
-
'WeakMap',
|
|
124
|
-
'WeakSet',
|
|
125
|
-
'Promise',
|
|
126
|
-
'Error',
|
|
127
|
-
'RegExp',
|
|
128
|
-
'Symbol',
|
|
129
|
-
'Proxy',
|
|
130
|
-
'Reflect',
|
|
131
|
-
'Intl',
|
|
132
|
-
'Buffer',
|
|
133
|
-
'process',
|
|
134
|
-
'module',
|
|
135
|
-
'exports',
|
|
136
|
-
'require',
|
|
137
|
-
'global',
|
|
138
|
-
'globalThis',
|
|
139
|
-
'window',
|
|
140
|
-
'document',
|
|
141
|
-
'parseInt',
|
|
142
|
-
'parseFloat',
|
|
143
|
-
'isNaN',
|
|
144
|
-
'isFinite',
|
|
145
|
-
'encodeURI',
|
|
146
|
-
'decodeURI',
|
|
147
|
-
'encodeURIComponent',
|
|
148
|
-
'decodeURIComponent',
|
|
149
|
-
'setTimeout',
|
|
150
|
-
'setInterval',
|
|
151
|
-
'clearTimeout',
|
|
152
|
-
'clearInterval',
|
|
153
|
-
'fetch',
|
|
154
|
-
'URL',
|
|
155
|
-
'URLSearchParams',
|
|
156
|
-
];
|
|
157
|
-
|
|
158
|
-
const NEXT_EXPECT = {
|
|
159
|
-
function: 'function',
|
|
160
|
-
class: 'class',
|
|
161
|
-
interface: 'class',
|
|
162
|
-
enum: 'class',
|
|
163
|
-
type: 'type',
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
const typeOverlay = TYPES.filter((word) => STORAGE.includes(word));
|
|
167
|
-
|
|
168
|
-
const WORD_STYLE = {
|
|
169
|
-
...wordStyle(CONTROL, 'control'),
|
|
170
|
-
...wordStyle(LITERALS, 'literal'),
|
|
171
|
-
...wordStyle(BUILTINS, 'className'),
|
|
172
|
-
...wordStyle(STORAGE, 'storage'),
|
|
173
|
-
...wordStyle(typeOverlay, 'type'),
|
|
174
|
-
};
|
|
175
|
-
|
|
176
|
-
const TS_WORD_STYLE = {
|
|
177
|
-
...WORD_STYLE,
|
|
178
|
-
...wordStyle(TYPES, 'type'),
|
|
20
|
+
const JSX_NAME = /[A-Za-z0-9_$.:-]/;
|
|
21
|
+
const JSX_TEXT = /[^<{]/;
|
|
22
|
+
const REGEX_AFTER_OP = /[=(:,[?!&|{};]$/;
|
|
23
|
+
const REGEX_AFTER_WORD = /(?:return|case|throw|=>|typeof|void)$/;
|
|
24
|
+
|
|
25
|
+
const TEMPLATE = {
|
|
26
|
+
style: 'template',
|
|
27
|
+
quote: '`',
|
|
28
|
+
raw: false,
|
|
29
|
+
multiline: true,
|
|
30
|
+
dollarIdent: false,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const STORAGE = words(`
|
|
34
|
+
const let var function class extends static async get set constructor new
|
|
35
|
+
typeof instanceof void delete yield await import export from as default
|
|
36
|
+
type interface implements enum namespace module declare abstract readonly
|
|
37
|
+
private public protected override satisfies
|
|
38
|
+
`);
|
|
39
|
+
|
|
40
|
+
const CONTROL = words(`
|
|
41
|
+
if else for while do switch case break continue return throw try catch
|
|
42
|
+
finally with of in
|
|
43
|
+
`);
|
|
44
|
+
|
|
45
|
+
const LITERALS = words('true false null undefined NaN Infinity this super');
|
|
46
|
+
|
|
47
|
+
const TYPES = words(`
|
|
48
|
+
string number boolean symbol bigint object any unknown never void keyof
|
|
49
|
+
infer unique asserts is
|
|
50
|
+
`);
|
|
51
|
+
|
|
52
|
+
const BUILTINS = words(`
|
|
53
|
+
console Math JSON Date Array Object String Number Boolean Map Set WeakMap
|
|
54
|
+
WeakSet Promise Error RegExp Symbol Proxy Reflect Intl Buffer process module
|
|
55
|
+
exports require global globalThis window document parseInt parseFloat isNaN
|
|
56
|
+
isFinite encodeURI decodeURI encodeURIComponent decodeURIComponent
|
|
57
|
+
setTimeout setInterval clearTimeout clearInterval fetch URL URLSearchParams
|
|
58
|
+
`);
|
|
59
|
+
|
|
60
|
+
const NEXT_EXPECT = wordStyles({
|
|
61
|
+
function: ['function'],
|
|
62
|
+
class: ['class', 'interface', 'enum'],
|
|
63
|
+
type: ['type'],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const wordTable = (types) =>
|
|
67
|
+
wordStyles({
|
|
68
|
+
control: CONTROL,
|
|
69
|
+
literal: LITERALS,
|
|
70
|
+
className: BUILTINS,
|
|
71
|
+
storage: STORAGE,
|
|
72
|
+
type: types,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const WORD_STYLE = wordTable(TYPES.filter((word) => STORAGE.includes(word)));
|
|
76
|
+
const TS_WORD_STYLE = wordTable(TYPES);
|
|
77
|
+
|
|
78
|
+
const tailText = (out, size) => {
|
|
79
|
+
let text = '';
|
|
80
|
+
for (let i = out.length - 1; i >= 0; i--) {
|
|
81
|
+
text = out[i].text + text;
|
|
82
|
+
if (text.trimEnd().length >= size) break;
|
|
83
|
+
}
|
|
84
|
+
return text;
|
|
179
85
|
};
|
|
180
86
|
|
|
181
|
-
const
|
|
182
|
-
const
|
|
183
|
-
|
|
184
|
-
return table[word];
|
|
87
|
+
const isRegexContext = (out) => {
|
|
88
|
+
const prev = tailText(out, 6).trimEnd();
|
|
89
|
+
return !prev || REGEX_AFTER_OP.test(prev) || REGEX_AFTER_WORD.test(prev);
|
|
185
90
|
};
|
|
186
91
|
|
|
187
|
-
const
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
if (rhs.startsWith('function')) return 'function';
|
|
193
|
-
if (source[k2] === '(') return 'function';
|
|
194
|
-
return 'variable';
|
|
92
|
+
const assignStyle = (source, k) => {
|
|
93
|
+
const rhs = scanWhile(source, k + 1, /\s/);
|
|
94
|
+
if (source.startsWith('async', rhs)) return 'function';
|
|
95
|
+
if (source.startsWith('function', rhs)) return 'function';
|
|
96
|
+
return source[rhs] === '(' ? 'function' : 'variable';
|
|
195
97
|
};
|
|
196
98
|
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
return
|
|
99
|
+
const trailStyle = (source, k, out) => {
|
|
100
|
+
if (source[k] === '=') return assignStyle(source, k);
|
|
101
|
+
return tailText(out, 1).endsWith('.') ? 'property' : 'variable';
|
|
200
102
|
};
|
|
201
103
|
|
|
202
|
-
const
|
|
203
|
-
({ expectName }) => EXPECT_STYLE[expectName],
|
|
204
|
-
styleKeyword,
|
|
205
|
-
styleConstant,
|
|
206
|
-
stylePascal,
|
|
207
|
-
styleCall,
|
|
208
|
-
styleAssign,
|
|
209
|
-
styleProperty,
|
|
210
|
-
];
|
|
211
|
-
|
|
212
|
-
const identStyle = (ctx) => {
|
|
213
|
-
for (const rule of IDENT_RULES) {
|
|
214
|
-
const style = rule(ctx);
|
|
215
|
-
if (style) return style;
|
|
216
|
-
}
|
|
217
|
-
return 'variable';
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
const readRegex = (source, i) => {
|
|
104
|
+
const regexEnd = (source, i) => {
|
|
221
105
|
let j = i + 1;
|
|
222
106
|
const n = source.length;
|
|
223
107
|
while (j < n) {
|
|
@@ -234,82 +118,22 @@ const readRegex = (source, i) => {
|
|
|
234
118
|
j += 1;
|
|
235
119
|
continue;
|
|
236
120
|
}
|
|
237
|
-
if (source[j] === '/')
|
|
238
|
-
j += 1;
|
|
239
|
-
while (j < n && /[a-z]/i.test(source[j])) j += 1;
|
|
240
|
-
break;
|
|
241
|
-
}
|
|
121
|
+
if (source[j] === '/') return scanWhile(source, j + 1, /[a-z]/i);
|
|
242
122
|
if (source[j] === '\n') break;
|
|
243
123
|
j += 1;
|
|
244
124
|
}
|
|
245
|
-
return
|
|
246
|
-
};
|
|
247
|
-
|
|
248
|
-
const isRegexContext = (out) => {
|
|
249
|
-
const prev = tokensText(out).trimEnd();
|
|
250
|
-
if (!prev) return true;
|
|
251
|
-
const afterOperator = /[=(:,[?!&|{};]$/.test(prev);
|
|
252
|
-
const afterKeyword = /(?:return|case|throw|=>|typeof|void)$/.test(prev);
|
|
253
|
-
return afterOperator || afterKeyword;
|
|
254
|
-
};
|
|
255
|
-
|
|
256
|
-
const highlightTemplate = (source, i, highlightExpr) => {
|
|
257
|
-
const n = source.length;
|
|
258
|
-
let j = i + 1;
|
|
259
|
-
const parts = [];
|
|
260
|
-
emit(parts, 'template', '`');
|
|
261
|
-
let buf = '';
|
|
262
|
-
const flush = () => {
|
|
263
|
-
if (buf) {
|
|
264
|
-
emit(parts, 'template', buf);
|
|
265
|
-
buf = '';
|
|
266
|
-
}
|
|
267
|
-
};
|
|
268
|
-
while (j < n) {
|
|
269
|
-
if (source[j] === '\\') {
|
|
270
|
-
flush();
|
|
271
|
-
emit(parts, 'escape', source.slice(j, j + 2));
|
|
272
|
-
j += 2;
|
|
273
|
-
continue;
|
|
274
|
-
}
|
|
275
|
-
if (source[j] === '`') {
|
|
276
|
-
flush();
|
|
277
|
-
emit(parts, 'template', '`');
|
|
278
|
-
j += 1;
|
|
279
|
-
break;
|
|
280
|
-
}
|
|
281
|
-
if (source[j] === '$' && source[j + 1] === '{') {
|
|
282
|
-
flush();
|
|
283
|
-
emit(parts, 'interpolation', '${');
|
|
284
|
-
j += 2;
|
|
285
|
-
const interp = readTemplateInterp(source, j);
|
|
286
|
-
const inner = highlightExpr(interp.expr);
|
|
287
|
-
for (const t of inner) parts.push(t);
|
|
288
|
-
j = interp.end;
|
|
289
|
-
if (source[j] === '}') {
|
|
290
|
-
emit(parts, 'interpolation', '}');
|
|
291
|
-
j += 1;
|
|
292
|
-
}
|
|
293
|
-
continue;
|
|
294
|
-
}
|
|
295
|
-
buf += source[j];
|
|
296
|
-
j += 1;
|
|
297
|
-
}
|
|
298
|
-
flush();
|
|
299
|
-
return { tokens: parts, end: j };
|
|
125
|
+
return j;
|
|
300
126
|
};
|
|
301
127
|
|
|
302
128
|
const isJsxTagStart = (source, i) => {
|
|
303
|
-
if (source[i
|
|
304
|
-
if (i > 0 && /[A-Za-z0-9_$]/.test(source[i - 1])) return false;
|
|
129
|
+
if (i > 0 && IDENT_CHAR.test(source[i - 1])) return false;
|
|
305
130
|
const next = source[i + 1];
|
|
306
131
|
if (next === '>' || next === '/') return true;
|
|
307
|
-
if (
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
const after = source[skipWs(source, j)];
|
|
132
|
+
if (!IDENT_START.test(next || '')) return false;
|
|
133
|
+
const nameEnd = scanWhile(source, i + 1, JSX_NAME);
|
|
134
|
+
const after = source[scanWhile(source, nameEnd, /\s/)];
|
|
311
135
|
if (after === '>' || after === '/' || after === '{') return true;
|
|
312
|
-
return
|
|
136
|
+
return IDENT_START.test(after || '');
|
|
313
137
|
};
|
|
314
138
|
|
|
315
139
|
const jsxNameStyle = (name) => {
|
|
@@ -317,280 +141,127 @@ const jsxNameStyle = (name) => {
|
|
|
317
141
|
return 'tag';
|
|
318
142
|
};
|
|
319
143
|
|
|
320
|
-
const
|
|
321
|
-
let j = i;
|
|
322
|
-
while (j < source.length && /[A-Za-z0-9_$.:-]/.test(source[j])) j += 1;
|
|
323
|
-
return { text: source.slice(i, j), end: j };
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
const highlightJsxExpr = (source, i, inner) => {
|
|
327
|
-
const out = [];
|
|
144
|
+
const pushJsxExpr = (out, source, i, inner) => {
|
|
328
145
|
emit(out, 'interpolation', '{');
|
|
329
|
-
|
|
330
|
-
const tokens = inner(interp.expr);
|
|
331
|
-
for (const t of tokens) out.push(t);
|
|
332
|
-
let end = interp.end;
|
|
333
|
-
if (source[end] === '}') {
|
|
334
|
-
emit(out, 'interpolation', '}');
|
|
335
|
-
end += 1;
|
|
336
|
-
}
|
|
337
|
-
return { tokens: out, end };
|
|
146
|
+
return pushInterpolation(out, source, i + 1, inner);
|
|
338
147
|
};
|
|
339
148
|
|
|
340
|
-
const highlightJsxTagTail = (source,
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
149
|
+
const highlightJsxTagTail = (source, start, inner, out) => {
|
|
150
|
+
let i = start;
|
|
151
|
+
const take = (style, end) => {
|
|
152
|
+
emit(out, style, source.slice(i, end));
|
|
153
|
+
i = end;
|
|
154
|
+
};
|
|
155
|
+
while (i < source.length) {
|
|
344
156
|
const ch = source[i];
|
|
345
157
|
if (ch === '>') {
|
|
346
|
-
|
|
347
|
-
i
|
|
348
|
-
break;
|
|
158
|
+
take('punct', i + 1);
|
|
159
|
+
return { end: i, selfClosing: false };
|
|
349
160
|
}
|
|
350
161
|
if (ch === '/' && source[i + 1] === '>') {
|
|
351
|
-
|
|
352
|
-
i
|
|
353
|
-
selfClosing = true;
|
|
354
|
-
break;
|
|
355
|
-
}
|
|
356
|
-
if (/\s/.test(ch)) {
|
|
357
|
-
emit(out, 'plain', ch);
|
|
358
|
-
i += 1;
|
|
359
|
-
continue;
|
|
360
|
-
}
|
|
361
|
-
if (ch === '{') {
|
|
362
|
-
const expr = highlightJsxExpr(source, i, inner);
|
|
363
|
-
for (const t of expr.tokens) out.push(t);
|
|
364
|
-
i = expr.end;
|
|
365
|
-
continue;
|
|
366
|
-
}
|
|
367
|
-
if (QUOTES.includes(ch)) {
|
|
368
|
-
const { text, end } = readString(source, i);
|
|
369
|
-
emit(out, 'string', text);
|
|
370
|
-
i = end;
|
|
371
|
-
continue;
|
|
162
|
+
take('punct', i + 2);
|
|
163
|
+
return { end: i, selfClosing: true };
|
|
372
164
|
}
|
|
373
|
-
if (ch === '
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
if (/[A-Za-z_$]/.test(ch)) {
|
|
379
|
-
const { text, end } = readJsxName(source, i);
|
|
380
|
-
emit(out, 'attr', text);
|
|
381
|
-
i = end;
|
|
382
|
-
continue;
|
|
383
|
-
}
|
|
384
|
-
emit(out, 'punct', ch);
|
|
385
|
-
i += 1;
|
|
165
|
+
if (ch === '{') i = pushJsxExpr(out, source, i, inner);
|
|
166
|
+
else if (QUOTES.includes(ch)) take('string', stringEnd(source, i));
|
|
167
|
+
else if (IDENT_START.test(ch)) take('attr', scanWhile(source, i, JSX_NAME));
|
|
168
|
+
else if (/\s/.test(ch)) take('plain', i + 1);
|
|
169
|
+
else take('punct', i + 1);
|
|
386
170
|
}
|
|
387
|
-
return { end: i, selfClosing };
|
|
171
|
+
return { end: i, selfClosing: false };
|
|
388
172
|
};
|
|
389
173
|
|
|
390
|
-
const highlightJsxElement = (source,
|
|
174
|
+
const highlightJsxElement = (source, start, inner) => {
|
|
391
175
|
const out = [];
|
|
392
|
-
|
|
176
|
+
let i = start + 1;
|
|
393
177
|
emit(out, 'punct', '<');
|
|
394
|
-
i
|
|
395
|
-
|
|
396
|
-
if (source[i] === '/') {
|
|
178
|
+
const closing = source[i] === '/';
|
|
179
|
+
if (closing) {
|
|
397
180
|
emit(out, 'punct', '/');
|
|
398
181
|
i += 1;
|
|
399
|
-
closing = true;
|
|
400
182
|
}
|
|
401
|
-
const
|
|
402
|
-
const name =
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
i = nameRead.end;
|
|
406
|
-
}
|
|
407
|
-
const tail = highlightJsxTagTail(source, i, inner, out);
|
|
183
|
+
const nameEnd = scanWhile(source, i, JSX_NAME);
|
|
184
|
+
const name = source.slice(i, nameEnd);
|
|
185
|
+
emit(out, jsxNameStyle(name), name);
|
|
186
|
+
const tail = highlightJsxTagTail(source, nameEnd, inner, out);
|
|
408
187
|
i = tail.end;
|
|
409
188
|
if (closing || tail.selfClosing) return { tokens: out, end: i };
|
|
410
|
-
while (i <
|
|
189
|
+
while (i < source.length) {
|
|
411
190
|
if (source.startsWith('</', i)) {
|
|
412
|
-
const
|
|
413
|
-
|
|
414
|
-
return { tokens: out, end: close.end };
|
|
191
|
+
const end = appendNode(out, highlightJsxElement(source, i, inner));
|
|
192
|
+
return { tokens: out, end };
|
|
415
193
|
}
|
|
416
194
|
if (source[i] === '{') {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
i =
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
for (const t of child.tokens) out.push(t);
|
|
425
|
-
i = child.end;
|
|
426
|
-
continue;
|
|
427
|
-
}
|
|
428
|
-
let j = i;
|
|
429
|
-
while (j < n && source[j] !== '<' && source[j] !== '{') j += 1;
|
|
430
|
-
if (j === i) {
|
|
431
|
-
emit(out, 'plain', source[i]);
|
|
432
|
-
i += 1;
|
|
433
|
-
continue;
|
|
195
|
+
i = pushJsxExpr(out, source, i, inner);
|
|
196
|
+
} else if (source[i] === '<' && isJsxTagStart(source, i)) {
|
|
197
|
+
i = appendNode(out, highlightJsxElement(source, i, inner));
|
|
198
|
+
} else {
|
|
199
|
+
const end = source[i] === '<' ? i + 1 : scanWhile(source, i, JSX_TEXT);
|
|
200
|
+
emit(out, 'plain', source.slice(i, end));
|
|
201
|
+
i = end;
|
|
434
202
|
}
|
|
435
|
-
emit(out, 'plain', source.slice(i, j));
|
|
436
|
-
i = j;
|
|
437
203
|
}
|
|
438
204
|
return { tokens: out, end: i };
|
|
439
205
|
};
|
|
440
206
|
|
|
441
|
-
const
|
|
442
|
-
|
|
443
|
-
const { text, end } = readUntilNl(source, i);
|
|
444
|
-
return { end, tokens: [{ text, style: 'comment' }] };
|
|
445
|
-
};
|
|
446
|
-
|
|
447
|
-
const readStarComment = (source, i) => {
|
|
448
|
-
if (source[i + 1] !== '*') return null;
|
|
449
|
-
const { text, end } = readBlockComment(source, i);
|
|
450
|
-
return { end, tokens: [{ text, style: 'comment' }] };
|
|
451
|
-
};
|
|
452
|
-
|
|
453
|
-
const readRegexAt = (source, i, scan) => {
|
|
454
|
-
if (!isRegexContext(scan.out)) return null;
|
|
455
|
-
const { text, end } = readRegex(source, i);
|
|
456
|
-
return { end, tokens: [{ text, style: 'regex' }] };
|
|
457
|
-
};
|
|
458
|
-
|
|
459
|
-
const readOperatorAt = (source, i) => {
|
|
460
|
-
const { text, end } = readJsOperator(source, i);
|
|
461
|
-
const tokens = [{ text, style: 'operator' }];
|
|
462
|
-
return { end, tokens, expectName: null };
|
|
463
|
-
};
|
|
464
|
-
|
|
465
|
-
const readSlashToken = (source, i, scan) =>
|
|
466
|
-
readLineComment(source, i) ||
|
|
467
|
-
readStarComment(source, i) ||
|
|
468
|
-
readRegexAt(source, i, scan) ||
|
|
469
|
-
readOperatorAt(source, i);
|
|
470
|
-
|
|
471
|
-
const readQuoted = (source, i) => {
|
|
472
|
-
const { text, end } = readString(source, i);
|
|
473
|
-
return { end, tokens: [{ text, style: 'string' }] };
|
|
474
|
-
};
|
|
475
|
-
|
|
476
|
-
const readTemplateAt = (source, i, scan) => {
|
|
477
|
-
const result = highlightTemplate(source, i, scan.inner);
|
|
478
|
-
return { end: result.end, tokens: result.tokens };
|
|
479
|
-
};
|
|
480
|
-
|
|
481
|
-
const readNumberAt = (source, i) => {
|
|
482
|
-
const ch = source[i];
|
|
483
|
-
const next = source[i + 1];
|
|
484
|
-
const dotted = ch === '.' && DIGITS.includes(next || '');
|
|
485
|
-
if (!DIGITS.includes(ch) && !dotted) return null;
|
|
486
|
-
const { text, end } = readJsNumber(source, i);
|
|
487
|
-
return { end, tokens: [{ text, style: 'number' }] };
|
|
488
|
-
};
|
|
489
|
-
|
|
490
|
-
const readPunctAt = (source, i) => {
|
|
491
|
-
const text = source[i];
|
|
492
|
-
return { end: i + 1, tokens: [{ text, style: 'punct' }] };
|
|
493
|
-
};
|
|
494
|
-
|
|
495
|
-
const readDecorator = (source, i) => {
|
|
496
|
-
let j = i + 1;
|
|
497
|
-
while (j < source.length && /[A-Za-z0-9_$]/.test(source[j])) j += 1;
|
|
498
|
-
const text = source.slice(i, j);
|
|
499
|
-
const tokens = [{ text, style: 'decorator' }];
|
|
500
|
-
return { end: j, tokens, expectName: null };
|
|
501
|
-
};
|
|
502
|
-
|
|
503
|
-
const nextExpectName = (word, expectName) => {
|
|
504
|
-
if (EXPECT_STYLE[expectName]) return null;
|
|
505
|
-
return NEXT_EXPECT[word] ?? null;
|
|
506
|
-
};
|
|
507
|
-
|
|
508
|
-
const readIdentAt = (source, i, scan) => {
|
|
509
|
-
const word = peekIdent(source, i);
|
|
510
|
-
if (!word) return null;
|
|
511
|
-
const end = i + word.length;
|
|
512
|
-
const k = skipWs(source, end);
|
|
513
|
-
const after = source[k];
|
|
514
|
-
const { ts, out } = scan;
|
|
515
|
-
const ctx = { word, ts, after, source, k, out, expectName: scan.expectName };
|
|
516
|
-
const style = identStyle(ctx);
|
|
517
|
-
const tokens = [{ text: word, style }];
|
|
518
|
-
const expectName = nextExpectName(word, scan.expectName);
|
|
519
|
-
return { end, tokens, expectName };
|
|
520
|
-
};
|
|
521
|
-
|
|
522
|
-
const readJsxAt = (source, i, scan) => {
|
|
523
|
-
if (!scan.jsx || !isJsxTagStart(source, i)) return null;
|
|
524
|
-
const node = highlightJsxElement(source, i, scan.inner);
|
|
525
|
-
const tokens = node.tokens;
|
|
526
|
-
const end = node.end;
|
|
527
|
-
return { end, tokens, expectName: null };
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
const readPlain = (source, i) => {
|
|
531
|
-
const text = source[i];
|
|
532
|
-
return { end: i + 1, tokens: [{ text, style: 'plain' }] };
|
|
533
|
-
};
|
|
534
|
-
|
|
535
|
-
const CHAR_READER_GROUPS = [
|
|
536
|
-
{ chars: '/', reader: readSlashToken },
|
|
537
|
-
{
|
|
538
|
-
chars: '<',
|
|
539
|
-
reader: (source, i, scan) =>
|
|
540
|
-
readJsxAt(source, i, scan) || readOperatorAt(source, i),
|
|
541
|
-
},
|
|
542
|
-
{
|
|
543
|
-
chars: '.',
|
|
544
|
-
reader: (source, i) => readNumberAt(source, i) || readPunctAt(source, i),
|
|
545
|
-
},
|
|
546
|
-
{ chars: '`', reader: readTemplateAt },
|
|
547
|
-
{ chars: QUOTES, reader: readQuoted },
|
|
548
|
-
{ chars: '@', reader: readDecorator },
|
|
549
|
-
{ chars: DIGITS, reader: readNumberAt },
|
|
550
|
-
{ chars: IDENT_START, reader: readIdentAt },
|
|
551
|
-
{ chars: OP_CHARS, reader: readOperatorAt },
|
|
552
|
-
{ chars: PUNCT_CHARS, reader: readPunctAt },
|
|
553
|
-
];
|
|
554
|
-
|
|
555
|
-
const indexCharReaders = (groups) => {
|
|
556
|
-
const table = Object.create(null);
|
|
557
|
-
for (const group of groups) {
|
|
558
|
-
for (const ch of group.chars) table[ch] = group.reader;
|
|
559
|
-
}
|
|
560
|
-
return table;
|
|
561
|
-
};
|
|
562
|
-
|
|
563
|
-
const CHAR_READERS = indexCharReaders(CHAR_READER_GROUPS);
|
|
564
|
-
|
|
565
|
-
const readToken = (source, i, scan) => {
|
|
566
|
-
const reader = CHAR_READERS[source[i]] ?? readPlain;
|
|
567
|
-
const result = reader(source, i, scan);
|
|
568
|
-
if (result && result.end > i) return result;
|
|
569
|
-
return readPlain(source, i);
|
|
570
|
-
};
|
|
571
|
-
|
|
572
|
-
const highlightJsFamily = (source, options = {}) => {
|
|
573
|
-
const ts = options.ts === true;
|
|
574
|
-
const jsx = options.jsx === true;
|
|
207
|
+
const highlightJsFamily = (source, options) => {
|
|
208
|
+
const table = options.ts ? TS_WORD_STYLE : WORD_STYLE;
|
|
575
209
|
const inner = (expr) => highlightJsFamily(expr, options);
|
|
576
210
|
const out = [];
|
|
577
|
-
const scan = { ts, jsx, inner, out, expectName: null };
|
|
578
211
|
let i = 0;
|
|
212
|
+
let expectName = null;
|
|
213
|
+
const take = (style, end) => {
|
|
214
|
+
emit(out, style, source.slice(i, end));
|
|
215
|
+
i = end;
|
|
216
|
+
};
|
|
579
217
|
while (i < source.length) {
|
|
580
|
-
const
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
218
|
+
const ch = source[i];
|
|
219
|
+
const next = source[i + 1];
|
|
220
|
+
if (ch === '/' && next === '/') {
|
|
221
|
+
take('comment', lineEnd(source, i));
|
|
222
|
+
} else if (ch === '/' && next === '*') {
|
|
223
|
+
take('comment', blockCommentEnd(source, i));
|
|
224
|
+
} else if (ch === '/' && isRegexContext(out)) {
|
|
225
|
+
take('regex', regexEnd(source, i));
|
|
226
|
+
} else if (ch === '<' && options.jsx && isJsxTagStart(source, i)) {
|
|
227
|
+
i = appendNode(out, highlightJsxElement(source, i, inner));
|
|
228
|
+
expectName = null;
|
|
229
|
+
} else if (ch === '`') {
|
|
230
|
+
const node = highlightInterpolated(source, i, i + 1, TEMPLATE, inner);
|
|
231
|
+
i = appendNode(out, node);
|
|
232
|
+
} else if (QUOTES.includes(ch)) {
|
|
233
|
+
take('string', stringEnd(source, i));
|
|
234
|
+
} else if (ch === '@') {
|
|
235
|
+
take('decorator', scanWhile(source, i + 1, IDENT_CHAR));
|
|
236
|
+
expectName = null;
|
|
237
|
+
} else if (
|
|
238
|
+
DIGITS.includes(ch) ||
|
|
239
|
+
(ch === '.' && DIGITS.includes(next || ''))
|
|
240
|
+
) {
|
|
241
|
+
take('number', numberEnd(source, i));
|
|
242
|
+
} else if (IDENT_START.test(ch)) {
|
|
243
|
+
const end = scanWhile(source, i + 1, IDENT_CHAR);
|
|
244
|
+
const word = source.slice(i, end);
|
|
245
|
+
const k = scanWhile(source, end, /\s/);
|
|
246
|
+
const style =
|
|
247
|
+
EXPECT_STYLE[expectName] ||
|
|
248
|
+
table[word] ||
|
|
249
|
+
nameStyle(word, source[k]) ||
|
|
250
|
+
trailStyle(source, k, out);
|
|
251
|
+
const nextExpect = NEXT_EXPECT[word] ?? null;
|
|
252
|
+
take(style, end);
|
|
253
|
+
expectName = EXPECT_STYLE[expectName] ? null : nextExpect;
|
|
254
|
+
} else if (OP_CHARS.includes(ch)) {
|
|
255
|
+
take('operator', operatorEnd(source, i));
|
|
256
|
+
expectName = null;
|
|
257
|
+
} else {
|
|
258
|
+
take(PUNCT_CHARS.includes(ch) ? 'punct' : 'plain', i + 1);
|
|
585
259
|
}
|
|
586
260
|
}
|
|
587
261
|
return out;
|
|
588
262
|
};
|
|
589
263
|
|
|
590
|
-
const highlight = (source, lang) =>
|
|
591
|
-
const options = OPTIONS[lang] ?? {};
|
|
592
|
-
return highlightJsFamily(source, options);
|
|
593
|
-
};
|
|
264
|
+
const highlight = (source, lang) => highlightJsFamily(source, OPTIONS[lang]);
|
|
594
265
|
|
|
595
266
|
module.exports = {
|
|
596
267
|
langs: ['js', 'mjs', 'ts', 'jsx', 'tsx'],
|