reslop 0.2.1 → 0.2.4

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 (81) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +19 -4
  3. package/bin/reslop.js +4 -2
  4. package/lib/ansi.js +120 -4
  5. package/lib/api.js +2 -1
  6. package/lib/cli.js +21 -9
  7. package/lib/clipboard.js +6 -3
  8. package/lib/deps-diff.js +61 -115
  9. package/lib/deps.js +57 -54
  10. package/lib/detect.js +4 -2
  11. package/lib/diff/align.js +2 -1
  12. package/lib/diff/diff.js +6 -3
  13. package/lib/diff/display.js +2 -1
  14. package/lib/diff/inline.js +2 -1
  15. package/lib/diff/patch.js +2 -1
  16. package/lib/diff/unit.js +4 -2
  17. package/lib/editor.js +22 -4
  18. package/lib/files.js +38 -12
  19. package/lib/git-branches.js +150 -165
  20. package/lib/git-deps.js +20 -60
  21. package/lib/git-worktree.js +508 -0
  22. package/lib/git.js +23 -580
  23. package/lib/github.js +2 -1
  24. package/lib/gitlab.js +2 -1
  25. package/lib/highlight/bash.js +2 -1
  26. package/lib/highlight/css.js +2 -1
  27. package/lib/highlight/csv.js +2 -1
  28. package/lib/highlight/dot.js +2 -1
  29. package/lib/highlight/html.js +2 -1
  30. package/lib/highlight/json.js +2 -1
  31. package/lib/highlight/log.js +2 -1
  32. package/lib/highlight/txt.js +2 -1
  33. package/lib/highlight.js +2 -1
  34. package/lib/keys.js +12 -6
  35. package/lib/manifest.js +4 -2
  36. package/lib/npm-commands.js +592 -0
  37. package/lib/remote.js +20 -13
  38. package/lib/render/branches.js +25 -41
  39. package/lib/render/chrome.js +163 -128
  40. package/lib/render/commits.js +727 -65
  41. package/lib/render/diff.js +130 -47
  42. package/lib/render/files.js +4 -12
  43. package/lib/render/frame.js +41 -13
  44. package/lib/render/notes.js +4 -11
  45. package/lib/render/npm.js +216 -0
  46. package/lib/render/present.js +2 -1
  47. package/lib/render/primitives.js +34 -50
  48. package/lib/render/render.js +2 -1
  49. package/lib/render/unit.js +14 -11
  50. package/lib/report-model.js +129 -0
  51. package/lib/report-parse.js +89 -0
  52. package/lib/report-render.js +104 -0
  53. package/lib/report-run.js +166 -0
  54. package/lib/report-test.js +507 -0
  55. package/lib/report-tools.js +337 -0
  56. package/lib/review-merge.js +2 -1
  57. package/lib/review.js +6 -2
  58. package/lib/session/actions.js +77 -44
  59. package/lib/session/branches.js +13 -49
  60. package/lib/session/change-actions.js +2 -1
  61. package/lib/session/commit.js +113 -53
  62. package/lib/session/compose.js +83 -14
  63. package/lib/session/dispatch.js +42 -22
  64. package/lib/session/files-pane.js +16 -21
  65. package/lib/session/items.js +6 -3
  66. package/lib/session/lifecycle.js +12 -5
  67. package/lib/session/load.js +4 -2
  68. package/lib/session/navigation.js +2 -0
  69. package/lib/session/npm.js +388 -0
  70. package/lib/session/ops.js +15 -1
  71. package/lib/session/pointer.js +30 -3
  72. package/lib/session/review.js +2 -1
  73. package/lib/session/terminal.js +4 -2
  74. package/lib/session/update.js +2 -1
  75. package/lib/session/view.js +41 -4
  76. package/lib/session.js +44 -22
  77. package/lib/source.js +6 -3
  78. package/lib/update.js +5 -9
  79. package/lib/utilities.js +4 -2
  80. package/lib/wrap.js +3 -1
  81. package/package.json +6 -6
@@ -1,9 +1,11 @@
1
1
  'use strict';
2
2
 
3
3
  const ansi = require('../ansi.js');
4
- const { displayLines } = require('../diff/diff.js');
4
+ const diff = require('../diff/diff.js');
5
+ const { displayLines } = diff;
5
6
  const highlight = require('../highlight.js');
6
- const { detectLang } = require('../detect.js');
7
+ const detect = require('../detect.js');
8
+ const { detectLang } = detect;
7
9
  const files = require('../files.js');
8
10
  const wrap = require('../wrap.js');
9
11
  const primitives = require('./primitives.js');
@@ -11,7 +13,8 @@ const primitives = require('./primitives.js');
11
13
  const { itemPath } = files;
12
14
  const { tokenize, overlayTokens } = highlight;
13
15
  const { wrapPlain } = wrap;
14
- const { THEME, CODE_FG, codeFg, paint, visibleWidth, truncateVisible } = ansi;
16
+ const { THEME, CODE_FG, codeFg, paint, visibleWidth } = ansi;
17
+ const { truncateVisible, graphemes, graphemeWidth } = ansi;
15
18
  const { GUTTER_W, fill, splitWidths, paintSplitRule, paneResult } = primitives;
16
19
 
17
20
  const STYLES = {
@@ -77,10 +80,43 @@ const STAGED_STYLES = {
77
80
  ctx: STYLES.ctx,
78
81
  };
79
82
 
80
- const SOFT_WRAP = ['warn', 'note'];
81
83
  const delCharBg = () => ansi.bg(THEME.delCharBg);
82
84
  const addCharBg = () => ansi.bg(THEME.addCharBg);
83
85
 
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
+ };
101
+
102
+ const clipPieces = (pieces, skip) => {
103
+ if (skip <= 0) return pieces;
104
+ const out = [];
105
+ let drop = skip;
106
+ for (const piece of pieces) {
107
+ const text = piece.text ?? '';
108
+ const width = visibleWidth(text);
109
+ if (drop >= width) {
110
+ drop -= width;
111
+ continue;
112
+ }
113
+ const shown = drop > 0 ? skipText(text, drop) : text;
114
+ drop = 0;
115
+ if (shown) out.push({ ...piece, text: shown });
116
+ }
117
+ return out;
118
+ };
119
+
84
120
  const paintSpans = (pieces, lineStyle, budget, color) => {
85
121
  let out = '';
86
122
  let used = 0;
@@ -124,14 +160,14 @@ const paintAuditRule = (width, color) => {
124
160
  return paint(bar, style.baseFg, style.baseBg, color);
125
161
  };
126
162
 
127
- const paintDiffLine = (line, width, color, origin, lang) => {
163
+ const paintDiffLine = (line, width, color, origin, lang, scrollCol = 0) => {
128
164
  if (line.type === 'noteSep') return paintAuditRule(width, color);
129
165
  if (line.type === 'note') return paintAuditNote(line, width, color);
130
166
  const styles = origin === 'staged' ? STAGED_STYLES : STYLES;
131
167
  const style = styles[line.type] ?? styles.ctx;
132
168
  const diffSpans = line.spans ?? [{ text: line.text, changed: false }];
133
169
  const tokens = tokenize(lang, line.text ?? '');
134
- const pieces = overlayTokens(tokens, diffSpans);
170
+ const pieces = clipPieces(overlayTokens(tokens, diffSpans), scrollCol);
135
171
  const gutterW = Math.min(GUTTER_W, Math.max(0, width));
136
172
  const label = `${line.mark ?? style.mark} `.slice(0, gutterW);
137
173
  const gutter = paint(label, CODE_FG.punct, style.baseBg, color);
@@ -140,17 +176,24 @@ const paintDiffLine = (line, width, color, origin, lang) => {
140
176
  return gutter + rest;
141
177
  };
142
178
 
143
- const paintSplitHalf = (line, width, color, origin, lang) => {
179
+ const paintSplitHalf = (line, width, color, origin, lang, scrollCol) => {
144
180
  if (!line || width <= 0) {
145
181
  return fill(width, THEME.ctxFg, THEME.ctxBg, color);
146
182
  }
147
- return paintDiffLine(line, width, color, origin, lang);
183
+ return paintDiffLine(line, width, color, origin, lang, scrollCol);
148
184
  };
149
185
 
150
- const paintSplitRow = (row, width, color, origin, lang) => {
186
+ const paintSplitRow = (row, width, color, origin, lang, scrollCol) => {
151
187
  const { leftW, rightW } = splitWidths(width);
152
- const left = paintSplitHalf(row.left, leftW, color, origin, lang);
153
- const right = paintSplitHalf(row.right, rightW, color, origin, lang);
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
+ );
154
197
  return `${left}${paintSplitRule(color)}${right}`;
155
198
  };
156
199
 
@@ -183,6 +226,34 @@ const dropLeadingEmpty = (lines, split) => {
183
226
  return i ? lines.slice(i) : lines;
184
227
  };
185
228
 
229
+ const cutSpans = (spans, count) => {
230
+ const taken = [];
231
+ const rest = [];
232
+ let left = count;
233
+ let i = 0;
234
+ while (i < spans.length) {
235
+ const span = spans[i];
236
+ const text = span.text ?? '';
237
+ if (left <= 0) {
238
+ rest.push(span);
239
+ i += 1;
240
+ continue;
241
+ }
242
+ if (text.length <= left) {
243
+ taken.push(span);
244
+ left -= text.length;
245
+ i += 1;
246
+ continue;
247
+ }
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
+ }
254
+ return { taken, rest };
255
+ };
256
+
186
257
  const wrapSoftLine = (line, width) => {
187
258
  const gutterW = Math.min(GUTTER_W, Math.max(0, width));
188
259
  const budget = Math.max(1, width - gutterW);
@@ -191,11 +262,14 @@ const wrapSoftLine = (line, width) => {
191
262
  const { length } = rows;
192
263
  const pieces = new Array(length);
193
264
  let used = 0;
265
+ let spans = line.spans ?? [{ text: line.text ?? '', changed }];
194
266
  for (let i = 0; i < length; i++) {
195
267
  const text = rows[i];
196
268
  const last = i === length - 1;
197
- const spans = [{ text, changed }];
198
- const next = { ...line, text, spans, editLast: last };
269
+ const cut = cutSpans(spans, text.length);
270
+ spans = cut.rest;
271
+ const nextSpans = cut.taken.length ? cut.taken : [{ text, changed }];
272
+ const next = { ...line, text, spans: nextSpans, editLast: last };
199
273
  if (line.editStart !== undefined) {
200
274
  next.editStart = line.editStart + used;
201
275
  if (last) next.editEnd = line.editEnd;
@@ -207,40 +281,30 @@ const wrapSoftLine = (line, width) => {
207
281
  return pieces;
208
282
  };
209
283
 
210
- const shouldWrap = (line) => {
211
- if (!line) return false;
212
- if (SOFT_WRAP.includes(line.type)) return true;
213
- return line.wrap === true;
214
- };
215
-
216
- const expandSoftRows = (lines, width, split) => {
284
+ const expandSoftRows = (lines, width, split, wrapView) => {
217
285
  const { leftW, rightW } = splitWidths(width);
218
286
  const out = [];
287
+ const partsOf = (line, lineW) => {
288
+ 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];
292
+ return wrapSoftLine(line, lineW);
293
+ };
219
294
  for (const row of lines) {
220
295
  if (!split) {
221
- if (!shouldWrap(row)) {
222
- out.push(row);
223
- continue;
224
- }
225
- for (const part of wrapSoftLine(row, width)) out.push(part);
226
- continue;
227
- }
228
- const right = row.right;
229
- if (shouldWrap(right)) {
230
- const parts = wrapSoftLine(right, rightW);
231
- out.push({ left: row.left, right: parts[0] });
232
- for (let i = 1; i < parts.length; i++) {
233
- out.push({ left: null, right: parts[i] });
234
- }
296
+ for (const part of partsOf(row, width)) out.push(part);
235
297
  continue;
236
298
  }
237
- const left = row.left;
238
- if (shouldWrap(left)) {
239
- const parts = wrapSoftLine(left, leftW);
240
- for (const part of parts) out.push({ left: part, right: part });
241
- continue;
299
+ const leftParts = partsOf(row.left, leftW);
300
+ const rightParts = partsOf(row.right, rightW);
301
+ const n = Math.max(leftParts.length, rightParts.length);
302
+ for (let i = 0; i < n; i++) {
303
+ out.push({
304
+ left: leftParts[i] ?? null,
305
+ right: rightParts[i] ?? null,
306
+ });
242
307
  }
243
- out.push(row);
244
308
  }
245
309
  return out;
246
310
  };
@@ -251,7 +315,7 @@ const onCodePiece = (line, cursor) => {
251
315
  return cursor <= line.editEnd;
252
316
  };
253
317
 
254
- const codeCursorInRows = (rows, split, width, cursor) => {
318
+ const codeCursorInRows = (rows, split, width, cursor, scrollCol = 0) => {
255
319
  if (cursor === undefined || cursor === null) return null;
256
320
  const { leftW, rightW } = splitWidths(width);
257
321
  for (let i = 0; i < rows.length; i++) {
@@ -261,8 +325,9 @@ const codeCursorInRows = (rows, split, width, cursor) => {
261
325
  const gutterW = Math.min(GUTTER_W, Math.max(0, lineW));
262
326
  const take = Math.min(cursor - line.editStart, line.text.length);
263
327
  const col = visibleWidth(line.text.slice(0, take));
328
+ const shown = Math.max(0, col - scrollCol);
264
329
  const x0 = split ? leftW + 1 : 0;
265
- return { x: x0 + gutterW + col + 1, row: i };
330
+ return { x: x0 + gutterW + shown + 1, row: i };
266
331
  }
267
332
  return null;
268
333
  };
@@ -279,22 +344,39 @@ const paintBodyDiff = (view, width, color) => {
279
344
  const overlay = view.codeOverlay;
280
345
  const lang = detectLang(itemPath(item));
281
346
  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;
282
350
  const raw = dropLeadingEmpty(bodyLines(item, layout, overlay), splitBody);
283
- const lines = expandSoftRows(raw, width, splitBody);
351
+ const lines = expandSoftRows(raw, width, splitBody, !editing);
284
352
  const { length } = lines;
285
353
  const body = new Array(length);
286
354
  if (splitBody) {
287
355
  for (let i = 0; i < length; i++) {
288
- body[i] = paintSplitRow(lines[i], width, color, item.origin, lang);
356
+ body[i] = paintSplitRow(
357
+ lines[i],
358
+ width,
359
+ color,
360
+ item.origin,
361
+ lang,
362
+ scrollCol,
363
+ );
289
364
  }
290
365
  } else {
291
366
  for (let i = 0; i < length; i++) {
292
- body[i] = paintDiffLine(lines[i], width, color, item.origin, lang);
367
+ body[i] = paintDiffLine(
368
+ lines[i],
369
+ width,
370
+ color,
371
+ item.origin,
372
+ lang,
373
+ scrollCol,
374
+ );
293
375
  }
294
376
  }
295
377
  const edit = overlay ? overlay.cursor : null;
296
- const cursor = codeCursorInRows(lines, splitBody, width, edit);
297
- return paneResult({ body, fileHits: [], todoOwners: [], cursor, splitBody });
378
+ const cursor = codeCursorInRows(lines, splitBody, width, edit, scrollCol);
379
+ return paneResult({ body, cursor, splitBody });
298
380
  };
299
381
 
300
382
  module.exports = {
@@ -304,4 +386,5 @@ module.exports = {
304
386
  codeInnerWidth,
305
387
  paintBodyDiff,
306
388
  codeCursorInRows,
389
+ expandSoftRows,
307
390
  };
@@ -5,9 +5,9 @@ const files = require('../files.js');
5
5
  const primitives = require('./primitives.js');
6
6
  const { measureColumns, padColumns } = primitives;
7
7
 
8
- const { isTodosEntry } = files;
8
+ const { isTodosEntry, shortAge } = files;
9
9
  const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
10
- const { fill, FILE_MARK, paneResult, paintCursorList } = primitives;
10
+ const { fill, FILE_MARK, paintCursorList } = primitives;
11
11
 
12
12
  const FILE_STATUS_FG = {
13
13
  unstaged: THEME.delLineFg,
@@ -41,7 +41,7 @@ const fileRowFields = (entry) => {
41
41
  const status = entry.status ?? '';
42
42
  const n = `${staged ?? 0}`;
43
43
  const m = `${remaining ?? 0}`;
44
- const date = entry.date ?? '';
44
+ const date = shortAge(entry.date ?? '');
45
45
  return { add, del, status, n, m, date };
46
46
  };
47
47
 
@@ -134,7 +134,7 @@ const paintBodyFiles = (view, width, color, bodyH, headerLines) => {
134
134
  const cursor = view.fileCursor ?? 0;
135
135
  const paintRow = (entry, rowWidth, rowColor, selected) =>
136
136
  paintFileRow(entry, rowWidth, rowColor, selected, cols);
137
- const painted = paintCursorList(
137
+ return paintCursorList(
138
138
  files,
139
139
  cursor,
140
140
  width,
@@ -145,14 +145,6 @@ const paintBodyFiles = (view, width, color, bodyH, headerLines) => {
145
145
  null,
146
146
  view.listScroll,
147
147
  );
148
- return paneResult({
149
- body: painted.body,
150
- fileHits: painted.fileHits,
151
- todoOwners: [],
152
- cursor: painted.cursor,
153
- splitBody: false,
154
- listScroll: painted.offset,
155
- });
156
148
  };
157
149
 
158
150
  module.exports = { paintBodyFiles };
@@ -1,25 +1,28 @@
1
1
  'use strict';
2
2
 
3
3
  const ansi = require('../ansi.js');
4
- const { overlayRows } = require('../select.js');
4
+ const select = require('../select.js');
5
+ const { overlayRows } = select;
5
6
  const primitives = require('./primitives.js');
6
7
  const chrome = require('./chrome.js');
7
8
  const files = require('./files.js');
8
9
  const branches = require('./branches.js');
9
10
  const commits = require('./commits.js');
10
11
  const notes = require('./notes.js');
12
+ const npm = require('./npm.js');
11
13
  const diff = require('./diff.js');
12
14
  const unit = require('./unit.js');
13
15
 
14
16
  const { RESET } = ansi;
15
17
  const { LIST_PANES, paintBodyFill, paneResult } = primitives;
16
- const { paintHeader, paintButtons, paintStatusLine } = chrome;
18
+ const { paintHeader, paintFooter, footerHits, paintStatusLine } = chrome;
17
19
  const { layoutButtons, statusBranchHit, statusChoiceHits } = chrome;
18
20
  const { hiddenActions, dimFooterActions, extraFooterActions } = chrome;
19
21
  const { paintNotePanel, paintBodyTodo } = notes;
20
22
  const { paintBodyFiles } = files;
21
23
  const { paintBodyBranches } = branches;
22
24
  const { paintBodyCommits } = commits;
25
+ const { paintBodyNpm } = npm;
23
26
  const { paintBodyDiff } = diff;
24
27
  const { paintBodyUnit } = unit;
25
28
 
@@ -28,25 +31,20 @@ const bodyKind = (view) => {
28
31
  if (pane === 'files' || pane === 'branches' || pane === 'commits') {
29
32
  return pane;
30
33
  }
34
+ if (pane === 'npm') return 'npm';
31
35
  if (pane === 'unit') return 'unit';
32
36
  if (!view.item) return 'empty';
33
37
  if (view.item.origin === 'todo') return 'todo';
34
38
  return 'diff';
35
39
  };
36
40
 
37
- const paintBodyEmpty = () =>
38
- paneResult({
39
- body: [],
40
- fileHits: [],
41
- todoOwners: [],
42
- cursor: null,
43
- splitBody: false,
44
- });
41
+ const paintBodyEmpty = () => paneResult();
45
42
 
46
43
  const BODY_PAINT = {
47
44
  files: paintBodyFiles,
48
45
  branches: paintBodyBranches,
49
46
  commits: paintBodyCommits,
47
+ npm: paintBodyNpm,
50
48
  empty: paintBodyEmpty,
51
49
  todo: paintBodyTodo,
52
50
  diff: paintBodyDiff,
@@ -75,6 +73,33 @@ const bodyFillKind = (listPane, splitBody) => {
75
73
  return 'plain';
76
74
  };
77
75
 
76
+ const ownerSpan = (owners, focus) => {
77
+ let first = -1;
78
+ let last = -1;
79
+ for (let i = 0; i < owners.length; i++) {
80
+ if (owners[i] !== focus) continue;
81
+ if (first < 0) first = i;
82
+ last = i;
83
+ }
84
+ return { first, last };
85
+ };
86
+
87
+ const revealScroll = (owners, focus, height, prefer) => {
88
+ const span = ownerSpan(owners, focus);
89
+ if (span.first < 0) return prefer;
90
+ const room = Math.max(1, height);
91
+ let start = prefer;
92
+ if (span.first < start) start = span.first;
93
+ if (span.last >= start + room) {
94
+ const fits = span.last - span.first + 1 <= room;
95
+ start = fits ? span.last - room + 1 : span.first;
96
+ }
97
+ const maxStart = Math.max(0, owners.length - room);
98
+ if (start > maxStart) start = maxStart;
99
+ if (start < 0) start = 0;
100
+ return start;
101
+ };
102
+
78
103
  const mapBodyCursor = (cursor, shift, scroll, headerLines, shown) => {
79
104
  if (!cursor) return null;
80
105
  const row = cursor.row + shift;
@@ -119,7 +144,10 @@ const windowBody = (view, width, color, bodyH, headerLines, paintedBody) => {
119
144
  }
120
145
  const scrollRaw = view.scroll ?? 0;
121
146
  const scrollMax = Math.max(0, body.length - bodyH);
122
- const scroll = listPane ? 0 : Math.min(scrollRaw, scrollMax);
147
+ let scroll = listPane ? 0 : Math.min(scrollRaw, scrollMax);
148
+ if (view.item && view.item.origin === 'todo') {
149
+ scroll = revealScroll(todoOwners, view.todoFocus ?? 0, bodyH, scroll);
150
+ }
123
151
  const end = Math.min(body.length, scroll + bodyH);
124
152
  const copied = Math.max(0, end - scroll);
125
153
  const slice = new Array(bodyH);
@@ -165,7 +193,7 @@ const stackFrame = (view, width, color, rows, layout, windowed, notes) => {
165
193
  for (const row of notes.noteRows) rows.push(row);
166
194
  const status = view.status ?? '';
167
195
  rows.push(paintStatusLine(view, status, width, color));
168
- rows.push(paintButtons(layout, width, color));
196
+ rows.push(paintFooter(view, layout, width, color));
169
197
  return overlayRows(rows, view.selection);
170
198
  };
171
199
 
@@ -207,7 +235,7 @@ const renderFrame = (view, options = {}) => {
207
235
  return {
208
236
  text,
209
237
  rows: painted,
210
- buttons: layout.hits,
238
+ buttons: footerHits(view, layout),
211
239
  fileHits,
212
240
  todoHits,
213
241
  templateHits,
@@ -7,7 +7,7 @@ const primitives = require('./primitives.js');
7
7
 
8
8
  const { REPO_TODOS_LABEL } = files;
9
9
  const { wrapMultiline, cursorInWrap } = wrap;
10
- const { THEME, visibleWidth, truncateVisible, seq, EL, RESET, BOLD } = ansi;
10
+ const { THEME, truncateVisible } = ansi;
11
11
  const { paintBar, paintCheckBar, windowRows } = primitives;
12
12
  const { LIST_PANES, CHECK_LEAD, paneResult } = primitives;
13
13
 
@@ -73,6 +73,7 @@ const paintTodoList = (texts, focus, width, color, edit) => {
73
73
  fgRgb,
74
74
  bgRgb,
75
75
  color,
76
+ true,
76
77
  );
77
78
  rows.push(painted);
78
79
  owners.push(i);
@@ -168,16 +169,8 @@ const paintNotePanel = (view, width, color, cap) => {
168
169
  return paintIdleNote(view, width, color);
169
170
  };
170
171
 
171
- const paintTodoCaption = (width, color) => {
172
- const text = ` ${REPO_TODOS_LABEL}`;
173
- const clipped = truncateVisible(text, width);
174
- const pad = Math.max(0, width - visibleWidth(clipped));
175
- const filled = clipped + ' '.repeat(pad);
176
- if (!color) return filled;
177
- const fgRgb = THEME.buttonHotFg;
178
- const bgRgb = THEME.ctxBg;
179
- return `${BOLD}${seq(fgRgb, bgRgb)}${EL}${filled}${RESET}`;
180
- };
172
+ const paintTodoCaption = (width, color) =>
173
+ paintBar(` ${REPO_TODOS_LABEL}`, width, THEME.mutedFg, THEME.ctxBg, color);
181
174
 
182
175
  const paintBodyTodo = (view, width, color) => {
183
176
  const texts = view.todos ?? [];