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,71 +1,38 @@
1
1
  'use strict';
2
2
 
3
3
  const core = require('./core.js');
4
- const { QUOTES, emit, readBlockComment, readString } = core;
4
+ const { QUOTES, emit, scanWhile, blockCommentEnd, stringEnd } = core;
5
5
 
6
- const CSS_NEXT_STYLE = {
7
- ':': 'property',
8
- '{': 'tag',
9
- };
6
+ const CSS_NEXT_STYLE = { ':': 'property', '{': 'tag' };
7
+ const CSS_NAME = /[A-Za-z0-9_-]/;
10
8
 
11
9
  const highlight = (source) => {
12
10
  const out = [];
13
11
  let i = 0;
14
- const n = source.length;
15
- while (i < n) {
12
+ const take = (style, end) => {
13
+ emit(out, style, source.slice(i, end));
14
+ i = end;
15
+ };
16
+ while (i < source.length) {
16
17
  const ch = source[i];
17
- const next = source[i + 1];
18
- if (ch === '/' && next === '*') {
19
- const { text, end } = readBlockComment(source, i);
20
- emit(out, 'comment', text);
21
- i = end;
22
- continue;
23
- }
24
- if (QUOTES.includes(ch)) {
25
- const { text, end } = readString(source, i);
26
- emit(out, 'string', text);
27
- i = end;
28
- continue;
29
- }
30
- if (ch === '@') {
31
- let j = i + 1;
32
- while (j < n && /[A-Za-z0-9-]/.test(source[j])) j += 1;
33
- emit(out, 'keyword', source.slice(i, j));
34
- i = j;
35
- continue;
36
- }
37
- if ('#.'.includes(ch)) {
38
- let j = i + 1;
39
- while (j < n && /[A-Za-z0-9_-]/.test(source[j])) j += 1;
40
- const kind = ch === '#' ? 'constant' : 'className';
41
- emit(out, kind, source.slice(i, j));
42
- i = j;
43
- continue;
44
- }
45
- if (/[0-9]/.test(ch)) {
46
- let j = i;
47
- while (j < n && /[0-9.%]/.test(source[j])) j += 1;
48
- emit(out, 'number', source.slice(i, j));
49
- i = j;
50
- continue;
51
- }
52
- if (/[A-Za-z_-]/.test(ch)) {
53
- let j = i + 1;
54
- while (j < n && /[A-Za-z0-9_-]/.test(source[j])) j += 1;
55
- const word = source.slice(i, j);
56
- let k = j;
57
- while (k < n && /\s/.test(source[k])) k += 1;
58
- emit(out, CSS_NEXT_STYLE[source[k]] ?? 'plain', word);
59
- i = j;
60
- continue;
61
- }
62
- if (/[{};:,]/.test(ch)) {
63
- emit(out, 'punct', ch);
64
- i += 1;
65
- continue;
18
+ if (ch === '/' && source[i + 1] === '*') {
19
+ take('comment', blockCommentEnd(source, i));
20
+ } else if (QUOTES.includes(ch)) {
21
+ take('string', stringEnd(source, i));
22
+ } else if (ch === '@') {
23
+ take('keyword', scanWhile(source, i + 1, /[A-Za-z0-9-]/));
24
+ } else if (ch === '#' || ch === '.') {
25
+ const style = ch === '#' ? 'constant' : 'className';
26
+ take(style, scanWhile(source, i + 1, CSS_NAME));
27
+ } else if (/[0-9]/.test(ch)) {
28
+ take('number', scanWhile(source, i, /[0-9.%]/));
29
+ } else if (/[A-Za-z_-]/.test(ch)) {
30
+ const end = scanWhile(source, i + 1, CSS_NAME);
31
+ const after = source[scanWhile(source, end, /\s/)];
32
+ take(CSS_NEXT_STYLE[after] ?? 'plain', end);
33
+ } else {
34
+ take(/[{};:,]/.test(ch) ? 'punct' : 'plain', i + 1);
66
35
  }
67
- emit(out, 'plain', ch);
68
- i += 1;
69
36
  }
70
37
  return out;
71
38
  };
@@ -1,65 +1,47 @@
1
1
  'use strict';
2
2
 
3
3
  const core = require('./core.js');
4
- const { emit } = core;
4
+ const { emit, highlightLines } = core;
5
5
 
6
6
  const splitCsv = (line) => {
7
7
  const cells = [];
8
8
  let cur = '';
9
- let inQ = false;
9
+ let inQuotes = false;
10
10
  for (let i = 0; i < line.length; i += 1) {
11
11
  const ch = line[i];
12
- if (ch === '"') {
13
- if (inQ && line[i + 1] === '"') {
14
- cur += '"';
15
- i += 1;
16
- } else {
17
- inQ = !inQ;
18
- cur += ch;
19
- }
20
- continue;
21
- }
22
- if (ch === ',' && !inQ) {
12
+ if (ch === '"' && inQuotes && line[i + 1] === '"') {
13
+ cur += '"';
14
+ i += 1;
15
+ } else if (ch === '"') {
16
+ inQuotes = !inQuotes;
17
+ cur += ch;
18
+ } else if (ch === ',' && !inQuotes) {
23
19
  cells.push(cur);
24
20
  cur = '';
25
- continue;
21
+ } else {
22
+ cur += ch;
26
23
  }
27
- cur += ch;
28
24
  }
29
25
  cells.push(cur);
30
26
  return cells;
31
27
  };
32
28
 
33
- const highlightCsvCell = (cell, row, col) => {
34
- if (row === 0) return { text: cell, style: 'function' };
35
- const num = /^-?\d+(\.\d+)?$/.test(cell.trim());
36
- if (num) return { text: cell, style: 'number' };
37
- if (col % 2 === 0) return { text: cell, style: 'string' };
38
- return { text: cell, style: 'variable' };
29
+ const cellStyle = (cell, col) => {
30
+ if (/^-?\d+(\.\d+)?$/.test(cell.trim())) return 'number';
31
+ return col % 2 === 0 ? 'string' : 'variable';
39
32
  };
40
33
 
41
- const highlightCsvLine = (line, row) => {
42
- if (!line) return [];
34
+ const highlightCsvLine = (line) => {
43
35
  const cells = splitCsv(line);
44
36
  const out = [];
45
37
  for (let col = 0; col < cells.length; col += 1) {
46
38
  if (col) emit(out, 'punct', ',');
47
- const cell = highlightCsvCell(cells[col], row, col);
48
- emit(out, cell.style, cell.text);
39
+ emit(out, cellStyle(cells[col], col), cells[col]);
49
40
  }
50
41
  return out;
51
42
  };
52
43
 
53
- const highlight = (source) => {
54
- const lines = source.split(/\r?\n/);
55
- const out = [];
56
- for (let row = 0; row < lines.length; row += 1) {
57
- if (row) emit(out, 'plain', '\n');
58
- for (const t of highlightCsvLine(lines[row], row === 0 ? 1 : row)) {
59
- out.push(t);
60
- }
61
- }
62
- return out;
44
+ module.exports = {
45
+ langs: ['csv'],
46
+ highlight: highlightLines(highlightCsvLine, /\r?\n/),
63
47
  };
64
-
65
- module.exports = { langs: ['csv'], highlight };
@@ -1,245 +1,104 @@
1
1
  'use strict';
2
2
 
3
- const hl = require('./core.js');
4
- const { SQUOTE, QUOTES, EXPECT_STYLE, emit, wordStyle } = hl;
5
- const { peekIdent, skipWs, readUntilNl, readBlockComment } = hl;
6
- const { readJsNumber, readJsOperator, readTemplateInterp } = hl;
7
- const { 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, numberEnd, operatorEnd } = core;
7
+ const { highlightInterpolated, nameStyle } = core;
8
8
 
9
- const STORAGE = [
10
- 'abstract',
11
- 'as',
12
- 'base',
13
- 'class',
14
- 'const',
15
- 'covariant',
16
- 'deferred',
17
- 'enum',
18
- 'export',
19
- 'extends',
20
- 'extension',
21
- 'external',
22
- 'factory',
23
- 'final',
24
- 'get',
25
- 'hide',
26
- 'implements',
27
- 'import',
28
- 'interface',
29
- 'late',
30
- 'library',
31
- 'mixin',
32
- 'on',
33
- 'operator',
34
- 'part',
35
- 'required',
36
- 'sealed',
37
- 'set',
38
- 'show',
39
- 'static',
40
- 'typedef',
41
- 'var',
42
- 'with',
43
- ];
9
+ const STORAGE = words(`
10
+ abstract as base class const covariant deferred enum export extends
11
+ extension external factory final get hide implements import interface late
12
+ library mixin on operator part required sealed set show static typedef var
13
+ with
14
+ `);
44
15
 
45
- const CONTROL = [
46
- 'assert',
47
- 'async',
48
- 'await',
49
- 'break',
50
- 'case',
51
- 'catch',
52
- 'continue',
53
- 'default',
54
- 'do',
55
- 'else',
56
- 'for',
57
- 'if',
58
- 'in',
59
- 'is',
60
- 'new',
61
- 'rethrow',
62
- 'return',
63
- 'switch',
64
- 'sync',
65
- 'throw',
66
- 'try',
67
- 'when',
68
- 'while',
69
- 'yield',
70
- ];
16
+ const CONTROL = words(`
17
+ assert async await break case catch continue default do else for if in is
18
+ new rethrow return switch sync throw try when while yield
19
+ `);
71
20
 
72
- const LITERALS = ['true', 'false', 'null', 'this', 'super'];
21
+ const LITERALS = words('true false null this super');
73
22
 
74
- const TYPES = ['void', 'dynamic', 'Never', 'int', 'double', 'num', 'bool'];
23
+ const TYPES = words('void dynamic Never int double num bool');
75
24
 
76
- const NEXT_EXPECT = {
77
- class: 'class',
78
- mixin: 'class',
79
- enum: 'class',
80
- extension: 'class',
81
- typedef: 'type',
82
- };
25
+ const NEXT_EXPECT = wordStyles({
26
+ class: ['class', 'mixin', 'enum', 'extension'],
27
+ type: ['typedef'],
28
+ });
83
29
 
84
- const WORD_STYLE = {
85
- ...wordStyle(CONTROL, 'control'),
86
- ...wordStyle(LITERALS, 'literal'),
87
- ...wordStyle(STORAGE, 'storage'),
88
- ...wordStyle(TYPES, 'type'),
89
- };
30
+ const WORD_STYLE = wordStyles({
31
+ control: CONTROL,
32
+ literal: LITERALS,
33
+ storage: STORAGE,
34
+ type: TYPES,
35
+ });
90
36
 
91
- const dartStringOpen = (source, i) => {
92
- let at = i;
37
+ const stringOpen = (source, i) => {
93
38
  const raw = source[i] === 'r';
94
- if (raw) at = i + 1;
39
+ const at = raw ? i + 1 : i;
95
40
  let quote = '';
96
- const triple = SQUOTE.repeat(3);
97
- const dtriple = '"'.repeat(3);
98
- if (source.startsWith(triple, at) || source.startsWith(dtriple, at)) {
41
+ if (source.startsWith(`'''`, at) || source.startsWith('"""', at)) {
99
42
  quote = source.slice(at, at + 3);
100
43
  } else if (QUOTES.includes(source[at] || '')) {
101
44
  quote = source[at];
102
45
  }
103
46
  if (!quote) return null;
104
- return { raw, quote, body: at + quote.length };
105
- };
106
-
107
- const dartIdentStyle = (ctx) => {
108
- const known = WORD_STYLE[ctx.word];
109
- if (known) return known;
110
- if (EXPECT_STYLE[ctx.expectName]) return EXPECT_STYLE[ctx.expectName];
111
- if (styleConstant(ctx)) return 'constant';
112
- if (stylePascal(ctx)) return 'className';
113
- if (styleCall(ctx)) return 'function';
114
- return 'variable';
115
- };
116
-
117
- const highlightDartString = (source, i, inner) => {
118
- const open = dartStringOpen(source, i);
119
- const n = source.length;
120
- const parts = [];
121
- emit(parts, 'string', source.slice(i, open.body));
122
- let j = open.body;
123
- let buf = '';
124
- const flush = () => {
125
- if (!buf) return;
126
- emit(parts, 'string', buf);
127
- buf = '';
128
- };
129
- const quote = open.quote;
130
- while (j < n) {
131
- if (!open.raw && source[j] === '\\') {
132
- flush();
133
- emit(parts, 'escape', source.slice(j, j + 2));
134
- j += 2;
135
- continue;
136
- }
137
- if (source.startsWith(quote, j)) {
138
- flush();
139
- emit(parts, 'string', quote);
140
- j += quote.length;
141
- break;
142
- }
143
- if (!open.raw && source[j] === '$') {
144
- const next = source[j + 1];
145
- if (next === '{') {
146
- flush();
147
- emit(parts, 'interpolation', '${');
148
- j += 2;
149
- const interp = readTemplateInterp(source, j);
150
- for (const t of inner(interp.expr)) parts.push(t);
151
- j = interp.end;
152
- if (source[j] === '}') {
153
- emit(parts, 'interpolation', '}');
154
- j += 1;
155
- }
156
- continue;
157
- }
158
- if (/[A-Za-z_$]/.test(next || '')) {
159
- flush();
160
- const ident = peekIdent(source, j + 1);
161
- emit(parts, 'interpolation', `$${ident}`);
162
- j += 1 + ident.length;
163
- continue;
164
- }
165
- }
166
- if (quote.length === 1 && source[j] === '\n') break;
167
- buf += source[j];
168
- j += 1;
169
- }
170
- flush();
171
- return { tokens: parts, end: j };
47
+ const multiline = quote.length === 3;
48
+ const kind = { style: 'string', quote, raw, multiline, dollarIdent: true };
49
+ return { body: at + quote.length, kind };
172
50
  };
173
51
 
174
52
  const highlight = (source) => {
175
- const inner = (expr) => highlight(expr);
176
53
  const out = [];
177
54
  let i = 0;
178
- const n = source.length;
179
55
  let expectName = null;
180
- while (i < n) {
56
+ const take = (style, end) => {
57
+ emit(out, style, source.slice(i, end));
58
+ i = end;
59
+ };
60
+ while (i < source.length) {
181
61
  const ch = source[i];
182
62
  const next = source[i + 1];
63
+ const open = stringOpen(source, i);
183
64
  if (ch === '/' && next === '/') {
184
- const { text, end } = readUntilNl(source, i);
185
- emit(out, 'comment', text);
186
- i = end;
187
- continue;
188
- }
189
- if (ch === '/' && next === '*') {
190
- const { text, end } = readBlockComment(source, i);
191
- emit(out, 'comment', text);
192
- i = end;
193
- continue;
194
- }
195
- if (dartStringOpen(source, i)) {
196
- const node = highlightDartString(source, i, inner);
197
- for (const t of node.tokens) out.push(t);
198
- i = node.end;
65
+ take('comment', lineEnd(source, i));
66
+ } else if (ch === '/' && next === '*') {
67
+ take('comment', blockCommentEnd(source, i));
68
+ } else if (open) {
69
+ const node = highlightInterpolated(
70
+ source,
71
+ i,
72
+ open.body,
73
+ open.kind,
74
+ highlight,
75
+ );
76
+ i = appendNode(out, node);
199
77
  expectName = null;
200
- continue;
201
- }
202
- if (/[0-9]/.test(ch) || (ch === '.' && /[0-9]/.test(next || ''))) {
203
- const { text, end } = readJsNumber(source, i);
204
- emit(out, 'number', text);
205
- i = end;
206
- continue;
207
- }
208
- if (ch === '@' || ch === '#') {
209
- let j = i + 1;
210
- while (j < n && /[A-Za-z0-9_$]/.test(source[j])) j += 1;
211
- const kind = ch === '@' ? 'decorator' : 'constant';
212
- emit(out, kind, source.slice(i, j));
213
- i = j;
78
+ } else if (/[0-9]/.test(ch) || (ch === '.' && /[0-9]/.test(next || ''))) {
79
+ take('number', numberEnd(source, i));
80
+ } else if (ch === '@' || ch === '#') {
81
+ const style = ch === '@' ? 'decorator' : 'constant';
82
+ take(style, scanWhile(source, i + 1, IDENT_CHAR));
214
83
  expectName = null;
215
- continue;
216
- }
217
- if (/[A-Za-z_$]/.test(ch)) {
218
- const word = peekIdent(source, i);
219
- const end = i + word.length;
220
- const k = skipWs(source, end);
221
- const after = source[k];
222
- const ctx = { word, after, expectName };
223
- emit(out, dartIdentStyle(ctx), word);
224
- if (EXPECT_STYLE[expectName]) expectName = null;
225
- else expectName = NEXT_EXPECT[word] ?? null;
226
- i = end;
227
- continue;
228
- }
229
- if (/[=<>!+\-*/%&|^~?:]/.test(ch)) {
230
- const { text, end } = readJsOperator(source, i);
231
- emit(out, 'operator', text);
232
- i = end;
84
+ } else if (IDENT_START.test(ch)) {
85
+ const end = scanWhile(source, i + 1, IDENT_CHAR);
86
+ const word = source.slice(i, end);
87
+ const after = source[scanWhile(source, end, /\s/)];
88
+ const style =
89
+ WORD_STYLE[word] ||
90
+ EXPECT_STYLE[expectName] ||
91
+ nameStyle(word, after) ||
92
+ 'variable';
93
+ const nextExpect = NEXT_EXPECT[word] ?? null;
94
+ take(style, end);
95
+ expectName = EXPECT_STYLE[expectName] ? null : nextExpect;
96
+ } else if (/[=<>!+\-*/%&|^~?:]/.test(ch)) {
97
+ take('operator', operatorEnd(source, i));
233
98
  expectName = null;
234
- continue;
235
- }
236
- if (/[{}()[\];,.]/.test(ch)) {
237
- emit(out, 'punct', ch);
238
- i += 1;
239
- continue;
99
+ } else {
100
+ take(/[{}()[\];,.]/.test(ch) ? 'punct' : 'plain', i + 1);
240
101
  }
241
- emit(out, 'plain', ch);
242
- i += 1;
243
102
  }
244
103
  return out;
245
104
  };
@@ -3,30 +3,30 @@
3
3
  const core = require('./core.js');
4
4
  const { emit, highlightLines } = core;
5
5
 
6
+ const ASSIGN_RE = /^(\s*)([A-Za-z_][A-Za-z0-9_.-]*)(\s*=\s*)(.*)$/;
7
+
6
8
  const highlightDotLine = (line) => {
7
- if (!line) return [];
8
9
  const out = [];
9
- const t = line.trimStart();
10
- if (t.startsWith('#') || t.startsWith(';')) {
10
+ const trimmed = line.trimStart();
11
+ if (trimmed.startsWith('#') || trimmed.startsWith(';')) {
11
12
  emit(out, 'comment', line);
12
13
  return out;
13
14
  }
14
- if (t.startsWith('!')) {
15
- const lead = line.slice(0, line.length - t.length);
16
- emit(out, 'plain', lead);
15
+ if (trimmed.startsWith('!')) {
16
+ emit(out, 'plain', line.slice(0, line.length - trimmed.length));
17
17
  emit(out, 'operator', '!');
18
- emit(out, 'plain', t.slice(1));
18
+ emit(out, 'plain', trimmed.slice(1));
19
19
  return out;
20
20
  }
21
- const eq = line.match(/^(\s*)([A-Za-z_][A-Za-z0-9_.-]*)(\s*=\s*)(.*)$/);
22
- if (eq) {
23
- emit(out, 'plain', eq[1]);
24
- emit(out, 'variable', eq[2]);
25
- emit(out, 'operator', eq[3]);
26
- emit(out, 'string', eq[4]);
21
+ const assign = ASSIGN_RE.exec(line);
22
+ if (!assign) {
23
+ emit(out, 'plain', line);
27
24
  return out;
28
25
  }
29
- emit(out, 'plain', line);
26
+ emit(out, 'plain', assign[1]);
27
+ emit(out, 'variable', assign[2]);
28
+ emit(out, 'operator', assign[3]);
29
+ emit(out, 'string', assign[4]);
30
30
  return out;
31
31
  };
32
32
 
@@ -1,85 +1,64 @@
1
1
  'use strict';
2
2
 
3
3
  const core = require('./core.js');
4
- const { SQUOTE, emit, skipQuoted } = core;
4
+ const { QUOTES, emit, scanWhile, quotedEnd } = core;
5
5
 
6
6
  const colorHtmlTag = (tag) => {
7
7
  const out = [];
8
8
  let i = 1;
9
- const n = tag.length;
9
+ const take = (style, end) => {
10
+ emit(out, style, tag.slice(i, end));
11
+ i = end;
12
+ };
10
13
  emit(out, 'punct', '<');
11
- if (tag[i] === '/') {
12
- emit(out, 'punct', '/');
13
- i += 1;
14
- }
15
- let j = i;
16
- while (j < n && /[A-Za-z0-9:-]/.test(tag[j])) j += 1;
17
- emit(out, 'tag', tag.slice(i, j));
18
- i = j;
19
- while (i < n) {
20
- if (tag[i] === '>' || (tag[i] === '/' && tag[i + 1] === '>')) {
21
- emit(out, 'punct', tag.slice(i));
22
- break;
23
- }
24
- if (/\s/.test(tag[i])) {
25
- emit(out, 'plain', tag[i]);
26
- i += 1;
27
- continue;
28
- }
29
- if (tag[i] === '"' || tag[i] === SQUOTE) {
30
- const q = tag[i];
31
- let k = i + 1;
32
- while (k < n && tag[k] !== q) k += 1;
33
- k = Math.min(k + 1, n);
34
- emit(out, 'string', tag.slice(i, k));
35
- i = k;
36
- continue;
14
+ if (tag[i] === '/') take('punct', i + 1);
15
+ take('tag', scanWhile(tag, i, /[A-Za-z0-9:-]/));
16
+ while (i < tag.length) {
17
+ const ch = tag[i];
18
+ if (ch === '>' || (ch === '/' && tag[i + 1] === '>')) {
19
+ take('punct', tag.length);
20
+ } else if (/\s/.test(ch)) {
21
+ take('plain', i + 1);
22
+ } else if (QUOTES.includes(ch)) {
23
+ const close = tag.indexOf(ch, i + 1);
24
+ take('string', close < 0 ? tag.length : close + 1);
25
+ } else if (/[A-Za-z_:]/.test(ch)) {
26
+ take('attr', scanWhile(tag, i + 1, /[A-Za-z0-9_:.-]/));
27
+ } else {
28
+ take('punct', i + 1);
37
29
  }
38
- if (/[A-Za-z_:]/.test(tag[i])) {
39
- let k = i + 1;
40
- while (k < n && /[A-Za-z0-9_:.-]/.test(tag[k])) k += 1;
41
- emit(out, 'attr', tag.slice(i, k));
42
- i = k;
43
- continue;
44
- }
45
- emit(out, 'punct', tag[i]);
46
- i += 1;
47
30
  }
48
31
  return out;
49
32
  };
50
33
 
34
+ const tagEnd = (source, i) => {
35
+ const n = source.length;
36
+ let j = i + 1;
37
+ while (j < n && source[j] !== '>') {
38
+ j = QUOTES.includes(source[j]) ? quotedEnd(source, j) : j + 1;
39
+ }
40
+ return j < n ? j + 1 : j;
41
+ };
42
+
51
43
  const highlight = (source) => {
52
44
  const out = [];
53
- let i = 0;
54
45
  const n = source.length;
46
+ let i = 0;
55
47
  while (i < n) {
48
+ let end;
56
49
  if (source.startsWith('<!--', i)) {
57
- let j = source.indexOf('-->', i + 4);
58
- j = j === -1 ? n : j + 3;
59
- emit(out, 'comment', source.slice(i, j));
60
- i = j;
61
- continue;
62
- }
63
- if (source[i] === '<') {
64
- let j = i + 1;
65
- while (j < n && source[j] !== '>') {
66
- if (source[j] === '"' || source[j] === SQUOTE) {
67
- j = skipQuoted(source, j);
68
- continue;
69
- }
70
- j += 1;
71
- }
72
- if (j < n) j += 1;
73
- for (const t of colorHtmlTag(source.slice(i, j))) out.push(t);
74
- i = j;
75
- continue;
76
- }
77
- let j = i + 1;
78
- while (j < n && source[j] !== '<' && !source.startsWith('<!--', j)) {
79
- j += 1;
50
+ const close = source.indexOf('-->', i + 4);
51
+ end = close < 0 ? n : close + 3;
52
+ emit(out, 'comment', source.slice(i, end));
53
+ } else if (source[i] === '<') {
54
+ end = tagEnd(source, i);
55
+ for (const token of colorHtmlTag(source.slice(i, end))) out.push(token);
56
+ } else {
57
+ const next = source.indexOf('<', i + 1);
58
+ end = next < 0 ? n : next;
59
+ emit(out, 'plain', source.slice(i, end));
80
60
  }
81
- emit(out, 'plain', source.slice(i, j));
82
- i = j;
61
+ i = end;
83
62
  }
84
63
  return out;
85
64
  };