reslop 0.2.5 → 0.2.7

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 (114) hide show
  1. package/CHANGELOG.md +22 -1
  2. package/LICENSE +1 -1
  3. package/README.md +11 -11
  4. package/bin/reslop.js +3 -8
  5. package/lib/ansi.js +72 -19
  6. package/lib/api.js +1 -1
  7. package/lib/capabilities.js +8 -8
  8. package/lib/cli.js +61 -75
  9. package/lib/clipboard.js +39 -47
  10. package/lib/dashboard/git.js +128 -0
  11. package/lib/dashboard/model.js +357 -0
  12. package/lib/dashboard/npm.js +290 -0
  13. package/lib/dashboard/tree.js +278 -0
  14. package/lib/deps-diff.js +197 -402
  15. package/lib/deps.js +113 -240
  16. package/lib/detect.js +8 -18
  17. package/lib/diff/align.js +41 -113
  18. package/lib/diff/diff.js +12 -43
  19. package/lib/diff/display.js +25 -36
  20. package/lib/diff/edit.js +6 -20
  21. package/lib/diff/inline.js +76 -144
  22. package/lib/diff/items.js +33 -47
  23. package/lib/diff/parse.js +28 -56
  24. package/lib/diff/patch.js +12 -33
  25. package/lib/diff/unit.js +79 -178
  26. package/lib/editor.js +52 -69
  27. package/lib/files.js +79 -91
  28. package/lib/find.js +61 -0
  29. package/lib/git-branches.js +254 -416
  30. package/lib/git-deps.js +148 -272
  31. package/lib/git-reword-todo.js +5 -13
  32. package/lib/git-worktree.js +161 -214
  33. package/lib/git.js +21 -49
  34. package/lib/github.js +67 -181
  35. package/lib/gitlab.js +40 -90
  36. package/lib/highlight/bash.js +55 -177
  37. package/lib/highlight/core.js +155 -141
  38. package/lib/highlight/css.js +25 -58
  39. package/lib/highlight/csv.js +19 -37
  40. package/lib/highlight/dart.js +72 -213
  41. package/lib/highlight/dot.js +14 -14
  42. package/lib/highlight/html.js +42 -63
  43. package/lib/highlight/js.js +177 -506
  44. package/lib/highlight/json.js +17 -37
  45. package/lib/highlight/log.js +19 -25
  46. package/lib/highlight/txt.js +7 -10
  47. package/lib/highlight.js +17 -46
  48. package/lib/keys.js +75 -83
  49. package/lib/manifest.js +67 -126
  50. package/lib/npm-commands.js +120 -169
  51. package/lib/remote.js +140 -190
  52. package/lib/render/branches.js +63 -61
  53. package/lib/render/chrome.js +412 -347
  54. package/lib/render/commits.js +160 -361
  55. package/lib/render/dash-activity.js +212 -0
  56. package/lib/render/dash-blocks.js +496 -0
  57. package/lib/render/dash-table.js +295 -0
  58. package/lib/render/dashboard.js +62 -0
  59. package/lib/render/diff.js +123 -258
  60. package/lib/render/files.js +60 -86
  61. package/lib/render/frame.js +82 -117
  62. package/lib/render/notes.js +150 -210
  63. package/lib/render/npm.js +213 -177
  64. package/lib/render/packages.js +93 -0
  65. package/lib/render/present.js +4 -11
  66. package/lib/render/primitives.js +77 -172
  67. package/lib/render/render.js +5 -14
  68. package/lib/render/report.js +43 -0
  69. package/lib/render/repos.js +226 -0
  70. package/lib/render/search.js +25 -0
  71. package/lib/render/tiles.js +355 -0
  72. package/lib/render/unit.js +15 -31
  73. package/lib/report-model.js +110 -33
  74. package/lib/report-parse.js +56 -37
  75. package/lib/report-render.js +102 -59
  76. package/lib/report-run.js +87 -77
  77. package/lib/report-test.js +221 -99
  78. package/lib/report-tools.js +96 -154
  79. package/lib/review-merge.js +33 -41
  80. package/lib/review.js +69 -129
  81. package/lib/runs.js +225 -0
  82. package/lib/select.js +53 -91
  83. package/lib/session/accessors.js +14 -21
  84. package/lib/session/actions.js +189 -65
  85. package/lib/session/branches.js +164 -222
  86. package/lib/session/change-actions.js +49 -119
  87. package/lib/session/commit.js +298 -352
  88. package/lib/session/compose.js +572 -715
  89. package/lib/session/dashboard.js +530 -0
  90. package/lib/session/dispatch.js +197 -171
  91. package/lib/session/files-pane.js +92 -45
  92. package/lib/session/find.js +94 -0
  93. package/lib/session/items.js +6 -17
  94. package/lib/session/lifecycle.js +420 -466
  95. package/lib/session/load.js +1 -14
  96. package/lib/session/navigation.js +4 -2
  97. package/lib/session/npm.js +403 -420
  98. package/lib/session/ops.js +5 -13
  99. package/lib/session/packages.js +143 -0
  100. package/lib/session/pointer.js +30 -14
  101. package/lib/session/progress.js +39 -30
  102. package/lib/session/review.js +1 -3
  103. package/lib/session/tasks.js +277 -236
  104. package/lib/session/templates.js +51 -59
  105. package/lib/session/terminal.js +85 -132
  106. package/lib/session/view.js +59 -80
  107. package/lib/session/watch.js +201 -212
  108. package/lib/session/workspace.js +455 -0
  109. package/lib/session.js +95 -161
  110. package/lib/update.js +44 -111
  111. package/lib/utilities.js +46 -88
  112. package/lib/workspace.js +166 -0
  113. package/lib/wrap.js +29 -55
  114. package/package.json +1 -1
@@ -2,264 +2,204 @@
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 };
77
86
  };
78
87
 
79
- const paintTodoList = (texts, focus, width, color, edit) => {
80
- const inner = todoInnerWidth(width);
81
- const rows = [];
82
- const owners = [];
83
- const checks = [];
84
- const editHits = [];
85
- 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);
105
- }
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
- }
111
- return { rows, owners, checks, cursor, editHits };
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 }];
112
94
  };
113
95
 
114
- const noteInnerWidth = (width) => Math.max(1, width - NOTE_PAD * 2);
96
+ const paintSectionHead = (title, width, color) =>
97
+ paintBar(` ${title}`, width, THEME.taskHeadFg, THEME.taskHeadBg, color);
115
98
 
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
- };
99
+ const paintSectionGap = (width, color) =>
100
+ paintBar('', width, THEME.ctxFg, THEME.ctxBg, color);
123
101
 
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);
102
+ const paintBodyTodo = (view, width, color) => {
103
+ const focus = view.tasksFocus ?? 0;
104
+ const inner = todoInnerWidth(width);
105
+ const body = [];
106
+ const taskOwners = [];
107
+ const taskChecks = [];
108
+ let editHits = [];
109
+ let cursor = null;
110
+ let index = 0;
111
+ const sections = taskLists(view);
112
+ for (let sectionAt = 0; sectionAt < sections.length; sectionAt++) {
113
+ const section = sections[sectionAt];
114
+ if (section.title) {
115
+ body.push(paintSectionHead(section.title, width, color));
116
+ taskOwners.push(null);
117
+ taskChecks.push(false);
118
+ }
119
+ for (const text of section.texts) {
120
+ const selected = index === focus;
121
+ const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
122
+ const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
123
+ const todo = todoParts(text, inner);
124
+ const rowStart = body.length;
125
+ const wrapped = wrapTodoItem(text, todo);
126
+ for (let line = 0; line < wrapped.length; line++) {
127
+ const row = ` ${wrapped[line]}`;
128
+ body.push(paintCheckBar(row, width, fgRgb, bgRgb, color, true));
129
+ taskOwners.push(index);
130
+ taskChecks.push(line === 0);
131
+ }
132
+ if (view.taskEdit && selected) {
133
+ const edit = todoEdit(todo, view.taskEdit, rowStart, width);
134
+ cursor = edit.cursor;
135
+ editHits = edit.hits;
136
+ }
137
+ index += 1;
138
+ }
139
+ const last = sectionAt === sections.length - 1;
140
+ if (section.title && !last) {
141
+ body.push(paintSectionGap(width, color));
142
+ taskOwners.push(null);
143
+ taskChecks.push(false);
144
+ }
145
+ }
146
+ return paneResult({ body, editHits, taskOwners, taskChecks, cursor });
128
147
  };
129
148
 
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
- });
149
+ const noteLine = (text, width) =>
150
+ `${' '.repeat(NOTE_PAD)}${truncateVisible(text, noteInnerWidth(width))}`;
140
151
 
141
152
  const paintComposeNote = (view, width, color, cap) => {
142
153
  const inner = noteInnerWidth(width);
143
- const bgRgb = THEME.noteBg;
144
- const compose = view.compose;
154
+ const { compose } = view;
145
155
  const doc = wrapDoc(compose.text ?? '', inner);
146
- const wrapped = doc.map((piece) => piece.text);
147
156
  const { row, col } = cursorInWrap(compose.text, compose.cursor ?? 0, inner);
148
- const shown = windowRows(wrapped, row, INPUT_MAX);
157
+ const shown = windowRows(doc, row, INPUT_MAX);
158
+ const templates = view.templates ?? [];
159
+ const room = Math.max(0, cap - shown.rows.length);
160
+ const count = Math.min(templates.length, room);
161
+ const focus = view.templateIndex ?? -1;
149
162
  const rows = [];
150
163
  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
- }
164
+ for (let i = 0; i < count; i++) {
165
+ const selected = i === focus;
166
+ const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
167
+ const bgRgb = selected ? THEME.buttonBg : THEME.noteBg;
168
+ const line = noteLine(templates[i].text ?? '', width);
169
+ rows.push(paintBar(line, width, fgRgb, bgRgb, color));
170
+ templateHits.push({ row: i, cursor: i });
163
171
  }
164
- const templateCount = rows.length;
165
172
  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));
173
+ for (const piece of shown.rows) {
174
+ const span = editSpan(1, NOTE_PAD + 1, width + 1, piece.start, piece.text);
175
+ editHits.push({ row: rows.length, ...span });
176
+ const line = noteLine(piece.text, width);
177
+ rows.push(paintBar(line, width, THEME.chromeFg, THEME.noteBg, color));
178
178
  }
179
- return {
180
- rows,
181
- cursor: {
182
- x: NOTE_PAD + col + 1,
183
- row: templateCount + row - shown.offset,
184
- },
185
- templateHits,
186
- editHits,
187
- };
179
+ const cursor = { x: NOTE_PAD + col + 1, row: count + row - shown.offset };
180
+ return { rows, cursor, templateHits, editHits };
188
181
  };
189
182
 
190
183
  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: [] };
184
+ if (!view.noteText) return NO_NOTE;
185
+ const note = `feedback: ${view.noteText}`;
186
+ const wrapped = wrapMultiline(note, noteInnerWidth(width));
187
+ const rows = wrapped.slice(0, NOTE_MAX).map((text) => {
188
+ const line = noteLine(text, width);
189
+ return paintCheckBar(line, width, THEME.chromeFg, THEME.noteBg, color);
190
+ });
191
+ return { rows, cursor: null, templateHits: [], editHits: [] };
209
192
  };
210
193
 
211
194
  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)) {
195
+ if (view.compose?.kind === 'feedback') {
215
196
  return paintComposeNote(view, width, color, cap);
216
197
  }
217
- const pane = view.pane ?? 'diff';
218
- if (LIST_PANES.includes(pane)) return empty;
198
+ if (isListPane(view.pane)) return NO_NOTE;
219
199
  return paintIdleNote(view, width, color);
220
200
  };
221
201
 
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
202
  module.exports = {
262
- todoInnerWidth,
263
203
  todoBodyWidth,
264
204
  noteInnerWidth,
265
205
  paintNotePanel,