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
@@ -2,102 +2,61 @@
2
2
 
3
3
  const ansi = require('../ansi.js');
4
4
  const diff = require('../diff/diff.js');
5
- const { displayLines } = diff;
6
5
  const highlight = require('../highlight.js');
7
6
  const detect = require('../detect.js');
8
- const { detectLang } = detect;
9
7
  const files = require('../files.js');
10
8
  const wrap = require('../wrap.js');
11
9
  const primitives = require('./primitives.js');
12
10
 
11
+ const { displayLines } = diff;
12
+ const { detectLang } = detect;
13
13
  const { itemPath } = files;
14
14
  const { tokenize, overlayTokens } = highlight;
15
15
  const { wrapPlain } = wrap;
16
- const { THEME, CODE_FG, codeFg, paint, visibleWidth } = ansi;
17
- const { truncateVisible, graphemes, graphemeWidth } = ansi;
18
- const { GUTTER_W, fill, splitWidths, paintSplitRule, paneResult } = primitives;
16
+ const { THEME, CODE_FG, codeFg, paint, visibleWidth, truncateVisible } = ansi;
17
+ const { GUTTER_W, fill, splitWidths, paintSplitRule } = primitives;
18
+ const { paneResult, sliceVisible } = primitives;
19
+
20
+ const lineStyle = (baseFg, baseBg, chBg = baseBg, mark = ' ') => ({
21
+ baseFg,
22
+ baseBg,
23
+ chBg,
24
+ mark,
25
+ });
19
26
 
20
27
  const STYLES = {
21
- del: {
22
- baseFg: THEME.delLineFg,
23
- baseBg: THEME.delLineBg,
24
- chBg: THEME.delCharBg,
25
- mark: '-',
26
- },
27
- add: {
28
- baseFg: THEME.addLineFg,
29
- baseBg: THEME.addLineBg,
30
- chBg: THEME.addCharBg,
31
- mark: '+',
32
- },
33
- warn: {
34
- baseFg: THEME.delLineFg,
35
- baseBg: THEME.delLineBg,
36
- chBg: THEME.delCharBg,
37
- mark: ' ',
38
- },
39
- note: {
40
- baseFg: THEME.chromeFg,
41
- baseBg: THEME.noteBg,
42
- chBg: THEME.noteBg,
43
- mark: ' ',
44
- },
45
- noteSep: {
46
- baseFg: THEME.mutedFg,
47
- baseBg: THEME.chromeBg,
48
- chBg: THEME.chromeBg,
49
- mark: ' ',
50
- },
51
- ctx: {
52
- baseFg: THEME.ctxFg,
53
- baseBg: THEME.ctxBg,
54
- chBg: THEME.ctxBg,
55
- mark: ' ',
56
- },
28
+ del: lineStyle(THEME.delLineFg, THEME.delLineBg, THEME.delCharBg, '-'),
29
+ add: lineStyle(THEME.addLineFg, THEME.addLineBg, THEME.addCharBg, '+'),
30
+ warn: lineStyle(THEME.delLineFg, THEME.delLineBg, THEME.delCharBg),
31
+ note: lineStyle(THEME.chromeFg, THEME.noteBg),
32
+ noteSep: lineStyle(THEME.mutedFg, THEME.chromeBg),
33
+ ctx: lineStyle(THEME.ctxFg, THEME.ctxBg),
57
34
  };
58
35
 
59
36
  const STAGED_STYLES = {
60
- del: {
61
- baseFg: THEME.stagedDelLineFg,
62
- baseBg: THEME.stagedDelLineBg,
63
- chBg: THEME.stagedDelCharBg,
64
- mark: '-',
65
- },
66
- add: {
67
- baseFg: THEME.stagedAddLineFg,
68
- baseBg: THEME.stagedAddLineBg,
69
- chBg: THEME.stagedAddCharBg,
70
- mark: '+',
71
- },
72
- warn: {
73
- baseFg: THEME.stagedDelLineFg,
74
- baseBg: THEME.stagedDelLineBg,
75
- chBg: THEME.stagedDelCharBg,
76
- mark: ' ',
77
- },
78
- note: STYLES.note,
79
- noteSep: STYLES.noteSep,
80
- ctx: STYLES.ctx,
37
+ ...STYLES,
38
+ del: lineStyle(
39
+ THEME.stagedDelLineFg,
40
+ THEME.stagedDelLineBg,
41
+ THEME.stagedDelCharBg,
42
+ '-',
43
+ ),
44
+ add: lineStyle(
45
+ THEME.stagedAddLineFg,
46
+ THEME.stagedAddLineBg,
47
+ THEME.stagedAddCharBg,
48
+ '+',
49
+ ),
50
+ warn: lineStyle(
51
+ THEME.stagedDelLineFg,
52
+ THEME.stagedDelLineBg,
53
+ THEME.stagedDelCharBg,
54
+ ),
81
55
  };
82
56
 
83
- const delCharBg = () => ansi.bg(THEME.delCharBg);
84
- const addCharBg = () => ansi.bg(THEME.addCharBg);
57
+ const gutterWidth = (width) => Math.min(GUTTER_W, Math.max(0, width));
85
58
 
86
- const skipText = (text, skip) => {
87
- if (skip <= 0) return text ?? '';
88
- const parts = graphemes(text ?? '');
89
- let used = 0;
90
- let i = 0;
91
- while (i < parts.length) {
92
- const w = graphemeWidth(parts[i], used);
93
- if (used + w > skip) break;
94
- used += w;
95
- i += 1;
96
- }
97
- let out = '';
98
- for (; i < parts.length; i++) out += parts[i];
99
- return out;
100
- };
59
+ const textWidth = (width) => Math.max(1, width - gutterWidth(width));
101
60
 
102
61
  const clipPieces = (pieces, skip) => {
103
62
  if (skip <= 0) return pieces;
@@ -110,54 +69,42 @@ const clipPieces = (pieces, skip) => {
110
69
  drop -= width;
111
70
  continue;
112
71
  }
113
- const shown = drop > 0 ? skipText(text, drop) : text;
72
+ const shown = drop > 0 ? sliceVisible(text, drop, Infinity) : text;
114
73
  drop = 0;
115
74
  if (shown) out.push({ ...piece, text: shown });
116
75
  }
117
76
  return out;
118
77
  };
119
78
 
120
- const paintSpans = (pieces, lineStyle, budget, color) => {
79
+ const paintSpans = (pieces, style, budget, color) => {
121
80
  let out = '';
122
81
  let used = 0;
123
82
  for (const piece of pieces) {
124
83
  if (used >= budget) break;
125
- const room = budget - used;
126
- const text = truncateVisible(piece.text, room);
84
+ const text = truncateVisible(piece.text, budget - used);
127
85
  const w = visibleWidth(text);
128
86
  if (w <= 0) continue;
129
- const fgRgb = codeFg(piece.style);
130
- const bgRgb = piece.changed ? lineStyle.chBg : lineStyle.baseBg;
131
- out += paint(text, fgRgb, bgRgb, color);
87
+ const bgRgb = piece.changed ? style.chBg : style.baseBg;
88
+ out += paint(text, codeFg(piece.style), bgRgb, color);
132
89
  used += w;
133
90
  }
134
- if (used < budget) {
135
- out += fill(budget - used, CODE_FG.plain, lineStyle.baseBg, color);
136
- }
137
- return out;
91
+ return out + fill(budget - used, CODE_FG.plain, style.baseBg, color);
138
92
  };
139
93
 
140
94
  const paintAuditNote = (line, width, color) => {
141
- const style = STYLES.note;
142
- const gutterW = Math.min(GUTTER_W, Math.max(0, width));
143
- const label = `${style.mark} `.slice(0, gutterW);
144
- const gutter = paint(label, style.baseFg, style.baseBg, color);
95
+ const { baseFg, baseBg, mark } = STYLES.note;
96
+ const gutterW = gutterWidth(width);
97
+ const label = `${mark} `.slice(0, gutterW);
145
98
  const budget = Math.max(0, width - gutterW);
146
99
  const text = truncateVisible(line.text ?? '', budget);
147
- const used = visibleWidth(text);
148
- let out = gutter;
149
- if (text) out += paint(text, style.baseFg, style.baseBg, color);
150
- if (used < budget) {
151
- out += fill(budget - used, style.baseFg, style.baseBg, color);
152
- }
153
- return out;
100
+ let out = paint(label, baseFg, baseBg, color);
101
+ out += paint(text, baseFg, baseBg, color);
102
+ return out + fill(budget - visibleWidth(text), baseFg, baseBg, color);
154
103
  };
155
104
 
156
105
  const paintAuditRule = (width, color) => {
157
- const style = STYLES.noteSep;
158
- const n = Math.max(0, width);
159
- const bar = '─'.repeat(n);
160
- return paint(bar, style.baseFg, style.baseBg, color);
106
+ const { baseFg, baseBg } = STYLES.noteSep;
107
+ return paint('─'.repeat(Math.max(0, width)), baseFg, baseBg, color);
161
108
  };
162
109
 
163
110
  const paintDiffLine = (line, width, color, origin, lang, scrollCol = 0) => {
@@ -168,32 +115,21 @@ const paintDiffLine = (line, width, color, origin, lang, scrollCol = 0) => {
168
115
  const diffSpans = line.spans ?? [{ text: line.text, changed: false }];
169
116
  const tokens = tokenize(lang, line.text ?? '');
170
117
  const pieces = clipPieces(overlayTokens(tokens, diffSpans), scrollCol);
171
- const gutterW = Math.min(GUTTER_W, Math.max(0, width));
118
+ const gutterW = gutterWidth(width);
172
119
  const label = `${line.mark ?? style.mark} `.slice(0, gutterW);
173
120
  const gutter = paint(label, CODE_FG.punct, style.baseBg, color);
174
121
  const budget = Math.max(0, width - gutterW);
175
- const rest = paintSpans(pieces, style, budget, color);
176
- return gutter + rest;
177
- };
178
-
179
- const paintSplitHalf = (line, width, color, origin, lang, scrollCol) => {
180
- if (!line || width <= 0) {
181
- return fill(width, THEME.ctxFg, THEME.ctxBg, color);
182
- }
183
- return paintDiffLine(line, width, color, origin, lang, scrollCol);
122
+ return gutter + paintSpans(pieces, style, budget, color);
184
123
  };
185
124
 
186
125
  const paintSplitRow = (row, width, color, origin, lang, scrollCol) => {
187
126
  const { leftW, rightW } = splitWidths(width);
188
- const left = paintSplitHalf(row.left, leftW, color, origin, lang, scrollCol);
189
- const right = paintSplitHalf(
190
- row.right,
191
- rightW,
192
- color,
193
- origin,
194
- lang,
195
- scrollCol,
196
- );
127
+ const half = (line, halfW) => {
128
+ if (!line) return fill(halfW, THEME.ctxFg, THEME.ctxBg, color);
129
+ return paintDiffLine(line, halfW, color, origin, lang, scrollCol);
130
+ };
131
+ const left = half(row.left, leftW);
132
+ const right = half(row.right, rightW);
197
133
  return `${left}${paintSplitRule(color)}${right}`;
198
134
  };
199
135
 
@@ -213,178 +149,137 @@ const sourceEmpty = (line) => {
213
149
  return !(line.text ?? '').trim();
214
150
  };
215
151
 
216
- const displayRowEmpty = (row, split) => {
152
+ const rowEmpty = (row, split) => {
217
153
  if (!split) return sourceEmpty(row);
218
154
  return sourceEmpty(row.left) && sourceEmpty(row.right);
219
155
  };
220
156
 
221
157
  const dropLeadingEmpty = (lines, split) => {
222
- let i = 0;
223
- while (i < lines.length && displayRowEmpty(lines[i], split)) {
224
- i += 1;
225
- }
226
- return i ? lines.slice(i) : lines;
158
+ const at = lines.findIndex((row) => !rowEmpty(row, split));
159
+ if (at < 0) return [];
160
+ return at ? lines.slice(at) : lines;
227
161
  };
228
162
 
229
163
  const cutSpans = (spans, count) => {
230
164
  const taken = [];
231
165
  const rest = [];
232
166
  let left = count;
233
- let i = 0;
234
- while (i < spans.length) {
235
- const span = spans[i];
167
+ for (const span of spans) {
236
168
  const text = span.text ?? '';
237
169
  if (left <= 0) {
238
170
  rest.push(span);
239
- i += 1;
240
- continue;
241
- }
242
- if (text.length <= left) {
171
+ } else if (text.length <= left) {
243
172
  taken.push(span);
244
173
  left -= text.length;
245
- i += 1;
246
- continue;
174
+ } else {
175
+ const changed = span.changed === true;
176
+ taken.push({ text: text.slice(0, left), changed });
177
+ rest.push({ text: text.slice(left), changed });
178
+ left = 0;
247
179
  }
248
- const changed = span.changed === true;
249
- taken.push({ text: text.slice(0, left), changed });
250
- rest.push({ text: text.slice(left), changed });
251
- left = 0;
252
- i += 1;
253
180
  }
254
181
  return { taken, rest };
255
182
  };
256
183
 
257
184
  const wrapSoftLine = (line, width) => {
258
- const gutterW = Math.min(GUTTER_W, Math.max(0, width));
259
- const budget = Math.max(1, width - gutterW);
260
- const rows = wrapPlain(line.text ?? '', budget);
185
+ const rows = wrapPlain(line.text ?? '', textWidth(width));
261
186
  const changed = line.wrap === true;
262
- const { length } = rows;
263
- const pieces = new Array(length);
264
187
  let used = 0;
265
188
  let spans = line.spans ?? [{ text: line.text ?? '', changed }];
266
- for (let i = 0; i < length; i++) {
267
- const text = rows[i];
268
- const last = i === length - 1;
189
+ return rows.map((text, index) => {
190
+ const last = index === rows.length - 1;
269
191
  const cut = cutSpans(spans, text.length);
270
192
  spans = cut.rest;
271
193
  const nextSpans = cut.taken.length ? cut.taken : [{ text, changed }];
272
194
  const next = { ...line, text, spans: nextSpans, editLast: last };
273
195
  if (line.editStart !== undefined) {
274
196
  next.editStart = line.editStart + used;
275
- if (last) next.editEnd = line.editEnd;
276
- else next.editEnd = next.editStart + text.length;
197
+ next.editEnd = last ? line.editEnd : next.editStart + text.length;
277
198
  }
278
199
  used += text.length;
279
- pieces[i] = next;
280
- }
281
- return pieces;
200
+ return next;
201
+ });
282
202
  };
283
203
 
284
204
  const expandSoftRows = (lines, width, split, wrapView) => {
285
205
  const { leftW, rightW } = splitWidths(width);
286
- const out = [];
287
206
  const partsOf = (line, lineW) => {
288
207
  if (!wrapView || !line || line.type === 'noteSep') return [line];
289
- const gutterW = Math.min(GUTTER_W, Math.max(0, lineW));
290
- const budget = Math.max(1, lineW - gutterW);
291
- if (visibleWidth(line.text ?? '') <= budget) return [line];
208
+ if (visibleWidth(line.text ?? '') <= textWidth(lineW)) return [line];
292
209
  return wrapSoftLine(line, lineW);
293
210
  };
211
+ if (!split) return lines.flatMap((line) => partsOf(line, width));
212
+ const out = [];
294
213
  for (const row of lines) {
295
- if (!split) {
296
- for (const part of partsOf(row, width)) out.push(part);
297
- continue;
298
- }
299
214
  const leftParts = partsOf(row.left, leftW);
300
215
  const rightParts = partsOf(row.right, rightW);
301
216
  const n = Math.max(leftParts.length, rightParts.length);
302
217
  for (let i = 0; i < n; i++) {
303
- out.push({
304
- left: leftParts[i] ?? null,
305
- right: rightParts[i] ?? null,
306
- });
218
+ out.push({ left: leftParts[i] ?? null, right: rightParts[i] ?? null });
307
219
  }
308
220
  }
309
221
  return out;
310
222
  };
311
223
 
312
- const onCodePiece = (line, cursor) => {
313
- if (!line || line.editStart === undefined) return false;
314
- if (cursor < line.editStart) return false;
315
- return cursor <= line.editEnd;
316
- };
317
-
318
- const codeCursorInRows = (rows, split, width, cursor, scrollCol = 0) => {
319
- if (cursor === undefined || cursor === null) return null;
224
+ const codeHits = (rows, split, width, overlay, editing, scrollCol) => {
320
225
  const { leftW, rightW } = splitWidths(width);
321
- for (let i = 0; i < rows.length; i++) {
322
- const line = split ? rows[i].right : rows[i];
323
- if (!onCodePiece(line, cursor)) continue;
324
- const lineW = split ? rightW : width;
325
- const gutterW = Math.min(GUTTER_W, Math.max(0, lineW));
326
- const take = Math.min(cursor - line.editStart, line.text.length);
226
+ const lineW = split ? rightW : width;
227
+ const x0 = split ? leftW + 1 : 0;
228
+ const textX = x0 + gutterWidth(lineW) + 1;
229
+ const at = overlay ? overlay.cursor : null;
230
+ const hasCursor = at !== null && at !== undefined;
231
+ const editHits = [];
232
+ let cursor = null;
233
+ for (let row = 0; row < rows.length; row++) {
234
+ const line = split ? rows[row].right : rows[row];
235
+ if (!line || line.editStart === undefined) continue;
236
+ if (editing) {
237
+ editHits.push({
238
+ row,
239
+ x0: x0 + 1,
240
+ textX,
241
+ x1: x0 + lineW + 1,
242
+ scroll: scrollCol,
243
+ start: line.editStart,
244
+ text: line.text ?? '',
245
+ limit: line.editEnd,
246
+ live: true,
247
+ });
248
+ }
249
+ if (cursor || !hasCursor) continue;
250
+ const onPiece = at >= line.editStart && at <= line.editEnd;
251
+ if (!onPiece) continue;
252
+ const take = Math.min(at - line.editStart, line.text.length);
327
253
  const col = visibleWidth(line.text.slice(0, take));
328
- const shown = Math.max(0, col - scrollCol);
329
- const x0 = split ? leftW + 1 : 0;
330
- return { x: x0 + gutterW + shown + 1, row: i };
254
+ cursor = { x: textX + Math.max(0, col - scrollCol), row };
331
255
  }
332
- return null;
256
+ return { cursor, editHits };
333
257
  };
334
258
 
335
- const codeInnerWidth = (width, layout = 'unified') => {
336
- const lineW = layout === 'side' ? splitWidths(width).rightW : width;
337
- const gutterW = Math.min(GUTTER_W, Math.max(0, lineW));
338
- return Math.max(1, lineW - gutterW);
339
- };
259
+ const codeInnerWidth = (width, layout = 'unified') =>
260
+ textWidth(layout === 'side' ? splitWidths(width).rightW : width);
340
261
 
341
262
  const paintBodyDiff = (view, width, color) => {
342
- const item = view.item;
263
+ const { item, codeOverlay: overlay } = view;
343
264
  const layout = view.layout ?? 'unified';
344
- const overlay = view.codeOverlay;
345
265
  const lang = detectLang(itemPath(item));
346
266
  const splitBody = layout === 'side' && !item.file.isBinary;
347
- const editing = Boolean(overlay && typeof overlay.cursor === 'number');
348
- const picked = editing ? overlay.scrollCol : 0;
349
- const scrollCol = picked ?? 0;
267
+ const editing = typeof overlay?.cursor === 'number';
268
+ const scrollCol = (editing ? overlay.scrollCol : 0) ?? 0;
350
269
  const raw = dropLeadingEmpty(bodyLines(item, layout, overlay), splitBody);
351
270
  const lines = expandSoftRows(raw, width, splitBody, !editing);
352
- const { length } = lines;
353
- const body = new Array(length);
354
- if (splitBody) {
355
- for (let i = 0; i < length; i++) {
356
- body[i] = paintSplitRow(
357
- lines[i],
358
- width,
359
- color,
360
- item.origin,
361
- lang,
362
- scrollCol,
363
- );
364
- }
365
- } else {
366
- for (let i = 0; i < length; i++) {
367
- body[i] = paintDiffLine(
368
- lines[i],
369
- width,
370
- color,
371
- item.origin,
372
- lang,
373
- scrollCol,
374
- );
375
- }
376
- }
377
- const edit = overlay ? overlay.cursor : null;
378
- const cursor = codeCursorInRows(lines, splitBody, width, edit, scrollCol);
379
- return paneResult({ body, cursor, splitBody });
271
+ const paintRow = splitBody ? paintSplitRow : paintDiffLine;
272
+ const body = lines.map((line) =>
273
+ paintRow(line, width, color, item.origin, lang, scrollCol),
274
+ );
275
+ const hits = codeHits(lines, splitBody, width, overlay, editing, scrollCol);
276
+ return paneResult({ body, splitBody, ...hits });
380
277
  };
381
278
 
382
279
  module.exports = {
383
- delCharBg,
384
- addCharBg,
385
280
  paintDiffLine,
386
281
  codeInnerWidth,
387
282
  paintBodyDiff,
388
- codeCursorInRows,
389
283
  expandSoftRows,
284
+ codeHits,
390
285
  };
@@ -3,11 +3,11 @@
3
3
  const ansi = require('../ansi.js');
4
4
  const files = require('../files.js');
5
5
  const primitives = require('./primitives.js');
6
- const { measureColumns, padColumns } = primitives;
7
6
 
8
- const { isTodosEntry, shortAge } = files;
9
- const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
10
- const { fill, FILE_MARK, paintCursorList } = primitives;
7
+ const { isTasksEntry, shortAge } = files;
8
+ const { THEME, paint, visibleWidth, trimVisible, seq, EL } = ansi;
9
+ const { fill, markPrefix, paintCursorList } = primitives;
10
+ const { measureColumns, padColumns } = primitives;
11
11
 
12
12
  const FILE_STATUS_FG = {
13
13
  unstaged: THEME.delLineFg,
@@ -27,124 +27,70 @@ const FILE_COL_ALIGN = {
27
27
 
28
28
  const FILE_DATE_GAP = ' ';
29
29
 
30
- const signedCount = (sign, n) => `${sign}${n ?? 0}`;
31
-
32
30
  const fileRowFields = (entry) => {
33
- if (isTodosEntry(entry)) {
34
- const n = `${entry.staged ?? 0}`;
35
- const m = `${entry.remaining ?? 0}`;
31
+ const n = `${entry.staged ?? 0}`;
32
+ const m = `${entry.remaining ?? 0}`;
33
+ if (isTasksEntry(entry)) {
36
34
  return { add: '', del: '', status: '', n, m, date: '' };
37
35
  }
38
- const { added, removed, staged, remaining } = entry;
39
- const add = signedCount('+', added);
40
- const del = signedCount('-', removed);
36
+ const add = `+${entry.added ?? 0}`;
37
+ const del = `-${entry.removed ?? 0}`;
41
38
  const status = entry.status ?? '';
42
- const n = `${staged ?? 0}`;
43
- const m = `${remaining ?? 0}`;
44
- const date = shortAge(entry.date ?? '');
45
- return { add, del, status, n, m, date };
39
+ return { add, del, status, n, m, date: shortAge(entry.date) };
46
40
  };
47
41
 
48
- const fileRowFg = (status, selected) => {
49
- const statusFg = FILE_STATUS_FG[status];
42
+ const fileRowFg = (entry, selected) => {
43
+ if (isTasksEntry(entry)) return THEME.buttonHotFg;
44
+ const statusFg = FILE_STATUS_FG[entry.status];
50
45
  if (statusFg) return statusFg;
51
- if (selected) return THEME.chromeFg;
52
- return THEME.mutedFg;
53
- };
54
-
55
- const joinFileDate = (parts, cols) => {
56
- if (!cols?.date) return '';
57
- return `${FILE_DATE_GAP}${parts.date}`;
46
+ return selected ? THEME.chromeFg : THEME.mutedFg;
58
47
  };
59
48
 
60
- const joinFileMeta = (parts) => {
61
- const { add, del, status, n, m } = parts;
62
- return ` ${add} ${del} ${status} ${n}/${m}`;
63
- };
64
-
65
- const paintFileSuffix = (parts, cols, fgRgb, bgRgb, color) => {
66
- const { add, del, status, n, m } = parts;
49
+ const paintFileMeta = (parts, fgRgb, bgRgb, color) => {
67
50
  const gap = paint(' ', fgRgb, bgRgb, color);
68
- const addText = paint(add, THEME.addLineFg, bgRgb, color);
69
- const delText = paint(del, THEME.delLineFg, bgRgb, color);
70
- const statusText = paint(status, fgRgb, bgRgb, color);
71
- const nText = paint(n, fgRgb, bgRgb, color);
72
- const slash = paint('/', fgRgb, bgRgb, color);
73
- const mText = paint(m, fgRgb, bgRgb, color);
74
- const counts = addText + gap + delText;
75
- const meta = statusText + gap + nText + slash + mText;
76
- if (!cols?.date) return gap + counts + gap + meta;
77
- const dateGap = paint(FILE_DATE_GAP, fgRgb, bgRgb, color);
78
- const age = paint(parts.date, THEME.mutedFg, bgRgb, color);
79
- return gap + counts + gap + meta + dateGap + age;
80
- };
81
-
82
- const paintTodosRow = (entry, width, color, selected, cols) => {
83
- const inner = Math.max(1, width - 1);
84
- const mark = selected ? FILE_MARK : ' ';
85
- const prefix = ` ${mark} `;
86
- const parts = padColumns(fileRowFields(entry), cols, FILE_COL_ALIGN);
87
- const meta = joinFileMeta(parts);
88
- const dated = joinFileDate(parts, cols);
89
- const suffix = `${meta}${dated}`;
90
- const pathW = Math.max(1, inner - visibleWidth(prefix + suffix));
91
- const clipped = truncateVisible(entry.path, pathW);
92
- const pad = Math.max(0, pathW - visibleWidth(clipped));
93
- const left = `${prefix}${clipped}${' '.repeat(pad)}`;
94
- const fgRgb = THEME.buttonHotFg;
95
- const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
96
- const edge = fill(1, fgRgb, bgRgb, color);
97
- if (!color) return `${left}${suffix}${edge}`;
98
- let out = `${seq(fgRgb, bgRgb)}${EL}`;
99
- out += paint(left, fgRgb, bgRgb, color, true);
100
- out += paint(meta, fgRgb, bgRgb, color, true);
101
- if (!dated) return out + edge;
102
- out += paint(FILE_DATE_GAP, fgRgb, bgRgb, color);
103
- out += paint(parts.date, THEME.mutedFg, bgRgb, color);
104
- return out + edge;
51
+ let out = gap + paint(parts.add, THEME.addLineFg, bgRgb, color);
52
+ out += gap + paint(parts.del, THEME.delLineFg, bgRgb, color);
53
+ out += gap + paint(parts.status, fgRgb, bgRgb, color);
54
+ out += gap + paint(parts.n, fgRgb, bgRgb, color);
55
+ out += paint('/', fgRgb, bgRgb, color);
56
+ return out + paint(parts.m, fgRgb, bgRgb, color);
105
57
  };
106
58
 
107
59
  const paintFileRow = (entry, width, color, selected, cols) => {
108
- if (isTodosEntry(entry)) {
109
- return paintTodosRow(entry, width, color, selected, cols);
110
- }
60
+ const tasks = isTasksEntry(entry);
111
61
  const inner = Math.max(1, width - 1);
112
- const mark = selected ? FILE_MARK : ' ';
113
- const prefix = ` ${mark} `;
62
+ const prefix = markPrefix(selected);
114
63
  const parts = padColumns(fileRowFields(entry), cols, FILE_COL_ALIGN);
115
- const suffix = `${joinFileMeta(parts)}${joinFileDate(parts, cols)}`;
116
- const pathW = Math.max(1, inner - visibleWidth(prefix + suffix));
117
- const { path, status } = entry;
118
- const clipped = truncateVisible(path, pathW);
119
- const pad = Math.max(0, pathW - visibleWidth(clipped));
120
- const left = `${prefix}${clipped}${' '.repeat(pad)}`;
121
- const fgRgb = fileRowFg(status, selected);
64
+ const { add, del, status, n, m } = parts;
65
+ const meta = ` ${add} ${del} ${status} ${n}/${m}`;
66
+ const date = cols.date ? `${FILE_DATE_GAP}${parts.date}` : '';
67
+ const pathW = Math.max(1, inner - visibleWidth(prefix + meta + date));
68
+ const path = trimVisible(entry.path, pathW);
69
+ const pad = ' '.repeat(Math.max(0, pathW - visibleWidth(path)));
70
+ const left = `${prefix}${path}${pad}`;
71
+ const fgRgb = fileRowFg(entry, selected);
122
72
  const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
123
73
  const edge = fill(1, fgRgb, bgRgb, color);
124
- if (!color) return `${left}${suffix}${edge}`;
74
+ if (!color) return `${left}${meta}${date}${edge}`;
125
75
  let out = `${seq(fgRgb, bgRgb)}${EL}`;
126
- out += paint(left, fgRgb, bgRgb, color);
127
- out += paintFileSuffix(parts, cols, fgRgb, bgRgb, color);
76
+ out += paint(left, fgRgb, bgRgb, color, tasks);
77
+ if (tasks) out += paint(meta, fgRgb, bgRgb, color, true);
78
+ else out += paintFileMeta(parts, fgRgb, bgRgb, color);
79
+ if (date) {
80
+ out += paint(FILE_DATE_GAP, fgRgb, bgRgb, color);
81
+ out += paint(parts.date, THEME.mutedFg, bgRgb, color);
82
+ }
128
83
  return out + edge;
129
84
  };
130
85
 
131
86
  const paintBodyFiles = (view, width, color, bodyH, headerLines) => {
132
- const files = view.files ?? [];
133
- const cols = measureColumns(files, fileRowFields, FILE_COL_ALIGN);
87
+ const entries = view.files ?? [];
88
+ const cols = measureColumns(entries, fileRowFields, FILE_COL_ALIGN);
134
89
  const cursor = view.fileCursor ?? 0;
135
- const paintRow = (entry, rowWidth, rowColor, selected) =>
136
- paintFileRow(entry, rowWidth, rowColor, selected, cols);
137
- return paintCursorList(
138
- files,
139
- cursor,
140
- width,
141
- color,
142
- bodyH,
143
- headerLines,
144
- paintRow,
145
- null,
146
- view.listScroll,
147
- );
90
+ const pane = { width, color, bodyH, headerLines, offset: view.listScroll };
91
+ const paintRow = (entry, selected) =>
92
+ paintFileRow(entry, width, color, selected, cols);
93
+ return paintCursorList(entries, cursor, pane, paintRow);
148
94
  };
149
95
 
150
96
  module.exports = { paintBodyFiles };