reslop 0.2.6 → 0.3.0
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 +27 -1
- package/README.md +16 -14
- package/bin/reslop.js +8 -5
- package/lib/agents/agents.js +504 -0
- package/lib/agents/allow.js +71 -0
- package/lib/agents/allow.json +16 -0
- package/lib/agents/models.js +460 -0
- package/lib/api.js +15 -12
- package/lib/cli.js +61 -66
- package/lib/{files.js → common/files.js} +51 -52
- package/lib/common/find.js +61 -0
- package/lib/common/format.js +101 -0
- package/lib/{utilities.js → common/process.js} +7 -95
- package/lib/common/tasks.json +27 -0
- package/lib/common/utilities.js +112 -0
- package/lib/dashboard/git.js +9 -16
- package/lib/dashboard/model.js +117 -51
- package/lib/dashboard/npm.js +133 -32
- package/lib/dashboard/tree.js +27 -30
- package/lib/{session → dashboard}/watch.js +41 -31
- package/lib/dashboard/workspace.js +151 -0
- package/lib/{deps.js → deps/deps.js} +31 -45
- package/lib/{deps-diff.js → deps/diff.js} +35 -25
- package/lib/{manifest.js → deps/manifest.js} +47 -30
- package/lib/diff/align.js +1 -2
- package/lib/diff/diff.js +2 -2
- package/lib/diff/display.js +29 -5
- package/lib/diff/inline.js +10 -14
- package/lib/diff/items.js +6 -4
- package/lib/diff/patch.js +39 -3
- package/lib/diff/unit.js +45 -16
- package/lib/{git-branches.js → git/branches.js} +53 -48
- package/lib/{git-deps.js → git/deps.js} +123 -95
- package/lib/git/git.js +71 -0
- package/lib/git/worktree.js +475 -0
- package/lib/highlight/bash.js +1 -2
- package/lib/highlight/csv.js +1 -2
- package/lib/highlight/dot.js +1 -2
- package/lib/{highlight.js → highlight/highlight.js} +42 -11
- package/lib/highlight/html.js +20 -18
- package/lib/highlight/json.js +1 -2
- package/lib/highlight/log.js +1 -2
- package/lib/highlight/txt.js +1 -2
- package/lib/{session → input}/actions.js +240 -71
- package/lib/{editor.js → input/editor.js} +7 -8
- package/lib/{keys.js → input/keys.js} +36 -39
- package/lib/input/line-edit.js +77 -0
- package/lib/input/menu-pick.js +70 -0
- package/lib/render/agents.js +640 -0
- package/lib/render/branches.js +4 -5
- package/lib/render/commits.js +54 -64
- package/lib/render/{dash-activity.js → dashboard/activity.js} +25 -40
- package/lib/render/dashboard/blocks.js +490 -0
- package/lib/render/{dashboard.js → dashboard/dashboard.js} +25 -22
- package/lib/render/diff.js +166 -45
- package/lib/render/field.js +131 -0
- package/lib/render/files.js +22 -8
- package/lib/render/footer.js +383 -0
- package/lib/render/frame.js +127 -25
- package/lib/render/header.js +176 -0
- package/lib/render/log.js +319 -0
- package/lib/render/menu.js +60 -0
- package/lib/render/notes.js +29 -13
- package/lib/render/npm.js +322 -196
- package/lib/render/packages.js +89 -0
- package/lib/render/primitives.js +109 -16
- package/lib/render/render.js +7 -11
- package/lib/render/repos.js +214 -0
- package/lib/render/status.js +370 -0
- package/lib/render/status.json +85 -0
- package/lib/render/{dash-table.js → table.js} +19 -61
- package/lib/render/tiles.js +203 -47
- package/lib/render/unit.js +32 -13
- package/lib/{report-model.js → report/model.js} +4 -6
- package/lib/{report-parse.js → report/parse.js} +9 -14
- package/lib/{report-render.js → report/render.js} +46 -14
- package/lib/{report-run.js → report/run.js} +42 -30
- package/lib/{report-test.js → report/test.js} +56 -55
- package/lib/{report-tools.js → report/tools.js} +24 -33
- package/lib/review/agent-instructions.md +9 -0
- package/lib/review/format.js +426 -0
- package/lib/{review-merge.js → review/merge.js} +9 -8
- package/lib/review/review.js +190 -0
- package/lib/review/store.js +281 -0
- package/lib/review/templates.js +83 -0
- package/lib/runs/commands.js +14 -0
- package/lib/runs/logs.js +299 -0
- package/lib/runs/output.js +174 -0
- package/lib/runs/process.js +113 -0
- package/lib/{runs.js → runs/runs.js} +84 -36
- package/lib/runs/scripts.js +150 -0
- package/lib/session/agents/agents.js +958 -0
- package/lib/session/agents/job.js +320 -0
- package/lib/session/agents/jobs.js +355 -0
- package/lib/session/agents/options.js +153 -0
- package/lib/session/agents/plans.js +83 -0
- package/lib/session/branches.js +36 -44
- package/lib/session/change-actions.js +34 -100
- package/lib/session/commit.js +122 -56
- package/lib/session/compose.js +197 -146
- package/lib/session/dashboard.js +183 -37
- package/lib/session/dispatch.js +120 -22
- package/lib/session/files-pane.js +60 -24
- package/lib/session/find.js +81 -0
- package/lib/session/import.js +82 -0
- package/lib/session/items.js +8 -17
- package/lib/session/lifecycle.js +96 -66
- package/lib/session/load.js +2 -4
- package/lib/session/navigation.js +8 -3
- package/lib/session/npm/history.js +249 -0
- package/lib/session/npm/npm.js +815 -0
- package/lib/session/npm/packages.js +150 -0
- package/lib/session/ops.js +125 -34
- package/lib/session/pointer.js +216 -10
- package/lib/session/prefs.js +112 -0
- package/lib/session/reads.js +109 -0
- package/lib/session/review.js +28 -10
- package/lib/session/run-panel.js +40 -0
- package/lib/{session.js → session/session.js} +166 -59
- package/lib/session/tasks.js +321 -66
- package/lib/session/templates.js +1 -1
- package/lib/session/terminal.js +94 -12
- package/lib/session/unit.js +35 -30
- package/lib/session/update.js +194 -6
- package/lib/session/view.js +72 -59
- package/lib/session/workspace.js +416 -0
- package/lib/{capabilities.js → source/capabilities.js} +3 -12
- package/lib/{github.js → source/github.js} +102 -32
- package/lib/{gitlab.js → source/gitlab.js} +87 -10
- package/lib/{remote.js → source/remote.js} +144 -40
- package/lib/source/review-threads.graphql +20 -0
- package/lib/source/source.js +74 -0
- package/lib/term/ansi.js +396 -0
- package/lib/{clipboard.js → term/clipboard.js} +3 -6
- package/lib/{select.js → term/select.js} +2 -2
- package/lib/term/themes/dark.json +122 -0
- package/lib/term/themes/light.json +122 -0
- package/lib/term/wide.txt +21 -0
- package/lib/{wrap.js → term/wrap.js} +4 -4
- package/package.json +2 -2
- package/lib/ansi.js +0 -505
- package/lib/detect.js +0 -34
- package/lib/diff/edit.js +0 -33
- package/lib/git-worktree.js +0 -437
- package/lib/git.js +0 -79
- package/lib/npm-commands.js +0 -522
- package/lib/render/chrome.js +0 -628
- package/lib/render/dash-blocks.js +0 -245
- package/lib/render/present.js +0 -19
- package/lib/render/report.js +0 -43
- package/lib/review.js +0 -744
- package/lib/session/accessors.js +0 -26
- package/lib/session/edit-click.js +0 -57
- package/lib/session/npm.js +0 -452
- package/lib/session/progress.js +0 -41
- package/lib/source.js +0 -36
- package/lib/update.js +0 -192
- /package/lib/{git-reword-todo.js → git/reword-todo.js} +0 -0
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {
|
|
4
|
+
IS_WIN,
|
|
5
|
+
npmBin,
|
|
6
|
+
runProc,
|
|
7
|
+
INSTALL_MS,
|
|
8
|
+
} = require('../../common/process.js');
|
|
9
|
+
const { oneLine } = require('../../common/utilities.js');
|
|
10
|
+
|
|
11
|
+
class PackageController {
|
|
12
|
+
constructor(ui) {
|
|
13
|
+
this.ui = ui;
|
|
14
|
+
this.dropName = '';
|
|
15
|
+
this.addDev = false;
|
|
16
|
+
this.running = false;
|
|
17
|
+
this.job = null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
rows() {
|
|
21
|
+
const data = this.ui.dashboard.packagesView();
|
|
22
|
+
const modules = data.modules;
|
|
23
|
+
return modules && modules.packages ? modules.packages : [];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
selected() {
|
|
27
|
+
return this.rows()[this.ui.nav.packagesCursor] ?? null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
canWrite() {
|
|
31
|
+
if (this.running) return false;
|
|
32
|
+
const ui = this.ui;
|
|
33
|
+
const caps = ui.capabilities;
|
|
34
|
+
if (!ui.readOnly && (!caps || caps.changes !== false)) return true;
|
|
35
|
+
ui.status = 'read only';
|
|
36
|
+
return false;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
need() {
|
|
40
|
+
if (!this.canWrite()) return null;
|
|
41
|
+
const pkg = this.selected();
|
|
42
|
+
if (pkg) return pkg;
|
|
43
|
+
this.ui.status = 'not a package';
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
create() {
|
|
48
|
+
if (!this.canWrite()) return;
|
|
49
|
+
const pkg = this.selected();
|
|
50
|
+
if (pkg && pkg.transitive) {
|
|
51
|
+
this.ui.status = 'transitive';
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.addDev = Boolean(pkg && pkg.dev);
|
|
55
|
+
this.ui.composer.openCompose('package', '');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
finishAdd() {
|
|
59
|
+
const editor = this.ui.composer.editor;
|
|
60
|
+
if (!editor) return;
|
|
61
|
+
const name = editor.text.trim();
|
|
62
|
+
if (!name || /\s/.test(name) || name.startsWith('-')) {
|
|
63
|
+
this.ui.status = 'name';
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
const args = ['install', name];
|
|
67
|
+
if (this.addDev) args.push('--save-dev');
|
|
68
|
+
this.ui.composer.closeCompose();
|
|
69
|
+
this.job = this.exec(args, 'npm i', `installed ${name}`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
askDrop() {
|
|
73
|
+
const pkg = this.need();
|
|
74
|
+
if (!pkg) return;
|
|
75
|
+
if (pkg.transitive) {
|
|
76
|
+
this.ui.status = 'transitive';
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
this.dropName = pkg.name;
|
|
80
|
+
this.ui.mode = 'confirmDrop';
|
|
81
|
+
this.ui.status = '';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
cancelDrop() {
|
|
85
|
+
this.dropName = '';
|
|
86
|
+
this.ui.mode = 'review';
|
|
87
|
+
this.ui.status = '';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
confirmDrop() {
|
|
91
|
+
const name = this.dropName;
|
|
92
|
+
this.dropName = '';
|
|
93
|
+
this.ui.mode = 'review';
|
|
94
|
+
if (!name) return;
|
|
95
|
+
const done = `dropped ${name}`;
|
|
96
|
+
this.job = this.exec(['uninstall', name], 'npm uninstall', done);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
updateWanted() {
|
|
100
|
+
const pkg = this.need();
|
|
101
|
+
if (!pkg) return;
|
|
102
|
+
const done = `updated ${pkg.name}`;
|
|
103
|
+
this.job = this.exec(['update', pkg.name], 'npm i', done);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
updateLatest() {
|
|
107
|
+
const pkg = this.need();
|
|
108
|
+
if (!pkg) return;
|
|
109
|
+
const args = ['install', `${pkg.name}@latest`];
|
|
110
|
+
if (pkg.dev) args.push('--save-dev');
|
|
111
|
+
this.job = this.exec(args, 'npm i', `updated ${pkg.name}`);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async exec(args, busy, done) {
|
|
115
|
+
const ui = this.ui;
|
|
116
|
+
this.running = true;
|
|
117
|
+
ui.status = '';
|
|
118
|
+
ui.ops.setBusy(busy);
|
|
119
|
+
ui.ignoreWatch(INSTALL_MS);
|
|
120
|
+
ui.paint();
|
|
121
|
+
const run = async () => {
|
|
122
|
+
try {
|
|
123
|
+
const hook = ui.repo && ui.repo.runPackage;
|
|
124
|
+
const options = { cwd: ui.top, shell: IS_WIN };
|
|
125
|
+
const started = hook
|
|
126
|
+
? hook(ui.top, args)
|
|
127
|
+
: runProc(npmBin(), args, options);
|
|
128
|
+
const result = await started;
|
|
129
|
+
return result;
|
|
130
|
+
} catch (error) {
|
|
131
|
+
return { error, status: 1, stderr: '' };
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
const result = await run();
|
|
135
|
+
this.running = false;
|
|
136
|
+
ui.ops.clearBusy();
|
|
137
|
+
const failed = !result || result.error || result.status;
|
|
138
|
+
if (failed) {
|
|
139
|
+
const message = result && result.error ? result.error.message : '';
|
|
140
|
+
const stderr = result ? result.stderr : '';
|
|
141
|
+
ui.status = oneLine(stderr, message || 'npm failed');
|
|
142
|
+
} else {
|
|
143
|
+
ui.status = done;
|
|
144
|
+
ui.dashboard.tasks.npm.request();
|
|
145
|
+
}
|
|
146
|
+
ui.paint();
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = { PackageController };
|
package/lib/session/ops.js
CHANGED
|
@@ -1,16 +1,17 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const { errorMessage } = require('../common/utilities.js');
|
|
4
|
+
const { npmBusyKind } = require('./items.js');
|
|
5
5
|
|
|
6
|
-
const
|
|
7
|
-
|
|
8
|
-
const allowAsync = uiOpen === true || ASYNC_WITHOUT_UI.includes(name);
|
|
9
|
-
const isAsync = allowAsync && typeof asyncFn === 'function';
|
|
10
|
-
return { async: isAsync, write: isAsync ? asyncFn : repo[name] };
|
|
11
|
-
};
|
|
6
|
+
const QUIET_KIND = ['staged', 'unstaged'];
|
|
7
|
+
const POLL_MS = 5;
|
|
12
8
|
|
|
13
|
-
const
|
|
9
|
+
const pause = (ms) =>
|
|
10
|
+
new Promise((resolve) => {
|
|
11
|
+
setTimeout(resolve, ms);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
const showsBusy = (kind) => Boolean(kind) && !QUIET_KIND.includes(kind);
|
|
14
15
|
|
|
15
16
|
class ChangeQueue {
|
|
16
17
|
constructor(ui, runner) {
|
|
@@ -22,6 +23,7 @@ class ChangeQueue {
|
|
|
22
23
|
this.draining = false;
|
|
23
24
|
this.pumpQueued = false;
|
|
24
25
|
this.holding = false;
|
|
26
|
+
this.long = false;
|
|
25
27
|
this.resume = null;
|
|
26
28
|
this.reloading = false;
|
|
27
29
|
}
|
|
@@ -43,6 +45,7 @@ class ChangeQueue {
|
|
|
43
45
|
this.draining = false;
|
|
44
46
|
this.pumpQueued = false;
|
|
45
47
|
this.holding = false;
|
|
48
|
+
this.long = false;
|
|
46
49
|
this.reloading = false;
|
|
47
50
|
this.noteFree();
|
|
48
51
|
}
|
|
@@ -87,6 +90,7 @@ class ChangeQueue {
|
|
|
87
90
|
async takeJob(stamp) {
|
|
88
91
|
const job = this.jobs[this.head];
|
|
89
92
|
this.head += 1;
|
|
93
|
+
this.long = showsBusy(job.kind);
|
|
90
94
|
this.armBusy(job.kind);
|
|
91
95
|
try {
|
|
92
96
|
await job.write(this.ui.top);
|
|
@@ -101,12 +105,20 @@ class ChangeQueue {
|
|
|
101
105
|
return job.reload ? 'reload' : 'ok';
|
|
102
106
|
}
|
|
103
107
|
|
|
108
|
+
// Only writes that land in milliseconds are queued or running: none shows
|
|
109
|
+
// a spinner and none waits behind a long git operation
|
|
110
|
+
quick() {
|
|
111
|
+
const working = this.draining || this.pumpQueued;
|
|
112
|
+
if (!working || this.long || this.hasPendingBusy()) return false;
|
|
113
|
+
return this.holding || !this.runner.gitBusy;
|
|
114
|
+
}
|
|
115
|
+
|
|
104
116
|
schedule() {
|
|
105
117
|
if (this.draining || this.pumpQueued) return;
|
|
106
118
|
this.pumpQueued = true;
|
|
107
119
|
queueMicrotask(() => {
|
|
108
120
|
this.pumpQueued = false;
|
|
109
|
-
|
|
121
|
+
this.runner.track(this.run());
|
|
110
122
|
});
|
|
111
123
|
}
|
|
112
124
|
|
|
@@ -158,9 +170,89 @@ class OpsRunner {
|
|
|
158
170
|
this.ui = ui;
|
|
159
171
|
this.gitBusy = false;
|
|
160
172
|
this.busy = '';
|
|
173
|
+
this.pending = new Set();
|
|
174
|
+
this.writing = Promise.resolve();
|
|
175
|
+
this.writers = 0;
|
|
176
|
+
this.holds = 0;
|
|
177
|
+
this.held = [];
|
|
161
178
|
this.queue = new ChangeQueue(ui, this);
|
|
162
179
|
}
|
|
163
180
|
|
|
181
|
+
track(promise) {
|
|
182
|
+
const settled = () => this.pending.delete(promise);
|
|
183
|
+
this.pending.add(promise);
|
|
184
|
+
promise.then(settled, settled);
|
|
185
|
+
return promise;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
exclusive(write) {
|
|
189
|
+
const start = async () => write(this.ui.top);
|
|
190
|
+
const turn = this.writers ? this.writing.then(start) : start();
|
|
191
|
+
const release = () => {
|
|
192
|
+
this.writers -= 1;
|
|
193
|
+
};
|
|
194
|
+
this.writers += 1;
|
|
195
|
+
this.writing = turn.then(release, release);
|
|
196
|
+
return turn;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Stages one item in its turn. An install can run for minutes, so it gets
|
|
200
|
+
// the lock to take around its own file and index steps instead
|
|
201
|
+
add(item) {
|
|
202
|
+
const { repo, top } = this.ui;
|
|
203
|
+
const locked = (write) => this.exclusive(write);
|
|
204
|
+
if (npmBusyKind(item)) return repo.add(top, item, { locked });
|
|
205
|
+
return locked((root) => repo.add(root, item));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
unstage(item) {
|
|
209
|
+
return this.exclusive((top) => this.ui.repo.unstage(top, item));
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Input that arrives while a read is held waits for it, so keys keep order
|
|
213
|
+
hold(reading) {
|
|
214
|
+
const fail = (error) => {
|
|
215
|
+
this.ui.status = errorMessage(error);
|
|
216
|
+
};
|
|
217
|
+
const release = (value) => {
|
|
218
|
+
this.holds -= 1;
|
|
219
|
+
if (!this.holds) this.resume();
|
|
220
|
+
return value;
|
|
221
|
+
};
|
|
222
|
+
this.holds += 1;
|
|
223
|
+
return this.track(reading.catch(fail).then(release));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
defer(event) {
|
|
227
|
+
if (!this.holds) return false;
|
|
228
|
+
this.held.push(event);
|
|
229
|
+
return true;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
resume() {
|
|
233
|
+
const { ui, held } = this;
|
|
234
|
+
while (held.length && !this.holds && !ui.done) ui.handleEvent(held.shift());
|
|
235
|
+
if (ui.done) return void ui.finishInput();
|
|
236
|
+
ui.paint();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
unlanded() {
|
|
240
|
+
return this.writers > 0 || this.queue.quick();
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Waits for quick repo writes to land, but never longer than the limit
|
|
244
|
+
async landed(limit) {
|
|
245
|
+
const deadline = Date.now() + limit;
|
|
246
|
+
while (this.unlanded() && Date.now() < deadline) await pause(POLL_MS);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
async idle() {
|
|
250
|
+
await Promise.resolve();
|
|
251
|
+
while (this.pending.size || this.queue.pumpQueued) {
|
|
252
|
+
await Promise.allSettled([...this.pending]);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
164
256
|
setBusy(kind) {
|
|
165
257
|
this.busy = kind;
|
|
166
258
|
this.ui.startProgress();
|
|
@@ -174,21 +266,10 @@ class OpsRunner {
|
|
|
174
266
|
reset() {
|
|
175
267
|
this.gitBusy = false;
|
|
176
268
|
this.busy = '';
|
|
269
|
+
this.held.length = 0;
|
|
177
270
|
this.queue.reset();
|
|
178
271
|
}
|
|
179
272
|
|
|
180
|
-
runRepo(label, write) {
|
|
181
|
-
const { ui } = this;
|
|
182
|
-
try {
|
|
183
|
-
write(ui.top);
|
|
184
|
-
ui.status = label;
|
|
185
|
-
return true;
|
|
186
|
-
} catch (error) {
|
|
187
|
-
ui.status = error.message;
|
|
188
|
-
return false;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
|
|
192
273
|
finishBusy(done, after) {
|
|
193
274
|
const { ui } = this;
|
|
194
275
|
if (ui.done || ui.mode === 'confirmPush') return void ui.paint();
|
|
@@ -197,7 +278,11 @@ class OpsRunner {
|
|
|
197
278
|
ui.paint();
|
|
198
279
|
}
|
|
199
280
|
|
|
200
|
-
|
|
281
|
+
runBusy(kind, done, write, after) {
|
|
282
|
+
return this.track(this.busyWrite(kind, done, write, after));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async busyWrite(kind, done, write, after) {
|
|
201
286
|
const { ui } = this;
|
|
202
287
|
if (this.gitBusy) return;
|
|
203
288
|
this.gitBusy = true;
|
|
@@ -223,17 +308,23 @@ class OpsRunner {
|
|
|
223
308
|
|
|
224
309
|
runWrite(name, args, kind, done, after, leave = null) {
|
|
225
310
|
const { ui } = this;
|
|
226
|
-
const
|
|
227
|
-
const write = (top) => picked.write(top, ...args);
|
|
228
|
-
if (picked.async) {
|
|
229
|
-
if (leave) leave();
|
|
230
|
-
const refresh = () => after({ doneStatus: done });
|
|
231
|
-
return void this.runBusy(kind, done, write, refresh);
|
|
232
|
-
}
|
|
233
|
-
if (!this.runRepo(done, write)) return;
|
|
311
|
+
const write = (top) => ui.repo[name](top, ...args);
|
|
234
312
|
if (leave) leave();
|
|
235
|
-
after({});
|
|
236
|
-
|
|
313
|
+
const refresh = () => after({ doneStatus: done });
|
|
314
|
+
return this.runBusy(kind, done, write, refresh);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async apply(label, write, after) {
|
|
318
|
+
const { ui } = this;
|
|
319
|
+
try {
|
|
320
|
+
await this.exclusive(write);
|
|
321
|
+
ui.status = label;
|
|
322
|
+
if (after) after();
|
|
323
|
+
return true;
|
|
324
|
+
} catch (error) {
|
|
325
|
+
ui.status = error.message;
|
|
326
|
+
return false;
|
|
327
|
+
}
|
|
237
328
|
}
|
|
238
329
|
|
|
239
330
|
enqueueLazy(job) {
|
|
@@ -241,4 +332,4 @@ class OpsRunner {
|
|
|
241
332
|
}
|
|
242
333
|
}
|
|
243
334
|
|
|
244
|
-
module.exports = {
|
|
335
|
+
module.exports = { OpsRunner };
|
package/lib/session/pointer.js
CHANGED
|
@@ -1,13 +1,53 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const { extractText, isRange } = require('../term/select.js');
|
|
4
|
+
const { scrollAtThumb, scrollThumb } = require('../render/primitives.js');
|
|
5
|
+
const { indexAtRow } = require('../term/wrap.js');
|
|
5
6
|
const filesPane = require('./files-pane.js');
|
|
6
|
-
const {
|
|
7
|
-
const
|
|
8
|
-
const {
|
|
9
|
-
|
|
10
|
-
const
|
|
7
|
+
const { hitAction } = require('../input/keys.js');
|
|
8
|
+
const { copyText } = require('../term/clipboard.js');
|
|
9
|
+
const { stopImport } = require('./import.js');
|
|
10
|
+
|
|
11
|
+
const hitAt = (hits, cell) => {
|
|
12
|
+
for (const hit of hits) {
|
|
13
|
+
if (hit.y !== cell.y) continue;
|
|
14
|
+
const x0 = hit.x0 ?? hit.textX;
|
|
15
|
+
if (cell.x < x0) continue;
|
|
16
|
+
if (hit.x1 !== undefined && cell.x >= hit.x1) continue;
|
|
17
|
+
return hit;
|
|
18
|
+
}
|
|
19
|
+
return null;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const offsetOf = (hit, cellX) => {
|
|
23
|
+
const shown = cellX - hit.textX;
|
|
24
|
+
const col = Math.max(0, shown) + (hit.scroll || 0);
|
|
25
|
+
const at = indexAtRow(hit.start || 0, hit.text, col);
|
|
26
|
+
if (hit.limit !== undefined && at > hit.limit) return hit.limit;
|
|
27
|
+
return at;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const focusField = (ui, hit) => {
|
|
31
|
+
if (!hit.field || !ui.npm) return true;
|
|
32
|
+
if (hit.field === ui.npm.editField) return true;
|
|
33
|
+
return ui.npm.focusField(hit.field);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const placeClick = (ui, cell) => {
|
|
37
|
+
if (ui.mode !== 'compose' || !ui.editor) return false;
|
|
38
|
+
const hits = ui.lastFrame && ui.lastFrame.editHits;
|
|
39
|
+
if (!hits || !hits.length) return false;
|
|
40
|
+
const hit = hitAt(hits, cell);
|
|
41
|
+
if (!hit) return false;
|
|
42
|
+
if (!focusField(ui, hit)) {
|
|
43
|
+
ui.paint();
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
ui.editor.place(offsetOf(hit, cell.x));
|
|
47
|
+
ui.composer.syncCodeScroll();
|
|
48
|
+
ui.paint();
|
|
49
|
+
return true;
|
|
50
|
+
};
|
|
11
51
|
|
|
12
52
|
const mouseCell = (ui, event) => {
|
|
13
53
|
const size = ui.lastSize ?? ui.getSize();
|
|
@@ -16,9 +56,68 @@ const mouseCell = (ui, event) => {
|
|
|
16
56
|
return { x, y };
|
|
17
57
|
};
|
|
18
58
|
|
|
59
|
+
const onLogBar = (bar, cell) => {
|
|
60
|
+
if (!bar || cell.x !== bar.x) return false;
|
|
61
|
+
const index = cell.y - bar.y;
|
|
62
|
+
return index >= 0 && index < bar.height;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
const logLocal = (bar, cell) => {
|
|
66
|
+
const local = cell.y - bar.y - bar.track;
|
|
67
|
+
if (local < 0) return 0;
|
|
68
|
+
const last = bar.rows - 1;
|
|
69
|
+
if (local > last) return last;
|
|
70
|
+
return local;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const logGrab = (local, thumb) => {
|
|
74
|
+
if (local <= thumb.at) return 0;
|
|
75
|
+
const end = thumb.at + thumb.size - 1;
|
|
76
|
+
if (local >= end) return thumb.size - 1;
|
|
77
|
+
return local - thumb.at;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const moveLogBar = (ui, cell) => {
|
|
81
|
+
const bar = ui.lastFrame && ui.lastFrame.scrollBar;
|
|
82
|
+
const drag = ui.nav.scrollDrag;
|
|
83
|
+
if (!bar || !drag) return;
|
|
84
|
+
const top = logLocal(bar, cell) - drag.grab;
|
|
85
|
+
const at = scrollAtThumb(top, drag.thumb);
|
|
86
|
+
const panel = ui.pane === 'npm' ? ui.npm : ui.agents;
|
|
87
|
+
if (panel.viewing) panel.placeLog(at);
|
|
88
|
+
else panel.placeRuns(at);
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const startLogDrag = (ui, cell) => {
|
|
92
|
+
const bar = ui.lastFrame && ui.lastFrame.scrollBar;
|
|
93
|
+
if (!onLogBar(bar, cell)) return false;
|
|
94
|
+
const thumb = scrollThumb(bar.count, bar.rows, bar.start);
|
|
95
|
+
if (!thumb) return false;
|
|
96
|
+
const nav = ui.nav;
|
|
97
|
+
const local = logLocal(bar, cell);
|
|
98
|
+
nav.scrollDrag = { grab: logGrab(local, thumb), thumb };
|
|
99
|
+
nav.mouseAnchor = null;
|
|
100
|
+
nav.pendingClick = null;
|
|
101
|
+
nav.selection = null;
|
|
102
|
+
moveLogBar(ui, cell);
|
|
103
|
+
return true;
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
const dismissImport = (ui, event) => {
|
|
107
|
+
if (ui.mode !== 'import') return;
|
|
108
|
+
const frame = ui.lastFrame;
|
|
109
|
+
const cell = mouseCell(ui, event);
|
|
110
|
+
const onField = frame && cell.y === frame.height - 1;
|
|
111
|
+
if (onField) return;
|
|
112
|
+
stopImport(ui);
|
|
113
|
+
};
|
|
114
|
+
|
|
19
115
|
const onPress = (ui, event) => {
|
|
116
|
+
dismissImport(ui, event);
|
|
20
117
|
const nav = ui.nav;
|
|
21
118
|
const cell = mouseCell(ui, event);
|
|
119
|
+
if (startLogDrag(ui, cell)) return;
|
|
120
|
+
nav.scrollDrag = null;
|
|
22
121
|
nav.mouseAnchor = cell;
|
|
23
122
|
nav.pendingClick = null;
|
|
24
123
|
nav.selection = null;
|
|
@@ -30,11 +129,13 @@ const onPress = (ui, event) => {
|
|
|
30
129
|
|
|
31
130
|
const onDrag = (ui, event) => {
|
|
32
131
|
const nav = ui.nav;
|
|
132
|
+
const cell = mouseCell(ui, event);
|
|
133
|
+
if (nav.scrollDrag) return void moveLogBar(ui, cell);
|
|
33
134
|
if (!nav.mouseAnchor) return;
|
|
34
135
|
nav.pendingClick = null;
|
|
35
136
|
nav.selection = {
|
|
36
137
|
start: nav.mouseAnchor,
|
|
37
|
-
end:
|
|
138
|
+
end: cell,
|
|
38
139
|
};
|
|
39
140
|
};
|
|
40
141
|
|
|
@@ -89,12 +190,48 @@ const copySelection = (ui) => {
|
|
|
89
190
|
ui.status = ok ? 'copied' : 'copy failed';
|
|
90
191
|
};
|
|
91
192
|
|
|
193
|
+
const COMBO_FIELDS = ['model', 'effort', 'context', 'plan'];
|
|
194
|
+
|
|
195
|
+
const onAgentRunHit = (ui, hit) => {
|
|
196
|
+
ui.agents.focus = 'runs';
|
|
197
|
+
ui.agents.runCursor = hit.cursor;
|
|
198
|
+
const again = armDoubleClick(ui.nav, 'agent-runs', hit.cursor);
|
|
199
|
+
if (!again || ui.mode !== 'review') return;
|
|
200
|
+
const session = ui.agents.selectedSession();
|
|
201
|
+
if (ui.agents.openRunRow(session)) return;
|
|
202
|
+
ui.agents.start();
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const onAgentHit = (ui, hit) => {
|
|
206
|
+
if (hit.side === 'runs') return void onAgentRunHit(ui, hit);
|
|
207
|
+
ui.agents.focusCli();
|
|
208
|
+
const changed = ui.nav.agentCursor !== hit.cursor;
|
|
209
|
+
ui.nav.agentCursor = hit.cursor;
|
|
210
|
+
if (changed) {
|
|
211
|
+
ui.agents.runCursor = 0;
|
|
212
|
+
ui.agents.runScroll = 0;
|
|
213
|
+
}
|
|
214
|
+
const combo = COMBO_FIELDS.includes(hit.field);
|
|
215
|
+
if (combo) return void ui.agents.openPick(hit.field);
|
|
216
|
+
if (hit.field === 'fast') {
|
|
217
|
+
ui.agents.toggleFast();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
const again = armDoubleClick(ui.nav, 'agents', hit.cursor);
|
|
221
|
+
if (again && ui.mode === 'review') ui.agents.start();
|
|
222
|
+
};
|
|
223
|
+
|
|
92
224
|
const RELEASE_TARGETS = [
|
|
93
225
|
{
|
|
94
226
|
match: () => true,
|
|
95
227
|
hits: 'statusHits',
|
|
96
228
|
onHit: (ui, hit) => {
|
|
97
229
|
if (hit.id === 'branch') return void ui.gitBranches.onBranch();
|
|
230
|
+
if (hit.id === 'plan') return void ui.composer.tasks.togglePlanMenu();
|
|
231
|
+
if (hit.id === 'plan-item') {
|
|
232
|
+
return void ui.composer.tasks.choosePlan(hit.cursor);
|
|
233
|
+
}
|
|
234
|
+
if (ui.composer.tasks.planOpen) ui.composer.tasks.closePlanMenu();
|
|
98
235
|
ui.confirmChoice(hit.id);
|
|
99
236
|
},
|
|
100
237
|
},
|
|
@@ -103,6 +240,13 @@ const RELEASE_TARGETS = [
|
|
|
103
240
|
hits: 'fileHits',
|
|
104
241
|
onHit: (ui, hit) => ui.dashboard.open(hit.cursor),
|
|
105
242
|
},
|
|
243
|
+
{
|
|
244
|
+
match: (ui) => ui.pane === 'repos',
|
|
245
|
+
hits: 'fileHits',
|
|
246
|
+
onHit: (ui, hit) => {
|
|
247
|
+
void ui.workspace.openAt(hit.cursor);
|
|
248
|
+
},
|
|
249
|
+
},
|
|
106
250
|
{
|
|
107
251
|
match: (ui) => ui.pane === 'files',
|
|
108
252
|
hits: 'fileHits',
|
|
@@ -131,12 +275,33 @@ const RELEASE_TARGETS = [
|
|
|
131
275
|
if (again) filesPane.onOpenFile(ui);
|
|
132
276
|
},
|
|
133
277
|
},
|
|
278
|
+
{
|
|
279
|
+
match: (ui) => ui.pane === 'packages',
|
|
280
|
+
hits: 'fileHits',
|
|
281
|
+
onHit: (ui, hit) => {
|
|
282
|
+
ui.nav.packagesCursor = hit.cursor;
|
|
283
|
+
},
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
match: (ui) => ui.pane === 'agents',
|
|
287
|
+
hits: 'fileHits',
|
|
288
|
+
onHit: onAgentHit,
|
|
289
|
+
},
|
|
134
290
|
{
|
|
135
291
|
match: (ui) => ui.pane === 'npm',
|
|
136
292
|
hits: 'fileHits',
|
|
137
293
|
onHit: (ui, hit) => {
|
|
294
|
+
if (hit.side === 'runs') {
|
|
295
|
+
ui.npm.runCursor = hit.cursor;
|
|
296
|
+
ui.npm.focusRuns();
|
|
297
|
+
const again = armDoubleClick(ui.nav, 'npm-runs', hit.cursor);
|
|
298
|
+
if (again && ui.mode === 'review') ui.npm.run();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
ui.npm.focusCommands();
|
|
138
302
|
const again = armDoubleClick(ui.nav, 'npm', hit.cursor);
|
|
139
303
|
ui.nav.npmCursor = hit.cursor;
|
|
304
|
+
ui.npm.clampRuns();
|
|
140
305
|
if (again && ui.mode === 'review') ui.npm.run();
|
|
141
306
|
},
|
|
142
307
|
},
|
|
@@ -155,9 +320,45 @@ const RELEASE_TARGETS = [
|
|
|
155
320
|
},
|
|
156
321
|
];
|
|
157
322
|
|
|
323
|
+
const frameHits = (ui, accept) => (ui.lastFrame?.fileHits ?? []).filter(accept);
|
|
324
|
+
|
|
325
|
+
const switchAgentCombo = (ui, cell) => {
|
|
326
|
+
const hits = frameHits(ui, (hit) => COMBO_FIELDS.includes(hit.field));
|
|
327
|
+
return clickHit(ui, hits, cell, (ui, hit) => {
|
|
328
|
+
const same =
|
|
329
|
+
ui.agents.pick.field === hit.field && ui.nav.agentCursor === hit.cursor;
|
|
330
|
+
if (same) {
|
|
331
|
+
ui.agents.closePick();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
ui.agents.dismissPick();
|
|
335
|
+
ui.agents.focusCli();
|
|
336
|
+
ui.nav.agentCursor = hit.cursor;
|
|
337
|
+
ui.agents.openPick(hit.field);
|
|
338
|
+
if (!ui.agents.pick) ui.paint();
|
|
339
|
+
});
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const releaseAgentMenu = (ui, cell) => {
|
|
343
|
+
const hits = frameHits(ui, (hit) => hit.field === 'menu');
|
|
344
|
+
const accept = (ui, hit) => ui.agents.acceptPick(hit.cursor);
|
|
345
|
+
if (clickHit(ui, hits, cell, accept)) return;
|
|
346
|
+
if (switchAgentCombo(ui, cell)) return;
|
|
347
|
+
ui.agents.closePick();
|
|
348
|
+
};
|
|
349
|
+
|
|
158
350
|
const onRelease = (ui, event) => {
|
|
351
|
+
dismissImport(ui, event);
|
|
159
352
|
const nav = ui.nav;
|
|
160
353
|
const cell = mouseCell(ui, event);
|
|
354
|
+
if (nav.scrollDrag) {
|
|
355
|
+
moveLogBar(ui, cell);
|
|
356
|
+
nav.scrollDrag = null;
|
|
357
|
+
nav.mouseAnchor = null;
|
|
358
|
+
nav.pendingClick = null;
|
|
359
|
+
nav.selection = null;
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
161
362
|
if (nav.mouseAnchor) {
|
|
162
363
|
nav.selection = { start: nav.mouseAnchor, end: cell };
|
|
163
364
|
}
|
|
@@ -165,7 +366,10 @@ const onRelease = (ui, event) => {
|
|
|
165
366
|
nav.pendingClick = null;
|
|
166
367
|
nav.mouseAnchor = null;
|
|
167
368
|
if (clickButton(ui, cell, clickId)) return;
|
|
168
|
-
if (
|
|
369
|
+
if (placeClick(ui, cell)) return;
|
|
370
|
+
if (ui.pane === 'agents' && ui.agents.pick) {
|
|
371
|
+
return void releaseAgentMenu(ui, cell);
|
|
372
|
+
}
|
|
169
373
|
const frame = ui.lastFrame;
|
|
170
374
|
for (const target of RELEASE_TARGETS) {
|
|
171
375
|
if (!target.match(ui)) continue;
|
|
@@ -193,4 +397,6 @@ const handlePointer = (ui, event) => {
|
|
|
193
397
|
handler(ui, event);
|
|
194
398
|
};
|
|
195
399
|
|
|
196
|
-
module.exports = {
|
|
400
|
+
module.exports = {
|
|
401
|
+
handlePointer,
|
|
402
|
+
};
|