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,7 +2,6 @@
2
2
 
3
3
  const ansi = require('../ansi.js');
4
4
  const select = require('../select.js');
5
- const { overlayRows } = select;
6
5
  const primitives = require('./primitives.js');
7
6
  const chrome = require('./chrome.js');
8
7
  const files = require('./files.js');
@@ -12,242 +11,189 @@ const notes = require('./notes.js');
12
11
  const npm = require('./npm.js');
13
12
  const diff = require('./diff.js');
14
13
  const unit = require('./unit.js');
14
+ const dashboard = require('./dashboard.js');
15
15
 
16
16
  const { RESET } = ansi;
17
- const { LIST_PANES, paintBodyFill, paneResult } = primitives;
17
+ const { overlayRows, markEditorSelection } = select;
18
+ const { isListPane, isTaskView, paintBodyFill, paneResult } = primitives;
18
19
  const { paintHeader, paintFooter, footerHits, paintStatusLine } = chrome;
19
- const { layoutButtons, statusBranchHit, statusChoiceHits } = chrome;
20
- const { hiddenActions, dimFooterActions, extraFooterActions } = chrome;
20
+ const { footerLayout, statusBranchHit, statusChoiceHits } = chrome;
21
21
  const { paintNotePanel, paintBodyTodo } = notes;
22
- const { paintBodyFiles } = files;
23
- const { paintBodyBranches } = branches;
24
- const { paintBodyCommits } = commits;
25
- const { paintBodyNpm } = npm;
26
22
  const { paintBodyDiff } = diff;
27
- const { paintBodyUnit } = unit;
28
23
 
29
- const bodyKind = (view) => {
30
- const pane = view.pane ?? 'diff';
31
- if (pane === 'files' || pane === 'branches' || pane === 'commits') {
32
- return pane;
33
- }
34
- if (pane === 'npm') return 'npm';
35
- if (pane === 'unit') return 'unit';
36
- if (!view.item) return 'empty';
37
- if (view.item.origin === 'todo') return 'todo';
38
- return 'diff';
24
+ const PANE_BODY = {
25
+ dashboard: dashboard.paintBodyDashboard,
26
+ files: files.paintBodyFiles,
27
+ branches: branches.paintBodyBranches,
28
+ commits: commits.paintBodyCommits,
29
+ npm: npm.paintBodyNpm,
30
+ unit: unit.paintBodyUnit,
31
+ tasks: paintBodyTodo,
39
32
  };
40
33
 
41
34
  const paintBodyEmpty = () => paneResult();
42
35
 
43
- const BODY_PAINT = {
44
- files: paintBodyFiles,
45
- branches: paintBodyBranches,
46
- commits: paintBodyCommits,
47
- npm: paintBodyNpm,
48
- empty: paintBodyEmpty,
49
- todo: paintBodyTodo,
50
- diff: paintBodyDiff,
51
- unit: paintBodyUnit,
52
- };
53
-
54
- const paintFrameBody = (view, width, color, bodyH, headerLines) => {
55
- const paint = BODY_PAINT[bodyKind(view)];
56
- return paint(view, width, color, bodyH, headerLines);
36
+ const bodyPainter = (view) => {
37
+ if (Object.hasOwn(PANE_BODY, view.pane)) return PANE_BODY[view.pane];
38
+ if (!view.item) return paintBodyEmpty;
39
+ return paintBodyDiff;
57
40
  };
58
41
 
59
42
  const frameNotes = (view, width, height, color) => {
60
43
  const notesCap = Math.max(0, height - 4);
61
- const notesWanted = paintNotePanel(view, width, color, notesCap);
62
- const noteRows = notesWanted.rows.slice(0, notesCap);
63
- let noteCursor = notesWanted.cursor;
64
- if (noteCursor && noteCursor.row >= noteRows.length) noteCursor = null;
65
- const chrome = 3 + noteRows.length;
66
- const bodyH = Math.max(1, height - chrome);
67
- return { notesWanted, noteRows, noteCursor, bodyH };
44
+ const panel = paintNotePanel(view, width, color, notesCap);
45
+ const rows = panel.rows.slice(0, notesCap);
46
+ const inside = panel.cursor && panel.cursor.row < rows.length;
47
+ const cursor = inside ? panel.cursor : null;
48
+ const bodyH = Math.max(1, height - 3 - rows.length);
49
+ return { panel, rows, cursor, bodyH };
68
50
  };
69
51
 
70
- const bodyFillKind = (listPane, splitBody) => {
71
- if (listPane) return 'files';
72
- if (splitBody) return 'split';
73
- return 'plain';
52
+ const revealScroll = (owners, focus, height, prefer) => {
53
+ const first = owners.indexOf(focus);
54
+ if (first < 0) return prefer;
55
+ const last = owners.lastIndexOf(focus);
56
+ const room = Math.max(1, height);
57
+ let start = Math.min(prefer, first);
58
+ if (last >= start + room) {
59
+ start = last - first + 1 <= room ? last - room + 1 : first;
60
+ }
61
+ return Math.max(0, Math.min(start, owners.length - room));
74
62
  };
75
63
 
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 };
64
+ const windowY = (win, row) => {
65
+ const at = row + win.shift - win.scroll;
66
+ if (at < 0 || at >= win.shown) return -1;
67
+ return win.headerLines + at + 1;
85
68
  };
86
69
 
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;
70
+ const mapEditHits = (hits, win) => {
71
+ const out = [];
72
+ for (const hit of hits) {
73
+ const y = windowY(win, hit.row);
74
+ if (y >= 0) out.push({ ...hit, y });
96
75
  }
97
- const maxStart = Math.max(0, owners.length - room);
98
- if (start > maxStart) start = maxStart;
99
- if (start < 0) start = 0;
100
- return start;
76
+ return out;
101
77
  };
102
78
 
103
- const mapBodyCursor = (cursor, shift, scroll, headerLines, shown) => {
79
+ const mapBodyCursor = (cursor, win) => {
104
80
  if (!cursor) return null;
105
- const row = cursor.row + shift;
106
- if (row < scroll || row >= scroll + shown) return null;
107
- return {
108
- x: cursor.x,
109
- y: headerLines + (row - scroll) + 1,
110
- };
81
+ const y = windowY(win, cursor.row);
82
+ if (y < 0) return null;
83
+ const point = { x: cursor.x, y };
84
+ if (typeof cursor.scroll === 'number') point.scroll = cursor.scroll;
85
+ return point;
86
+ };
87
+
88
+ const bodyFillKind = (listPane, splitBody) => {
89
+ if (listPane) return 'files';
90
+ return splitBody ? 'split' : 'plain';
111
91
  };
112
92
 
113
- const windowBody = (view, width, color, bodyH, headerLines, paintedBody) => {
114
- const sourceBody = paintedBody.body;
115
- const sourceOwners = paintedBody.todoOwners;
116
- const sourceChecks = paintedBody.todoChecks ?? [];
117
- const splitBody = paintedBody.splitBody === true;
118
- const pane = view.pane ?? 'diff';
119
- const listPane = LIST_PANES.includes(pane);
120
- const fillKind = bodyFillKind(listPane, splitBody);
93
+ const windowBody = (view, painted, frame) => {
94
+ const { width, color, bodyH, headerLines } = frame;
95
+ const listPane = isListPane(view.pane);
96
+ const fillKind = bodyFillKind(listPane, painted.splitBody);
121
97
  const filler = paintBodyFill(width, color, fillKind);
122
- const lead = !listPane && sourceBody.length > 0;
123
- const body = new Array(sourceBody.length + (lead ? 1 : 0));
124
- let bodyAt = 0;
125
- let shift = 0;
126
- if (lead) {
127
- body[bodyAt++] = filler;
128
- shift = 1;
129
- }
130
- for (let i = 0; i < sourceBody.length; i++) body[bodyAt++] = sourceBody[i];
131
- const ownerLead = lead && sourceOwners.length;
132
- const todoOwners = new Array(sourceOwners.length + (ownerLead ? 1 : 0));
133
- let ownerAt = 0;
134
- if (ownerLead) todoOwners[ownerAt++] = null;
135
- for (let i = 0; i < sourceOwners.length; i++) {
136
- todoOwners[ownerAt++] = sourceOwners[i];
137
- }
138
- const checkLead = lead && sourceChecks.length;
139
- const todoChecks = new Array(sourceChecks.length + (checkLead ? 1 : 0));
140
- let checkAt = 0;
141
- if (checkLead) todoChecks[checkAt++] = false;
142
- for (let i = 0; i < sourceChecks.length; i++) {
143
- todoChecks[checkAt++] = sourceChecks[i];
144
- }
145
- const scrollRaw = view.scroll ?? 0;
98
+ const shift = !listPane && painted.body.length > 0 ? 1 : 0;
99
+ const lead = (items, first) =>
100
+ shift && items.length ? [first, ...items] : items;
101
+ const body = lead(painted.body, filler);
102
+ const taskOwners = lead(painted.taskOwners, null);
103
+ const taskChecks = lead(painted.taskChecks, false);
146
104
  const scrollMax = Math.max(0, body.length - bodyH);
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);
105
+ let scroll = listPane ? 0 : Math.min(view.scroll ?? 0, scrollMax);
106
+ if (isTaskView(view)) {
107
+ scroll = revealScroll(taskOwners, view.tasksFocus ?? 0, bodyH, scroll);
150
108
  }
151
- const end = Math.min(body.length, scroll + bodyH);
152
- const copied = Math.max(0, end - scroll);
153
- const slice = new Array(bodyH);
154
- for (let i = 0; i < copied; i++) slice[i] = body[scroll + i];
155
- const todoHits = [];
156
- for (let i = 0; i < copied; i++) {
157
- const owner = todoOwners[scroll + i];
109
+ const slice = body.slice(scroll, scroll + bodyH);
110
+ const win = { shift, scroll, shown: slice.length, headerLines };
111
+ const taskHits = [];
112
+ for (let i = 0; i < slice.length; i++) {
113
+ const owner = taskOwners[scroll + i];
158
114
  if (owner === undefined || owner === null) continue;
159
115
  const y = headerLines + i + 1;
160
- if (todoChecks[scroll + i]) {
161
- todoHits.push({ y, cursor: owner, x0: 1, x1: 4, check: true });
116
+ if (taskChecks[scroll + i]) {
117
+ taskHits.push({ y, cursor: owner, x0: 1, x1: 4, check: true });
162
118
  }
163
- todoHits.push({ y, cursor: owner });
119
+ taskHits.push({ y, cursor: owner });
164
120
  }
165
- const cursor = mapBodyCursor(
166
- paintedBody.cursor,
167
- shift,
121
+ while (slice.length < bodyH) slice.push(filler);
122
+ return {
123
+ slice,
168
124
  scroll,
169
- headerLines,
170
- copied,
171
- );
172
- for (let i = copied; i < bodyH; i++) slice[i] = filler;
173
- const listScroll = paintedBody.listScroll ?? 0;
174
- const fileHits = paintedBody.fileHits;
175
- return { slice, scroll, scrollMax, listScroll, todoHits, fileHits, cursor };
125
+ scrollMax,
126
+ listScroll: painted.listScroll,
127
+ taskHits,
128
+ fileHits: painted.fileHits,
129
+ editHits: mapEditHits(painted.editHits, win),
130
+ cursor: mapBodyCursor(painted.cursor, win),
131
+ };
176
132
  };
177
133
 
178
- const notePointerHits = (notesWanted, noteRows, noteCursor, bodyH) => {
179
- let cursor = null;
180
- if (noteCursor) {
181
- cursor = { x: noteCursor.x, y: 2 + bodyH + noteCursor.row };
182
- }
134
+ const notePointerHits = (notes, bodyH) => {
135
+ const { panel, rows, cursor } = notes;
136
+ const top = 2 + bodyH;
183
137
  const templateHits = [];
184
- for (const hit of notesWanted.templateHits ?? []) {
185
- if (hit.row >= noteRows.length) continue;
186
- templateHits.push({ y: 2 + bodyH + hit.row, cursor: hit.cursor });
138
+ for (const hit of panel.templateHits) {
139
+ if (hit.row < rows.length) {
140
+ templateHits.push({ y: top + hit.row, cursor: hit.cursor });
141
+ }
187
142
  }
188
- return { cursor, templateHits };
189
- };
190
-
191
- const stackFrame = (view, width, color, rows, layout, windowed, notes) => {
192
- for (const row of windowed.slice) rows.push(row);
193
- for (const row of notes.noteRows) rows.push(row);
194
- const status = view.status ?? '';
195
- rows.push(paintStatusLine(view, status, width, color));
196
- rows.push(paintFooter(view, layout, width, color));
197
- return overlayRows(rows, view.selection);
143
+ const editHits = [];
144
+ for (const hit of panel.editHits) {
145
+ if (hit.row < rows.length) editHits.push({ ...hit, y: top + hit.row });
146
+ }
147
+ const point = cursor ? { x: cursor.x, y: top + cursor.row } : null;
148
+ return { cursor: point, templateHits, editHits };
198
149
  };
199
150
 
200
151
  const renderFrame = (view, options = {}) => {
201
152
  const width = Math.max(20, options.width ?? 80);
202
153
  const height = Math.max(6, options.height ?? 24);
203
154
  const color = options.color ?? true;
204
- const rows = [paintHeader(view, width, color)];
205
- const extra = extraFooterActions(view);
206
- const hidden = hiddenActions(view);
207
- const dimmed = dimFooterActions(view);
208
- const layout = layoutButtons(width, false, hidden, extra, dimmed);
155
+ const headerLines = 1;
156
+ const layout = footerLayout(view, width);
209
157
  const notes = frameNotes(view, width, height, color);
210
- const headerLines = rows.length;
211
- const bodyH = notes.bodyH;
212
- const paintedBody = paintFrameBody(view, width, color, bodyH, headerLines);
213
- const windowed = windowBody(
214
- view,
215
- width,
216
- color,
217
- bodyH,
218
- headerLines,
219
- paintedBody,
158
+ const { bodyH } = notes;
159
+ const paint = bodyPainter(view);
160
+ const painted = paint(view, width, color, bodyH, headerLines);
161
+ const frame = { width, color, bodyH, headerLines };
162
+ const windowed = windowBody(view, painted, frame);
163
+ const rows = overlayRows(
164
+ [
165
+ paintHeader(view, width, color),
166
+ ...windowed.slice,
167
+ ...notes.rows,
168
+ paintStatusLine(view, view.status ?? '', width, color),
169
+ paintFooter(view, layout, width, color),
170
+ ],
171
+ view.selection,
220
172
  );
221
- const painted = stackFrame(view, width, color, rows, layout, windowed, notes);
222
- const text = painted.map((row) => (color ? row + RESET : row)).join('\n');
223
- const hits = notePointerHits(
224
- notes.notesWanted,
225
- notes.noteRows,
226
- notes.noteCursor,
227
- bodyH,
228
- );
229
- const statusY = painted.length - 1;
173
+ const hits = notePointerHits(notes, bodyH);
174
+ const statusY = rows.length - 1;
230
175
  const branchHit = statusBranchHit(view, width, statusY);
231
176
  const statusHits = branchHit ? [branchHit] : statusChoiceHits(view, statusY);
232
- const { fileHits, todoHits, scroll, scrollMax, listScroll } = windowed;
233
- const { templateHits } = hits;
234
- const footerY = painted.length;
177
+ const editHits = [...windowed.editHits, ...hits.editHits];
178
+ const shown = markEditorSelection(rows, view, editHits, color);
179
+ const text = shown.map((row) => (color ? row + RESET : row)).join('\n');
235
180
  return {
236
181
  text,
237
- rows: painted,
182
+ rows: shown,
238
183
  buttons: footerHits(view, layout),
239
- fileHits,
240
- todoHits,
241
- templateHits,
184
+ fileHits: windowed.fileHits,
185
+ taskHits: windowed.taskHits,
186
+ templateHits: hits.templateHits,
187
+ editHits,
242
188
  statusHits,
243
- footerY,
244
- height: footerY,
245
- scroll,
246
- scrollMax,
247
- listScroll,
189
+ footerY: rows.length,
190
+ height: rows.length,
191
+ scroll: windowed.scroll,
192
+ scrollMax: windowed.scrollMax,
193
+ listScroll: windowed.listScroll,
248
194
  cursor: hits.cursor ?? windowed.cursor,
249
195
  bodyH,
250
196
  };
251
197
  };
252
198
 
253
- module.exports = { BODY_PAINT, renderFrame };
199
+ module.exports = { renderFrame };