reslop 0.3.1 → 0.3.3
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 +28 -1
- package/README.md +30 -4
- package/lib/agents/acp.js +309 -0
- package/lib/agents/agents.js +274 -438
- 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/script.js +93 -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 +84 -34
- 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/git/worktree.js
CHANGED
|
@@ -3,13 +3,16 @@
|
|
|
3
3
|
const fs = require('node:fs');
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
|
|
6
|
-
const { runProc, isAbort } = require('../common/process.js');
|
|
6
|
+
const { runProc, isAbort, abortError } = require('../common/process.js');
|
|
7
7
|
const { oneLine } = require('../common/utilities.js');
|
|
8
8
|
const diff = require('../diff/diff.js');
|
|
9
9
|
const { parseDiff, synthesizeNewFile, itemsFromFiles } = diff;
|
|
10
10
|
const files = require('../common/files.js');
|
|
11
11
|
const { groupByPath } = files;
|
|
12
12
|
const { itemPath, isReadOnlyOrigin, lineDelta, REVIEW_DIR } = files;
|
|
13
|
+
const local = require('./local.js');
|
|
14
|
+
const { worktreeRoot, branchOf, remoteOrigin } = local;
|
|
15
|
+
const { parseStatus } = require('./status.js');
|
|
13
16
|
const { replaceBlockAdds } = require('../diff/patch.js');
|
|
14
17
|
|
|
15
18
|
const GIT_CONFIG = [
|
|
@@ -20,10 +23,30 @@ const GIT_CONFIG = [
|
|
|
20
23
|
];
|
|
21
24
|
const DIFF_OPTS = ['--no-color', '--no-ext-diff', '--no-renames', '-U3'];
|
|
22
25
|
const BINARY_SIZE = 1000000;
|
|
26
|
+
const STATUS_ARGS = [
|
|
27
|
+
'-c',
|
|
28
|
+
'core.quotepath=false',
|
|
29
|
+
'-c',
|
|
30
|
+
'color.ui=false',
|
|
31
|
+
'-c',
|
|
32
|
+
'diff.renames=false',
|
|
33
|
+
'status',
|
|
34
|
+
'-vv',
|
|
35
|
+
'--no-renames',
|
|
36
|
+
'--no-ahead-behind',
|
|
37
|
+
'-uall',
|
|
38
|
+
];
|
|
39
|
+
const STATUS_ENV = { LC_MESSAGES: 'C', LANGUAGE: 'C' };
|
|
23
40
|
|
|
24
41
|
const gitOptions = (cwd, extra) => ({
|
|
25
42
|
cwd,
|
|
26
|
-
env: {
|
|
43
|
+
env: {
|
|
44
|
+
...process.env,
|
|
45
|
+
GIT_OPTIONAL_LOCKS: '0',
|
|
46
|
+
GIT_TERMINAL_PROMPT: '0',
|
|
47
|
+
GCM_INTERACTIVE: 'Never',
|
|
48
|
+
...extra.env,
|
|
49
|
+
},
|
|
27
50
|
input: extra.input,
|
|
28
51
|
signal: extra.signal,
|
|
29
52
|
});
|
|
@@ -183,17 +206,12 @@ const untrackedItems = async (top, names) => {
|
|
|
183
206
|
return itemsFromFiles(files, 'untracked');
|
|
184
207
|
};
|
|
185
208
|
|
|
186
|
-
const
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
const items = untrackedItemsFrom(top, namesFromLs(result.stdout));
|
|
191
|
-
let added = 0;
|
|
192
|
-
for (const item of items) added += lineDelta(item).added;
|
|
193
|
-
return added;
|
|
194
|
-
} catch {
|
|
195
|
-
return 0;
|
|
209
|
+
const addedOf = (names, top) => {
|
|
210
|
+
let added = 0;
|
|
211
|
+
for (const item of untrackedItemsFrom(top, names)) {
|
|
212
|
+
added += lineDelta(item).added;
|
|
196
213
|
}
|
|
214
|
+
return added;
|
|
197
215
|
};
|
|
198
216
|
|
|
199
217
|
const gitText = async (top, spec) => {
|
|
@@ -240,13 +258,23 @@ const readDepSides = async (top, rev, origin, rel) => {
|
|
|
240
258
|
return { oldText, newText };
|
|
241
259
|
};
|
|
242
260
|
|
|
243
|
-
const
|
|
261
|
+
const toplevelGit = async (cwd, signal) => {
|
|
244
262
|
const args = ['rev-parse', '--show-toplevel'];
|
|
245
263
|
const result = await runGit(args, cwd, { signal });
|
|
246
264
|
if (result.status !== 0) throw new Error('not a git repository');
|
|
247
265
|
return result.stdout.trim();
|
|
248
266
|
};
|
|
249
267
|
|
|
268
|
+
const toplevel = async (cwd, signal) => {
|
|
269
|
+
if (signal && signal.aborted) throw abortError();
|
|
270
|
+
if (process.env.GIT_DIR || process.env.GIT_WORK_TREE) {
|
|
271
|
+
return toplevelGit(cwd, signal);
|
|
272
|
+
}
|
|
273
|
+
const found = worktreeRoot(cwd);
|
|
274
|
+
if (found) return found;
|
|
275
|
+
return toplevelGit(cwd, signal);
|
|
276
|
+
};
|
|
277
|
+
|
|
250
278
|
const resolveRev = async (cwd, spec) => {
|
|
251
279
|
if (!spec) return null;
|
|
252
280
|
const args = ['rev-parse', '--verify', `${spec}^{commit}`];
|
|
@@ -259,20 +287,19 @@ const shortRev = async (top, commit) => {
|
|
|
259
287
|
return result.status === 0 ? result.stdout.trim() : commit.slice(0, 7);
|
|
260
288
|
};
|
|
261
289
|
|
|
262
|
-
const
|
|
290
|
+
const currentBranch = async (top) => {
|
|
291
|
+
const root = worktreeRoot(top);
|
|
292
|
+
if (root) return branchOf(root);
|
|
293
|
+
const args = ['rev-parse', '--abbrev-ref', 'HEAD'];
|
|
294
|
+
const result = await runGit(args, top);
|
|
263
295
|
if (result.status !== 0) return '';
|
|
264
296
|
const name = result.stdout.trim();
|
|
265
297
|
return name === 'HEAD' ? '' : name;
|
|
266
298
|
};
|
|
267
299
|
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
const currentBranch = async (top) => branchName(await runGit(BRANCH_ARGS, top));
|
|
272
|
-
|
|
273
|
-
const repositoryUrl = (result) => {
|
|
274
|
-
if (result.status !== 0) return '';
|
|
275
|
-
let value = result.stdout.trim();
|
|
300
|
+
const repositoryUrl = (raw) => {
|
|
301
|
+
let value = `${raw ?? ''}`.trim();
|
|
302
|
+
if (!value) return '';
|
|
276
303
|
const scp = /^(?:[^@/\s]+@)?([^:/\s]+):(.+)$/.exec(value);
|
|
277
304
|
if (scp && !value.includes('://')) value = `https://${scp[1]}/${scp[2]}`;
|
|
278
305
|
else if (value.startsWith('git+')) value = value.slice(4);
|
|
@@ -303,12 +330,9 @@ const worktreeSnapshot = (top, outputs, untracked, branch, repoUrl) => {
|
|
|
303
330
|
|
|
304
331
|
const snapshotCommit = async (top, commit, files) => {
|
|
305
332
|
const parsed = dropReviewItems(itemsFromFiles(files, 'commit'));
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
runGit(REMOTE_ARGS, top).then(repositoryUrl),
|
|
310
|
-
];
|
|
311
|
-
const [revShort, branch, repoUrl] = await Promise.all(names);
|
|
333
|
+
const revShort = await shortRev(top, commit);
|
|
334
|
+
const branch = await currentBranch(top);
|
|
335
|
+
const repoUrl = repositoryUrl(remoteOrigin(top));
|
|
312
336
|
return { top, parsed, rev: commit, revShort, branch, repositoryUrl: repoUrl };
|
|
313
337
|
};
|
|
314
338
|
|
|
@@ -324,6 +348,38 @@ const worktreeCommands = (paths) => [
|
|
|
324
348
|
untrackedArgs(paths),
|
|
325
349
|
];
|
|
326
350
|
|
|
351
|
+
const gitView = async (top, paths, signal) => {
|
|
352
|
+
const commands = worktreeCommands(paths);
|
|
353
|
+
const running = commands.map((args) => runGit(args, top, { signal }));
|
|
354
|
+
const results = await Promise.all(running);
|
|
355
|
+
const outputs = results.map((result) => requireOk(result).stdout);
|
|
356
|
+
return {
|
|
357
|
+
staged: outputs[0],
|
|
358
|
+
work: outputs[1],
|
|
359
|
+
untracked: namesFromLs(outputs[2]),
|
|
360
|
+
};
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const worktreeView = async (top, paths, signal) => {
|
|
364
|
+
// status -vv prints every diff and ignores the pathspec
|
|
365
|
+
if (paths.length) return gitView(top, paths, signal);
|
|
366
|
+
const extra = { signal, env: STATUS_ENV };
|
|
367
|
+
const result = await runGit(STATUS_ARGS, top, extra);
|
|
368
|
+
if (result.status !== 0) return gitView(top, paths, signal);
|
|
369
|
+
const parsed = parseStatus(result.stdout);
|
|
370
|
+
if (!parsed) return gitView(top, paths, signal);
|
|
371
|
+
return parsed;
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
const countUntrackedAdded = async (top) => {
|
|
375
|
+
try {
|
|
376
|
+
const view = await worktreeView(top, []);
|
|
377
|
+
return addedOf(view.untracked, top);
|
|
378
|
+
} catch {
|
|
379
|
+
return 0;
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
327
383
|
const readSnapshot = async (cwd, paths, options) => {
|
|
328
384
|
const { commit: rev, signal } = options;
|
|
329
385
|
const top = await toplevel(cwd, signal);
|
|
@@ -331,14 +387,11 @@ const readSnapshot = async (cwd, paths, options) => {
|
|
|
331
387
|
const files = await runSteps(commitDiff(rev, paths), top, signal);
|
|
332
388
|
return snapshotCommit(top, rev, files);
|
|
333
389
|
}
|
|
334
|
-
const
|
|
335
|
-
const
|
|
336
|
-
const
|
|
337
|
-
const repoUrl = repositoryUrl(
|
|
338
|
-
const
|
|
339
|
-
const outputs = results.map((result) => requireOk(result).stdout);
|
|
340
|
-
const names = namesFromLs(outputs[2]);
|
|
341
|
-
const untracked = await untrackedItems(top, names);
|
|
390
|
+
const view = await worktreeView(top, paths, signal);
|
|
391
|
+
const untracked = await untrackedItems(top, view.untracked);
|
|
392
|
+
const branch = await currentBranch(top);
|
|
393
|
+
const repoUrl = repositoryUrl(remoteOrigin(top));
|
|
394
|
+
const outputs = [view.staged, view.work];
|
|
342
395
|
return worktreeSnapshot(top, outputs, untracked, branch, repoUrl);
|
|
343
396
|
};
|
|
344
397
|
|
|
@@ -475,6 +528,29 @@ const stagePath = async (top, rel) => {
|
|
|
475
528
|
if (rel) await gitOk(['add', '--', rel], top);
|
|
476
529
|
};
|
|
477
530
|
|
|
531
|
+
const sumDelta = (items) => {
|
|
532
|
+
let added = 0;
|
|
533
|
+
let removed = 0;
|
|
534
|
+
for (const item of items) {
|
|
535
|
+
const delta = lineDelta(item);
|
|
536
|
+
added += delta.added;
|
|
537
|
+
removed += delta.removed;
|
|
538
|
+
}
|
|
539
|
+
return { added, removed };
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
const diffCounts = async (top) => {
|
|
543
|
+
const view = await worktreeView(top, []);
|
|
544
|
+
const staged = sumDelta(itemsFromFiles(parseDiff(view.staged), 'staged'));
|
|
545
|
+
const open = sumDelta(itemsFromFiles(parseDiff(view.work), 'unstaged'));
|
|
546
|
+
return {
|
|
547
|
+
stagedAdded: staged.added,
|
|
548
|
+
unstagedAdded: open.added + addedOf(view.untracked, top),
|
|
549
|
+
stagedRemoved: staged.removed,
|
|
550
|
+
unstagedRemoved: open.removed,
|
|
551
|
+
};
|
|
552
|
+
};
|
|
553
|
+
|
|
478
554
|
module.exports = {
|
|
479
555
|
requireOk,
|
|
480
556
|
gitOk,
|
|
@@ -492,6 +568,7 @@ module.exports = {
|
|
|
492
568
|
currentBranch,
|
|
493
569
|
fileText,
|
|
494
570
|
countUntrackedAdded,
|
|
571
|
+
diffCounts,
|
|
495
572
|
listScopeFiles,
|
|
496
573
|
readSnapshot,
|
|
497
574
|
editItem,
|
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;
|