reslop 0.3.0 → 0.3.2
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.
- package/CHANGELOG.md +29 -1
- package/README.md +21 -3
- package/lib/agents/acp.js +309 -0
- package/lib/agents/agents.js +166 -370
- package/lib/agents/allow.js +141 -52
- package/lib/agents/allow.json +16 -13
- package/lib/agents/models.js +250 -376
- package/lib/agents/registry.js +437 -0
- package/lib/agents/run.js +234 -0
- package/lib/agents/transcript.js +143 -0
- package/lib/cli.js +2 -1
- package/lib/common/process.js +26 -1
- package/lib/dashboard/git.js +11 -1
- package/lib/dashboard/reports.js +80 -0
- package/lib/dashboard/watch.js +43 -8
- package/lib/dashboard/workspace.js +19 -20
- package/lib/dashboard/worktrees.js +145 -0
- package/lib/git/deps.js +29 -0
- package/lib/git/dir.js +50 -0
- package/lib/git/git.js +1 -1
- package/lib/git/linked.js +152 -0
- package/lib/git/local.js +179 -0
- package/lib/git/status.js +126 -0
- package/lib/git/worktree.js +137 -31
- package/lib/input/actions.js +83 -2
- package/lib/input/editor.js +9 -0
- package/lib/input/keys.js +39 -3
- package/lib/input/line-edit.js +8 -3
- package/lib/render/agents.js +64 -36
- package/lib/render/branches.js +55 -27
- package/lib/render/field.js +19 -5
- package/lib/render/footer.js +14 -4
- package/lib/render/frame.js +16 -2
- package/lib/render/log.js +5 -4
- package/lib/render/menu.js +103 -6
- package/lib/render/name-edit.js +31 -0
- package/lib/render/repos.js +52 -8
- package/lib/render/status.js +22 -4
- package/lib/report/render.js +4 -7
- package/lib/report/run.js +16 -23
- package/lib/review/agent-instructions.md +13 -8
- package/lib/review/format.js +0 -6
- package/lib/runs/process.js +3 -3
- package/lib/runs/runs.js +27 -4
- package/lib/session/agents/agents.js +227 -194
- package/lib/session/agents/away.js +48 -0
- package/lib/session/agents/job.js +23 -86
- package/lib/session/agents/jobs.js +14 -27
- package/lib/session/agents/options.js +45 -101
- package/lib/session/agents/plans.js +53 -0
- package/lib/session/agents/view.js +34 -0
- package/lib/session/branches.js +18 -2
- package/lib/session/compose.js +20 -6
- package/lib/session/dispatch.js +43 -3
- package/lib/session/editor-pointer.js +98 -0
- package/lib/session/files-pane.js +3 -0
- package/lib/session/import.js +12 -1
- package/lib/session/lifecycle.js +70 -10
- package/lib/session/navigation.js +5 -0
- package/lib/session/npm/history.js +3 -1
- package/lib/session/npm/npm.js +111 -13
- package/lib/session/ops.js +3 -0
- package/lib/session/pointer.js +304 -46
- package/lib/session/session.js +17 -2
- package/lib/session/tasks.js +1 -1
- package/lib/session/terminal.js +2 -2
- package/lib/session/text-menu.js +50 -0
- package/lib/session/view.js +19 -2
- package/lib/session/workspace.js +179 -38
- package/lib/session/worktrees.js +280 -0
- package/lib/term/select.js +2 -1
- package/lib/term/themes/dark.json +4 -0
- package/lib/term/themes/light.json +4 -0
- package/package.json +2 -2
package/lib/render/agents.js
CHANGED
|
@@ -17,7 +17,7 @@ const { flexCell, tableLines, titleAside } = table;
|
|
|
17
17
|
const logPaint = require('./log.js');
|
|
18
18
|
const { LOG_PAD_X } = logPaint;
|
|
19
19
|
const { paintNpmLog, withLogScroller } = logPaint;
|
|
20
|
-
const { paintField } = require('./field.js');
|
|
20
|
+
const { paintField, queryTones, fieldHit } = require('./field.js');
|
|
21
21
|
|
|
22
22
|
const BUBBLE = '●';
|
|
23
23
|
const BUBBLE_GAP = 1;
|
|
@@ -45,14 +45,8 @@ const COL_TITLES = {
|
|
|
45
45
|
|
|
46
46
|
const agentFields = (row) => {
|
|
47
47
|
if (!row.bin) {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
model: row.model || '',
|
|
51
|
-
effort: '',
|
|
52
|
-
fast: '',
|
|
53
|
-
context: '',
|
|
54
|
-
plan: 'not installed',
|
|
55
|
-
};
|
|
48
|
+
const none = { model: '', effort: '', fast: '', context: '', plan: '' };
|
|
49
|
+
return { name: row.name, ...none };
|
|
56
50
|
}
|
|
57
51
|
const levels = row.efforts ?? [];
|
|
58
52
|
return {
|
|
@@ -166,7 +160,28 @@ const cellFace = (text, width, query, end = false) => {
|
|
|
166
160
|
return end && !query ? `${pad}${shown}` : `${shown}${pad}`;
|
|
167
161
|
};
|
|
168
162
|
|
|
163
|
+
// A missing agent has no settings: the row says what would bring it
|
|
164
|
+
const paintHintRow = (row, width, color, selected, cols) => {
|
|
165
|
+
const prefix = ' '.repeat(PAD_W);
|
|
166
|
+
const tail = ' '.repeat(TAIL);
|
|
167
|
+
const gap = cols.gap || ' ';
|
|
168
|
+
const name = cellFace(row.name, cols.nameW, '');
|
|
169
|
+
const taken = PAD_W + TAIL + visibleWidth(name) + visibleWidth(gap);
|
|
170
|
+
const hint = cellFace(row.hint || 'not installed', width - taken, '');
|
|
171
|
+
const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
|
|
172
|
+
const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
|
|
173
|
+
const text = `${prefix}${name}${gap}${hint}${tail}`;
|
|
174
|
+
if (!color) return { row: text, spans: [] };
|
|
175
|
+
let out = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
176
|
+
out += paint(`${prefix}${name}`, THEME.mutedFg, bgRgb, color);
|
|
177
|
+
out += paint(gap, fgRgb, bgRgb, color);
|
|
178
|
+
out += paint(hint, THEME.warnFg, bgRgb, color);
|
|
179
|
+
out += paint(tail, fgRgb, bgRgb, color);
|
|
180
|
+
return { row: out, spans: [] };
|
|
181
|
+
};
|
|
182
|
+
|
|
169
183
|
const paintAgentRow = (row, width, color, selected, cols, search) => {
|
|
184
|
+
if (!row.bin) return paintHintRow(row, width, color, selected, cols);
|
|
170
185
|
const fields = agentFields(row);
|
|
171
186
|
const prefix = ' '.repeat(PAD_W);
|
|
172
187
|
const rest = Math.max(1, width - PAD_W - TAIL);
|
|
@@ -183,6 +198,17 @@ const paintAgentRow = (row, width, color, selected, cols, search) => {
|
|
|
183
198
|
),
|
|
184
199
|
plan: cellFace(fields.plan, cols.planW, typedQuery(search, 'plan'), true),
|
|
185
200
|
};
|
|
201
|
+
if (!color && search?.editor) {
|
|
202
|
+
const size = colW(cols, search.field);
|
|
203
|
+
search.editor.reveal(size);
|
|
204
|
+
cells[search.field] = paintField(
|
|
205
|
+
search.query,
|
|
206
|
+
search.editor,
|
|
207
|
+
size,
|
|
208
|
+
false,
|
|
209
|
+
queryTones(THEME.warnFg, THEME.buttonBg),
|
|
210
|
+
);
|
|
211
|
+
}
|
|
186
212
|
let mid = name;
|
|
187
213
|
for (const field of VALUE_FIELDS) {
|
|
188
214
|
if (colW(cols, field)) mid += `${gap}${cells[field]}`;
|
|
@@ -193,11 +219,9 @@ const paintAgentRow = (row, width, color, selected, cols, search) => {
|
|
|
193
219
|
const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
|
|
194
220
|
const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
|
|
195
221
|
if (!color) return { row: `${prefix}${mid}${pad}${tail}`, spans };
|
|
196
|
-
const
|
|
197
|
-
const modelFg = row.bin ? THEME.shaFg : THEME.shaDarkFg;
|
|
198
|
-
const valueFg = row.bin ? THEME.buttonHotFg : fgRgb;
|
|
222
|
+
const valueFg = THEME.buttonHotFg;
|
|
199
223
|
const tones = {
|
|
200
|
-
model: { fg:
|
|
224
|
+
model: { fg: THEME.shaFg, bold: true },
|
|
201
225
|
effort: { fg: valueFg, bold: false },
|
|
202
226
|
fast: { fg: valueFg, bold: false, glue: true },
|
|
203
227
|
context: { fg: valueFg, bold: false },
|
|
@@ -205,7 +229,7 @@ const paintAgentRow = (row, width, color, selected, cols, search) => {
|
|
|
205
229
|
};
|
|
206
230
|
let out = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
207
231
|
out += paint(prefix, fgRgb, bgRgb, color);
|
|
208
|
-
out += paint(name,
|
|
232
|
+
out += paint(name, valueFg, bgRgb, color, true);
|
|
209
233
|
for (const field of VALUE_FIELDS) {
|
|
210
234
|
const size = colW(cols, field);
|
|
211
235
|
if (!size) continue;
|
|
@@ -219,14 +243,10 @@ const paintAgentRow = (row, width, color, selected, cols, search) => {
|
|
|
219
243
|
out += paint(gap, valueFg, bgRgb, color);
|
|
220
244
|
if (query && search.editor) {
|
|
221
245
|
search.editor.reveal(size);
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
select: { fg: THEME.chromeFg, bg: THEME.checkBg },
|
|
225
|
-
caret: { fg: THEME.searchOnFg, bg: THEME.searchOnBg },
|
|
226
|
-
};
|
|
227
|
-
out += paintField(query, search.editor, size, color, queryTones);
|
|
246
|
+
const typed = queryTones(THEME.warnFg, bgRgb);
|
|
247
|
+
out += paintField(query, search.editor, size, color, typed);
|
|
228
248
|
} else if (query) {
|
|
229
|
-
out += paint(face, THEME.
|
|
249
|
+
out += paint(face, THEME.warnFg, bgRgb, color, true);
|
|
230
250
|
} else {
|
|
231
251
|
out += paint(face, tone.fg, bgRgb, color, tone.bold);
|
|
232
252
|
}
|
|
@@ -273,7 +293,7 @@ const overlayMenu = (painted, view, cols, gap, width, color, headerLines) => {
|
|
|
273
293
|
const boxW = menuBoxWidth(options, columnW, width - x);
|
|
274
294
|
if (x + boxW > width) x = Math.max(0, width - boxW);
|
|
275
295
|
const origin = down ? rowAt + 1 : Math.max(0, rowAt - box.span);
|
|
276
|
-
const faces = menuFaces(options, box, boxW, color, off);
|
|
296
|
+
const faces = menuFaces(options, box, boxW, color, view.agentQuery, off);
|
|
277
297
|
const body = painted.body.slice();
|
|
278
298
|
const fileHits = painted.fileHits.slice();
|
|
279
299
|
for (let i = 0; i < faces.length && origin + i < body.length; i++) {
|
|
@@ -289,7 +309,13 @@ const overlayMenu = (painted, view, cols, gap, width, color, headerLines) => {
|
|
|
289
309
|
field: 'menu',
|
|
290
310
|
});
|
|
291
311
|
}
|
|
292
|
-
|
|
312
|
+
const editHits = painted.editHits.slice();
|
|
313
|
+
if (view.agentEditor) {
|
|
314
|
+
const start = fieldX(cols, gap, field);
|
|
315
|
+
const hit = fieldHit(view.agentEditor, start, columnW, rowHit.y);
|
|
316
|
+
editHits.push({ ...hit, row: rowAt });
|
|
317
|
+
}
|
|
318
|
+
return { ...painted, body, fileHits, editHits, menuScroll: box.start };
|
|
293
319
|
};
|
|
294
320
|
|
|
295
321
|
const chooserSplit = (width) => {
|
|
@@ -501,16 +527,15 @@ const paintAgentLog = (view, width, color, bodyH) => {
|
|
|
501
527
|
return log;
|
|
502
528
|
};
|
|
503
529
|
|
|
504
|
-
const
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
const withRunScroller = (painted, count, start, bodyH, width, color) => {
|
|
530
|
+
const withRunScroller = (
|
|
531
|
+
painted,
|
|
532
|
+
count,
|
|
533
|
+
start,
|
|
534
|
+
bodyH,
|
|
535
|
+
width,
|
|
536
|
+
color,
|
|
537
|
+
runCursor,
|
|
538
|
+
) => {
|
|
514
539
|
const pad = bodyH >= LIST_PAD * 2 + 1 ? LIST_PAD : 0;
|
|
515
540
|
const rows = Math.max(1, bodyH - pad * 2);
|
|
516
541
|
const overflow = count > rows;
|
|
@@ -520,9 +545,11 @@ const withRunScroller = (painted, count, start, bodyH, width, color) => {
|
|
|
520
545
|
let mark = '';
|
|
521
546
|
if (overflow && onText) mark = scrollMark(at, count, rows, start);
|
|
522
547
|
if (overflow && !onText) mark = 'track';
|
|
523
|
-
|
|
548
|
+
const selected = onText && runCursor === start + at;
|
|
549
|
+
const rowBg = selected ? THEME.checkBg : null;
|
|
550
|
+
return withLogScroller(line, mark, width, color, rowBg);
|
|
524
551
|
});
|
|
525
|
-
const fileHits =
|
|
552
|
+
const fileHits = painted.fileHits;
|
|
526
553
|
if (!overflow) return { ...painted, body, fileHits };
|
|
527
554
|
const scrollBar = {
|
|
528
555
|
col: width,
|
|
@@ -599,6 +626,7 @@ const paintBodyAgents = (view, width, color, bodyH, headerLines) => {
|
|
|
599
626
|
bodyH,
|
|
600
627
|
width,
|
|
601
628
|
color,
|
|
629
|
+
runCursor,
|
|
602
630
|
);
|
|
603
631
|
};
|
|
604
632
|
|
|
@@ -623,7 +651,7 @@ const agentName = (item) => {
|
|
|
623
651
|
|
|
624
652
|
const agentsBlock = (model, width, height, ctx, tile) => {
|
|
625
653
|
const data = model.agents;
|
|
626
|
-
if (!data || !data.ready) return waiting('detecting
|
|
654
|
+
if (!data || !data.ready) return waiting('detecting agents…');
|
|
627
655
|
const aside = [seg(`${data.running || 0}`, 'key', true)];
|
|
628
656
|
const titleLine = titleAside(tile, aside, width);
|
|
629
657
|
const items = data.items ?? [];
|
package/lib/render/branches.js
CHANGED
|
@@ -5,9 +5,12 @@ const { shortAge } = require('../common/format.js');
|
|
|
5
5
|
const primitives = require('./primitives.js');
|
|
6
6
|
|
|
7
7
|
const { THEME, paint, visibleWidth, trimVisible, seq, EL } = ansi;
|
|
8
|
-
const {
|
|
9
|
-
const {
|
|
8
|
+
const { markPrefix, branchLabel, paintCursorList } = primitives;
|
|
9
|
+
const { paintBodyFill } = primitives;
|
|
10
10
|
const { measureColumns, padColumns, joinColumns, paintColumns } = primitives;
|
|
11
|
+
const { paintNameEdit } = require('./name-edit.js');
|
|
12
|
+
const { paintTreeBand } = require('./repos.js');
|
|
13
|
+
const { tileHits, pageFill } = require('./tiles.js');
|
|
11
14
|
|
|
12
15
|
const BRANCH_COL_ALIGN = {
|
|
13
16
|
sha: 'end',
|
|
@@ -92,27 +95,23 @@ const paintBranchRow = (entry, width, color, selected, cols) => {
|
|
|
92
95
|
return out + paint(tail, fgRgb, bgRgb, color);
|
|
93
96
|
};
|
|
94
97
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
const textX = MARK_W + 1;
|
|
111
|
-
const edit = editSpan(1, textX, textX + rest, 0, text);
|
|
112
|
-
const cursor = { x: textX + win.col, scroll: win.scroll };
|
|
113
|
-
return { row, cursor, edits: [{ ...edit, scroll: win.scroll }] };
|
|
114
|
-
};
|
|
98
|
+
// A title and the five lines of a tile
|
|
99
|
+
const TREES_H = 6;
|
|
100
|
+
const LIST_MIN_H = 4;
|
|
101
|
+
// A blank line above the tiles, one under them, one under the list
|
|
102
|
+
const TREES_GAPS = 3;
|
|
103
|
+
|
|
104
|
+
// The tiles of the worktrees show when the list of branches can spare it
|
|
105
|
+
const treesHeight = (bodyH) =>
|
|
106
|
+
bodyH - TREES_GAPS - LIST_MIN_H >= TREES_H ? TREES_H : 0;
|
|
107
|
+
|
|
108
|
+
const shiftRows = (list, by) => ({
|
|
109
|
+
...list,
|
|
110
|
+
cursor: list.cursor ? { ...list.cursor, row: list.cursor.row + by } : null,
|
|
111
|
+
editHits: list.editHits.map((span) => ({ ...span, row: span.row + by })),
|
|
112
|
+
});
|
|
115
113
|
|
|
114
|
+
// The worktrees above the branches; one element is selected, a tile or a branch
|
|
116
115
|
const paintBodyBranches = (view, width, color, bodyH, headerLines) => {
|
|
117
116
|
const branches = view.branches ?? [];
|
|
118
117
|
const cols = measureColumns(branches, branchRowFields, BRANCH_COL_ALIGN);
|
|
@@ -123,12 +122,41 @@ const paintBodyBranches = (view, width, color, bodyH, headerLines) => {
|
|
|
123
122
|
cols.name = Math.min(BRANCH_NAME_MAX, cols.name);
|
|
124
123
|
const cursor = view.branchCursor ?? 0;
|
|
125
124
|
const { compose } = view;
|
|
126
|
-
const
|
|
127
|
-
const
|
|
128
|
-
const
|
|
125
|
+
const trees = view.trees ? treesHeight(bodyH) : 0;
|
|
126
|
+
const typing = compose?.kind === 'branch' || compose?.kind === 'worktree';
|
|
127
|
+
const typed = typing ? paintNameEdit(compose, width, color) : null;
|
|
128
|
+
const above = trees && compose?.kind === 'worktree' ? typed : null;
|
|
129
|
+
const edit = above ? null : typed;
|
|
130
|
+
const lead = above ? 1 : 0;
|
|
131
|
+
const top = trees ? lead + 1 + trees + 1 : 0;
|
|
132
|
+
const room = trees ? bodyH - top - 1 : bodyH;
|
|
133
|
+
const pane = { width, color, bodyH: room, headerLines: headerLines + top };
|
|
134
|
+
pane.offset = view.listScroll;
|
|
135
|
+
if (trees) pane.pad = 0;
|
|
136
|
+
const picked = !view.treeFocus;
|
|
129
137
|
const paintRow = (entry, selected) =>
|
|
130
|
-
paintBranchRow(entry, width, color, selected, cols);
|
|
131
|
-
|
|
138
|
+
paintBranchRow(entry, width, color, selected && picked, cols);
|
|
139
|
+
const list = paintCursorList(branches, cursor, pane, paintRow, edit);
|
|
140
|
+
if (!trees) return list;
|
|
141
|
+
const band = paintTreeBand(view.trees, width, trees, color);
|
|
142
|
+
const hits = tileHits(band.bands, headerLines + lead + 1);
|
|
143
|
+
const treeHits = hits.map((hit) => ({ ...hit, tree: true }));
|
|
144
|
+
const head = above ? [above.row] : [];
|
|
145
|
+
const gap = pageFill(width, color);
|
|
146
|
+
const tail = paintBodyFill(width, color, 'files');
|
|
147
|
+
const body = [...head, gap, ...band.rows, gap, ...list.body, tail];
|
|
148
|
+
const fileHits = [...treeHits, ...list.fileHits];
|
|
149
|
+
const moved = shiftRows(list, top);
|
|
150
|
+
const typedAbove = above ? { ...above.cursor, row: 0 } : null;
|
|
151
|
+
const spans = above ? above.edits.map((span) => ({ ...span, row: 0 })) : [];
|
|
152
|
+
return {
|
|
153
|
+
...moved,
|
|
154
|
+
body,
|
|
155
|
+
fileHits,
|
|
156
|
+
cursor: typedAbove ?? moved.cursor,
|
|
157
|
+
editHits: [...spans, ...moved.editHits],
|
|
158
|
+
gridCols: band.cols,
|
|
159
|
+
};
|
|
132
160
|
};
|
|
133
161
|
|
|
134
162
|
module.exports = { paintBodyBranches };
|
package/lib/render/field.js
CHANGED
|
@@ -4,6 +4,16 @@ const ansi = require('../term/ansi.js');
|
|
|
4
4
|
|
|
5
5
|
const { THEME, paint, graphemes, graphemeWidth, visibleWidth } = ansi;
|
|
6
6
|
const { fileMatches } = require('../common/find.js');
|
|
7
|
+
const { editSpan } = require('./primitives.js');
|
|
8
|
+
|
|
9
|
+
// Inline fields paint their own selection; their spans map pointer positions.
|
|
10
|
+
const fieldHit = (editor, x, width, y) => ({
|
|
11
|
+
...editSpan(x + 1, x + 1, x + width + 1, 0, editor.text),
|
|
12
|
+
editor,
|
|
13
|
+
y,
|
|
14
|
+
scroll: editor.scrollCol,
|
|
15
|
+
live: false,
|
|
16
|
+
});
|
|
7
17
|
|
|
8
18
|
const pieceKind = (index, cursor, lo, hi) => {
|
|
9
19
|
if (index === cursor) return 'caret';
|
|
@@ -62,6 +72,12 @@ const paintField = (text, editor, room, color, tones) => {
|
|
|
62
72
|
return out + paint(pad, plain.fg, plain.bg, color);
|
|
63
73
|
};
|
|
64
74
|
|
|
75
|
+
const queryTones = (fgRgb, bgRgb) => ({
|
|
76
|
+
plain: { fg: fgRgb, bg: bgRgb },
|
|
77
|
+
select: { fg: THEME.chromeFg, bg: THEME.checkBg },
|
|
78
|
+
caret: { fg: THEME.searchOnFg, bg: THEME.searchOnBg },
|
|
79
|
+
});
|
|
80
|
+
|
|
65
81
|
const paintPlanFace = (view, face, fgRgb, bgRgb, color) => {
|
|
66
82
|
const editor = view.planEditor;
|
|
67
83
|
if (!editor || !view.planQuery) {
|
|
@@ -69,11 +85,7 @@ const paintPlanFace = (view, face, fgRgb, bgRgb, color) => {
|
|
|
69
85
|
}
|
|
70
86
|
const room = visibleWidth(face);
|
|
71
87
|
if (room) editor.reveal(room);
|
|
72
|
-
const tones =
|
|
73
|
-
plain: { fg: THEME.searchFg, bg: THEME.searchBg },
|
|
74
|
-
select: { fg: THEME.chromeFg, bg: THEME.checkBg },
|
|
75
|
-
caret: { fg: THEME.searchOnFg, bg: THEME.searchOnBg },
|
|
76
|
-
};
|
|
88
|
+
const tones = queryTones(fgRgb, bgRgb);
|
|
77
89
|
return paintField(view.planQuery, editor, room, color, tones);
|
|
78
90
|
};
|
|
79
91
|
|
|
@@ -125,7 +137,9 @@ const paintFindStatus = (view, width, color) => {
|
|
|
125
137
|
|
|
126
138
|
module.exports = {
|
|
127
139
|
paintField,
|
|
140
|
+
queryTones,
|
|
128
141
|
paintPlanFace,
|
|
129
142
|
paintImportStatus,
|
|
130
143
|
paintFindStatus,
|
|
144
|
+
fieldHit,
|
|
131
145
|
};
|
package/lib/render/footer.js
CHANGED
|
@@ -296,8 +296,10 @@ const agentListDim = (view) => {
|
|
|
296
296
|
const off = [];
|
|
297
297
|
if (!installed || levels.length < 2) off.push('agentEffort');
|
|
298
298
|
if (!installed) off.push('agentFast');
|
|
299
|
-
if (!installed ||
|
|
299
|
+
if (!installed || sizes.length < 2) off.push('agentContext');
|
|
300
300
|
if ((view.agentPlanCount ?? 0) < 2) off.push('agentReview');
|
|
301
|
+
if (installed || !row || !row.installable) off.push('agentInstall');
|
|
302
|
+
if (!view.agentCanStart) off.push('agentStart');
|
|
301
303
|
return off;
|
|
302
304
|
};
|
|
303
305
|
|
|
@@ -330,14 +332,16 @@ const dimFooterActions = (view) => {
|
|
|
330
332
|
const GIT_FOOTER = ['add', 'unstage', 'revert', 'ignore'];
|
|
331
333
|
|
|
332
334
|
const FOOTER_EXTRA = {
|
|
333
|
-
repos: ['theme'],
|
|
335
|
+
repos: ['theme', 'worktreeNew', 'worktreeDrop'],
|
|
334
336
|
dashboard: ['theme', 'pull', 'push'],
|
|
335
337
|
unit: ['add', 'unstage', 'revert', 'feedback', 'code', 'lines'],
|
|
336
338
|
files: [...GIT_FOOTER, 'code'],
|
|
337
339
|
diff: [...GIT_FOOTER, 'feedback', 'code', 'layout', 'lines'],
|
|
338
|
-
branches: ['newBranch', 'rebase', 'drop', 'pull', 'push'],
|
|
340
|
+
branches: ['newBranch', 'worktreeBranch', 'rebase', 'drop', 'pull', 'push'],
|
|
341
|
+
trees: ['worktreeNew', 'worktreeDrop', 'pull', 'push'],
|
|
339
342
|
commits: ['newCommit', 'apply', 'reword', 'drop', 'view', 'pull', 'push'],
|
|
340
343
|
npmLog: ['npmVerbose', 'npmStop', 'npmRerun'],
|
|
344
|
+
npmFailedLog: ['npmVerbose', 'npmSolve', 'npmRerun'],
|
|
341
345
|
npm: ['npmEdit', 'npmNew', 'npmDrop', 'npmLogs'],
|
|
342
346
|
packages: ['packageNew', 'packageDrop', 'packageWanted', 'packageLatest'],
|
|
343
347
|
agentLog: ['agentStop', 'agentRerun'],
|
|
@@ -347,13 +351,18 @@ const FOOTER_EXTRA = {
|
|
|
347
351
|
'agentFast',
|
|
348
352
|
'agentContext',
|
|
349
353
|
'agentReview',
|
|
354
|
+
'agentInstall',
|
|
355
|
+
'agentStart',
|
|
350
356
|
],
|
|
351
357
|
tasks: ['plan', 'import', 'drop', 'check', 'taskNudge'],
|
|
352
358
|
};
|
|
353
359
|
|
|
354
360
|
const extraFooterActions = (view) => {
|
|
355
361
|
const { pane } = view;
|
|
356
|
-
if (pane === '
|
|
362
|
+
if (pane === 'branches' && view.treeFocus) return FOOTER_EXTRA.trees;
|
|
363
|
+
if (pane === 'npm' && view.npmView) {
|
|
364
|
+
return view.npmCanSolve ? FOOTER_EXTRA.npmFailedLog : FOOTER_EXTRA.npmLog;
|
|
365
|
+
}
|
|
357
366
|
if (pane === 'npm') return FOOTER_EXTRA.npm;
|
|
358
367
|
if (pane === 'agents' && view.agentView) return FOOTER_EXTRA.agentLog;
|
|
359
368
|
if (pane === 'agents') return FOOTER_EXTRA.agents;
|
|
@@ -380,4 +389,5 @@ module.exports = {
|
|
|
380
389
|
paintFooter,
|
|
381
390
|
footerHits,
|
|
382
391
|
footerLayout,
|
|
392
|
+
dimFooterActions,
|
|
383
393
|
};
|
package/lib/render/frame.js
CHANGED
|
@@ -18,11 +18,12 @@ const dashboard = require('./dashboard/dashboard.js');
|
|
|
18
18
|
const packages = require('./packages.js');
|
|
19
19
|
const agents = require('./agents.js');
|
|
20
20
|
const repos = require('./repos.js');
|
|
21
|
+
const { contextMenu } = require('./menu.js');
|
|
21
22
|
|
|
22
23
|
const { isListPane, isTaskView, paintBodyFill, paneResult } = primitives;
|
|
23
24
|
const { bodyRow } = primitives;
|
|
24
25
|
const { paintStatusLine, statusBranchHit, statusChoiceHits } = status;
|
|
25
|
-
const { statusPlanHit, planMenu } = status;
|
|
26
|
+
const { statusPlanHit, statusEditHits, planMenu } = status;
|
|
26
27
|
|
|
27
28
|
const PANE_BODY = {
|
|
28
29
|
dashboard: dashboard.paintBodyDashboard,
|
|
@@ -250,8 +251,20 @@ const renderFrame = (view, options = {}) => {
|
|
|
250
251
|
}
|
|
251
252
|
statusHits.push(...menu.hits);
|
|
252
253
|
}
|
|
253
|
-
const editHits = [
|
|
254
|
+
const editHits = [
|
|
255
|
+
...windowed.editHits,
|
|
256
|
+
...hits.editHits,
|
|
257
|
+
...statusEditHits(view, width, statusY),
|
|
258
|
+
];
|
|
254
259
|
const shown = markEditorSelection(rows, view, editHits, color);
|
|
260
|
+
const popup = contextMenu(view.contextMenu, width, rows.length, color);
|
|
261
|
+
if (popup) {
|
|
262
|
+
for (const line of popup.lines) {
|
|
263
|
+
const index = line.y - 1;
|
|
264
|
+
if (index <= 0 || index >= rows.length - 1) continue;
|
|
265
|
+
shown[index] = stampVisible(shown[index], popup.x, line.text);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
255
268
|
const text = shown.map((row) => (color ? row + RESET : row)).join('\n');
|
|
256
269
|
return {
|
|
257
270
|
text,
|
|
@@ -262,6 +275,7 @@ const renderFrame = (view, options = {}) => {
|
|
|
262
275
|
templateHits: hits.templateHits,
|
|
263
276
|
editHits,
|
|
264
277
|
statusHits,
|
|
278
|
+
contextHits: popup ? popup.hits : [],
|
|
265
279
|
footerY: rows.length,
|
|
266
280
|
height: rows.length,
|
|
267
281
|
scroll: windowed.scroll,
|
package/lib/render/log.js
CHANGED
|
@@ -263,14 +263,15 @@ const paintLogRow = (view, lines, index, width, color) => {
|
|
|
263
263
|
return fill(width, THEME.ctxFg, THEME.ctxBg, color);
|
|
264
264
|
};
|
|
265
265
|
|
|
266
|
-
const paintScrollCell = (mark, color) => {
|
|
267
|
-
|
|
266
|
+
const paintScrollCell = (mark, color, rowBg) => {
|
|
267
|
+
let bgRgb = rowBg ?? THEME.buttonBg;
|
|
268
|
+
if (mark === 'thumb') bgRgb = THEME.taskHeadBg;
|
|
268
269
|
return paint(' ', THEME.chromeFg, bgRgb, color);
|
|
269
270
|
};
|
|
270
271
|
|
|
271
|
-
const withLogScroller = (line, mark, width, color) => {
|
|
272
|
+
const withLogScroller = (line, mark, width, color, rowBg) => {
|
|
272
273
|
const text = clipAnsi(line, Math.max(0, width - 1));
|
|
273
|
-
return `${text}${paintScrollCell(mark, color)}`;
|
|
274
|
+
return `${text}${paintScrollCell(mark, color, rowBg)}`;
|
|
274
275
|
};
|
|
275
276
|
|
|
276
277
|
const paintNpmLog = (view, width, color, bodyH) => {
|
package/lib/render/menu.js
CHANGED
|
@@ -4,6 +4,11 @@ const { clamp } = require('../common/utilities.js');
|
|
|
4
4
|
const ansi = require('../term/ansi.js');
|
|
5
5
|
const { listWindowStart, scrollMark } = require('./primitives.js');
|
|
6
6
|
const { THEME, paint, visibleWidth, trimVisible } = ansi;
|
|
7
|
+
const {
|
|
8
|
+
actionLetter,
|
|
9
|
+
actionCaption,
|
|
10
|
+
buttonWord,
|
|
11
|
+
} = require('../input/actions.js');
|
|
7
12
|
|
|
8
13
|
const menuBoxWidth = (options, columnW, room) => {
|
|
9
14
|
let widest = Math.max(columnW, 4);
|
|
@@ -13,7 +18,24 @@ const menuBoxWidth = (options, columnW, room) => {
|
|
|
13
18
|
return clamp(widest + 2, 4, room);
|
|
14
19
|
};
|
|
15
20
|
|
|
16
|
-
const
|
|
21
|
+
const paintQuery = (text, query, fgRgb, bgRgb, color, bold) => {
|
|
22
|
+
const needle = `${query ?? ''}`.trim().toLowerCase();
|
|
23
|
+
if (!color || !needle) return paint(text, fgRgb, bgRgb, color, bold);
|
|
24
|
+
const hay = text.toLowerCase();
|
|
25
|
+
let out = '';
|
|
26
|
+
let from = 0;
|
|
27
|
+
let at = hay.indexOf(needle, from);
|
|
28
|
+
while (at >= 0) {
|
|
29
|
+
const end = at + needle.length;
|
|
30
|
+
out += paint(text.slice(from, at), fgRgb, bgRgb, color, bold);
|
|
31
|
+
out += paint(text.slice(at, end), THEME.warnFg, bgRgb, color, true);
|
|
32
|
+
from = end;
|
|
33
|
+
at = hay.indexOf(needle, from);
|
|
34
|
+
}
|
|
35
|
+
return out + paint(text.slice(from), fgRgb, bgRgb, color, bold);
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const menuItem = (label, boxW, color, selected, mark, disabled, query) => {
|
|
17
39
|
const barW = mark ? 1 : 0;
|
|
18
40
|
const faceW = Math.max(1, boxW - barW);
|
|
19
41
|
const room = Math.max(0, faceW - 1);
|
|
@@ -25,7 +47,7 @@ const menuItem = (label, boxW, color, selected, mark, disabled) => {
|
|
|
25
47
|
const fgRgb = disabled ? THEME.mutedFg : THEME.chromeFg;
|
|
26
48
|
const hotFg = hot ? THEME.buttonHotFg : fgRgb;
|
|
27
49
|
const bgRgb = hot ? THEME.checkBg : THEME.noteBg;
|
|
28
|
-
let row =
|
|
50
|
+
let row = paintQuery(face, query, hotFg, bgRgb, color, hot);
|
|
29
51
|
if (!mark) return row;
|
|
30
52
|
const barBg = mark === 'thumb' ? THEME.taskHeadBg : THEME.buttonBg;
|
|
31
53
|
row += paint(' ', fgRgb, barBg, color);
|
|
@@ -40,7 +62,7 @@ const dropBox = (options, cursor, scroll, room) => {
|
|
|
40
62
|
return { focus, inner, span: inner, start, count };
|
|
41
63
|
};
|
|
42
64
|
|
|
43
|
-
const menuFaces = (options, box, boxW, color, off = new Set()) => {
|
|
65
|
+
const menuFaces = (options, box, boxW, color, query = '', off = new Set()) => {
|
|
44
66
|
const faces = [];
|
|
45
67
|
for (let i = 0; i < box.inner; i++) {
|
|
46
68
|
const index = box.start + i;
|
|
@@ -50,11 +72,86 @@ const menuFaces = (options, box, boxW, color, off = new Set()) => {
|
|
|
50
72
|
const focused = index === box.focus;
|
|
51
73
|
const face =
|
|
52
74
|
label === undefined
|
|
53
|
-
? menuItem('none', boxW, color, false, mark, false)
|
|
54
|
-
: menuItem(label, boxW, color, focused, mark, disabled);
|
|
75
|
+
? menuItem('none', boxW, color, false, mark, false, '')
|
|
76
|
+
: menuItem(label, boxW, color, focused, mark, disabled, query);
|
|
55
77
|
faces.push({ index, face });
|
|
56
78
|
}
|
|
57
79
|
return faces;
|
|
58
80
|
};
|
|
59
81
|
|
|
60
|
-
|
|
82
|
+
const contextText = (id, keyWidth) => {
|
|
83
|
+
if (!keyWidth) return buttonWord(id);
|
|
84
|
+
const mark = actionLetter(id) ?? '';
|
|
85
|
+
const caption = actionCaption(id);
|
|
86
|
+
const key = mark.padEnd(keyWidth);
|
|
87
|
+
return `${key} ${caption}`;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const contextLabel = (id, width, color, selected, disabled, keyWidth) => {
|
|
91
|
+
const label = contextText(id, keyWidth);
|
|
92
|
+
const mark = actionLetter(id) ?? '';
|
|
93
|
+
const shown = trimVisible(label, Math.max(1, width - 2));
|
|
94
|
+
const pad = ' '.repeat(Math.max(0, width - 2 - visibleWidth(shown)));
|
|
95
|
+
const face = ` ${shown}${pad} `;
|
|
96
|
+
if (!color || !mark) return face;
|
|
97
|
+
const bgRgb = selected && !disabled ? THEME.popupHotBg : THEME.popupBg;
|
|
98
|
+
const fgRgb = disabled ? THEME.popupDisabledFg : THEME.popupFg;
|
|
99
|
+
if (disabled) return paint(face, fgRgb, bgRgb, color);
|
|
100
|
+
const at = shown.indexOf(mark);
|
|
101
|
+
if (at < 0) return paint(face, fgRgb, bgRgb, color);
|
|
102
|
+
let out = paint(face.slice(0, at + 1), fgRgb, bgRgb, color);
|
|
103
|
+
out += paint(mark, THEME.buttonHotFg, bgRgb, color, true);
|
|
104
|
+
return out + paint(face.slice(at + mark.length + 1), fgRgb, bgRgb, color);
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const contextMenu = (model, width, height, color) => {
|
|
108
|
+
if (!model || !model.actions || !model.actions.length) return null;
|
|
109
|
+
const keyWidth = model.editor
|
|
110
|
+
? Math.max(
|
|
111
|
+
...model.actions.map((id) => visibleWidth(actionLetter(id) ?? '')),
|
|
112
|
+
)
|
|
113
|
+
: 0;
|
|
114
|
+
const labels = model.actions.map((id) => contextText(id, keyWidth));
|
|
115
|
+
const widest = labels.reduce(
|
|
116
|
+
(size, label) => Math.max(size, visibleWidth(label)),
|
|
117
|
+
4,
|
|
118
|
+
);
|
|
119
|
+
const boxW = Math.min(width, widest + 2);
|
|
120
|
+
const count = model.actions.length;
|
|
121
|
+
const inner = Math.min(count, Math.max(1, height - 3));
|
|
122
|
+
const x = clamp(model.x ?? 0, 0, Math.max(0, width - boxW));
|
|
123
|
+
const anchorY = clamp(model.y ?? 2, 2, Math.max(2, height - 1));
|
|
124
|
+
const y = Math.min(anchorY, Math.max(2, height - inner - 1));
|
|
125
|
+
const cursor = clamp(model.cursor ?? 0, 0, count - 1);
|
|
126
|
+
const start = listWindowStart(cursor, 0, inner, count);
|
|
127
|
+
const lines = [];
|
|
128
|
+
const hits = [];
|
|
129
|
+
for (let row = 0; row < inner; row++) {
|
|
130
|
+
const index = start + row;
|
|
131
|
+
const id = model.actions[index];
|
|
132
|
+
const disabled = (model.disabled ?? []).includes(id);
|
|
133
|
+
const face = contextLabel(
|
|
134
|
+
id,
|
|
135
|
+
boxW,
|
|
136
|
+
color,
|
|
137
|
+
index === cursor,
|
|
138
|
+
disabled,
|
|
139
|
+
keyWidth,
|
|
140
|
+
);
|
|
141
|
+
lines.push({
|
|
142
|
+
y: y + row,
|
|
143
|
+
text: face,
|
|
144
|
+
});
|
|
145
|
+
hits.push({
|
|
146
|
+
id,
|
|
147
|
+
cursor: index,
|
|
148
|
+
disabled,
|
|
149
|
+
y: y + row,
|
|
150
|
+
x0: x,
|
|
151
|
+
x1: x + boxW,
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return { x, lines, hits };
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
module.exports = { menuBoxWidth, dropBox, menuFaces, contextMenu };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ansi = require('../term/ansi.js');
|
|
4
|
+
const primitives = require('./primitives.js');
|
|
5
|
+
|
|
6
|
+
const { THEME, paint, visibleWidth, seq, EL } = ansi;
|
|
7
|
+
const { MARK_W, markPrefix, editSpan, fieldWindow } = primitives;
|
|
8
|
+
|
|
9
|
+
// The row where a name is typed: of a new branch or of a new worktree
|
|
10
|
+
const paintNameEdit = (compose, width, color) => {
|
|
11
|
+
const prefix = markPrefix(true);
|
|
12
|
+
const rest = Math.max(1, width - MARK_W);
|
|
13
|
+
const text = `${compose.text ?? ''}`.replaceAll('\n', ' ');
|
|
14
|
+
const win = fieldWindow(text, compose.cursor, compose.scrollCol, rest);
|
|
15
|
+
const gap = ' '.repeat(Math.max(0, rest - visibleWidth(win.text)));
|
|
16
|
+
const fgRgb = THEME.chromeFg;
|
|
17
|
+
const bgRgb = THEME.buttonBg;
|
|
18
|
+
let row = `${prefix}${win.text}${gap}`;
|
|
19
|
+
if (color) {
|
|
20
|
+
row = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
21
|
+
row += paint(prefix, fgRgb, bgRgb, color);
|
|
22
|
+
row += paint(win.text, THEME.buttonHotFg, bgRgb, color);
|
|
23
|
+
row += paint(gap, fgRgb, bgRgb, color);
|
|
24
|
+
}
|
|
25
|
+
const textX = MARK_W + 1;
|
|
26
|
+
const edit = editSpan(1, textX, textX + rest, 0, text);
|
|
27
|
+
const cursor = { x: textX + win.col, scroll: win.scroll };
|
|
28
|
+
return { row, cursor, edits: [{ ...edit, scroll: win.scroll }] };
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
module.exports = { paintNameEdit };
|