reslop 0.2.7 → 0.3.1
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 +30 -1
- package/README.md +16 -14
- package/bin/reslop.js +8 -5
- package/lib/agents/agents.js +573 -0
- package/lib/agents/allow.js +71 -0
- package/lib/agents/allow.json +17 -0
- package/lib/agents/models.js +467 -0
- package/lib/api.js +15 -12
- package/lib/cli.js +43 -68
- package/lib/{files.js → common/files.js} +48 -55
- package/lib/{find.js → common/find.js} +1 -1
- 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 +90 -49
- package/lib/dashboard/npm.js +17 -13
- package/lib/dashboard/tree.js +27 -30
- package/lib/{session → dashboard}/watch.js +36 -31
- package/lib/{workspace.js → dashboard/workspace.js} +23 -38
- 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} +151 -95
- package/lib/git/git.js +71 -0
- package/lib/git/worktree.js +504 -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 +114 -48
- package/lib/{editor.js → input/editor.js} +16 -8
- package/lib/{keys.js → input/keys.js} +24 -45
- package/lib/input/line-edit.js +78 -0
- package/lib/input/menu-pick.js +70 -0
- package/lib/render/agents.js +642 -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/{dash-blocks.js → dashboard/blocks.js} +131 -137
- 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 +9 -14
- package/lib/render/footer.js +385 -0
- package/lib/render/frame.js +123 -25
- package/lib/render/header.js +176 -0
- package/lib/render/log.js +320 -0
- package/lib/render/menu.js +60 -0
- package/lib/render/notes.js +16 -11
- package/lib/render/npm.js +322 -196
- package/lib/render/packages.js +9 -13
- package/lib/render/primitives.js +101 -15
- package/lib/render/render.js +7 -11
- package/lib/render/repos.js +13 -25
- package/lib/render/status.js +370 -0
- package/lib/render/status.json +85 -0
- package/lib/render/{dash-table.js → table.js} +13 -60
- package/lib/render/tiles.js +109 -48
- 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} +48 -19
- 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 +14 -0
- package/lib/review/format.js +420 -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 +976 -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 +33 -40
- package/lib/session/change-actions.js +34 -100
- package/lib/session/commit.js +91 -52
- package/lib/session/compose.js +189 -147
- package/lib/session/dashboard.js +23 -22
- package/lib/session/dispatch.js +86 -21
- package/lib/session/files-pane.js +47 -26
- package/lib/session/find.js +16 -29
- package/lib/session/import.js +93 -0
- package/lib/session/items.js +8 -17
- package/lib/session/lifecycle.js +78 -72
- package/lib/session/load.js +2 -4
- package/lib/session/navigation.js +6 -3
- package/lib/session/npm/history.js +250 -0
- package/lib/session/npm/npm.js +836 -0
- package/lib/session/{packages.js → npm/packages.js} +21 -14
- package/lib/session/ops.js +125 -34
- package/lib/session/pointer.js +202 -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} +158 -63
- 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 +65 -65
- package/lib/session/workspace.js +39 -78
- 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} +4 -3
- 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 -545
- package/lib/detect.js +0 -34
- package/lib/diff/edit.js +0 -33
- package/lib/git-worktree.js +0 -455
- package/lib/git.js +0 -79
- package/lib/npm-commands.js +0 -522
- package/lib/render/chrome.js +0 -806
- package/lib/render/present.js +0 -19
- package/lib/render/report.js +0 -43
- package/lib/render/search.js +0 -25
- package/lib/review.js +0 -745
- 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
package/lib/session/compose.js
CHANGED
|
@@ -1,32 +1,32 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const { cursorInWrap, wrapDoc } = wrap;
|
|
3
|
+
const { cursorInWrap, wrapDoc } = require('../term/wrap.js');
|
|
5
4
|
const render = require('../render/render.js');
|
|
6
|
-
const { noteInnerWidth, codeInnerWidth, todoBodyWidth } = render;
|
|
7
|
-
const
|
|
8
|
-
const {
|
|
9
|
-
const
|
|
10
|
-
const {
|
|
11
|
-
const
|
|
12
|
-
const { Editor } = editor;
|
|
13
|
-
const files = require('../files.js');
|
|
5
|
+
const { noteInnerWidth, codeInnerWidth, todoBodyWidth, activeDigits } = render;
|
|
6
|
+
const { commitMessageWidth } = require('../render/commits.js');
|
|
7
|
+
const { listedCommits } = require('./commit.js');
|
|
8
|
+
const { Editor } = require('../input/editor.js');
|
|
9
|
+
const { isTextKey } = require('../input/keys.js');
|
|
10
|
+
const files = require('../common/files.js');
|
|
14
11
|
const { itemPath, isTaskItem, isTasksEntry, listPath } = files;
|
|
15
12
|
const { isReadOnlyOrigin } = files;
|
|
16
|
-
const
|
|
17
|
-
const {
|
|
18
|
-
const
|
|
19
|
-
const {
|
|
20
|
-
const
|
|
21
|
-
const {
|
|
22
|
-
const templates = require('./templates.js');
|
|
23
|
-
const { TemplatePick } = templates;
|
|
24
|
-
const tasks = require('./tasks.js');
|
|
25
|
-
const { TaskPage } = tasks;
|
|
13
|
+
const { blockAddText } = require('../diff/diff.js');
|
|
14
|
+
const { itemFeedKey, canEditCode } = require('./items.js');
|
|
15
|
+
const review = require('../review/review.js');
|
|
16
|
+
const { checkLabel } = review;
|
|
17
|
+
const { TemplatePick } = require('./templates.js');
|
|
18
|
+
const { TaskPage } = require('./tasks.js');
|
|
26
19
|
const unit = require('./unit.js');
|
|
27
|
-
const clipboard = require('../clipboard.js');
|
|
28
|
-
|
|
29
|
-
|
|
20
|
+
const clipboard = require('../term/clipboard.js');
|
|
21
|
+
const { EDIT_OP, copyLine, cutLine } = require('../input/line-edit.js');
|
|
22
|
+
|
|
23
|
+
const COMPOSE_EDITS = [
|
|
24
|
+
'backspace',
|
|
25
|
+
'ctrl-backspace',
|
|
26
|
+
'delete',
|
|
27
|
+
'ctrl-z',
|
|
28
|
+
'ctrl-y',
|
|
29
|
+
];
|
|
30
30
|
const COMPOSE_CLIP = ['ctrl-x', 'ctrl-v'];
|
|
31
31
|
const COMPOSE_LEAVE = ['next', 'prev', 'tasks'];
|
|
32
32
|
|
|
@@ -78,17 +78,13 @@ const blankTail = (text) => {
|
|
|
78
78
|
return count;
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
+
const EDITOR_KEYS = {};
|
|
82
|
+
for (const key of Object.keys(EDIT_OP)) {
|
|
83
|
+
EDITOR_KEYS[key] = (c) => EDIT_OP[key](c.editor);
|
|
84
|
+
}
|
|
85
|
+
|
|
81
86
|
const COMPOSE_SPECIAL = {
|
|
82
|
-
|
|
83
|
-
delete: (c) => c.editor.delete(),
|
|
84
|
-
left: (c) => c.editor.move(-1),
|
|
85
|
-
right: (c) => c.editor.move(1),
|
|
86
|
-
'shift-left': (c) => c.editor.move(-1, true),
|
|
87
|
-
'shift-right': (c) => c.editor.move(1, true),
|
|
88
|
-
'ctrl-left': (c) => c.editor.moveWord(-1),
|
|
89
|
-
'ctrl-right': (c) => c.editor.moveWord(1),
|
|
90
|
-
'ctrl-shift-left': (c) => c.editor.moveWord(-1, true),
|
|
91
|
-
'ctrl-shift-right': (c) => c.editor.moveWord(1, true),
|
|
87
|
+
...EDITOR_KEYS,
|
|
92
88
|
up: (c) => c.moveLine(-1),
|
|
93
89
|
down: (c) => c.moveLine(1),
|
|
94
90
|
'shift-up': (c) => c.moveLine(-1, true),
|
|
@@ -118,7 +114,9 @@ class Composer {
|
|
|
118
114
|
}
|
|
119
115
|
|
|
120
116
|
reset() {
|
|
117
|
+
this.tasks.closePlanMenu();
|
|
121
118
|
this.fileDirty = false;
|
|
119
|
+
this.saving = false;
|
|
122
120
|
this.clear();
|
|
123
121
|
}
|
|
124
122
|
|
|
@@ -128,6 +126,7 @@ class Composer {
|
|
|
128
126
|
this.commitKind = null;
|
|
129
127
|
this.commitBody = '';
|
|
130
128
|
this.composeTaskId = null;
|
|
129
|
+
this.composeQuoteKey = null;
|
|
131
130
|
this.composeBaseline = '';
|
|
132
131
|
this.templateIndex = -1;
|
|
133
132
|
this.templateFocus = false;
|
|
@@ -157,7 +156,8 @@ class Composer {
|
|
|
157
156
|
if (kind !== 'code' && kind !== 'file') return;
|
|
158
157
|
if (!this.editor) return;
|
|
159
158
|
const width = this.viewSize().width;
|
|
160
|
-
|
|
159
|
+
const digits = activeDigits(this.ui);
|
|
160
|
+
this.editor.reveal(codeInnerWidth(width, this.ui.layout, digits));
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
followFileEdit() {
|
|
@@ -173,6 +173,7 @@ class Composer {
|
|
|
173
173
|
this.ui.mode = 'compose';
|
|
174
174
|
this.composeKind = kind;
|
|
175
175
|
this.composeTaskId = taskId;
|
|
176
|
+
this.composeQuoteKey = null;
|
|
176
177
|
this.composeBaseline = text;
|
|
177
178
|
this.fileOpened = kind === 'file' ? text : '';
|
|
178
179
|
this.editor = new Editor(text);
|
|
@@ -182,6 +183,11 @@ class Composer {
|
|
|
182
183
|
this.ui.nav.clearSelection();
|
|
183
184
|
}
|
|
184
185
|
|
|
186
|
+
openQuote(key, text) {
|
|
187
|
+
this.openCompose('tasks', text, null);
|
|
188
|
+
this.composeQuoteKey = key;
|
|
189
|
+
}
|
|
190
|
+
|
|
185
191
|
closeCompose() {
|
|
186
192
|
const ui = this.ui;
|
|
187
193
|
ui.mode = 'review';
|
|
@@ -205,7 +211,7 @@ class Composer {
|
|
|
205
211
|
commitFeedback(text) {
|
|
206
212
|
const item = this.ui.current();
|
|
207
213
|
if (!item || isTaskItem(item)) return;
|
|
208
|
-
|
|
214
|
+
this.notes.setFeedback(itemFeedKey(item), itemNote(item, text));
|
|
209
215
|
}
|
|
210
216
|
|
|
211
217
|
dropCodeNote(item, text) {
|
|
@@ -213,34 +219,52 @@ class Composer {
|
|
|
213
219
|
if (!notes) return;
|
|
214
220
|
const key = itemFeedKey(item);
|
|
215
221
|
if (!notes.code.has(key)) return;
|
|
216
|
-
setCode(
|
|
222
|
+
notes.setCode(key, { ...itemNote(item, text), clear: true });
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Edits that land in the worktree or index, not in the review notes
|
|
226
|
+
writesDisk() {
|
|
227
|
+
const kind = this.composeKind;
|
|
228
|
+
if (!this.editor) return false;
|
|
229
|
+
if (kind === 'file') return true;
|
|
230
|
+
return kind === 'code' && this.writesCodeFile(this.ui.current());
|
|
217
231
|
}
|
|
218
232
|
|
|
219
233
|
commitCode(text) {
|
|
234
|
+
const item = this.ui.current();
|
|
235
|
+
if (!canEditCode(item) || this.writesCodeFile(item)) return;
|
|
236
|
+
const note = itemNote(item, text);
|
|
237
|
+
const clear = text === blockAddText(item.hunk, item.blockId);
|
|
238
|
+
this.notes.setCode(itemFeedKey(item), clear ? { ...note, clear } : note);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
async write(action) {
|
|
220
242
|
const ui = this.ui;
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
setCode(this.notes, itemFeedKey(item), clear ? { ...note, clear } : note);
|
|
228
|
-
return true;
|
|
243
|
+
ui.ignoreWatch();
|
|
244
|
+
try {
|
|
245
|
+
await ui.ops.exclusive(action);
|
|
246
|
+
} catch (error) {
|
|
247
|
+
ui.status = error.message;
|
|
248
|
+
return false;
|
|
229
249
|
}
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
250
|
+
ui.ignoreWatch();
|
|
251
|
+
return true;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async writeCode(text) {
|
|
255
|
+
const ui = this.ui;
|
|
256
|
+
const item = ui.current();
|
|
257
|
+
if (text !== blockAddText(item.hunk, item.blockId)) {
|
|
258
|
+
const edit = (top) => ui.repo.edit(top, item, text);
|
|
259
|
+
const written = await this.write(edit);
|
|
260
|
+
if (!written) return false;
|
|
237
261
|
this.fileDirty = true;
|
|
238
262
|
}
|
|
239
263
|
this.dropCodeNote(item, text);
|
|
240
264
|
return true;
|
|
241
265
|
}
|
|
242
266
|
|
|
243
|
-
saveUnitFile(text) {
|
|
267
|
+
async saveUnitFile(text) {
|
|
244
268
|
const ui = this.ui;
|
|
245
269
|
const rel = ui.nav.reviewPath;
|
|
246
270
|
if (!rel || !ui.repo.writeFile) return true;
|
|
@@ -248,48 +272,37 @@ class Composer {
|
|
|
248
272
|
ui.status = 'read only';
|
|
249
273
|
return false;
|
|
250
274
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
return false;
|
|
256
|
-
}
|
|
257
|
-
ui.ignoreWatch();
|
|
275
|
+
const save = (top) => ui.repo.writeFile(top, rel, text);
|
|
276
|
+
const written = await this.write(save);
|
|
277
|
+
if (!written) return false;
|
|
278
|
+
ui.reads.keepText(rel, text);
|
|
258
279
|
this.composeBaseline = text;
|
|
259
280
|
this.fileDirty = true;
|
|
260
281
|
return true;
|
|
261
282
|
}
|
|
262
283
|
|
|
263
|
-
|
|
284
|
+
stageItems(rel) {
|
|
264
285
|
const ui = this.ui;
|
|
265
286
|
const repo = ui.repo;
|
|
266
|
-
if (!
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
ui.status = error.message;
|
|
286
|
-
return staged > 0;
|
|
287
|
-
}
|
|
288
|
-
ui.collection.keepOrigin(item, 'staged');
|
|
289
|
-
staged += 1;
|
|
290
|
-
}
|
|
291
|
-
if (staged) ui.ignoreWatch();
|
|
292
|
-
return staged > 0;
|
|
287
|
+
if (!repo.add) return;
|
|
288
|
+
const targets = ui.items.filter((item) => {
|
|
289
|
+
if (listPath(item) !== rel || isReadOnlyOrigin(item.origin)) return false;
|
|
290
|
+
return item.origin !== 'staged';
|
|
291
|
+
});
|
|
292
|
+
if (!targets.length) return;
|
|
293
|
+
for (const item of targets) ui.collection.keepOrigin(item, 'staged');
|
|
294
|
+
ui.ignoreWatch();
|
|
295
|
+
const write = async () => {
|
|
296
|
+
for (const item of targets) await ui.ops.add(item);
|
|
297
|
+
};
|
|
298
|
+
ui.ops.enqueueLazy({ kind: 'staged', done: '', reload: false, write });
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
async stageEditedFile(rel) {
|
|
302
|
+
const repo = this.ui.repo;
|
|
303
|
+
if (!rel || !this.canChange()) return;
|
|
304
|
+
if (!repo.stagePath) return void this.stageItems(rel);
|
|
305
|
+
await this.write((top) => repo.stagePath(top, rel));
|
|
293
306
|
}
|
|
294
307
|
|
|
295
308
|
finishSave(reload, doneStatus) {
|
|
@@ -300,84 +313,112 @@ class Composer {
|
|
|
300
313
|
if (!ui.status) ui.status = doneStatus;
|
|
301
314
|
return;
|
|
302
315
|
}
|
|
316
|
+
this.fileDirty = false;
|
|
303
317
|
ui.reloadAfterChange(() => {
|
|
304
318
|
if (!ui.status) ui.status = doneStatus;
|
|
305
319
|
});
|
|
306
320
|
}
|
|
307
321
|
|
|
308
|
-
|
|
322
|
+
// Closes the editor after a note or a block edit; reloads if a file changed
|
|
323
|
+
finishNote(doneStatus) {
|
|
324
|
+
const ui = this.ui;
|
|
325
|
+
if (this.fileDirty === true) return void this.finishSave(true, doneStatus);
|
|
326
|
+
ui.flushReview();
|
|
327
|
+
this.closeCompose();
|
|
328
|
+
ui.status = doneStatus;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async saveFile() {
|
|
309
332
|
const rel = this.ui.nav.reviewPath;
|
|
310
333
|
const opened = this.fileOpened;
|
|
311
|
-
const text = this.editor
|
|
312
|
-
|
|
313
|
-
|
|
334
|
+
const text = this.editor.text;
|
|
335
|
+
this.ui.nav.unitLine = this.editor.linePos().line;
|
|
336
|
+
const saved = await this.saveUnitFile(text);
|
|
337
|
+
if (!saved) return false;
|
|
314
338
|
const changed = text !== opened;
|
|
315
339
|
const reload = this.fileDirty === true;
|
|
316
|
-
if (changed) this.stageEditedFile(rel);
|
|
340
|
+
if (changed) await this.stageEditedFile(rel);
|
|
317
341
|
this.finishSave(reload, changed ? 'staged' : 'saved');
|
|
318
|
-
return
|
|
342
|
+
return true;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async saveCode() {
|
|
346
|
+
const ui = this.ui;
|
|
347
|
+
const item = ui.current();
|
|
348
|
+
const unstaged = item.origin !== 'staged';
|
|
349
|
+
const rel = itemPath(item);
|
|
350
|
+
const saved = await this.writeCode(this.editor.text);
|
|
351
|
+
if (!saved) return false;
|
|
352
|
+
const staging = unstaged && this.fileDirty === true;
|
|
353
|
+
if (staging) await this.stageEditedFile(rel);
|
|
354
|
+
this.finishNote(staging ? 'staged' : 'saved');
|
|
355
|
+
return true;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
saveEdit() {
|
|
359
|
+
return this.composeKind === 'file' ? this.saveFile() : this.saveCode();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// One save at a time; `after` runs once the edit is safely on disk
|
|
363
|
+
async saveToDisk(after) {
|
|
364
|
+
if (this.saving) return;
|
|
365
|
+
this.saving = true;
|
|
366
|
+
const settle = () => {
|
|
367
|
+
this.saving = false;
|
|
368
|
+
};
|
|
369
|
+
const saved = await this.saveEdit().finally(settle);
|
|
370
|
+
if (saved && after) after();
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Leaves the editor whether or not the edit could be written
|
|
374
|
+
async writeAndClose() {
|
|
375
|
+
const text = this.editor.text;
|
|
376
|
+
if (this.composeKind === 'file') await this.saveUnitFile(text);
|
|
377
|
+
else await this.writeCode(text);
|
|
378
|
+
this.closeCompose();
|
|
319
379
|
}
|
|
320
380
|
|
|
321
381
|
commitCompose() {
|
|
322
382
|
const kind = this.composeKind;
|
|
323
|
-
if (!kind || !this.editor) return
|
|
383
|
+
if (!kind || !this.editor) return;
|
|
324
384
|
const text = this.editor.text;
|
|
325
|
-
if (kind === 'feedback')
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
if (kind === 'tasks') {
|
|
330
|
-
this.tasks.commitTask(text);
|
|
331
|
-
return true;
|
|
332
|
-
}
|
|
333
|
-
if (kind === 'code') return this.commitCode(text);
|
|
334
|
-
if (kind === 'file') return this.saveUnitFile(text);
|
|
335
|
-
return true;
|
|
385
|
+
if (kind === 'feedback' && this.notes) this.commitFeedback(text);
|
|
386
|
+
if (kind === 'tasks') this.tasks.commitTask(text);
|
|
387
|
+
if (kind === 'code') this.commitCode(text);
|
|
336
388
|
}
|
|
337
389
|
|
|
390
|
+
// Notes save at once. An edit to a file answers `saving` and finishes later
|
|
338
391
|
saveCompose(opts = {}) {
|
|
339
392
|
const ui = this.ui;
|
|
340
393
|
const kind = this.composeKind;
|
|
341
394
|
if (COMMAND_KINDS.includes(kind)) return { kind };
|
|
342
|
-
if (
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
const rel = itemPath(item);
|
|
395
|
+
if (this.writesDisk()) {
|
|
396
|
+
ui.ops.hold(this.saveToDisk(opts.after));
|
|
397
|
+
return { kind: 'saving' };
|
|
398
|
+
}
|
|
347
399
|
const baseline = this.composeBaseline;
|
|
348
|
-
|
|
349
|
-
const reload = this.fileDirty === true;
|
|
400
|
+
this.commitCompose();
|
|
350
401
|
if (kind === 'feedback' && this.notes) {
|
|
351
402
|
const next = this.editor ? this.editor.text : '';
|
|
352
|
-
|
|
353
|
-
}
|
|
354
|
-
const staging = shouldStage && reload;
|
|
355
|
-
if (staging) this.stageEditedFile(rel);
|
|
356
|
-
const doneStatus = staging ? 'staged' : 'saved';
|
|
357
|
-
if (reload) {
|
|
358
|
-
this.finishSave(true, doneStatus);
|
|
359
|
-
} else {
|
|
360
|
-
ui.flushReview();
|
|
361
|
-
this.closeCompose();
|
|
362
|
-
ui.status = doneStatus;
|
|
403
|
+
this.notes.rememberTemplate(baseline, next);
|
|
363
404
|
}
|
|
405
|
+
this.finishNote('saved');
|
|
364
406
|
if (opts.editNext === true) this.tasks.editNextTask();
|
|
365
407
|
return { kind: 'saved' };
|
|
366
408
|
}
|
|
367
409
|
|
|
410
|
+
autosaveFile(text) {
|
|
411
|
+
if (this.saving || text === this.composeBaseline) return;
|
|
412
|
+
this.ui.ops.track(this.saveUnitFile(text));
|
|
413
|
+
}
|
|
414
|
+
|
|
368
415
|
autosave() {
|
|
369
416
|
const kind = this.composeKind;
|
|
370
417
|
const text = this.editor ? this.editor.text : '';
|
|
371
|
-
if (kind === 'file' && this.editor)
|
|
372
|
-
if (text === this.composeBaseline) return;
|
|
373
|
-
return void this.saveUnitFile(text);
|
|
374
|
-
}
|
|
375
|
-
const editingCode = kind === 'code';
|
|
418
|
+
if (kind === 'file' && this.editor) return void this.autosaveFile(text);
|
|
376
419
|
const reviewKind = kind !== null && !COMMAND_KINDS.includes(kind);
|
|
377
|
-
const
|
|
378
|
-
if (
|
|
379
|
-
this.commitCompose();
|
|
380
|
-
}
|
|
420
|
+
const noted = reviewKind && !this.writesDisk();
|
|
421
|
+
if (noted && (kind === 'code' || text.trim())) this.commitCompose();
|
|
381
422
|
if (this.notes && this.notes.dirty) this.ui.flushReview();
|
|
382
423
|
}
|
|
383
424
|
|
|
@@ -538,6 +579,12 @@ class Composer {
|
|
|
538
579
|
ui.status = 'read only';
|
|
539
580
|
return;
|
|
540
581
|
}
|
|
582
|
+
ui.ops.hold(this.editViewed());
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
async editViewed() {
|
|
586
|
+
const ui = this.ui;
|
|
587
|
+
await ui.reads.loadText(ui.nav.reviewPath);
|
|
541
588
|
const at = unit.editCursor(ui);
|
|
542
589
|
this.openCompose('file', at.text);
|
|
543
590
|
this.fileEditRows = at.rows ?? null;
|
|
@@ -554,8 +601,8 @@ class Composer {
|
|
|
554
601
|
ui.status = 'not a diff block';
|
|
555
602
|
return;
|
|
556
603
|
}
|
|
557
|
-
|
|
558
|
-
|
|
604
|
+
const edit = () => this.onUnitEdit();
|
|
605
|
+
ui.ops.hold(unit.showFile(ui, entry).then(edit));
|
|
559
606
|
}
|
|
560
607
|
|
|
561
608
|
onCode() {
|
|
@@ -563,7 +610,7 @@ class Composer {
|
|
|
563
610
|
if (ui.nav.pane === 'files') return void this.onFileEdit();
|
|
564
611
|
if (ui.nav.pane === 'unit') return void this.onUnitEdit();
|
|
565
612
|
const item = ui.current();
|
|
566
|
-
if (
|
|
613
|
+
if (!canEditCode(item)) {
|
|
567
614
|
ui.status = 'not a diff block';
|
|
568
615
|
return;
|
|
569
616
|
}
|
|
@@ -609,16 +656,11 @@ class Composer {
|
|
|
609
656
|
}
|
|
610
657
|
|
|
611
658
|
copy() {
|
|
612
|
-
|
|
613
|
-
if (!text) return;
|
|
614
|
-
const ok = clipboard.copyText(text, this.ui.stdout);
|
|
615
|
-
this.ui.status = ok ? 'copied' : 'copy failed';
|
|
659
|
+
copyLine(this.editor, this.ui);
|
|
616
660
|
}
|
|
617
661
|
|
|
618
662
|
cut() {
|
|
619
|
-
|
|
620
|
-
this.copy();
|
|
621
|
-
this.editor.removeSpan();
|
|
663
|
+
cutLine(this.editor, this.ui);
|
|
622
664
|
}
|
|
623
665
|
|
|
624
666
|
paste() {
|
|
@@ -646,10 +688,7 @@ class Composer {
|
|
|
646
688
|
this.afterEdit();
|
|
647
689
|
return result ?? null;
|
|
648
690
|
}
|
|
649
|
-
if (
|
|
650
|
-
return null;
|
|
651
|
-
}
|
|
652
|
-
if (key.length === 1 && key.charCodeAt(0) >= 32) {
|
|
691
|
+
if (isTextKey(key)) {
|
|
653
692
|
this.templates.clearTemplatePick();
|
|
654
693
|
this.editor.insert(key);
|
|
655
694
|
this.afterEdit();
|
|
@@ -660,12 +699,15 @@ class Composer {
|
|
|
660
699
|
applyDiskText(text) {
|
|
661
700
|
const editor = this.editor;
|
|
662
701
|
if (this.composeKind !== 'file' || !editor) return;
|
|
663
|
-
|
|
702
|
+
// The baseline is the last text known on disk: no news, keep the typing
|
|
703
|
+
if (text === editor.text || text === this.composeBaseline) return;
|
|
664
704
|
const cursor = editor.cursor;
|
|
665
705
|
editor.text = text;
|
|
666
706
|
editor.cursor = Math.min(cursor, text.length);
|
|
667
707
|
editor.anchor = null;
|
|
668
708
|
this.composeBaseline = text;
|
|
709
|
+
// An outside change is the new starting point, not an edit made here
|
|
710
|
+
this.fileOpened = text;
|
|
669
711
|
this.fileEditRows = unit.linesFor(this.ui, this.ui.nav.reviewPath, null);
|
|
670
712
|
this.syncCodeScroll();
|
|
671
713
|
this.followFileEdit();
|
package/lib/session/dashboard.js
CHANGED
|
@@ -1,22 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
5
|
-
const
|
|
6
|
-
const {
|
|
7
|
-
const
|
|
8
|
-
const {
|
|
9
|
-
const tree = require('../dashboard/tree.js');
|
|
10
|
-
const { FileIndex, HEAT_MS } = tree;
|
|
11
|
-
const gitInfo = require('../dashboard/git.js');
|
|
12
|
-
const { readGitSummary } = gitInfo;
|
|
13
|
-
const npmInfo = require('../dashboard/npm.js');
|
|
14
|
-
const { readNpmSummary } = npmInfo;
|
|
3
|
+
const { fileEntries, fileTotals } = require('../common/files.js');
|
|
4
|
+
const { noteCounts } = require('../review/review.js');
|
|
5
|
+
const { readRuns, settleRecord, isRunsRel } = require('../runs/runs.js');
|
|
6
|
+
const { FileIndex, HEAT_MS } = require('../dashboard/tree.js');
|
|
7
|
+
const { readGitSummary } = require('../dashboard/git.js');
|
|
8
|
+
const { readNpmSummary } = require('../dashboard/npm.js');
|
|
15
9
|
const model = require('../dashboard/model.js');
|
|
16
10
|
const { buildModel, minuteOf, MINUTE_MS, npmModel } = model;
|
|
17
11
|
const filesPane = require('./files-pane.js');
|
|
18
|
-
const
|
|
19
|
-
const { UNKNOWN_PATH, MODULES_STAMP } = watch;
|
|
12
|
+
const { UNKNOWN_PATH, MODULES_STAMP } = require('../dashboard/watch.js');
|
|
20
13
|
|
|
21
14
|
const TICK_ID = 'dashboard';
|
|
22
15
|
const HEAT_TIMER = 'dashboard-heat';
|
|
@@ -27,7 +20,7 @@ const BLINK_MS = 3000;
|
|
|
27
20
|
const MINUTE_KEEP = 360;
|
|
28
21
|
const SWITCHES = 4;
|
|
29
22
|
const RUN_MARK = { failed: 'fail', passed: 'pass', aborted: 'abort' };
|
|
30
|
-
const GIT_NAMES =
|
|
23
|
+
const GIT_NAMES = [
|
|
31
24
|
'HEAD',
|
|
32
25
|
'packed-refs',
|
|
33
26
|
'FETCH_HEAD',
|
|
@@ -36,8 +29,8 @@ const GIT_NAMES = new Set([
|
|
|
36
29
|
'logs',
|
|
37
30
|
'rebase-merge',
|
|
38
31
|
'rebase-apply',
|
|
39
|
-
]
|
|
40
|
-
const NPM_FILES =
|
|
32
|
+
];
|
|
33
|
+
const NPM_FILES = ['package.json', 'package-lock.json', MODULES_STAMP];
|
|
41
34
|
const AGE_FAST_MS = 1000;
|
|
42
35
|
const AGE_MID_MS = 30000;
|
|
43
36
|
const AGE_SLOW_MS = 600000;
|
|
@@ -85,11 +78,12 @@ const classify = (paths) => {
|
|
|
85
78
|
if (rel === UNKNOWN_PATH) {
|
|
86
79
|
found.all = true;
|
|
87
80
|
} else if (rel.startsWith('.git/')) {
|
|
88
|
-
|
|
81
|
+
const gitName = rel.split('/')[1];
|
|
82
|
+
if (GIT_NAMES.includes(gitName)) found.git = true;
|
|
89
83
|
} else if (isRunsRel(rel)) {
|
|
90
84
|
found.runs = true;
|
|
91
85
|
} else {
|
|
92
|
-
if (NPM_FILES.
|
|
86
|
+
if (NPM_FILES.includes(rel)) found.npm = true;
|
|
93
87
|
found.files.push(rel);
|
|
94
88
|
}
|
|
95
89
|
}
|
|
@@ -410,8 +404,12 @@ class Dashboard {
|
|
|
410
404
|
}
|
|
411
405
|
|
|
412
406
|
noteDisk(paths) {
|
|
413
|
-
if (!this.active) return;
|
|
414
407
|
const found = classify(paths);
|
|
408
|
+
const npm = this.ui.npm;
|
|
409
|
+
if ((found.runs || found.all) && npm && npm.refreshRecorded) {
|
|
410
|
+
npm.refreshRecorded();
|
|
411
|
+
}
|
|
412
|
+
if (!this.active) return;
|
|
415
413
|
if (found.all) return void this.requestAll();
|
|
416
414
|
const tasks = this.tasks;
|
|
417
415
|
if (found.files.length) {
|
|
@@ -465,9 +463,11 @@ class Dashboard {
|
|
|
465
463
|
npm: this.npm,
|
|
466
464
|
npmExtras: ui.lifecycle.npmExtras(),
|
|
467
465
|
npmRun: ui.npm.lastRun,
|
|
466
|
+
npmRuns: ui.npm.dashboardRuns(),
|
|
468
467
|
runs: this.liveRuns(),
|
|
469
468
|
notes: noteCounts(store),
|
|
470
469
|
store,
|
|
470
|
+
agents: ui.agents.summary(),
|
|
471
471
|
});
|
|
472
472
|
}
|
|
473
473
|
|
|
@@ -498,8 +498,8 @@ class Dashboard {
|
|
|
498
498
|
showFiles(scope) {
|
|
499
499
|
const ui = this.ui;
|
|
500
500
|
ui.showFiles();
|
|
501
|
-
if (scope === 'file') filesPane.onFileScope(ui);
|
|
502
|
-
else filesPane.onDiffScope(ui);
|
|
501
|
+
if (scope === 'file') filesPane.onFileScope(ui, true);
|
|
502
|
+
else filesPane.onDiffScope(ui, true);
|
|
503
503
|
ui.status = '';
|
|
504
504
|
}
|
|
505
505
|
|
|
@@ -513,6 +513,7 @@ class Dashboard {
|
|
|
513
513
|
npm: () => this.openPackages(),
|
|
514
514
|
run: () => ui.npm.open(),
|
|
515
515
|
tasks: () => ui.composer.tasks.openTasksPage(),
|
|
516
|
+
agents: () => ui.agents.open(),
|
|
516
517
|
};
|
|
517
518
|
const opener = openers[id];
|
|
518
519
|
if (!opener || ui.mode !== 'review') return;
|