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.
Files changed (109) hide show
  1. package/CHANGELOG.md +25 -1
  2. package/LICENSE +1 -1
  3. package/README.md +12 -13
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +32 -19
  6. package/lib/capabilities.js +8 -12
  7. package/lib/cli.js +40 -74
  8. package/lib/clipboard.js +48 -18
  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 +196 -404
  14. package/lib/deps.js +240 -399
  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 +138 -141
  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 +90 -206
  25. package/lib/editor.js +98 -55
  26. package/lib/files.js +86 -103
  27. package/lib/git-branches.js +254 -416
  28. package/lib/git-deps.js +182 -344
  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 +157 -132
  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 +16 -26
  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 +22 -35
  44. package/lib/highlight/txt.js +9 -22
  45. package/lib/highlight.js +17 -46
  46. package/lib/keys.js +62 -85
  47. package/lib/manifest.js +67 -130
  48. package/lib/npm-commands.js +119 -189
  49. package/lib/remote.js +141 -197
  50. package/lib/render/branches.js +66 -73
  51. package/lib/render/chrome.js +264 -323
  52. package/lib/render/commits.js +187 -500
  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 -228
  58. package/lib/render/files.js +45 -99
  59. package/lib/render/frame.js +129 -183
  60. package/lib/render/notes.js +144 -160
  61. package/lib/render/npm.js +234 -112
  62. package/lib/render/present.js +4 -11
  63. package/lib/render/primitives.js +131 -170
  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 -28
  68. package/lib/report-model.js +110 -33
  69. package/lib/report-parse.js +56 -38
  70. package/lib/report-render.js +102 -59
  71. package/lib/report-run.js +87 -78
  72. package/lib/report-test.js +256 -141
  73. package/lib/report-tools.js +96 -161
  74. package/lib/review-merge.js +114 -152
  75. package/lib/review.js +115 -189
  76. package/lib/runs.js +225 -0
  77. package/lib/select.js +89 -75
  78. package/lib/session/accessors.js +14 -21
  79. package/lib/session/actions.js +82 -63
  80. package/lib/session/branches.js +193 -247
  81. package/lib/session/change-actions.js +171 -214
  82. package/lib/session/commit.js +280 -342
  83. package/lib/session/compose.js +559 -674
  84. package/lib/session/dashboard.js +385 -0
  85. package/lib/session/dispatch.js +164 -157
  86. package/lib/session/edit-click.js +57 -0
  87. package/lib/session/files-pane.js +85 -53
  88. package/lib/session/items.js +73 -104
  89. package/lib/session/lifecycle.js +403 -541
  90. package/lib/session/load.js +48 -68
  91. package/lib/session/navigation.js +96 -87
  92. package/lib/session/npm.js +409 -345
  93. package/lib/session/ops.js +196 -63
  94. package/lib/session/pointer.js +23 -15
  95. package/lib/session/progress.js +33 -21
  96. package/lib/session/review.js +33 -54
  97. package/lib/session/tasks.js +333 -0
  98. package/lib/session/templates.js +51 -59
  99. package/lib/session/terminal.js +85 -136
  100. package/lib/session/unit.js +3 -4
  101. package/lib/session/update.js +76 -97
  102. package/lib/session/view.js +52 -83
  103. package/lib/session/watch.js +208 -204
  104. package/lib/session.js +112 -237
  105. package/lib/update.js +45 -116
  106. package/lib/utilities.js +67 -89
  107. package/lib/wrap.js +33 -44
  108. package/package.json +1 -1
  109. package/lib/session/todos.js +0 -293
@@ -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'],