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
@@ -2,264 +2,193 @@
2
2
 
3
3
  const ansi = require('../ansi.js');
4
4
  const wrap = require('../wrap.js');
5
- const files = require('../files.js');
6
5
  const primitives = require('./primitives.js');
7
-
8
- const { REPO_TASKS_LABEL } = files;
9
6
  const { wrapMultiline, wrapDoc, cursorInWrap } = wrap;
10
- const { THEME, truncateVisible } = ansi;
11
- const { paintBar, paintCheckBar, windowRows } = primitives;
12
- const { LIST_PANES, CHECK_LEAD, paneResult } = primitives;
7
+ const { THEME, paint, visibleWidth, truncateVisible, seq, EL, RESET } = ansi;
8
+ const { isListPane, paneResult, editSpan } = primitives;
13
9
 
14
- const NOTE_COMPOSE = ['feedback'];
15
10
  const INPUT_MAX = 4;
16
11
  const NOTE_MAX = 4;
17
12
  const NOTE_PAD = 1;
13
+ const CHECK_DONE = 'xX';
14
+ const CHECK_TOKEN = /\[([ xX])\]/;
15
+ const CHECK_LEAD = /^\[[ xX]\] /;
16
+ const TODO_LEAD_W = '[ ] '.length;
17
+ const NO_NOTE = { rows: [], cursor: null, templateHits: [], editHits: [] };
18
+
19
+ const paintBar = (text, width, fgRgb, bgRgb, color) => {
20
+ const clipped = truncateVisible(text, width);
21
+ const pad = Math.max(0, width - visibleWidth(clipped));
22
+ const filled = clipped + ' '.repeat(pad);
23
+ if (!color) return filled;
24
+ return `${seq(fgRgb, bgRgb)}${EL}${filled}${RESET}`;
25
+ };
18
26
 
19
- const todoInnerWidth = (width) => Math.max(1, width - 2);
27
+ const checkChip = (mark, fgRgb, bgRgb, plain) => {
28
+ if (plain) return { fgRgb, bgRgb };
29
+ if (CHECK_DONE.includes(mark)) {
30
+ return { fgRgb: THEME.checkDoneFg, bgRgb: THEME.checkDoneBg };
31
+ }
32
+ return { fgRgb: THEME.checkFg, bgRgb: THEME.checkBg };
33
+ };
20
34
 
21
- const checkLeadLen = (text) => {
22
- const match = CHECK_LEAD.exec(text);
23
- return match ? match[0].length : 0;
35
+ const paintCheckBar = (text, width, fgRgb, bgRgb, color, plain = false) => {
36
+ const clipped = truncateVisible(text, width);
37
+ const match = color ? CHECK_TOKEN.exec(clipped) : null;
38
+ if (!match) return paintBar(text, width, fgRgb, bgRgb, color);
39
+ const token = match[0];
40
+ const pad = Math.max(0, width - visibleWidth(clipped));
41
+ const before = clipped.slice(0, match.index);
42
+ const after = clipped.slice(match.index + token.length) + ' '.repeat(pad);
43
+ const chip = checkChip(match[1], fgRgb, bgRgb, plain);
44
+ let out = `${seq(fgRgb, bgRgb)}${EL}`;
45
+ out += paint(before, fgRgb, bgRgb, color);
46
+ out += paint(token, chip.fgRgb, chip.bgRgb, color);
47
+ return out + paint(after, fgRgb, bgRgb, color);
24
48
  };
25
49
 
26
- const TODO_LEAD_W = checkLeadLen('[ ] ');
50
+ const windowRows = (rows, focus, cap) => {
51
+ if (rows.length <= cap) return { rows, offset: 0 };
52
+ const start = Math.min(Math.max(0, focus - cap + 1), rows.length - cap);
53
+ return { rows: rows.slice(start, start + cap), offset: start };
54
+ };
55
+
56
+ const todoInnerWidth = (width) => Math.max(1, width - 2);
27
57
 
28
58
  const todoBodyWidth = (width) =>
29
59
  Math.max(1, todoInnerWidth(width) - TODO_LEAD_W);
30
60
 
31
- const wrapTodoItem = (text, inner) => {
32
- const lead = checkLeadLen(text);
33
- if (!lead) return wrapMultiline(text, inner);
34
- const mark = text.slice(0, lead);
35
- const body = text.slice(lead);
36
- const bodyWidth = Math.max(1, inner - lead);
37
- const hang = ' '.repeat(lead);
38
- const wrapped = wrapMultiline(body, bodyWidth);
39
- const { length } = wrapped;
40
- const lines = new Array(length);
41
- for (let i = 0; i < length; i++) {
42
- const prefix = i === 0 ? mark : hang;
43
- lines[i] = `${prefix}${wrapped[i]}`;
44
- }
45
- return lines;
61
+ const noteInnerWidth = (width) => Math.max(1, width - NOTE_PAD * 2);
62
+
63
+ const todoParts = (text, inner) => {
64
+ const match = CHECK_LEAD.exec(text);
65
+ const lead = match ? match[0].length : 0;
66
+ return { lead, body: text.slice(lead), bodyW: Math.max(1, inner - lead) };
46
67
  };
47
68
 
48
- const todoCursor = (text, edit, inner, rowStart) => {
49
- const lead = checkLeadLen(text);
50
- const body = text.slice(lead);
51
- const bodyWidth = Math.max(1, inner - lead);
52
- const pos = cursorInWrap(body, edit.cursor ?? 0, bodyWidth);
53
- return { x: pos.col + lead + 2, row: rowStart + pos.row };
69
+ const wrapTodoItem = (text, todo) => {
70
+ const mark = text.slice(0, todo.lead);
71
+ const hang = ' '.repeat(todo.lead);
72
+ const lines = wrapMultiline(todo.body, todo.bodyW);
73
+ return lines.map((line, index) => `${index ? hang : mark}${line}`);
54
74
  };
55
75
 
56
- const taskEditHits = (text, inner, rowStart, width) => {
57
- const lead = checkLeadLen(text);
58
- const body = text.slice(lead);
59
- const bodyWidth = Math.max(1, inner - lead);
60
- const doc = wrapDoc(body, bodyWidth);
61
- const textX = lead + 2;
62
- const hits = [];
63
- for (let line = 0; line < doc.length; line++) {
64
- const piece = doc[line];
65
- hits.push({
66
- row: rowStart + line,
67
- x0: lead + 1,
68
- textX,
69
- x1: width + 1,
70
- start: piece.start,
71
- text: piece.text,
72
- limit: piece.start + piece.text.length,
73
- live: true,
74
- });
75
- }
76
- return hits;
76
+ const todoEdit = (todo, edit, rowStart, width) => {
77
+ const pos = cursorInWrap(todo.body, edit.cursor ?? 0, todo.bodyW);
78
+ const cursor = { x: pos.col + todo.lead + 2, row: rowStart + pos.row };
79
+ const x0 = todo.lead + 1;
80
+ const doc = wrapDoc(todo.body, todo.bodyW);
81
+ const hits = doc.map((piece, line) => ({
82
+ row: rowStart + line,
83
+ ...editSpan(x0, x0 + 1, width + 1, piece.start, piece.text),
84
+ }));
85
+ return { cursor, hits };
86
+ };
87
+
88
+ const taskLists = (view) => {
89
+ const sections = view.taskSections;
90
+ if (sections && sections.length) return sections;
91
+ const tasks = view.tasks ?? [];
92
+ const texts = !tasks.length && view.noteText ? [view.noteText] : tasks;
93
+ return [{ title: '', texts }];
77
94
  };
78
95
 
79
- const paintTodoList = (texts, focus, width, color, edit) => {
96
+ const paintSectionHead = (title, width, color) =>
97
+ paintBar(` ${title}`, width, THEME.mutedFg, THEME.ctxBg, color);
98
+
99
+ const paintBodyTodo = (view, width, color) => {
100
+ const focus = view.tasksFocus ?? 0;
80
101
  const inner = todoInnerWidth(width);
81
- const rows = [];
82
- const owners = [];
83
- const checks = [];
84
- const editHits = [];
102
+ const body = [];
103
+ const taskOwners = [];
104
+ const taskChecks = [];
105
+ let editHits = [];
85
106
  let cursor = null;
86
- for (let i = 0; i < texts.length; i++) {
87
- const selected = i === focus;
88
- const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
89
- const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
90
- const text = texts[i];
91
- const wrapped = wrapTodoItem(text, inner);
92
- const rowStart = rows.length;
93
- for (let line = 0; line < wrapped.length; line++) {
94
- const painted = paintCheckBar(
95
- ` ${wrapped[line]}`,
96
- width,
97
- fgRgb,
98
- bgRgb,
99
- color,
100
- true,
101
- );
102
- rows.push(painted);
103
- owners.push(i);
104
- checks.push(line === 0);
107
+ let index = 0;
108
+ for (const section of taskLists(view)) {
109
+ if (section.title) {
110
+ body.push(paintSectionHead(section.title, width, color));
111
+ taskOwners.push(null);
112
+ taskChecks.push(false);
113
+ }
114
+ for (const text of section.texts) {
115
+ const selected = index === focus;
116
+ const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
117
+ const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
118
+ const todo = todoParts(text, inner);
119
+ const rowStart = body.length;
120
+ const wrapped = wrapTodoItem(text, todo);
121
+ for (let line = 0; line < wrapped.length; line++) {
122
+ const row = ` ${wrapped[line]}`;
123
+ body.push(paintCheckBar(row, width, fgRgb, bgRgb, color, true));
124
+ taskOwners.push(index);
125
+ taskChecks.push(line === 0);
126
+ }
127
+ if (view.taskEdit && selected) {
128
+ const edit = todoEdit(todo, view.taskEdit, rowStart, width);
129
+ cursor = edit.cursor;
130
+ editHits = edit.hits;
131
+ }
132
+ index += 1;
105
133
  }
106
- if (!edit || !selected) continue;
107
- cursor = todoCursor(text, edit, inner, rowStart);
108
- const hits = taskEditHits(text, inner, rowStart, width);
109
- for (const hit of hits) editHits.push(hit);
110
134
  }
111
- return { rows, owners, checks, cursor, editHits };
135
+ return paneResult({ body, editHits, taskOwners, taskChecks, cursor });
112
136
  };
113
137
 
114
- const noteInnerWidth = (width) => Math.max(1, width - NOTE_PAD * 2);
115
-
116
- const paintNoteLine = (text, width, fgRgb, bgRgb, color, withCheck) => {
117
- const inner = noteInnerWidth(width);
118
- const clipped = truncateVisible(text, inner);
119
- const line = `${' '.repeat(NOTE_PAD)}${clipped}`;
120
- if (withCheck) return paintCheckBar(line, width, fgRgb, bgRgb, color);
121
- return paintBar(line, width, fgRgb, bgRgb, color);
122
- };
123
-
124
- const paintTemplateRow = (text, selected, width, color) => {
125
- const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
126
- const bgRgb = selected ? THEME.buttonBg : THEME.noteBg;
127
- return paintNoteLine(text, width, fgRgb, bgRgb, color);
128
- };
129
-
130
- const noteEditHit = (piece, row, width) => ({
131
- row,
132
- x0: 1,
133
- textX: NOTE_PAD + 1,
134
- x1: width + 1,
135
- start: piece.start,
136
- text: piece.text,
137
- limit: piece.start + piece.text.length,
138
- live: true,
139
- });
138
+ const noteLine = (text, width) =>
139
+ `${' '.repeat(NOTE_PAD)}${truncateVisible(text, noteInnerWidth(width))}`;
140
140
 
141
141
  const paintComposeNote = (view, width, color, cap) => {
142
142
  const inner = noteInnerWidth(width);
143
- const bgRgb = THEME.noteBg;
144
- const compose = view.compose;
143
+ const { compose } = view;
145
144
  const doc = wrapDoc(compose.text ?? '', inner);
146
- const wrapped = doc.map((piece) => piece.text);
147
145
  const { row, col } = cursorInWrap(compose.text, compose.cursor ?? 0, inner);
148
- const shown = windowRows(wrapped, row, INPUT_MAX);
146
+ const shown = windowRows(doc, row, INPUT_MAX);
147
+ const templates = view.templates ?? [];
148
+ const room = Math.max(0, cap - shown.rows.length);
149
+ const count = Math.min(templates.length, room);
150
+ const focus = view.templateIndex ?? -1;
149
151
  const rows = [];
150
152
  const templateHits = [];
151
- if (compose.kind === 'feedback') {
152
- const all = view.templates ?? [];
153
- const room =
154
- cap === undefined ? all.length : Math.max(0, cap - shown.rows.length);
155
- const n = Math.min(all.length, room);
156
- const focus = view.templateIndex ?? -1;
157
- for (let i = 0; i < n; i++) {
158
- const text = all[i].text ?? '';
159
- const selected = i === focus;
160
- rows.push(paintTemplateRow(text, selected, width, color));
161
- templateHits.push({ row: i, cursor: i });
162
- }
153
+ for (let i = 0; i < count; i++) {
154
+ const selected = i === focus;
155
+ const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
156
+ const bgRgb = selected ? THEME.buttonBg : THEME.noteBg;
157
+ const line = noteLine(templates[i].text ?? '', width);
158
+ rows.push(paintBar(line, width, fgRgb, bgRgb, color));
159
+ templateHits.push({ row: i, cursor: i });
163
160
  }
164
- const templateCount = rows.length;
165
161
  const editHits = [];
166
- for (let i = 0; i < shown.rows.length; i++) {
167
- const piece = doc[shown.offset + i];
168
- const painted = paintNoteLine(
169
- shown.rows[i],
170
- width,
171
- THEME.chromeFg,
172
- bgRgb,
173
- color,
174
- );
175
- rows.push(painted);
176
- if (!piece) continue;
177
- editHits.push(noteEditHit(piece, templateCount + i, width));
162
+ for (const piece of shown.rows) {
163
+ const span = editSpan(1, NOTE_PAD + 1, width + 1, piece.start, piece.text);
164
+ editHits.push({ row: rows.length, ...span });
165
+ const line = noteLine(piece.text, width);
166
+ rows.push(paintBar(line, width, THEME.chromeFg, THEME.noteBg, color));
178
167
  }
179
- return {
180
- rows,
181
- cursor: {
182
- x: NOTE_PAD + col + 1,
183
- row: templateCount + row - shown.offset,
184
- },
185
- templateHits,
186
- editHits,
187
- };
168
+ const cursor = { x: NOTE_PAD + col + 1, row: count + row - shown.offset };
169
+ return { rows, cursor, templateHits, editHits };
188
170
  };
189
171
 
190
172
  const paintIdleNote = (view, width, color) => {
191
- const empty = { rows: [], cursor: null, templateHits: [], editHits: [] };
192
- const text = view.noteText;
193
- if (!text) return empty;
194
- const inner = noteInnerWidth(width);
195
- const wrapped = wrapMultiline(`feedback: ${text}`, inner);
196
- const rows = [];
197
- for (const line of wrapped.slice(0, NOTE_MAX)) {
198
- const painted = paintNoteLine(
199
- line,
200
- width,
201
- THEME.chromeFg,
202
- THEME.noteBg,
203
- color,
204
- true,
205
- );
206
- rows.push(painted);
207
- }
208
- return { rows, cursor: null, templateHits: [] };
173
+ if (!view.noteText) return NO_NOTE;
174
+ const note = `feedback: ${view.noteText}`;
175
+ const wrapped = wrapMultiline(note, noteInnerWidth(width));
176
+ const rows = wrapped.slice(0, NOTE_MAX).map((text) => {
177
+ const line = noteLine(text, width);
178
+ return paintCheckBar(line, width, THEME.chromeFg, THEME.noteBg, color);
179
+ });
180
+ return { rows, cursor: null, templateHits: [], editHits: [] };
209
181
  };
210
182
 
211
183
  const paintNotePanel = (view, width, color, cap) => {
212
- const empty = { rows: [], cursor: null, templateHits: [], editHits: [] };
213
- const compose = view.compose;
214
- if (compose && NOTE_COMPOSE.includes(compose.kind)) {
184
+ if (view.compose?.kind === 'feedback') {
215
185
  return paintComposeNote(view, width, color, cap);
216
186
  }
217
- const pane = view.pane ?? 'diff';
218
- if (LIST_PANES.includes(pane)) return empty;
187
+ if (isListPane(view.pane)) return NO_NOTE;
219
188
  return paintIdleNote(view, width, color);
220
189
  };
221
190
 
222
- const paintTodoCaption = (width, color) =>
223
- paintBar(` ${REPO_TASKS_LABEL}`, width, THEME.mutedFg, THEME.ctxBg, color);
224
-
225
- const paintBodyTodo = (view, width, color) => {
226
- const texts = view.tasks ?? [];
227
- let lines = texts;
228
- if (!lines.length && view.noteText) lines = [view.noteText];
229
- const painted = paintTodoList(
230
- lines,
231
- view.tasksFocus ?? 0,
232
- width,
233
- color,
234
- view.taskEdit,
235
- );
236
- const caption = paintTodoCaption(width, color);
237
- const body = [caption, ...painted.rows];
238
- const taskOwners = [null, ...painted.owners];
239
- const taskChecks = [false, ...painted.checks];
240
- let cursor = null;
241
- if (painted.cursor) {
242
- cursor = { x: painted.cursor.x, row: painted.cursor.row + 1 };
243
- }
244
- const editHits = [];
245
- for (const hit of painted.editHits) {
246
- editHits.push({ ...hit, row: hit.row + 1 });
247
- }
248
- const fileHits = [];
249
- const splitBody = false;
250
- return paneResult({
251
- body,
252
- fileHits,
253
- editHits,
254
- taskOwners,
255
- taskChecks,
256
- cursor,
257
- splitBody,
258
- });
259
- };
260
-
261
191
  module.exports = {
262
- todoInnerWidth,
263
192
  todoBodyWidth,
264
193
  noteInnerWidth,
265
194
  paintNotePanel,