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,112 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { isHashObject } = require('metautil');
|
|
6
|
+
const { THEME_NAMES } = require('../term/ansi.js');
|
|
7
|
+
const { REVIEW_DIR } = require('../common/files.js');
|
|
8
|
+
const { readJson, writeJson } = require('../common/utilities.js');
|
|
9
|
+
|
|
10
|
+
const PREFS = '.reslop';
|
|
11
|
+
|
|
12
|
+
const LAYOUT_LABEL = {
|
|
13
|
+
unified: 'unified',
|
|
14
|
+
mixed: 'mixed',
|
|
15
|
+
side: 'side-by-side',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const LAYOUT_VALUE = {
|
|
19
|
+
unified: 'unified',
|
|
20
|
+
mixed: 'mixed',
|
|
21
|
+
'side-by-side': 'side',
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const prefsFile = (root) => path.join(root, PREFS);
|
|
25
|
+
|
|
26
|
+
const planPath = (root, name) => path.join(root, REVIEW_DIR, name);
|
|
27
|
+
|
|
28
|
+
const readPrefs = (root) => {
|
|
29
|
+
const data = readJson(prefsFile(root));
|
|
30
|
+
if (!isHashObject(data)) return { agents: {} };
|
|
31
|
+
const agents = isHashObject(data.agents) ? data.agents : {};
|
|
32
|
+
return { ...data, agents };
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const writePrefs = (root, data) => {
|
|
36
|
+
const stored = { ...data };
|
|
37
|
+
delete stored.runs;
|
|
38
|
+
try {
|
|
39
|
+
fs.writeFileSync(prefsFile(root), `${JSON.stringify(stored, null, 2)}\n`);
|
|
40
|
+
} catch {
|
|
41
|
+
// The repo root can be read-only.
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const section = (data, name) => {
|
|
46
|
+
const value = data[name];
|
|
47
|
+
if (!isHashObject(value)) return {};
|
|
48
|
+
return { ...value };
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
const readUiPrefs = (root) => {
|
|
52
|
+
const data = readPrefs(root);
|
|
53
|
+
const editor = section(data, 'editor');
|
|
54
|
+
const general = section(data, 'general');
|
|
55
|
+
const commits = section(data, 'commits');
|
|
56
|
+
let lineNumbers = false;
|
|
57
|
+
if (typeof editor.lineNumbers === 'boolean') {
|
|
58
|
+
lineNumbers = editor.lineNumbers;
|
|
59
|
+
} else if (typeof data.lineNumbers === 'boolean') {
|
|
60
|
+
lineNumbers = data.lineNumbers;
|
|
61
|
+
}
|
|
62
|
+
const layout = LAYOUT_VALUE[editor.mode] || 'unified';
|
|
63
|
+
const theme = THEME_NAMES.includes(general.theme) ? general.theme : 'dark';
|
|
64
|
+
const commitView = commits.view === 'full' ? 'full' : 'brief';
|
|
65
|
+
return { lineNumbers, layout, theme, commitView };
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const saveUiPrefs = (root, patch) => {
|
|
69
|
+
if (!root) return;
|
|
70
|
+
const data = readPrefs(root);
|
|
71
|
+
const editor = section(data, 'editor');
|
|
72
|
+
const legacy = typeof data.lineNumbers === 'boolean';
|
|
73
|
+
if (legacy && editor.lineNumbers === undefined) {
|
|
74
|
+
editor.lineNumbers = data.lineNumbers;
|
|
75
|
+
}
|
|
76
|
+
delete data.lineNumbers;
|
|
77
|
+
if (patch.lineNumbers !== undefined) {
|
|
78
|
+
editor.lineNumbers = patch.lineNumbers === true;
|
|
79
|
+
}
|
|
80
|
+
if (patch.layout) editor.mode = LAYOUT_LABEL[patch.layout] || 'unified';
|
|
81
|
+
const next = { ...data, editor };
|
|
82
|
+
if (patch.theme) {
|
|
83
|
+
const general = section(data, 'general');
|
|
84
|
+
general.theme = patch.theme;
|
|
85
|
+
next.general = general;
|
|
86
|
+
}
|
|
87
|
+
if (patch.commitView) {
|
|
88
|
+
const commits = section(data, 'commits');
|
|
89
|
+
const full = patch.commitView === 'full';
|
|
90
|
+
commits.view = full ? 'full' : 'brief';
|
|
91
|
+
next.commits = commits;
|
|
92
|
+
}
|
|
93
|
+
writePrefs(root, next);
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const writeStored = (file, data) => {
|
|
97
|
+
try {
|
|
98
|
+
writeJson(file, data);
|
|
99
|
+
} catch {
|
|
100
|
+
// The plan directory can be read-only.
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
module.exports = {
|
|
105
|
+
LAYOUT_LABEL,
|
|
106
|
+
planPath,
|
|
107
|
+
readPrefs,
|
|
108
|
+
writePrefs,
|
|
109
|
+
readUiPrefs,
|
|
110
|
+
saveUiPrefs,
|
|
111
|
+
writeStored,
|
|
112
|
+
};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Repo reads the screen needs on every frame: loaded ahead and kept here
|
|
4
|
+
class FileReads {
|
|
5
|
+
constructor(ui) {
|
|
6
|
+
this.ui = ui;
|
|
7
|
+
this.reset();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
reset() {
|
|
11
|
+
this.names = [];
|
|
12
|
+
this.textKey = null;
|
|
13
|
+
this.text = '';
|
|
14
|
+
this.stamp = 0;
|
|
15
|
+
this.wanted = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
keyOf(rel) {
|
|
19
|
+
const ui = this.ui;
|
|
20
|
+
return `${ui.top}\0${ui.rev ?? ''}\0${rel}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async readNames() {
|
|
24
|
+
const ui = this.ui;
|
|
25
|
+
if (!ui.repo.listFiles) return [];
|
|
26
|
+
const options = { commit: ui.rev };
|
|
27
|
+
try {
|
|
28
|
+
return await ui.repo.listFiles(ui.top || ui.cwd, ui.paths ?? [], options);
|
|
29
|
+
} catch {
|
|
30
|
+
return [];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async readText(rel) {
|
|
35
|
+
const ui = this.ui;
|
|
36
|
+
const key = this.keyOf(rel);
|
|
37
|
+
const repo = ui.repo;
|
|
38
|
+
const readable = rel && repo && typeof repo.fileText === 'function';
|
|
39
|
+
if (!readable) return { key, text: '' };
|
|
40
|
+
try {
|
|
41
|
+
return { key, text: await repo.fileText(ui.top, rel, ui.rev) };
|
|
42
|
+
} catch {
|
|
43
|
+
return { key, text: '' };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// What a reload needs besides the snapshot, applied together with it
|
|
48
|
+
async read() {
|
|
49
|
+
const nav = this.ui.nav;
|
|
50
|
+
const listed = nav.fileScope === 'file';
|
|
51
|
+
const rel = nav.pane === 'unit' ? nav.reviewPath : null;
|
|
52
|
+
const reads = [
|
|
53
|
+
listed ? this.readNames() : null,
|
|
54
|
+
rel ? this.readText(rel) : null,
|
|
55
|
+
];
|
|
56
|
+
const stamp = this.stamp;
|
|
57
|
+
const [names, viewed] = await Promise.all(reads);
|
|
58
|
+
return { names, viewed, stamp };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Text kept since the read began is newer than what the read found
|
|
62
|
+
apply(found) {
|
|
63
|
+
if (found.names) this.names = found.names;
|
|
64
|
+
if (found.viewed && found.stamp === this.stamp) this.keep(found.viewed);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
keep(viewed) {
|
|
68
|
+
this.textKey = viewed.key;
|
|
69
|
+
this.text = viewed.text;
|
|
70
|
+
this.stamp += 1;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
keepText(rel, text) {
|
|
74
|
+
this.keep({ key: this.keyOf(rel), text });
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async loadNames() {
|
|
78
|
+
this.names = await this.readNames();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async loadText(rel) {
|
|
82
|
+
const viewed = await this.readText(rel);
|
|
83
|
+
this.keep(viewed);
|
|
84
|
+
return viewed.text;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
cached(rel) {
|
|
88
|
+
if (!rel || this.textKey !== this.keyOf(rel)) return null;
|
|
89
|
+
return this.text;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// A path nobody loaded ahead is read now and painted when it arrives
|
|
93
|
+
textOf(rel) {
|
|
94
|
+
const text = this.cached(rel);
|
|
95
|
+
if (text !== null || !rel) return text ?? '';
|
|
96
|
+
const key = this.keyOf(rel);
|
|
97
|
+
if (this.wanted === key) return '';
|
|
98
|
+
this.wanted = key;
|
|
99
|
+
const ui = this.ui;
|
|
100
|
+
const done = () => {
|
|
101
|
+
this.wanted = null;
|
|
102
|
+
ui.paint();
|
|
103
|
+
};
|
|
104
|
+
ui.ops.track(this.loadText(rel).then(done, done));
|
|
105
|
+
return '';
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
module.exports = { FileReads };
|
package/lib/session/review.js
CHANGED
|
@@ -1,10 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const { REVIEW_DIR, ISSUE_FILE } = require('../common/files.js');
|
|
7
|
+
const review = require('../review/review.js');
|
|
8
|
+
const { ReviewStore } = review;
|
|
5
9
|
const { listReviewNames, loadTemplates, resolveReviewPath } = review;
|
|
6
10
|
const { loadReview, flushReview } = review;
|
|
7
11
|
|
|
12
|
+
const issueTasks = (imported) => {
|
|
13
|
+
const todos = imported.tasks ?? imported.todos ?? [];
|
|
14
|
+
return todos.filter((todo) => (todo.file || '') === ISSUE_FILE);
|
|
15
|
+
};
|
|
16
|
+
|
|
8
17
|
class ReviewController {
|
|
9
18
|
constructor() {
|
|
10
19
|
this.reset();
|
|
@@ -21,16 +30,17 @@ class ReviewController {
|
|
|
21
30
|
const templates = loadTemplates(dir);
|
|
22
31
|
const resolved = resolveReviewPath(dir, date, names, extra);
|
|
23
32
|
this.didResume = resolved.resume === true;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
33
|
+
const reviewPath = resolved.reviewPath;
|
|
34
|
+
if (this.didResume) this.store = loadReview(reviewPath, templates);
|
|
35
|
+
else this.store = new ReviewStore(reviewPath, templates);
|
|
27
36
|
return this.store;
|
|
28
37
|
}
|
|
29
38
|
|
|
30
39
|
applyImported(imported) {
|
|
31
40
|
if (!imported || !this.store) return false;
|
|
32
|
-
if (this.
|
|
33
|
-
|
|
41
|
+
if (this.importedRemote) return false;
|
|
42
|
+
const notes = this.didResume ? { tasks: issueTasks(imported) } : imported;
|
|
43
|
+
this.store.applyImportedNotes(notes);
|
|
34
44
|
this.importedRemote = true;
|
|
35
45
|
return true;
|
|
36
46
|
}
|
|
@@ -45,9 +55,17 @@ class ReviewController {
|
|
|
45
55
|
}
|
|
46
56
|
}
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
58
|
+
usePlan(dir, name) {
|
|
59
|
+
const store = this.store;
|
|
60
|
+
if (!store || !name) return false;
|
|
61
|
+
const next = path.join(dir, REVIEW_DIR, name);
|
|
62
|
+
if (next === store.reviewPath) return false;
|
|
63
|
+
this.flush();
|
|
64
|
+
const templates = store.templates;
|
|
65
|
+
const loaded = fs.existsSync(next);
|
|
66
|
+
if (loaded) this.store = loadReview(next, templates);
|
|
67
|
+
else this.store = new ReviewStore(next, templates);
|
|
68
|
+
return true;
|
|
51
69
|
}
|
|
52
70
|
}
|
|
53
71
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { clamp } = require('../common/utilities.js');
|
|
4
|
+
const { logScrollMax } = require('../render/log.js');
|
|
5
|
+
|
|
6
|
+
class RunPanel {
|
|
7
|
+
constructor(ui) {
|
|
8
|
+
this.ui = ui;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
barMax() {
|
|
12
|
+
const bar = this.ui.lastFrame && this.ui.lastFrame.scrollBar;
|
|
13
|
+
return bar ? Math.max(0, bar.count - bar.rows) : -1;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
setLog(start, max) {
|
|
17
|
+
const next = clamp(start, 0, max);
|
|
18
|
+
this.logScroll = next;
|
|
19
|
+
this.followEnd = next === max;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
placeLog(start) {
|
|
23
|
+
const max = this.barMax();
|
|
24
|
+
if (max >= 0) this.setLog(start, max);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
placeRuns(start) {
|
|
28
|
+
const max = this.barMax();
|
|
29
|
+
if (max >= 0) this.runScroll = clamp(start, 0, max);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
scrollLogBy(delta, text, running) {
|
|
33
|
+
const { lastSize, lastFrame } = this.ui;
|
|
34
|
+
const max = logScrollMax(text, lastSize, lastFrame, running);
|
|
35
|
+
const from = this.followEnd ? max : this.logScroll;
|
|
36
|
+
this.setLog(from + delta, max);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
module.exports = { RunPanel };
|
|
@@ -1,53 +1,58 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
5
|
-
const
|
|
6
|
-
const {
|
|
7
|
-
const
|
|
8
|
-
const {
|
|
9
|
-
const
|
|
10
|
-
const {
|
|
11
|
-
const
|
|
12
|
-
const {
|
|
13
|
-
const
|
|
14
|
-
const {
|
|
15
|
-
const
|
|
16
|
-
const {
|
|
17
|
-
const
|
|
18
|
-
const {
|
|
19
|
-
const
|
|
20
|
-
const {
|
|
21
|
-
const
|
|
22
|
-
const {
|
|
23
|
-
const
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
-
const progress = require('./session/progress.js');
|
|
28
|
-
const { Progress } = progress;
|
|
29
|
-
const compose = require('./session/compose.js');
|
|
30
|
-
const { Composer } = compose;
|
|
31
|
-
const ops = require('./session/ops.js');
|
|
32
|
-
const { OpsRunner } = ops;
|
|
33
|
-
const commit = require('./session/commit.js');
|
|
34
|
-
const { CommitController } = commit;
|
|
35
|
-
const branches = require('./session/branches.js');
|
|
36
|
-
const { BranchController } = branches;
|
|
37
|
-
const npm = require('./session/npm.js');
|
|
38
|
-
const { NpmController } = npm;
|
|
39
|
-
const accessors = require('./session/accessors.js');
|
|
40
|
-
const { bindAccessors, bindGetters } = accessors;
|
|
41
|
-
const view = require('./session/view.js');
|
|
42
|
-
const filesPane = require('./session/files-pane.js');
|
|
43
|
-
const lifecycle = require('./session/lifecycle.js');
|
|
44
|
-
const { Lifecycle } = lifecycle;
|
|
45
|
-
const dispatch = require('./session/dispatch.js');
|
|
3
|
+
const { renderFrame } = require('../render/render.js');
|
|
4
|
+
const { decodeChunk } = require('../input/keys.js');
|
|
5
|
+
const { AUTOSAVE_MS, hasNotes } = require('../review/review.js');
|
|
6
|
+
const { makeTaskItem } = require('../common/files.js');
|
|
7
|
+
const { sessionCapabilities } = require('../source/capabilities.js');
|
|
8
|
+
const { LIST_PANES } = require('../input/actions.js');
|
|
9
|
+
const { Terminal, Progress, LEAVE_TERM } = require('./terminal.js');
|
|
10
|
+
const { LoadCoordinator } = require('./load.js');
|
|
11
|
+
const { ItemCollection } = require('./items.js');
|
|
12
|
+
const { Navigation } = require('./navigation.js');
|
|
13
|
+
const { ReviewController } = require('./review.js');
|
|
14
|
+
const { UpdateCoordinator } = require('./update.js');
|
|
15
|
+
const { Composer } = require('./compose.js');
|
|
16
|
+
const { OpsRunner } = require('./ops.js');
|
|
17
|
+
const { FileReads } = require('./reads.js');
|
|
18
|
+
const { CommitController } = require('./commit.js');
|
|
19
|
+
const { BranchController } = require('./branches.js');
|
|
20
|
+
const { NpmController } = require('./npm/npm.js');
|
|
21
|
+
const { PackageController } = require('./npm/packages.js');
|
|
22
|
+
const { AgentsController } = require('./agents/agents.js');
|
|
23
|
+
const view = require('./view.js');
|
|
24
|
+
const filesPane = require('./files-pane.js');
|
|
25
|
+
const { Lifecycle } = require('./lifecycle.js');
|
|
26
|
+
const dispatch = require('./dispatch.js');
|
|
46
27
|
const { dispatchAction, confirmChoice, handleEvent } = dispatch;
|
|
47
|
-
const
|
|
48
|
-
const {
|
|
28
|
+
const { Dashboard } = require('./dashboard.js');
|
|
29
|
+
const { Workspace } = require('./workspace.js');
|
|
30
|
+
|
|
31
|
+
const bindAccessors = (target, owner, names) => {
|
|
32
|
+
for (const name of names) {
|
|
33
|
+
Object.defineProperty(target, name, {
|
|
34
|
+
configurable: true,
|
|
35
|
+
enumerable: true,
|
|
36
|
+
get: () => owner[name],
|
|
37
|
+
set: (value) => {
|
|
38
|
+
owner[name] = value;
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const bindGetters = (target, owner, names) => {
|
|
45
|
+
for (const name of names) {
|
|
46
|
+
Object.defineProperty(target, name, {
|
|
47
|
+
configurable: true,
|
|
48
|
+
enumerable: true,
|
|
49
|
+
get: () => owner[name],
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
};
|
|
49
53
|
|
|
50
54
|
const PROGRESS_MS = 160;
|
|
55
|
+
const QUIT_WAIT_MS = 3000;
|
|
51
56
|
|
|
52
57
|
const NAV_FIELDS = [
|
|
53
58
|
'pane',
|
|
@@ -56,6 +61,8 @@ const NAV_FIELDS = [
|
|
|
56
61
|
'branchCursor',
|
|
57
62
|
'commitCursor',
|
|
58
63
|
'npmCursor',
|
|
64
|
+
'packagesCursor',
|
|
65
|
+
'agentCursor',
|
|
59
66
|
'scroll',
|
|
60
67
|
'listScroll',
|
|
61
68
|
'reviewPath',
|
|
@@ -98,6 +105,13 @@ const assignOptions = (session, options) => {
|
|
|
98
105
|
session.auditMap = options.auditMap;
|
|
99
106
|
session.updateOpts = options.update;
|
|
100
107
|
session.dashboardHome = options.startPane === 'dashboard';
|
|
108
|
+
session.workspaceRoot = options.workspace ? options.workspace.root : '';
|
|
109
|
+
session.workspaceInfo = options.workspace ?? null;
|
|
110
|
+
session.loadPullRequest = options.loadPullRequest ?? null;
|
|
111
|
+
session.loadMergeRequest = options.loadMergeRequest ?? null;
|
|
112
|
+
session.loadGithubIssue = options.loadGithubIssue ?? null;
|
|
113
|
+
session.loadGitlabIssue = options.loadGitlabIssue ?? null;
|
|
114
|
+
session.fetchImpl = options.fetch ?? null;
|
|
101
115
|
};
|
|
102
116
|
|
|
103
117
|
const assignParts = (session, options) => {
|
|
@@ -112,10 +126,14 @@ const assignParts = (session, options) => {
|
|
|
112
126
|
session.updater = new UpdateCoordinator(session);
|
|
113
127
|
session.composer = new Composer(session);
|
|
114
128
|
session.ops = new OpsRunner(session);
|
|
129
|
+
session.reads = new FileReads(session);
|
|
115
130
|
session.commits = new CommitController(session);
|
|
116
131
|
session.gitBranches = new BranchController(session);
|
|
117
132
|
session.npm = new NpmController(session);
|
|
133
|
+
session.packages = new PackageController(session);
|
|
134
|
+
session.agents = new AgentsController(session);
|
|
118
135
|
session.dashboard = new Dashboard(session);
|
|
136
|
+
session.workspace = new Workspace(session);
|
|
119
137
|
session.lifecycle = new Lifecycle(session);
|
|
120
138
|
};
|
|
121
139
|
|
|
@@ -156,17 +174,22 @@ class Session {
|
|
|
156
174
|
this.review.reset();
|
|
157
175
|
this.composer.reset();
|
|
158
176
|
this.ops.reset();
|
|
177
|
+
this.reads.reset();
|
|
159
178
|
this.gitBranches.reset();
|
|
160
179
|
this.commits.reset();
|
|
161
180
|
this.npm.reset();
|
|
181
|
+
this.agents.reset();
|
|
162
182
|
this.dashboard.reset();
|
|
183
|
+
this.workspace.reset();
|
|
163
184
|
this.top = this.cwd;
|
|
164
185
|
this.branch = '';
|
|
165
186
|
this.status = '';
|
|
166
187
|
this.layout = 'unified';
|
|
188
|
+
this.lineNumbers = false;
|
|
167
189
|
this.done = false;
|
|
168
190
|
this.exitCode = 0;
|
|
169
191
|
this.mode = 'review';
|
|
192
|
+
this.quitWarning = '';
|
|
170
193
|
this.tasksItem = makeTaskItem();
|
|
171
194
|
this.progressFrame = 0;
|
|
172
195
|
this.emptyReview = false;
|
|
@@ -181,13 +204,13 @@ class Session {
|
|
|
181
204
|
this.progress.clear();
|
|
182
205
|
}
|
|
183
206
|
|
|
184
|
-
ensureRepo() {
|
|
207
|
+
async ensureRepo() {
|
|
185
208
|
if (!this.repo.toplevel) return;
|
|
186
|
-
this.top = this.repo.toplevel(this.cwd);
|
|
209
|
+
this.top = await this.repo.toplevel(this.cwd);
|
|
187
210
|
}
|
|
188
211
|
|
|
189
212
|
load() {
|
|
190
|
-
this.lifecycle.load();
|
|
213
|
+
return this.lifecycle.load();
|
|
191
214
|
}
|
|
192
215
|
|
|
193
216
|
loadReady() {
|
|
@@ -225,6 +248,21 @@ class Session {
|
|
|
225
248
|
if (LIST_PANES.includes(this.pane)) {
|
|
226
249
|
this.nav.listScroll = frame.listScroll;
|
|
227
250
|
}
|
|
251
|
+
const menuScroll = frame.menuScroll;
|
|
252
|
+
if (this.agents.pick && typeof menuScroll === 'number') {
|
|
253
|
+
this.agents.pick.scroll = menuScroll;
|
|
254
|
+
}
|
|
255
|
+
const planPick = this.composer.tasks.planPick;
|
|
256
|
+
if (planPick && typeof menuScroll === 'number') {
|
|
257
|
+
planPick.scroll = menuScroll;
|
|
258
|
+
}
|
|
259
|
+
if (typeof frame.runScroll === 'number') {
|
|
260
|
+
const panel = this.pane === 'npm' ? this.npm : this.agents;
|
|
261
|
+
panel.runScroll = frame.runScroll;
|
|
262
|
+
}
|
|
263
|
+
if (typeof frame.gridCols === 'number') {
|
|
264
|
+
this.workspace.cols = frame.gridCols;
|
|
265
|
+
}
|
|
228
266
|
if (this.nav.tasksOpen) this.nav.scroll = frame.scroll;
|
|
229
267
|
const editScroll = frame.cursor && frame.cursor.scroll;
|
|
230
268
|
if (this.editor && typeof editScroll === 'number') {
|
|
@@ -234,6 +272,7 @@ class Session {
|
|
|
234
272
|
this.term.paint(frame, size, composing ? frame.cursor : null);
|
|
235
273
|
this.scheduleAutosave();
|
|
236
274
|
this.dashboard.afterDraw();
|
|
275
|
+
this.workspace.afterDraw();
|
|
237
276
|
}
|
|
238
277
|
|
|
239
278
|
scheduleAutosave() {
|
|
@@ -284,7 +323,11 @@ class Session {
|
|
|
284
323
|
}
|
|
285
324
|
|
|
286
325
|
handleEvent(event) {
|
|
287
|
-
handleEvent(this, event);
|
|
326
|
+
if (!this.ops.defer(event)) handleEvent(this, event);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
finishInput() {
|
|
330
|
+
if (this.finishReadLoop) this.finishReadLoop();
|
|
288
331
|
}
|
|
289
332
|
|
|
290
333
|
pushInput(text) {
|
|
@@ -321,6 +364,7 @@ class Session {
|
|
|
321
364
|
if (result.kind === 'commit') this.commits.finishGitCommit();
|
|
322
365
|
if (result.kind === 'branch') this.gitBranches.finishCreateBranch();
|
|
323
366
|
if (result.kind === 'npm') this.npm.finishEdit();
|
|
367
|
+
if (result.kind === 'package') this.packages.finishAdd();
|
|
324
368
|
}
|
|
325
369
|
|
|
326
370
|
selectTemplate(index) {
|
|
@@ -331,28 +375,83 @@ class Session {
|
|
|
331
375
|
this.composer.autosave();
|
|
332
376
|
}
|
|
333
377
|
|
|
378
|
+
retarget({ cwd, top, repoName, pane }) {
|
|
379
|
+
this.lifecycle.stopWatch();
|
|
380
|
+
this.dashboard.stop();
|
|
381
|
+
this.loader.reset();
|
|
382
|
+
this.cwd = cwd;
|
|
383
|
+
if (top) this.top = top;
|
|
384
|
+
this.paths = [];
|
|
385
|
+
this.repoName = repoName;
|
|
386
|
+
this.branch = '';
|
|
387
|
+
this.rev = null;
|
|
388
|
+
this.revShort = '';
|
|
389
|
+
this.change = null;
|
|
390
|
+
this.dashboardHome = pane === 'dashboard';
|
|
391
|
+
this.collection.reset();
|
|
392
|
+
this.reads.reset();
|
|
393
|
+
this.review.reset();
|
|
394
|
+
this.nav.pane = pane;
|
|
395
|
+
this.nav.tasksOpen = false;
|
|
396
|
+
this.nav.scroll = 0;
|
|
397
|
+
this.nav.clearSelection();
|
|
398
|
+
this.status = '';
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
childWarning() {
|
|
402
|
+
const tests = this.npm && this.npm.running === true;
|
|
403
|
+
const agents = this.agents && this.agents.runningCount() > 0;
|
|
404
|
+
if (tests && agents) return ' exit will terminate tests and agents';
|
|
405
|
+
if (agents) return ' exit will terminate agents';
|
|
406
|
+
if (tests) return ' exit will terminate tests';
|
|
407
|
+
return '';
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
stopChildren() {
|
|
411
|
+
if (this.npm) this.npm.stopAll();
|
|
412
|
+
if (this.agents) this.agents.killAll();
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// A quick repo write still running lands first, so it is not left half done
|
|
416
|
+
exitNow() {
|
|
417
|
+
if (this.done || !this.ops.unlanded()) return void this.finishExit();
|
|
418
|
+
const exit = () => this.finishExit();
|
|
419
|
+
this.ops.hold(this.ops.landed(QUIT_WAIT_MS).then(exit));
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
finishExit() {
|
|
423
|
+
this.flushReview();
|
|
424
|
+
this.done = true;
|
|
425
|
+
this.exitCode = 0;
|
|
426
|
+
}
|
|
427
|
+
|
|
334
428
|
onQuit() {
|
|
429
|
+
const composer = this.composer;
|
|
430
|
+
if (this.mode === 'compose' && composer.writesDisk()) {
|
|
431
|
+
const quit = () => this.onQuit();
|
|
432
|
+
return void this.ops.hold(composer.writeAndClose().then(quit));
|
|
433
|
+
}
|
|
335
434
|
if (this.mode === 'compose') {
|
|
336
|
-
|
|
337
|
-
|
|
435
|
+
composer.commitCompose();
|
|
436
|
+
composer.closeCompose();
|
|
338
437
|
}
|
|
339
|
-
|
|
438
|
+
const warning = this.childWarning();
|
|
439
|
+
if (warning) {
|
|
440
|
+
this.quitWarning = warning;
|
|
340
441
|
this.mode = 'confirmQuit';
|
|
341
442
|
this.status = '';
|
|
342
443
|
return;
|
|
343
444
|
}
|
|
344
|
-
this.
|
|
345
|
-
this.exitCode = 0;
|
|
445
|
+
this.exitNow();
|
|
346
446
|
}
|
|
347
447
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
this.done = true;
|
|
352
|
-
this.exitCode = 0;
|
|
448
|
+
confirmQuit() {
|
|
449
|
+
this.stopChildren();
|
|
450
|
+
this.exitNow();
|
|
353
451
|
}
|
|
354
452
|
|
|
355
453
|
cancelQuit() {
|
|
454
|
+
this.quitWarning = '';
|
|
356
455
|
this.mode = 'review';
|
|
357
456
|
this.status = '';
|
|
358
457
|
}
|
|
@@ -367,6 +466,7 @@ class Session {
|
|
|
367
466
|
|
|
368
467
|
tickProgress() {
|
|
369
468
|
this.progressFrame += 1;
|
|
469
|
+
if (this.npm.running) this.npm.refreshRecorded();
|
|
370
470
|
this.paint();
|
|
371
471
|
}
|
|
372
472
|
|
|
@@ -385,7 +485,12 @@ class Session {
|
|
|
385
485
|
this.uiOpen = true;
|
|
386
486
|
this.term.enter();
|
|
387
487
|
try {
|
|
488
|
+
if (this.nav.pane === 'repos') this.workspace.start();
|
|
388
489
|
this.openLoad();
|
|
490
|
+
// Populate the agent model cache while the initial screen is loading.
|
|
491
|
+
// Opening the Agents pane can then use the cache instead of being the
|
|
492
|
+
// first place that starts its CLI probes.
|
|
493
|
+
void this.agents.loadModels();
|
|
389
494
|
this.openUpdate();
|
|
390
495
|
this.draw();
|
|
391
496
|
await this.readLoop();
|
|
@@ -412,6 +517,7 @@ class Session {
|
|
|
412
517
|
settled = true;
|
|
413
518
|
this.lifecycle.stopWatch();
|
|
414
519
|
this.dashboard.stop();
|
|
520
|
+
this.workspace.stop();
|
|
415
521
|
this.term.stopListening();
|
|
416
522
|
this.progress.clear();
|
|
417
523
|
this.term.clearTimers();
|
|
@@ -431,6 +537,7 @@ class Session {
|
|
|
431
537
|
this.term.startListening({ onData, onResize: () => this.draw() });
|
|
432
538
|
this.lifecycle.startWatch();
|
|
433
539
|
if (this.dashboardHome) this.dashboard.start();
|
|
540
|
+
if (this.nav.pane === 'repos') this.workspace.start();
|
|
434
541
|
});
|
|
435
542
|
}
|
|
436
543
|
}
|