reslop 0.2.7 → 0.3.1

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 (158) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +16 -14
  3. package/bin/reslop.js +8 -5
  4. package/lib/agents/agents.js +573 -0
  5. package/lib/agents/allow.js +71 -0
  6. package/lib/agents/allow.json +17 -0
  7. package/lib/agents/models.js +467 -0
  8. package/lib/api.js +15 -12
  9. package/lib/cli.js +43 -68
  10. package/lib/{files.js → common/files.js} +48 -55
  11. package/lib/{find.js → common/find.js} +1 -1
  12. package/lib/common/format.js +101 -0
  13. package/lib/{utilities.js → common/process.js} +7 -95
  14. package/lib/common/tasks.json +27 -0
  15. package/lib/common/utilities.js +112 -0
  16. package/lib/dashboard/git.js +9 -16
  17. package/lib/dashboard/model.js +90 -49
  18. package/lib/dashboard/npm.js +17 -13
  19. package/lib/dashboard/tree.js +27 -30
  20. package/lib/{session → dashboard}/watch.js +36 -31
  21. package/lib/{workspace.js → dashboard/workspace.js} +23 -38
  22. package/lib/{deps.js → deps/deps.js} +31 -45
  23. package/lib/{deps-diff.js → deps/diff.js} +35 -25
  24. package/lib/{manifest.js → deps/manifest.js} +47 -30
  25. package/lib/diff/align.js +1 -2
  26. package/lib/diff/diff.js +2 -2
  27. package/lib/diff/display.js +29 -5
  28. package/lib/diff/inline.js +10 -14
  29. package/lib/diff/items.js +6 -4
  30. package/lib/diff/patch.js +39 -3
  31. package/lib/diff/unit.js +45 -16
  32. package/lib/{git-branches.js → git/branches.js} +53 -48
  33. package/lib/{git-deps.js → git/deps.js} +151 -95
  34. package/lib/git/git.js +71 -0
  35. package/lib/git/worktree.js +504 -0
  36. package/lib/highlight/bash.js +1 -2
  37. package/lib/highlight/csv.js +1 -2
  38. package/lib/highlight/dot.js +1 -2
  39. package/lib/{highlight.js → highlight/highlight.js} +42 -11
  40. package/lib/highlight/html.js +20 -18
  41. package/lib/highlight/json.js +1 -2
  42. package/lib/highlight/log.js +1 -2
  43. package/lib/highlight/txt.js +1 -2
  44. package/lib/{session → input}/actions.js +114 -48
  45. package/lib/{editor.js → input/editor.js} +16 -8
  46. package/lib/{keys.js → input/keys.js} +24 -45
  47. package/lib/input/line-edit.js +78 -0
  48. package/lib/input/menu-pick.js +70 -0
  49. package/lib/render/agents.js +642 -0
  50. package/lib/render/branches.js +4 -5
  51. package/lib/render/commits.js +54 -64
  52. package/lib/render/{dash-activity.js → dashboard/activity.js} +25 -40
  53. package/lib/render/{dash-blocks.js → dashboard/blocks.js} +131 -137
  54. package/lib/render/{dashboard.js → dashboard/dashboard.js} +25 -22
  55. package/lib/render/diff.js +166 -45
  56. package/lib/render/field.js +131 -0
  57. package/lib/render/files.js +9 -14
  58. package/lib/render/footer.js +385 -0
  59. package/lib/render/frame.js +123 -25
  60. package/lib/render/header.js +176 -0
  61. package/lib/render/log.js +320 -0
  62. package/lib/render/menu.js +60 -0
  63. package/lib/render/notes.js +16 -11
  64. package/lib/render/npm.js +322 -196
  65. package/lib/render/packages.js +9 -13
  66. package/lib/render/primitives.js +101 -15
  67. package/lib/render/render.js +7 -11
  68. package/lib/render/repos.js +13 -25
  69. package/lib/render/status.js +370 -0
  70. package/lib/render/status.json +85 -0
  71. package/lib/render/{dash-table.js → table.js} +13 -60
  72. package/lib/render/tiles.js +109 -48
  73. package/lib/render/unit.js +32 -13
  74. package/lib/{report-model.js → report/model.js} +4 -6
  75. package/lib/{report-parse.js → report/parse.js} +9 -14
  76. package/lib/{report-render.js → report/render.js} +48 -19
  77. package/lib/{report-run.js → report/run.js} +42 -30
  78. package/lib/{report-test.js → report/test.js} +56 -55
  79. package/lib/{report-tools.js → report/tools.js} +24 -33
  80. package/lib/review/agent-instructions.md +14 -0
  81. package/lib/review/format.js +420 -0
  82. package/lib/{review-merge.js → review/merge.js} +9 -8
  83. package/lib/review/review.js +190 -0
  84. package/lib/review/store.js +281 -0
  85. package/lib/review/templates.js +83 -0
  86. package/lib/runs/commands.js +14 -0
  87. package/lib/runs/logs.js +299 -0
  88. package/lib/runs/output.js +174 -0
  89. package/lib/runs/process.js +113 -0
  90. package/lib/{runs.js → runs/runs.js} +84 -36
  91. package/lib/runs/scripts.js +150 -0
  92. package/lib/session/agents/agents.js +976 -0
  93. package/lib/session/agents/job.js +320 -0
  94. package/lib/session/agents/jobs.js +355 -0
  95. package/lib/session/agents/options.js +153 -0
  96. package/lib/session/agents/plans.js +83 -0
  97. package/lib/session/branches.js +33 -40
  98. package/lib/session/change-actions.js +34 -100
  99. package/lib/session/commit.js +91 -52
  100. package/lib/session/compose.js +189 -147
  101. package/lib/session/dashboard.js +23 -22
  102. package/lib/session/dispatch.js +86 -21
  103. package/lib/session/files-pane.js +47 -26
  104. package/lib/session/find.js +16 -29
  105. package/lib/session/import.js +93 -0
  106. package/lib/session/items.js +8 -17
  107. package/lib/session/lifecycle.js +78 -72
  108. package/lib/session/load.js +2 -4
  109. package/lib/session/navigation.js +6 -3
  110. package/lib/session/npm/history.js +250 -0
  111. package/lib/session/npm/npm.js +836 -0
  112. package/lib/session/{packages.js → npm/packages.js} +21 -14
  113. package/lib/session/ops.js +125 -34
  114. package/lib/session/pointer.js +202 -10
  115. package/lib/session/prefs.js +112 -0
  116. package/lib/session/reads.js +109 -0
  117. package/lib/session/review.js +28 -10
  118. package/lib/session/run-panel.js +40 -0
  119. package/lib/{session.js → session/session.js} +158 -63
  120. package/lib/session/tasks.js +321 -66
  121. package/lib/session/templates.js +1 -1
  122. package/lib/session/terminal.js +94 -12
  123. package/lib/session/unit.js +35 -30
  124. package/lib/session/update.js +194 -6
  125. package/lib/session/view.js +65 -65
  126. package/lib/session/workspace.js +39 -78
  127. package/lib/{capabilities.js → source/capabilities.js} +3 -12
  128. package/lib/{github.js → source/github.js} +102 -32
  129. package/lib/{gitlab.js → source/gitlab.js} +87 -10
  130. package/lib/{remote.js → source/remote.js} +144 -40
  131. package/lib/source/review-threads.graphql +20 -0
  132. package/lib/source/source.js +74 -0
  133. package/lib/term/ansi.js +396 -0
  134. package/lib/{clipboard.js → term/clipboard.js} +3 -6
  135. package/lib/{select.js → term/select.js} +4 -3
  136. package/lib/term/themes/dark.json +122 -0
  137. package/lib/term/themes/light.json +122 -0
  138. package/lib/term/wide.txt +21 -0
  139. package/lib/{wrap.js → term/wrap.js} +4 -4
  140. package/package.json +2 -2
  141. package/lib/ansi.js +0 -545
  142. package/lib/detect.js +0 -34
  143. package/lib/diff/edit.js +0 -33
  144. package/lib/git-worktree.js +0 -455
  145. package/lib/git.js +0 -79
  146. package/lib/npm-commands.js +0 -522
  147. package/lib/render/chrome.js +0 -806
  148. package/lib/render/present.js +0 -19
  149. package/lib/render/report.js +0 -43
  150. package/lib/render/search.js +0 -25
  151. package/lib/review.js +0 -745
  152. package/lib/session/accessors.js +0 -26
  153. package/lib/session/edit-click.js +0 -57
  154. package/lib/session/npm.js +0 -452
  155. package/lib/session/progress.js +0 -41
  156. package/lib/source.js +0 -36
  157. package/lib/update.js +0 -192
  158. /package/lib/{git-reword-todo.js → git/reword-todo.js} +0 -0
@@ -0,0 +1,385 @@
1
+ 'use strict';
2
+
3
+ const ansi = require('../term/ansi.js');
4
+ const actions = require('../input/actions.js');
5
+ const files = require('../common/files.js');
6
+ const primitives = require('./primitives.js');
7
+ const { fill, isTaskView } = primitives;
8
+ const { isTasksEntry } = files;
9
+ const { ACTION_IDS, actionLetter } = actions;
10
+ const { DASH_IDS, buttonWord } = actions;
11
+ const { FILES_GIT_DISABLED } = actions;
12
+ const { diffGitDisabled, branchGitDisabled, commitGitDisabled } = actions;
13
+ const { entryHasDiff, lineHasDiff, withoutMoves } = actions;
14
+ const { THEME, paint, visibleWidth } = ansi;
15
+ const { selectedFile, selectedBranch, selectedCommit } = require('./header.js');
16
+
17
+ const BUTTON_LEAD = ' ';
18
+
19
+ const BUTTON_GAP = ' ';
20
+
21
+ const HINT_ONLY = ['taskNudge'];
22
+
23
+ const BUTTON_EDGE = 1;
24
+
25
+ const BACK_ARROW = '🢐';
26
+
27
+ const BACK_KEY = 'esc';
28
+
29
+ const BUTTON_MARK = {
30
+ bgRgb: THEME.buttonBg,
31
+ fgRgb: THEME.buttonFg,
32
+ hotFg: THEME.buttonHotFg,
33
+ missFg: THEME.buttonHotFg,
34
+ missBold: true,
35
+ };
36
+
37
+ const QUIT_MARK = {
38
+ bgRgb: THEME.chromeBg,
39
+ fgRgb: THEME.mutedFg,
40
+ hotFg: THEME.warnFg,
41
+ missFg: THEME.mutedFg,
42
+ missBold: false,
43
+ };
44
+
45
+ const SAVE_HINT = 'Press **Ctrl-S** or **Enter** at EOF to save';
46
+
47
+ const HINT_HOT_FG = 0xff;
48
+
49
+ const buttonGap = (index) => (index ? BUTTON_GAP : BUTTON_LEAD);
50
+
51
+ const hintBody = (mark) => `${mark}${BACK_KEY}`;
52
+
53
+ const buttonLabel = (id, short) => (short ? actionLetter(id) : buttonWord(id));
54
+
55
+ const paintBackHint = (color, mark) => {
56
+ const bgRgb = THEME.buttonBg;
57
+ const rest = hintBody(mark).slice(mark.length);
58
+ const arrow = paint(mark, THEME.buttonHotFg, bgRgb, color, true);
59
+ return arrow + paint(rest, THEME.buttonFg, bgRgb, color);
60
+ };
61
+
62
+ const paintMarkedWord = (label, letter, color, mark) => {
63
+ const at = label.indexOf(letter);
64
+ if (at < 0) {
65
+ return paint(label, mark.missFg, mark.bgRgb, color, mark.missBold);
66
+ }
67
+ const before = label.slice(0, at);
68
+ const hot = label.slice(at, at + letter.length);
69
+ const after = label.slice(at + letter.length);
70
+ let out = paint(before, mark.fgRgb, mark.bgRgb, color);
71
+ out += paint(hot, mark.hotFg, mark.bgRgb, color, true);
72
+ return out + paint(after, mark.fgRgb, mark.bgRgb, color);
73
+ };
74
+
75
+ const footerIds = (hidden, extra, backId, globals, tail, after) => {
76
+ const ids = backId ? [backId] : [];
77
+ const push = (id) => {
78
+ if (!ids.includes(id)) ids.push(id);
79
+ };
80
+ for (const add of extra) push(add);
81
+ for (const id of globals) push(id);
82
+ if (tail) {
83
+ for (const id of ACTION_IDS) {
84
+ if (id === 'quit' || hidden.includes(id) || ids.includes(id)) continue;
85
+ ids.push(id);
86
+ }
87
+ }
88
+ for (const id of after) push(id);
89
+ return ids;
90
+ };
91
+
92
+ const buttonPiece = (id, index, short, backId, mark) => {
93
+ const back = id === backId;
94
+ const letter = back ? BACK_KEY : actionLetter(id);
95
+ const label = back ? hintBody(mark) : buttonLabel(id, short);
96
+ const gap = buttonGap(index);
97
+ return {
98
+ id,
99
+ piece: `${gap}${label}`,
100
+ gap,
101
+ letter,
102
+ label,
103
+ back,
104
+ mark: back ? mark : '',
105
+ };
106
+ };
107
+
108
+ const packGroup = (ids, short, start, backId, mark) => {
109
+ const parts = [];
110
+ for (let i = 0; i < ids.length; i++) {
111
+ parts.push(buttonPiece(ids[i], start + i, short, backId, mark));
112
+ }
113
+ return parts;
114
+ };
115
+
116
+ const groupWidth = (parts) => {
117
+ let width = 0;
118
+ for (const part of parts) width += visibleWidth(part.piece);
119
+ return width;
120
+ };
121
+
122
+ const placeGroup = (parts, hits, disabled, x) => {
123
+ const placed = [];
124
+ let cursor = x;
125
+ for (const part of parts) {
126
+ const w = visibleWidth(part.piece);
127
+ const off = disabled.includes(part.id);
128
+ const hint = HINT_ONLY.includes(part.id);
129
+ if (!off && !hint) hits.push({ id: part.id, x0: cursor, x1: cursor + w });
130
+ placed.push({ ...part, disabled: off });
131
+ cursor += w;
132
+ }
133
+ return { placed, cursor };
134
+ };
135
+
136
+ const splitFooter = (ids, options, limit) => {
137
+ const { disabled = [], backId = '', mark = BACK_ARROW } = options;
138
+ const { globals = [], after = [] } = options;
139
+ const rightIds = [];
140
+ for (const id of [...globals, ...after]) {
141
+ if (ids.includes(id) && !rightIds.includes(id)) rightIds.push(id);
142
+ }
143
+ const leftIds = ids.filter((id) => !rightIds.includes(id));
144
+ const pack = (leftShort, rightShort) => {
145
+ const left = packGroup(leftIds, leftShort, 0, backId, mark);
146
+ const start = left.length ? 1 : 0;
147
+ const right = packGroup(rightIds, rightShort, start, backId, mark);
148
+ return { left, right };
149
+ };
150
+ const wide = (packed) =>
151
+ groupWidth(packed.left) + groupWidth(packed.right) > limit;
152
+ let packed = pack(false, false);
153
+ if (wide(packed)) packed = pack(false, true);
154
+ if (wide(packed)) packed = pack(true, true);
155
+ const left = packed.left.slice();
156
+ const right = packed.right.slice();
157
+ while (groupWidth(left) + groupWidth(right) > limit && left.length) {
158
+ left.pop();
159
+ }
160
+ while (groupWidth(left) + groupWidth(right) > limit && right.length) {
161
+ right.pop();
162
+ }
163
+ const hits = [];
164
+ const leftPlace = placeGroup(left, hits, disabled, 0);
165
+ const parts = leftPlace.placed;
166
+ let x = leftPlace.cursor;
167
+ const gap = Math.max(0, limit - x - groupWidth(right));
168
+ if (gap && right.length) {
169
+ parts.push({ spacer: true, piece: ' '.repeat(gap) });
170
+ x += gap;
171
+ }
172
+ const rightPlace = placeGroup(right, hits, disabled, x);
173
+ parts.push(...rightPlace.placed);
174
+ return { hits, parts };
175
+ };
176
+
177
+ const layoutButtons = (width, options = {}) => {
178
+ const { compact = false, hidden = [], extra = [], disabled = [] } = options;
179
+ const { backId = '', mark = BACK_ARROW, globals = [] } = options;
180
+ const { tail = true, after = [] } = options;
181
+ const ids = footerIds(hidden, extra, backId, globals, tail, after);
182
+ const limit = Math.max(0, width - BUTTON_EDGE);
183
+ if (globals.length) return splitFooter(ids, options, limit);
184
+ const build = (short) => {
185
+ const hits = [];
186
+ const parts = [];
187
+ let x = 0;
188
+ for (const id of ids) {
189
+ const part = buttonPiece(id, parts.length, short, backId, mark);
190
+ const w = visibleWidth(part.piece);
191
+ if (x + w > limit && parts.length) break;
192
+ const off = disabled.includes(id);
193
+ const hint = HINT_ONLY.includes(id);
194
+ if (!off && !hint) hits.push({ id, x0: x, x1: x + w });
195
+ parts.push({ ...part, disabled: off });
196
+ x += w;
197
+ }
198
+ return { hits, parts };
199
+ };
200
+ if (compact) return build(true);
201
+ const words = build(false);
202
+ if (words.parts.length === ids.length) return words;
203
+ return build(true);
204
+ };
205
+
206
+ const paintButtons = (layout, width, color) => {
207
+ const fgRgb = THEME.buttonFg;
208
+ const bgRgb = THEME.buttonBg;
209
+ let out = '';
210
+ let used = 0;
211
+ for (let i = 0; i < layout.parts.length; i++) {
212
+ const part = layout.parts[i];
213
+ if (part.spacer) {
214
+ out += paint(part.piece, fgRgb, bgRgb, color);
215
+ used += visibleWidth(part.piece);
216
+ continue;
217
+ }
218
+ const gap = part.gap ?? buttonGap(i);
219
+ out += paint(gap, fgRgb, bgRgb, color);
220
+ if (part.back) out += paintBackHint(color, part.mark);
221
+ else if (part.disabled) out += paint(part.label, fgRgb, bgRgb, color);
222
+ else out += paintMarkedWord(part.label, part.letter, color, BUTTON_MARK);
223
+ used += visibleWidth(part.piece);
224
+ }
225
+ return out + fill(width - used, fgRgb, bgRgb, color);
226
+ };
227
+
228
+ const commitSaveHint = (view) => {
229
+ const composing = view.mode === 'compose';
230
+ const full = view.commitView === 'full';
231
+ const commit = view.compose?.kind === 'commit';
232
+ return composing && full && commit;
233
+ };
234
+
235
+ const paintSaveHintBar = (width, color) => {
236
+ const bgRgb = THEME.buttonBg;
237
+ const plain = `${BUTTON_LEAD}${SAVE_HINT.replaceAll('**', '')}`;
238
+ const pad = Math.max(0, width - visibleWidth(plain));
239
+ if (!color) return plain + ' '.repeat(pad);
240
+ let out = paint(BUTTON_LEAD, THEME.buttonFg, bgRgb, color);
241
+ const bits = SAVE_HINT.split('**');
242
+ for (let i = 0; i < bits.length; i++) {
243
+ const hot = i % 2 === 1;
244
+ const tone = hot ? HINT_HOT_FG : THEME.buttonFg;
245
+ out += paint(bits[i], tone, bgRgb, color, hot);
246
+ }
247
+ return out + fill(pad, THEME.buttonFg, bgRgb, color);
248
+ };
249
+
250
+ const paintFooter = (view, layout, width, color) => {
251
+ if (commitSaveHint(view)) return paintSaveHintBar(width, color);
252
+ return paintButtons(layout, width, color);
253
+ };
254
+
255
+ const footerHits = (view, layout) => (commitSaveHint(view) ? [] : layout.hits);
256
+
257
+ const filesDim = (view) => {
258
+ const entry = selectedFile(view);
259
+ if (isTasksEntry(entry)) return FILES_GIT_DISABLED;
260
+ return withoutMoves([], entryHasDiff(entry));
261
+ };
262
+
263
+ const unitDim = (view) => {
264
+ const off = [...diffGitDisabled(view.item)];
265
+ const lines = view.unitLines;
266
+ if (!lines || !lines.length) return off;
267
+ const line = lines[view.unitLine ?? 0];
268
+ return withoutMoves(off, lineHasDiff(line));
269
+ };
270
+
271
+ const commitsDim = (view) => {
272
+ const canCommit = (view.counts?.staged ?? 0) > 0;
273
+ const unstagedCurrent = !view.rev && !view.revShort;
274
+ const target = { ...selectedCommit(view), canCommit, unstagedCurrent };
275
+ return commitGitDisabled(target);
276
+ };
277
+
278
+ const PACKAGE_ROW_ACTIONS = ['packageDrop', 'packageWanted', 'packageLatest'];
279
+
280
+ const packagesDim = (view) => {
281
+ const listed = view.packages && view.packages.modules;
282
+ const rows = listed && listed.packages ? listed.packages : [];
283
+ const cursor = view.packagesCursor ?? 0;
284
+ const onRow = rows.length > 0 && cursor < rows.length;
285
+ if (!onRow) return PACKAGE_ROW_ACTIONS;
286
+ const row = rows[cursor];
287
+ return row && row.transitive ? ['packageNew', 'packageDrop'] : [];
288
+ };
289
+
290
+ const agentListDim = (view) => {
291
+ const list = view.agents ?? [];
292
+ const row = list[view.agentCursor ?? 0];
293
+ const installed = Boolean(row && row.bin);
294
+ const levels = (row && row.efforts) || [];
295
+ const sizes = (row && row.contexts) || [];
296
+ const off = [];
297
+ if (!installed || levels.length < 2) off.push('agentEffort');
298
+ if (!installed) off.push('agentFast');
299
+ if (!installed || !sizes.length) off.push('agentContext');
300
+ if ((view.agentPlanCount ?? 0) < 2) off.push('agentReview');
301
+ if (!view.agentCanContinue) off.push('agentContinue');
302
+ return off;
303
+ };
304
+
305
+ const agentsDim = (view) => {
306
+ if (!view.agentView) return agentListDim(view);
307
+ return view.agentRunning ? ['agentRerun'] : ['agentStop'];
308
+ };
309
+
310
+ const npmDim = (view) => {
311
+ if (view.npmView) return view.npmRunning ? [] : ['npmStop'];
312
+ return view.npmLogs ? [] : ['npmLogs'];
313
+ };
314
+
315
+ const FOOTER_DIM = {
316
+ files: filesDim,
317
+ unit: unitDim,
318
+ diff: (view) => diffGitDisabled(view.item),
319
+ branches: (view) => branchGitDisabled(selectedBranch(view)),
320
+ commits: commitsDim,
321
+ packages: packagesDim,
322
+ agents: agentsDim,
323
+ npm: npmDim,
324
+ };
325
+
326
+ const dimFooterActions = (view) => {
327
+ const pick = FOOTER_DIM[view.pane ?? 'diff'];
328
+ return pick ? pick(view) : [];
329
+ };
330
+
331
+ const GIT_FOOTER = ['add', 'unstage', 'revert', 'ignore'];
332
+
333
+ const FOOTER_EXTRA = {
334
+ repos: ['theme'],
335
+ dashboard: ['theme', 'pull', 'push'],
336
+ unit: ['add', 'unstage', 'revert', 'feedback', 'code', 'lines'],
337
+ files: [...GIT_FOOTER, 'code'],
338
+ diff: [...GIT_FOOTER, 'feedback', 'code', 'layout', 'lines'],
339
+ branches: ['newBranch', 'rebase', 'drop', 'pull', 'push'],
340
+ commits: ['newCommit', 'apply', 'reword', 'drop', 'view', 'pull', 'push'],
341
+ npmLog: ['npmVerbose', 'npmStop', 'npmRerun'],
342
+ npm: ['npmEdit', 'npmNew', 'npmDrop', 'npmLogs'],
343
+ packages: ['packageNew', 'packageDrop', 'packageWanted', 'packageLatest'],
344
+ agentLog: ['agentStop', 'agentRerun'],
345
+ agents: [
346
+ 'agentModel',
347
+ 'agentEffort',
348
+ 'agentFast',
349
+ 'agentContext',
350
+ 'agentReview',
351
+ 'agentContinue',
352
+ ],
353
+ tasks: ['plan', 'import', 'drop', 'check', 'taskNudge'],
354
+ };
355
+
356
+ const extraFooterActions = (view) => {
357
+ const { pane } = view;
358
+ if (pane === 'npm' && view.npmView) return FOOTER_EXTRA.npmLog;
359
+ if (pane === 'npm') return FOOTER_EXTRA.npm;
360
+ if (pane === 'agents' && view.agentView) return FOOTER_EXTRA.agentLog;
361
+ if (pane === 'agents') return FOOTER_EXTRA.agents;
362
+ const listed = FOOTER_EXTRA[pane];
363
+ if (listed) return listed;
364
+ if (isTaskView(view)) return FOOTER_EXTRA.tasks;
365
+ return GIT_FOOTER;
366
+ };
367
+
368
+ const footerLayout = (view, width) => {
369
+ const extra = extraFooterActions(view);
370
+ const disabled = dimFooterActions(view);
371
+ const backId = 'back';
372
+ const globals = view.pane === 'repos' ? [] : DASH_IDS;
373
+ const tail = false;
374
+ const options = { extra, disabled, backId, globals, tail };
375
+ return layoutButtons(width, options);
376
+ };
377
+
378
+ module.exports = {
379
+ QUIT_MARK,
380
+ paintMarkedWord,
381
+ layoutButtons,
382
+ paintFooter,
383
+ footerHits,
384
+ footerLayout,
385
+ };
@@ -1,27 +1,28 @@
1
1
  'use strict';
2
2
 
3
- const ansi = require('../ansi.js');
4
- const select = require('../select.js');
3
+ const { clamp } = require('../common/utilities.js');
4
+ const { ESC, RESET, stampVisible } = require('../term/ansi.js');
5
+ const { overlayRows, markEditorSelection } = require('../term/select.js');
5
6
  const primitives = require('./primitives.js');
6
- const chrome = require('./chrome.js');
7
+ const { paintHeader } = require('./header.js');
8
+ const { paintFooter, footerHits, footerLayout } = require('./footer.js');
9
+ const status = require('./status.js');
7
10
  const files = require('./files.js');
8
11
  const branches = require('./branches.js');
9
12
  const commits = require('./commits.js');
10
- const notes = require('./notes.js');
13
+ const { paintNotePanel, paintBodyTodo } = require('./notes.js');
11
14
  const npm = require('./npm.js');
12
- const diff = require('./diff.js');
15
+ const { paintBodyDiff, viewDigits, paintNumberFill } = require('./diff.js');
13
16
  const unit = require('./unit.js');
14
- const dashboard = require('./dashboard.js');
17
+ const dashboard = require('./dashboard/dashboard.js');
15
18
  const packages = require('./packages.js');
19
+ const agents = require('./agents.js');
16
20
  const repos = require('./repos.js');
17
21
 
18
- const { RESET } = ansi;
19
- const { overlayRows, markEditorSelection } = select;
20
22
  const { isListPane, isTaskView, paintBodyFill, paneResult } = primitives;
21
- const { paintHeader, paintFooter, footerHits, paintStatusLine } = chrome;
22
- const { footerLayout, statusBranchHit, statusChoiceHits } = chrome;
23
- const { paintNotePanel, paintBodyTodo } = notes;
24
- const { paintBodyDiff } = diff;
23
+ const { bodyRow } = primitives;
24
+ const { paintStatusLine, statusBranchHit, statusChoiceHits } = status;
25
+ const { statusPlanHit, planMenu } = status;
25
26
 
26
27
  const PANE_BODY = {
27
28
  dashboard: dashboard.paintBodyDashboard,
@@ -31,6 +32,7 @@ const PANE_BODY = {
31
32
  commits: commits.paintBodyCommits,
32
33
  npm: npm.paintBodyNpm,
33
34
  packages: packages.paintBodyPackages,
35
+ agents: agents.paintBodyAgents,
34
36
  unit: unit.paintBodyUnit,
35
37
  tasks: paintBodyTodo,
36
38
  };
@@ -53,16 +55,35 @@ const frameNotes = (view, width, height, color) => {
53
55
  return { panel, rows, cursor, bodyH };
54
56
  };
55
57
 
58
+ const headsOnly = (owners, first) => {
59
+ for (let i = 0; i < first; i++) {
60
+ if (owners[i] !== null && owners[i] !== undefined) return false;
61
+ }
62
+ return true;
63
+ };
64
+
65
+ const tailBlank = (owners, last) => {
66
+ for (let i = last + 1; i < owners.length; i++) {
67
+ if (owners[i] !== null && owners[i] !== undefined) return last;
68
+ }
69
+ return Math.max(last, owners.length - 1);
70
+ };
71
+
56
72
  const revealScroll = (owners, focus, height, prefer) => {
57
73
  const first = owners.indexOf(focus);
58
74
  if (first < 0) return prefer;
59
75
  const last = owners.lastIndexOf(focus);
60
76
  const room = Math.max(1, height);
61
- let start = Math.min(prefer, first);
62
- if (last >= start + room) {
63
- start = last - first + 1 <= room ? last - room + 1 : first;
77
+ const end = tailBlank(owners, last);
78
+ const top = headsOnly(owners, first) ? 0 : first;
79
+ let start = Math.min(prefer, top);
80
+ if (end >= start + room) {
81
+ const fits = end - top + 1 <= room;
82
+ if (fits || end > last) start = end - room + 1;
83
+ else start = first;
64
84
  }
65
- return Math.max(0, Math.min(start, owners.length - room));
85
+ const max = Math.max(0, owners.length - room);
86
+ return clamp(start, 0, max);
66
87
  };
67
88
 
68
89
  const windowY = (win, row) => {
@@ -89,28 +110,64 @@ const mapBodyCursor = (cursor, win) => {
89
110
  return point;
90
111
  };
91
112
 
113
+ const numberDigits = (view) => {
114
+ if (!view || view.lineNumbers !== true) return 0;
115
+ if (view.pane !== 'diff' && view.pane !== 'unit') return 0;
116
+ return viewDigits(view);
117
+ };
118
+
92
119
  const bodyFillKind = (listPane, splitBody) => {
93
120
  if (listPane) return 'files';
94
121
  return splitBody ? 'split' : 'plain';
95
122
  };
96
123
 
124
+ const mapScrollBar = (bar, win) => {
125
+ if (!bar) return null;
126
+ const row = bar.row + win.shift - win.scroll;
127
+ if (row < 0 || row >= win.shown) return null;
128
+ const height = Math.min(bar.height, win.shown - row);
129
+ return {
130
+ x: bar.col,
131
+ y: win.headerLines + row + 1,
132
+ height,
133
+ track: bar.track,
134
+ rows: bar.rows,
135
+ count: bar.count,
136
+ start: bar.start,
137
+ };
138
+ };
139
+
97
140
  const windowBody = (view, painted, frame) => {
98
141
  const { width, color, bodyH, headerLines } = frame;
99
142
  const listPane = isListPane(view.pane);
100
143
  const fillKind = bodyFillKind(listPane, painted.splitBody);
101
- const filler = paintBodyFill(width, color, fillKind);
102
- const shift = !listPane && painted.body.length > 0 ? 1 : 0;
144
+ const digits = numberDigits(view);
145
+ const filler = digits
146
+ ? paintNumberFill(width, color, digits, painted.splitBody)
147
+ : paintBodyFill(width, color, fillKind);
148
+ const count = painted.body.length;
149
+ const shift = !listPane && count > 0 ? 1 : 0;
103
150
  const lead = (items, first) =>
104
151
  shift && items.length ? [first, ...items] : items;
105
- const body = lead(painted.body, filler);
106
- const taskOwners = lead(painted.taskOwners, null);
107
- const taskChecks = lead(painted.taskChecks, false);
108
- const scrollMax = Math.max(0, body.length - bodyH);
152
+ let total = count + shift;
153
+ let taskOwners = lead(painted.taskOwners, null);
154
+ let taskChecks = lead(painted.taskChecks, false);
155
+ if (isTaskView(view) && total > bodyH) {
156
+ total += 1;
157
+ taskOwners = taskOwners.concat(null);
158
+ taskChecks = taskChecks.concat(false);
159
+ }
160
+ const scrollMax = Math.max(0, total - bodyH);
109
161
  let scroll = listPane ? 0 : Math.min(view.scroll ?? 0, scrollMax);
110
162
  if (isTaskView(view)) {
111
163
  scroll = revealScroll(taskOwners, view.tasksFocus ?? 0, bodyH, scroll);
112
164
  }
113
- const slice = body.slice(scroll, scroll + bodyH);
165
+ const slice = [];
166
+ for (let at = scroll; at < Math.min(total, scroll + bodyH); at++) {
167
+ const index = at - shift;
168
+ const inBody = index >= 0 && index < count;
169
+ slice.push(inBody ? bodyRow(painted.body, index) : filler);
170
+ }
114
171
  const win = { shift, scroll, shown: slice.length, headerLines };
115
172
  const taskHits = [];
116
173
  for (let i = 0; i < slice.length; i++) {
@@ -132,6 +189,7 @@ const windowBody = (view, painted, frame) => {
132
189
  fileHits: painted.fileHits,
133
190
  editHits: mapEditHits(painted.editHits, win),
134
191
  cursor: mapBodyCursor(painted.cursor, win),
192
+ scrollBar: mapScrollBar(painted.scrollBar, win),
135
193
  };
136
194
  };
137
195
 
@@ -177,7 +235,21 @@ const renderFrame = (view, options = {}) => {
177
235
  const hits = notePointerHits(notes, bodyH);
178
236
  const statusY = rows.length - 1;
179
237
  const branchHit = statusBranchHit(view, width, statusY);
180
- const statusHits = branchHit ? [branchHit] : statusChoiceHits(view, statusY);
238
+ const planHit = statusPlanHit(view, width, statusY);
239
+ const choices = statusChoiceHits(view, statusY);
240
+ const statusHits = [];
241
+ if (branchHit) statusHits.push(branchHit);
242
+ if (planHit) statusHits.push(planHit);
243
+ if (!statusHits.length) statusHits.push(...choices);
244
+ const menu = planMenu(view, width, color, statusY);
245
+ if (menu) {
246
+ for (const line of menu.lines) {
247
+ const index = line.y - 1;
248
+ if (index <= 0 || index >= rows.length) continue;
249
+ rows[index] = stampVisible(rows[index], menu.x, line.text);
250
+ }
251
+ statusHits.push(...menu.hits);
252
+ }
181
253
  const editHits = [...windowed.editHits, ...hits.editHits];
182
254
  const shown = markEditorSelection(rows, view, editHits, color);
183
255
  const text = shown.map((row) => (color ? row + RESET : row)).join('\n');
@@ -195,9 +267,35 @@ const renderFrame = (view, options = {}) => {
195
267
  scroll: windowed.scroll,
196
268
  scrollMax: windowed.scrollMax,
197
269
  listScroll: windowed.listScroll,
270
+ menuScroll: menu ? menu.scroll : painted.menuScroll,
271
+ runScroll: painted.runScroll,
272
+ gridCols: painted.gridCols,
198
273
  cursor: hits.cursor ?? windowed.cursor,
274
+ scrollBar: windowed.scrollBar,
199
275
  bodyH,
200
276
  };
201
277
  };
202
278
 
203
- module.exports = { renderFrame };
279
+ const presentCursor = (cursor) => {
280
+ if (!cursor || cursor.x <= 0 || cursor.y <= 0) return `${ESC}[?25l`;
281
+ const pos = `${ESC}[${cursor.y};${cursor.x}H`;
282
+ return `${pos}${ESC}[1 q${ESC}[?12h${ESC}[?25h`;
283
+ };
284
+
285
+ const presentRows = (rows, options = {}) => {
286
+ let out = `${ESC}[?25l${ESC}[?2026h`;
287
+ if (options.clear === true) out += `${ESC}[H${ESC}[2J`;
288
+ const prev = options.prev;
289
+ const same = prev && prev.length === rows.length ? prev : null;
290
+ for (let i = 0; i < rows.length; i++) {
291
+ if (same && same[i] === rows[i]) continue;
292
+ out += `${ESC}[${i + 1};1H${rows[i]}`;
293
+ }
294
+ return `${out}${ESC}[?2026l${presentCursor(options.cursor)}`;
295
+ };
296
+
297
+ module.exports = {
298
+ renderFrame,
299
+ presentCursor,
300
+ presentRows,
301
+ };