reslop 0.2.5 → 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.
Files changed (105) hide show
  1. package/CHANGELOG.md +12 -1
  2. package/LICENSE +1 -1
  3. package/README.md +11 -11
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +32 -19
  6. package/lib/capabilities.js +8 -8
  7. package/lib/cli.js +40 -74
  8. package/lib/clipboard.js +39 -47
  9. package/lib/dashboard/git.js +128 -0
  10. package/lib/dashboard/model.js +332 -0
  11. package/lib/dashboard/npm.js +193 -0
  12. package/lib/dashboard/tree.js +278 -0
  13. package/lib/deps-diff.js +197 -402
  14. package/lib/deps.js +113 -240
  15. package/lib/detect.js +8 -18
  16. package/lib/diff/align.js +41 -113
  17. package/lib/diff/diff.js +12 -43
  18. package/lib/diff/display.js +25 -36
  19. package/lib/diff/edit.js +6 -20
  20. package/lib/diff/inline.js +76 -144
  21. package/lib/diff/items.js +33 -47
  22. package/lib/diff/parse.js +28 -56
  23. package/lib/diff/patch.js +12 -33
  24. package/lib/diff/unit.js +79 -178
  25. package/lib/editor.js +52 -69
  26. package/lib/files.js +71 -89
  27. package/lib/git-branches.js +254 -416
  28. package/lib/git-deps.js +147 -272
  29. package/lib/git-reword-todo.js +5 -13
  30. package/lib/git-worktree.js +141 -212
  31. package/lib/git.js +21 -49
  32. package/lib/github.js +67 -181
  33. package/lib/gitlab.js +40 -90
  34. package/lib/highlight/bash.js +55 -177
  35. package/lib/highlight/core.js +155 -141
  36. package/lib/highlight/css.js +25 -58
  37. package/lib/highlight/csv.js +19 -37
  38. package/lib/highlight/dart.js +72 -213
  39. package/lib/highlight/dot.js +14 -14
  40. package/lib/highlight/html.js +42 -63
  41. package/lib/highlight/js.js +177 -506
  42. package/lib/highlight/json.js +17 -37
  43. package/lib/highlight/log.js +19 -25
  44. package/lib/highlight/txt.js +7 -10
  45. package/lib/highlight.js +17 -46
  46. package/lib/keys.js +49 -82
  47. package/lib/manifest.js +67 -126
  48. package/lib/npm-commands.js +120 -169
  49. package/lib/remote.js +140 -190
  50. package/lib/render/branches.js +63 -61
  51. package/lib/render/chrome.js +220 -333
  52. package/lib/render/commits.js +160 -361
  53. package/lib/render/dash-activity.js +212 -0
  54. package/lib/render/dash-blocks.js +245 -0
  55. package/lib/render/dash-table.js +290 -0
  56. package/lib/render/dashboard.js +62 -0
  57. package/lib/render/diff.js +123 -258
  58. package/lib/render/files.js +40 -85
  59. package/lib/render/frame.js +78 -117
  60. package/lib/render/notes.js +139 -210
  61. package/lib/render/npm.js +213 -177
  62. package/lib/render/present.js +4 -11
  63. package/lib/render/primitives.js +72 -174
  64. package/lib/render/render.js +5 -14
  65. package/lib/render/report.js +43 -0
  66. package/lib/render/tiles.js +260 -0
  67. package/lib/render/unit.js +15 -31
  68. package/lib/report-model.js +110 -33
  69. package/lib/report-parse.js +56 -37
  70. package/lib/report-render.js +102 -59
  71. package/lib/report-run.js +87 -77
  72. package/lib/report-test.js +221 -99
  73. package/lib/report-tools.js +96 -154
  74. package/lib/review-merge.js +33 -41
  75. package/lib/review.js +68 -129
  76. package/lib/runs.js +225 -0
  77. package/lib/select.js +53 -91
  78. package/lib/session/accessors.js +14 -21
  79. package/lib/session/actions.js +61 -43
  80. package/lib/session/branches.js +166 -222
  81. package/lib/session/change-actions.js +49 -119
  82. package/lib/session/commit.js +274 -355
  83. package/lib/session/compose.js +556 -714
  84. package/lib/session/dashboard.js +385 -0
  85. package/lib/session/dispatch.js +162 -170
  86. package/lib/session/files-pane.js +76 -44
  87. package/lib/session/items.js +6 -17
  88. package/lib/session/lifecycle.js +393 -466
  89. package/lib/session/load.js +1 -14
  90. package/lib/session/navigation.js +2 -2
  91. package/lib/session/npm.js +403 -420
  92. package/lib/session/ops.js +5 -13
  93. package/lib/session/pointer.js +16 -14
  94. package/lib/session/progress.js +39 -30
  95. package/lib/session/review.js +1 -3
  96. package/lib/session/tasks.js +276 -235
  97. package/lib/session/templates.js +51 -59
  98. package/lib/session/terminal.js +85 -132
  99. package/lib/session/view.js +47 -81
  100. package/lib/session/watch.js +196 -212
  101. package/lib/session.js +81 -161
  102. package/lib/update.js +44 -111
  103. package/lib/utilities.js +46 -88
  104. package/lib/wrap.js +29 -55
  105. package/package.json +1 -1
@@ -1,10 +1,10 @@
1
1
  'use strict';
2
2
 
3
- const hl = require('./core.js');
4
- const { QUOTES, EXPECT_STYLE, emit, tokensText, wordStyle } = hl;
5
- const { peekIdent, skipWs, readUntilNl, readBlockComment } = hl;
6
- const { readJsNumber, readJsOperator, readString } = hl;
7
- const { readTemplateInterp, styleConstant, stylePascal, styleCall } = hl;
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 IDENT_START = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_$';
19
- const OP_CHARS = '=!+-*%>&|^~?:';
20
- const PUNCT_CHARS = '{}()[];,';
21
-
22
- const STORAGE = [
23
- 'const',
24
- 'let',
25
- 'var',
26
- 'function',
27
- 'class',
28
- 'extends',
29
- 'static',
30
- 'async',
31
- 'get',
32
- 'set',
33
- 'constructor',
34
- 'new',
35
- 'typeof',
36
- 'instanceof',
37
- 'void',
38
- 'delete',
39
- 'yield',
40
- 'await',
41
- 'import',
42
- 'export',
43
- 'from',
44
- 'as',
45
- 'default',
46
- 'type',
47
- 'interface',
48
- 'implements',
49
- 'enum',
50
- 'namespace',
51
- 'module',
52
- 'declare',
53
- 'abstract',
54
- 'readonly',
55
- 'private',
56
- 'public',
57
- 'protected',
58
- 'override',
59
- 'satisfies',
60
- ];
61
-
62
- const CONTROL = [
63
- 'if',
64
- 'else',
65
- 'for',
66
- 'while',
67
- 'do',
68
- 'switch',
69
- 'case',
70
- 'break',
71
- 'continue',
72
- 'return',
73
- 'throw',
74
- 'try',
75
- 'catch',
76
- 'finally',
77
- 'with',
78
- 'of',
79
- 'in',
80
- ];
81
-
82
- const LITERALS = [
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 styleKeyword = ({ word, ts }) => {
182
- const table = ts ? TS_WORD_STYLE : WORD_STYLE;
183
- if (!Object.hasOwn(table, word)) return null;
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 styleAssign = ({ after, source, k }) => {
188
- if (after !== '=') return null;
189
- const k2 = skipWs(source, k + 1);
190
- const rhs = source.slice(k2, k2 + 8);
191
- if (rhs.startsWith('async')) return 'function';
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 styleProperty = ({ out }) => {
198
- const prev = tokensText(out).slice(-8);
199
- return /\.$/.test(prev) ? 'property' : null;
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 IDENT_RULES = [
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 { text: source.slice(i, j), end: j };
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] !== '<') return false;
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 (!/[A-Za-z_$]/.test(next || '')) return false;
308
- let j = i + 1;
309
- while (j < source.length && /[A-Za-z0-9_$.:-]/.test(source[j])) j += 1;
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 /[A-Za-z_$]/.test(after || '');
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 readJsxName = (source, i) => {
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
- const interp = readTemplateInterp(source, i + 1);
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, i, inner, out) => {
341
- const n = source.length;
342
- let selfClosing = false;
343
- while (i < n) {
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
- emit(out, 'punct', '>');
347
- i += 1;
348
- break;
158
+ take('punct', i + 1);
159
+ return { end: i, selfClosing: false };
349
160
  }
350
161
  if (ch === '/' && source[i + 1] === '>') {
351
- emit(out, 'punct', '/>');
352
- i += 2;
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
- emit(out, 'punct', '=');
375
- i += 1;
376
- continue;
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, i, inner) => {
174
+ const highlightJsxElement = (source, start, inner) => {
391
175
  const out = [];
392
- const n = source.length;
176
+ let i = start + 1;
393
177
  emit(out, 'punct', '<');
394
- i += 1;
395
- let closing = false;
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 nameRead = readJsxName(source, i);
402
- const name = nameRead.text;
403
- if (name) {
404
- emit(out, jsxNameStyle(name), name);
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 < n) {
189
+ while (i < source.length) {
411
190
  if (source.startsWith('</', i)) {
412
- const close = highlightJsxElement(source, i, inner);
413
- for (const t of close.tokens) out.push(t);
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
- const expr = highlightJsxExpr(source, i, inner);
418
- for (const t of expr.tokens) out.push(t);
419
- i = expr.end;
420
- continue;
421
- }
422
- if (source[i] === '<' && isJsxTagStart(source, i)) {
423
- const child = highlightJsxElement(source, i, inner);
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 readLineComment = (source, i) => {
442
- if (source[i + 1] !== '/') return null;
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 result = readToken(source, i, scan);
581
- for (const token of result.tokens) out.push(token);
582
- i = result.end;
583
- if (Object.hasOwn(result, 'expectName')) {
584
- scan.expectName = result.expectName;
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'],