reslop 0.3.1 → 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 +16 -1
- package/README.md +21 -3
- package/lib/agents/acp.js +309 -0
- package/lib/agents/agents.js +163 -436
- package/lib/agents/allow.js +141 -52
- package/lib/agents/allow.json +16 -14
- package/lib/agents/models.js +250 -383
- 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/dir.js +50 -0
- 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 +113 -36
- package/lib/input/actions.js +83 -5
- package/lib/input/keys.js +30 -1
- package/lib/input/line-edit.js +5 -1
- package/lib/render/agents.js +50 -24
- package/lib/render/branches.js +55 -27
- package/lib/render/field.js +19 -5
- package/lib/render/footer.js +14 -6
- package/lib/render/frame.js +16 -2
- 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/run.js +16 -23
- package/lib/review/agent-instructions.md +2 -2
- package/lib/runs/process.js +3 -3
- package/lib/runs/runs.js +27 -4
- package/lib/session/agents/agents.js +220 -205
- 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 -1
- package/lib/session/compose.js +13 -5
- package/lib/session/dispatch.js +43 -4
- package/lib/session/editor-pointer.js +98 -0
- package/lib/session/files-pane.js +3 -0
- package/lib/session/lifecycle.js +67 -10
- package/lib/session/navigation.js +5 -0
- package/lib/session/npm/history.js +2 -1
- package/lib/session/npm/npm.js +89 -12
- package/lib/session/ops.js +3 -0
- package/lib/session/pointer.js +304 -46
- package/lib/session/session.js +15 -2
- 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/themes/dark.json +4 -0
- package/lib/term/themes/light.json +4 -0
- package/package.json +2 -2
package/lib/input/actions.js
CHANGED
|
@@ -59,6 +59,7 @@ const NPM_ACTIONS = {
|
|
|
59
59
|
npmDrop: ['delete'],
|
|
60
60
|
npmLogs: ['l'],
|
|
61
61
|
npmStop: ['s'],
|
|
62
|
+
npmSolve: ['s'],
|
|
62
63
|
npmVerbose: ['v'],
|
|
63
64
|
npmRerun: ['r'],
|
|
64
65
|
};
|
|
@@ -69,7 +70,8 @@ const AGENT_ACTIONS = {
|
|
|
69
70
|
agentFast: ['a'],
|
|
70
71
|
agentContext: ['t'],
|
|
71
72
|
agentReview: ['p'],
|
|
72
|
-
|
|
73
|
+
agentInstall: ['i'],
|
|
74
|
+
agentStart: ['s'],
|
|
73
75
|
agentStop: ['s'],
|
|
74
76
|
agentRerun: ['r'],
|
|
75
77
|
};
|
|
@@ -80,7 +82,8 @@ const AGENT_LIST_ACTIONS = {
|
|
|
80
82
|
agentFast: AGENT_ACTIONS.agentFast,
|
|
81
83
|
agentContext: AGENT_ACTIONS.agentContext,
|
|
82
84
|
agentReview: AGENT_ACTIONS.agentReview,
|
|
83
|
-
|
|
85
|
+
agentInstall: AGENT_ACTIONS.agentInstall,
|
|
86
|
+
agentStart: AGENT_ACTIONS.agentStart,
|
|
84
87
|
};
|
|
85
88
|
|
|
86
89
|
const DASH_TILES = [
|
|
@@ -102,6 +105,11 @@ for (const tile of DASH_TILES) {
|
|
|
102
105
|
DASH_KEYS[tile.key] = tile.action;
|
|
103
106
|
}
|
|
104
107
|
const DASH_IDS = Object.keys(DASH_ACTIONS);
|
|
108
|
+
const WORKTREE_ACTIONS = {
|
|
109
|
+
worktreeNew: ['insert'],
|
|
110
|
+
worktreeDrop: ['delete'],
|
|
111
|
+
worktreeBranch: ['w'],
|
|
112
|
+
};
|
|
105
113
|
const DASH_ALLOWED = [
|
|
106
114
|
...Object.keys(DASH_BLOCKS),
|
|
107
115
|
'back',
|
|
@@ -117,17 +125,28 @@ const REPOS_ALLOWED = [
|
|
|
117
125
|
'repoDown',
|
|
118
126
|
'open',
|
|
119
127
|
'back',
|
|
128
|
+
'worktreeNew',
|
|
129
|
+
'worktreeDrop',
|
|
120
130
|
'quit',
|
|
121
131
|
'theme',
|
|
122
132
|
];
|
|
123
133
|
const DASH_HIDDEN = ACTION_IDS.filter((id) => id !== 'quit');
|
|
124
134
|
|
|
125
135
|
const ACTION_HOT = {
|
|
136
|
+
textCut: 'ctrl+x',
|
|
137
|
+
textCopy: 'ctrl+c',
|
|
138
|
+
textPaste: 'ctrl+v',
|
|
139
|
+
textDelete: 'del',
|
|
140
|
+
textSelectAll: 'ctrl+a',
|
|
141
|
+
open: 'enter',
|
|
126
142
|
prev: '←',
|
|
127
143
|
next: '→',
|
|
128
144
|
drop: 'del',
|
|
145
|
+
worktreeDrop: 'del',
|
|
146
|
+
worktreeBranch: 'w',
|
|
129
147
|
newBranch: 'ins',
|
|
130
148
|
newCommit: 'ins',
|
|
149
|
+
worktreeNew: 'ins',
|
|
131
150
|
feedback: 'q',
|
|
132
151
|
check: 'space',
|
|
133
152
|
taskUp: 'ctrl+up',
|
|
@@ -145,7 +164,13 @@ const ACTION_HOT = {
|
|
|
145
164
|
};
|
|
146
165
|
|
|
147
166
|
const ACTION_LABEL = {
|
|
167
|
+
textCut: 'ctrl+x cut',
|
|
168
|
+
textCopy: 'ctrl+c copy',
|
|
169
|
+
textPaste: 'ctrl+v paste',
|
|
170
|
+
textDelete: 'delete',
|
|
171
|
+
textSelectAll: 'ctrl+a select all',
|
|
148
172
|
...DASH_BLOCKS,
|
|
173
|
+
open: 'enter open',
|
|
149
174
|
revert: 'drop',
|
|
150
175
|
check: 'space',
|
|
151
176
|
taskUp: 'ctrl+up',
|
|
@@ -163,6 +188,9 @@ const ACTION_LABEL = {
|
|
|
163
188
|
drop: 'delete',
|
|
164
189
|
newBranch: 'insert',
|
|
165
190
|
newCommit: 'insert',
|
|
191
|
+
worktreeNew: 'insert',
|
|
192
|
+
worktreeDrop: 'delete',
|
|
193
|
+
worktreeBranch: 'worktree',
|
|
166
194
|
feedback: 'quote',
|
|
167
195
|
npmEdit: 'edit',
|
|
168
196
|
npmNew: 'insert',
|
|
@@ -173,6 +201,7 @@ const ACTION_LABEL = {
|
|
|
173
201
|
packageWanted: 'wanted',
|
|
174
202
|
packageLatest: 'latest',
|
|
175
203
|
npmStop: 'stop',
|
|
204
|
+
npmSolve: 'solve',
|
|
176
205
|
npmVerbose: 'verbose',
|
|
177
206
|
npmRerun: 're-run',
|
|
178
207
|
agentModel: 'model',
|
|
@@ -180,7 +209,8 @@ const ACTION_LABEL = {
|
|
|
180
209
|
agentFast: 'fast',
|
|
181
210
|
agentContext: 'context',
|
|
182
211
|
agentReview: 'plan',
|
|
183
|
-
|
|
212
|
+
agentInstall: 'install',
|
|
213
|
+
agentStart: 'start',
|
|
184
214
|
agentStop: 'stop',
|
|
185
215
|
agentRerun: 're-run',
|
|
186
216
|
quit: 'q',
|
|
@@ -387,7 +417,12 @@ const keysForActions = (actions) => {
|
|
|
387
417
|
const PANE_ONLY = ['branch', 'file', 'diff'];
|
|
388
418
|
const REVIEW_DROP = { d: 'revert' };
|
|
389
419
|
const DIFF_KEYS = { ...REVIEW_DROP, q: 'feedback', 'ctrl-l': 'lines' };
|
|
390
|
-
const BRANCH_KEYS =
|
|
420
|
+
const BRANCH_KEYS = {
|
|
421
|
+
...keysForActions(BRANCH_ACTIONS),
|
|
422
|
+
w: 'worktreeBranch',
|
|
423
|
+
left: 'treeLeft',
|
|
424
|
+
right: 'treeRight',
|
|
425
|
+
};
|
|
391
426
|
const COMMIT_KEYS = { ...keysForActions(COMMIT_ACTIONS), s: 'push' };
|
|
392
427
|
const NPM_KEYS = {
|
|
393
428
|
e: 'npmEdit',
|
|
@@ -416,6 +451,8 @@ const PANE_KEYS = {
|
|
|
416
451
|
j: 'repoDown',
|
|
417
452
|
p: 'repoLeft',
|
|
418
453
|
n: 'repoRight',
|
|
454
|
+
insert: 'worktreeNew',
|
|
455
|
+
delete: 'worktreeDrop',
|
|
419
456
|
},
|
|
420
457
|
};
|
|
421
458
|
|
|
@@ -430,7 +467,8 @@ const KEY_TO_ACTION = {
|
|
|
430
467
|
};
|
|
431
468
|
|
|
432
469
|
const actionFromKey = (key, pane) => {
|
|
433
|
-
|
|
470
|
+
const sideways = key === 'left' || key === 'right';
|
|
471
|
+
if (LIST_PANES.includes(pane) && sideways && pane !== 'branches') {
|
|
434
472
|
return null;
|
|
435
473
|
}
|
|
436
474
|
const paneKeys = PANE_KEYS[pane];
|
|
@@ -446,6 +484,7 @@ const ACTION_KEYS = {
|
|
|
446
484
|
...NPM_ACTIONS,
|
|
447
485
|
...COMMIT_ACTIONS,
|
|
448
486
|
...BRANCH_ACTIONS,
|
|
487
|
+
...WORKTREE_ACTIONS,
|
|
449
488
|
...ACTIONS,
|
|
450
489
|
};
|
|
451
490
|
|
|
@@ -453,6 +492,17 @@ const actionKeys = (id) => ACTION_KEYS[id] ?? [];
|
|
|
453
492
|
|
|
454
493
|
const actionLetter = (id) => ACTION_HOT[id] ?? actionKeys(id)[0];
|
|
455
494
|
|
|
495
|
+
const ACTION_CAPTION = {
|
|
496
|
+
open: 'open',
|
|
497
|
+
check: 'check',
|
|
498
|
+
textCut: 'cut',
|
|
499
|
+
textCopy: 'copy',
|
|
500
|
+
textPaste: 'paste',
|
|
501
|
+
textSelectAll: 'select all',
|
|
502
|
+
};
|
|
503
|
+
|
|
504
|
+
const actionCaption = (id) => ACTION_CAPTION[id] ?? ACTION_LABEL[id] ?? id;
|
|
505
|
+
|
|
456
506
|
const buttonWord = (id) => {
|
|
457
507
|
const word = ACTION_LABEL[id] ?? ACTION_HOT[id] ?? id;
|
|
458
508
|
const mark = actionLetter(id);
|
|
@@ -489,6 +539,15 @@ const CONFIRM = {
|
|
|
489
539
|
escape: 'decline',
|
|
490
540
|
},
|
|
491
541
|
},
|
|
542
|
+
confirmAgentInstall: {
|
|
543
|
+
choices: YES_NO,
|
|
544
|
+
prefix: (view) => ` install ${view.agentInstallPackage ?? ''}?`,
|
|
545
|
+
keys: {
|
|
546
|
+
y: 'confirm',
|
|
547
|
+
n: 'cancel',
|
|
548
|
+
escape: 'cancel',
|
|
549
|
+
},
|
|
550
|
+
},
|
|
492
551
|
confirmDrop: {
|
|
493
552
|
choices: YES_NO,
|
|
494
553
|
prefix: (view) => ` drop ${view.dropName ?? ''}?`,
|
|
@@ -498,6 +557,24 @@ const CONFIRM = {
|
|
|
498
557
|
escape: 'cancel',
|
|
499
558
|
},
|
|
500
559
|
},
|
|
560
|
+
confirmWorktree: {
|
|
561
|
+
choices: YES_NO,
|
|
562
|
+
prefix: (view) => ` remove ${view.dropName ?? ''}?`,
|
|
563
|
+
keys: {
|
|
564
|
+
y: 'confirm',
|
|
565
|
+
n: 'cancel',
|
|
566
|
+
escape: 'cancel',
|
|
567
|
+
},
|
|
568
|
+
},
|
|
569
|
+
confirmCreateWorktree: {
|
|
570
|
+
choices: YES_NO,
|
|
571
|
+
prefix: (view) => ` create ${view.worktreeTarget ?? ''}?`,
|
|
572
|
+
keys: {
|
|
573
|
+
y: 'confirm',
|
|
574
|
+
n: 'cancel',
|
|
575
|
+
escape: 'cancel',
|
|
576
|
+
},
|
|
577
|
+
},
|
|
501
578
|
confirmPush: {
|
|
502
579
|
choices: [{ letter: 'f', label: 'force push', lead: ' ' }],
|
|
503
580
|
prefix: ' Need force-push?',
|
|
@@ -574,6 +651,7 @@ module.exports = {
|
|
|
574
651
|
isFixupCommit,
|
|
575
652
|
actionFromKey,
|
|
576
653
|
actionLetter,
|
|
654
|
+
actionCaption,
|
|
577
655
|
buttonWord,
|
|
578
656
|
CONFIRM,
|
|
579
657
|
CONFIRM_MODE,
|
package/lib/input/keys.js
CHANGED
|
@@ -45,10 +45,15 @@ const CSI_TILDE = {
|
|
|
45
45
|
24: 'f12',
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
// Kitty CSI-u and xterm modifyOtherKeys. 13 is Enter, 10 is line feed.
|
|
48
49
|
const CSI_U = {
|
|
50
|
+
10: 'enter',
|
|
51
|
+
13: 'enter',
|
|
49
52
|
127: 'backspace',
|
|
50
53
|
};
|
|
51
54
|
|
|
55
|
+
const MODIFY_OTHER = '27';
|
|
56
|
+
|
|
52
57
|
const CSI_MAX = 16;
|
|
53
58
|
|
|
54
59
|
const LITERAL_KEY = {
|
|
@@ -80,6 +85,12 @@ const modifiedKey = (base, modText) => {
|
|
|
80
85
|
const ctrl = (bits & 4) !== 0;
|
|
81
86
|
const shift = (bits & 1) !== 0;
|
|
82
87
|
if (base === 'backspace' && ctrl) return 'ctrl-backspace';
|
|
88
|
+
if (base === 'enter') {
|
|
89
|
+
if (ctrl && shift) return 'ctrl-shift-enter';
|
|
90
|
+
if (ctrl) return 'ctrl-enter';
|
|
91
|
+
if (shift) return 'shift-enter';
|
|
92
|
+
return 'enter';
|
|
93
|
+
}
|
|
83
94
|
if (!arrow && !end) return base;
|
|
84
95
|
if (ctrl && shift && arrow) return `ctrl-shift-${base}`;
|
|
85
96
|
if (ctrl && arrow) return `ctrl-${base}`;
|
|
@@ -87,8 +98,19 @@ const modifiedKey = (base, modText) => {
|
|
|
87
98
|
return base;
|
|
88
99
|
};
|
|
89
100
|
|
|
101
|
+
const NEWLINE_KEYS = ['ctrl-enter', 'shift-enter', 'ctrl-shift-enter'];
|
|
102
|
+
|
|
103
|
+
const isNewlineKey = (key) => NEWLINE_KEYS.includes(key);
|
|
104
|
+
|
|
105
|
+
const otherModified = (params) => {
|
|
106
|
+
const base = CSI_U[params[2]];
|
|
107
|
+
if (!base) return null;
|
|
108
|
+
return modifiedKey(base, params[1] ?? '1');
|
|
109
|
+
};
|
|
110
|
+
|
|
90
111
|
const keyFromCsi = (paramsText, final) => {
|
|
91
112
|
const params = paramsText ? paramsText.split(';') : [];
|
|
113
|
+
if (final === '~' && params[0] === MODIFY_OTHER) return otherModified(params);
|
|
92
114
|
let base = CSI_KEYS[final];
|
|
93
115
|
if (final === '~') base = CSI_TILDE[params[0]];
|
|
94
116
|
if (final === 'u') base = CSI_U[params[0]];
|
|
@@ -115,6 +137,7 @@ const mouseKind = (button, press) => {
|
|
|
115
137
|
const wheel = MOUSE_WHEEL[button];
|
|
116
138
|
if (wheel) return wheel;
|
|
117
139
|
if (!press) return 'release';
|
|
140
|
+
if ((button & 35) === 35) return 'move';
|
|
118
141
|
return button & 32 ? 'drag' : 'press';
|
|
119
142
|
};
|
|
120
143
|
|
|
@@ -197,4 +220,10 @@ const hitAction = (hits, x) => {
|
|
|
197
220
|
return null;
|
|
198
221
|
};
|
|
199
222
|
|
|
200
|
-
module.exports = {
|
|
223
|
+
module.exports = {
|
|
224
|
+
ACTIONS,
|
|
225
|
+
decodeChunk,
|
|
226
|
+
hitAction,
|
|
227
|
+
isTextKey,
|
|
228
|
+
isNewlineKey,
|
|
229
|
+
};
|
package/lib/input/line-edit.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { Editor } = require('./editor.js');
|
|
4
|
-
const { isTextKey } = require('./keys.js');
|
|
4
|
+
const { isTextKey, isNewlineKey } = require('./keys.js');
|
|
5
5
|
const clipboard = require('../term/clipboard.js');
|
|
6
6
|
|
|
7
7
|
const copyLine = (editor, ui) => {
|
|
@@ -57,6 +57,10 @@ const lineKey = (editor, key, ui, ends) => {
|
|
|
57
57
|
copyLine(editor, ui);
|
|
58
58
|
return 'caret';
|
|
59
59
|
}
|
|
60
|
+
if (isNewlineKey(key)) {
|
|
61
|
+
editor.insert('\n');
|
|
62
|
+
return 'text';
|
|
63
|
+
}
|
|
60
64
|
const op = (ends ? LINE_ENDS : LINE_OP)[key];
|
|
61
65
|
if (!op && !isTextKey(key)) return null;
|
|
62
66
|
const before = editor.text;
|
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) => {
|
|
@@ -625,7 +651,7 @@ const agentName = (item) => {
|
|
|
625
651
|
|
|
626
652
|
const agentsBlock = (model, width, height, ctx, tile) => {
|
|
627
653
|
const data = model.agents;
|
|
628
|
-
if (!data || !data.ready) return waiting('detecting
|
|
654
|
+
if (!data || !data.ready) return waiting('detecting agents…');
|
|
629
655
|
const aside = [seg(`${data.running || 0}`, 'key', true)];
|
|
630
656
|
const titleLine = titleAside(tile, aside, width);
|
|
631
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,9 +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 (!
|
|
301
|
+
if (installed || !row || !row.installable) off.push('agentInstall');
|
|
302
|
+
if (!view.agentCanStart) off.push('agentStart');
|
|
302
303
|
return off;
|
|
303
304
|
};
|
|
304
305
|
|
|
@@ -331,14 +332,16 @@ const dimFooterActions = (view) => {
|
|
|
331
332
|
const GIT_FOOTER = ['add', 'unstage', 'revert', 'ignore'];
|
|
332
333
|
|
|
333
334
|
const FOOTER_EXTRA = {
|
|
334
|
-
repos: ['theme'],
|
|
335
|
+
repos: ['theme', 'worktreeNew', 'worktreeDrop'],
|
|
335
336
|
dashboard: ['theme', 'pull', 'push'],
|
|
336
337
|
unit: ['add', 'unstage', 'revert', 'feedback', 'code', 'lines'],
|
|
337
338
|
files: [...GIT_FOOTER, 'code'],
|
|
338
339
|
diff: [...GIT_FOOTER, 'feedback', 'code', 'layout', 'lines'],
|
|
339
|
-
branches: ['newBranch', 'rebase', 'drop', 'pull', 'push'],
|
|
340
|
+
branches: ['newBranch', 'worktreeBranch', 'rebase', 'drop', 'pull', 'push'],
|
|
341
|
+
trees: ['worktreeNew', 'worktreeDrop', 'pull', 'push'],
|
|
340
342
|
commits: ['newCommit', 'apply', 'reword', 'drop', 'view', 'pull', 'push'],
|
|
341
343
|
npmLog: ['npmVerbose', 'npmStop', 'npmRerun'],
|
|
344
|
+
npmFailedLog: ['npmVerbose', 'npmSolve', 'npmRerun'],
|
|
342
345
|
npm: ['npmEdit', 'npmNew', 'npmDrop', 'npmLogs'],
|
|
343
346
|
packages: ['packageNew', 'packageDrop', 'packageWanted', 'packageLatest'],
|
|
344
347
|
agentLog: ['agentStop', 'agentRerun'],
|
|
@@ -348,14 +351,18 @@ const FOOTER_EXTRA = {
|
|
|
348
351
|
'agentFast',
|
|
349
352
|
'agentContext',
|
|
350
353
|
'agentReview',
|
|
351
|
-
'
|
|
354
|
+
'agentInstall',
|
|
355
|
+
'agentStart',
|
|
352
356
|
],
|
|
353
357
|
tasks: ['plan', 'import', 'drop', 'check', 'taskNudge'],
|
|
354
358
|
};
|
|
355
359
|
|
|
356
360
|
const extraFooterActions = (view) => {
|
|
357
361
|
const { pane } = view;
|
|
358
|
-
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
|
+
}
|
|
359
366
|
if (pane === 'npm') return FOOTER_EXTRA.npm;
|
|
360
367
|
if (pane === 'agents' && view.agentView) return FOOTER_EXTRA.agentLog;
|
|
361
368
|
if (pane === 'agents') return FOOTER_EXTRA.agents;
|
|
@@ -382,4 +389,5 @@ module.exports = {
|
|
|
382
389
|
paintFooter,
|
|
383
390
|
footerHits,
|
|
384
391
|
footerLayout,
|
|
392
|
+
dimFooterActions,
|
|
385
393
|
};
|