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
package/lib/session/compose.js
CHANGED
|
@@ -1,29 +1,24 @@
|
|
|
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 {
|
|
13
|
-
const
|
|
14
|
-
const {
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
const
|
|
18
|
-
const {
|
|
19
|
-
const
|
|
20
|
-
const { setFeedback, setCode, checkLabel, rememberTemplate } = review;
|
|
21
|
-
const templates = require('./templates.js');
|
|
22
|
-
const { TemplatePick } = templates;
|
|
23
|
-
const tasks = require('./tasks.js');
|
|
24
|
-
const { TaskPage } = tasks;
|
|
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');
|
|
11
|
+
const { itemPath, isTaskItem, isTasksEntry, listPath } = files;
|
|
12
|
+
const { isReadOnlyOrigin } = files;
|
|
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');
|
|
25
19
|
const unit = require('./unit.js');
|
|
26
|
-
const clipboard = require('../clipboard.js');
|
|
20
|
+
const clipboard = require('../term/clipboard.js');
|
|
21
|
+
const { EDIT_OP, copyLine, cutLine } = require('../input/line-edit.js');
|
|
27
22
|
|
|
28
23
|
const COMPOSE_EDITS = ['backspace', 'delete', 'ctrl-z', 'ctrl-y'];
|
|
29
24
|
const COMPOSE_CLIP = ['ctrl-x', 'ctrl-v'];
|
|
@@ -37,10 +32,11 @@ const KIND = {
|
|
|
37
32
|
commit: { enter: 'save', tab: 'template', escape: 'cancel', move: 'note' },
|
|
38
33
|
branch: { enter: 'save', tab: 'template', escape: 'cancel', move: 'note' },
|
|
39
34
|
npm: { enter: 'save', tab: 'insert-tab', escape: 'cancel', move: 'note' },
|
|
35
|
+
package: { enter: 'save', tab: 'insert-tab', escape: 'cancel', move: 'note' },
|
|
40
36
|
};
|
|
41
37
|
|
|
42
|
-
const COMMAND_KINDS = ['commit', 'branch', 'npm'];
|
|
43
|
-
const NOTE_VIEWS = ['feedback', 'commit', 'branch', 'npm'];
|
|
38
|
+
const COMMAND_KINDS = ['commit', 'branch', 'npm', 'package'];
|
|
39
|
+
const NOTE_VIEWS = ['feedback', 'commit', 'branch', 'npm', 'package'];
|
|
44
40
|
|
|
45
41
|
const itemNote = (item, text) => {
|
|
46
42
|
const hunk = item.hunk;
|
|
@@ -76,17 +72,13 @@ const blankTail = (text) => {
|
|
|
76
72
|
return count;
|
|
77
73
|
};
|
|
78
74
|
|
|
75
|
+
const EDITOR_KEYS = {};
|
|
76
|
+
for (const key of Object.keys(EDIT_OP)) {
|
|
77
|
+
EDITOR_KEYS[key] = (c) => EDIT_OP[key](c.editor);
|
|
78
|
+
}
|
|
79
|
+
|
|
79
80
|
const COMPOSE_SPECIAL = {
|
|
80
|
-
|
|
81
|
-
delete: (c) => c.editor.delete(),
|
|
82
|
-
left: (c) => c.editor.move(-1),
|
|
83
|
-
right: (c) => c.editor.move(1),
|
|
84
|
-
'shift-left': (c) => c.editor.move(-1, true),
|
|
85
|
-
'shift-right': (c) => c.editor.move(1, true),
|
|
86
|
-
'ctrl-left': (c) => c.editor.moveWord(-1),
|
|
87
|
-
'ctrl-right': (c) => c.editor.moveWord(1),
|
|
88
|
-
'ctrl-shift-left': (c) => c.editor.moveWord(-1, true),
|
|
89
|
-
'ctrl-shift-right': (c) => c.editor.moveWord(1, true),
|
|
81
|
+
...EDITOR_KEYS,
|
|
90
82
|
up: (c) => c.moveLine(-1),
|
|
91
83
|
down: (c) => c.moveLine(1),
|
|
92
84
|
'shift-up': (c) => c.moveLine(-1, true),
|
|
@@ -116,7 +108,9 @@ class Composer {
|
|
|
116
108
|
}
|
|
117
109
|
|
|
118
110
|
reset() {
|
|
111
|
+
this.tasks.closePlanMenu();
|
|
119
112
|
this.fileDirty = false;
|
|
113
|
+
this.saving = false;
|
|
120
114
|
this.clear();
|
|
121
115
|
}
|
|
122
116
|
|
|
@@ -126,6 +120,7 @@ class Composer {
|
|
|
126
120
|
this.commitKind = null;
|
|
127
121
|
this.commitBody = '';
|
|
128
122
|
this.composeTaskId = null;
|
|
123
|
+
this.composeQuoteKey = null;
|
|
129
124
|
this.composeBaseline = '';
|
|
130
125
|
this.templateIndex = -1;
|
|
131
126
|
this.templateFocus = false;
|
|
@@ -155,7 +150,8 @@ class Composer {
|
|
|
155
150
|
if (kind !== 'code' && kind !== 'file') return;
|
|
156
151
|
if (!this.editor) return;
|
|
157
152
|
const width = this.viewSize().width;
|
|
158
|
-
|
|
153
|
+
const digits = activeDigits(this.ui);
|
|
154
|
+
this.editor.reveal(codeInnerWidth(width, this.ui.layout, digits));
|
|
159
155
|
}
|
|
160
156
|
|
|
161
157
|
followFileEdit() {
|
|
@@ -171,6 +167,7 @@ class Composer {
|
|
|
171
167
|
this.ui.mode = 'compose';
|
|
172
168
|
this.composeKind = kind;
|
|
173
169
|
this.composeTaskId = taskId;
|
|
170
|
+
this.composeQuoteKey = null;
|
|
174
171
|
this.composeBaseline = text;
|
|
175
172
|
this.fileOpened = kind === 'file' ? text : '';
|
|
176
173
|
this.editor = new Editor(text);
|
|
@@ -180,6 +177,11 @@ class Composer {
|
|
|
180
177
|
this.ui.nav.clearSelection();
|
|
181
178
|
}
|
|
182
179
|
|
|
180
|
+
openQuote(key, text) {
|
|
181
|
+
this.openCompose('tasks', text, null);
|
|
182
|
+
this.composeQuoteKey = key;
|
|
183
|
+
}
|
|
184
|
+
|
|
183
185
|
closeCompose() {
|
|
184
186
|
const ui = this.ui;
|
|
185
187
|
ui.mode = 'review';
|
|
@@ -203,7 +205,7 @@ class Composer {
|
|
|
203
205
|
commitFeedback(text) {
|
|
204
206
|
const item = this.ui.current();
|
|
205
207
|
if (!item || isTaskItem(item)) return;
|
|
206
|
-
|
|
208
|
+
this.notes.setFeedback(itemFeedKey(item), itemNote(item, text));
|
|
207
209
|
}
|
|
208
210
|
|
|
209
211
|
dropCodeNote(item, text) {
|
|
@@ -211,34 +213,52 @@ class Composer {
|
|
|
211
213
|
if (!notes) return;
|
|
212
214
|
const key = itemFeedKey(item);
|
|
213
215
|
if (!notes.code.has(key)) return;
|
|
214
|
-
setCode(
|
|
216
|
+
notes.setCode(key, { ...itemNote(item, text), clear: true });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Edits that land in the worktree or index, not in the review notes
|
|
220
|
+
writesDisk() {
|
|
221
|
+
const kind = this.composeKind;
|
|
222
|
+
if (!this.editor) return false;
|
|
223
|
+
if (kind === 'file') return true;
|
|
224
|
+
return kind === 'code' && this.writesCodeFile(this.ui.current());
|
|
215
225
|
}
|
|
216
226
|
|
|
217
227
|
commitCode(text) {
|
|
228
|
+
const item = this.ui.current();
|
|
229
|
+
if (!canEditCode(item) || this.writesCodeFile(item)) return;
|
|
230
|
+
const note = itemNote(item, text);
|
|
231
|
+
const clear = text === blockAddText(item.hunk, item.blockId);
|
|
232
|
+
this.notes.setCode(itemFeedKey(item), clear ? { ...note, clear } : note);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async write(action) {
|
|
218
236
|
const ui = this.ui;
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
setCode(this.notes, itemFeedKey(item), clear ? { ...note, clear } : note);
|
|
226
|
-
return true;
|
|
237
|
+
ui.ignoreWatch();
|
|
238
|
+
try {
|
|
239
|
+
await ui.ops.exclusive(action);
|
|
240
|
+
} catch (error) {
|
|
241
|
+
ui.status = error.message;
|
|
242
|
+
return false;
|
|
227
243
|
}
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
244
|
+
ui.ignoreWatch();
|
|
245
|
+
return true;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async writeCode(text) {
|
|
249
|
+
const ui = this.ui;
|
|
250
|
+
const item = ui.current();
|
|
251
|
+
if (text !== blockAddText(item.hunk, item.blockId)) {
|
|
252
|
+
const edit = (top) => ui.repo.edit(top, item, text);
|
|
253
|
+
const written = await this.write(edit);
|
|
254
|
+
if (!written) return false;
|
|
235
255
|
this.fileDirty = true;
|
|
236
256
|
}
|
|
237
257
|
this.dropCodeNote(item, text);
|
|
238
258
|
return true;
|
|
239
259
|
}
|
|
240
260
|
|
|
241
|
-
saveUnitFile(text) {
|
|
261
|
+
async saveUnitFile(text) {
|
|
242
262
|
const ui = this.ui;
|
|
243
263
|
const rel = ui.nav.reviewPath;
|
|
244
264
|
if (!rel || !ui.repo.writeFile) return true;
|
|
@@ -246,48 +266,37 @@ class Composer {
|
|
|
246
266
|
ui.status = 'read only';
|
|
247
267
|
return false;
|
|
248
268
|
}
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return false;
|
|
254
|
-
}
|
|
255
|
-
ui.ignoreWatch();
|
|
269
|
+
const save = (top) => ui.repo.writeFile(top, rel, text);
|
|
270
|
+
const written = await this.write(save);
|
|
271
|
+
if (!written) return false;
|
|
272
|
+
ui.reads.keepText(rel, text);
|
|
256
273
|
this.composeBaseline = text;
|
|
257
274
|
this.fileDirty = true;
|
|
258
275
|
return true;
|
|
259
276
|
}
|
|
260
277
|
|
|
261
|
-
|
|
278
|
+
stageItems(rel) {
|
|
262
279
|
const ui = this.ui;
|
|
263
280
|
const repo = ui.repo;
|
|
264
|
-
if (!
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
ui.status = error.message;
|
|
284
|
-
return staged > 0;
|
|
285
|
-
}
|
|
286
|
-
ui.collection.keepOrigin(item, 'staged');
|
|
287
|
-
staged += 1;
|
|
288
|
-
}
|
|
289
|
-
if (staged) ui.ignoreWatch();
|
|
290
|
-
return staged > 0;
|
|
281
|
+
if (!repo.add) return;
|
|
282
|
+
const targets = ui.items.filter((item) => {
|
|
283
|
+
if (listPath(item) !== rel || isReadOnlyOrigin(item.origin)) return false;
|
|
284
|
+
return item.origin !== 'staged';
|
|
285
|
+
});
|
|
286
|
+
if (!targets.length) return;
|
|
287
|
+
for (const item of targets) ui.collection.keepOrigin(item, 'staged');
|
|
288
|
+
ui.ignoreWatch();
|
|
289
|
+
const write = async () => {
|
|
290
|
+
for (const item of targets) await ui.ops.add(item);
|
|
291
|
+
};
|
|
292
|
+
ui.ops.enqueueLazy({ kind: 'staged', done: '', reload: false, write });
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async stageEditedFile(rel) {
|
|
296
|
+
const repo = this.ui.repo;
|
|
297
|
+
if (!rel || !this.canChange()) return;
|
|
298
|
+
if (!repo.stagePath) return void this.stageItems(rel);
|
|
299
|
+
await this.write((top) => repo.stagePath(top, rel));
|
|
291
300
|
}
|
|
292
301
|
|
|
293
302
|
finishSave(reload, doneStatus) {
|
|
@@ -298,84 +307,112 @@ class Composer {
|
|
|
298
307
|
if (!ui.status) ui.status = doneStatus;
|
|
299
308
|
return;
|
|
300
309
|
}
|
|
310
|
+
this.fileDirty = false;
|
|
301
311
|
ui.reloadAfterChange(() => {
|
|
302
312
|
if (!ui.status) ui.status = doneStatus;
|
|
303
313
|
});
|
|
304
314
|
}
|
|
305
315
|
|
|
306
|
-
|
|
316
|
+
// Closes the editor after a note or a block edit; reloads if a file changed
|
|
317
|
+
finishNote(doneStatus) {
|
|
318
|
+
const ui = this.ui;
|
|
319
|
+
if (this.fileDirty === true) return void this.finishSave(true, doneStatus);
|
|
320
|
+
ui.flushReview();
|
|
321
|
+
this.closeCompose();
|
|
322
|
+
ui.status = doneStatus;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
async saveFile() {
|
|
307
326
|
const rel = this.ui.nav.reviewPath;
|
|
308
327
|
const opened = this.fileOpened;
|
|
309
|
-
const text = this.editor
|
|
310
|
-
|
|
311
|
-
|
|
328
|
+
const text = this.editor.text;
|
|
329
|
+
this.ui.nav.unitLine = this.editor.linePos().line;
|
|
330
|
+
const saved = await this.saveUnitFile(text);
|
|
331
|
+
if (!saved) return false;
|
|
312
332
|
const changed = text !== opened;
|
|
313
333
|
const reload = this.fileDirty === true;
|
|
314
|
-
if (changed) this.stageEditedFile(rel);
|
|
334
|
+
if (changed) await this.stageEditedFile(rel);
|
|
315
335
|
this.finishSave(reload, changed ? 'staged' : 'saved');
|
|
316
|
-
return
|
|
336
|
+
return true;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
async saveCode() {
|
|
340
|
+
const ui = this.ui;
|
|
341
|
+
const item = ui.current();
|
|
342
|
+
const unstaged = item.origin !== 'staged';
|
|
343
|
+
const rel = itemPath(item);
|
|
344
|
+
const saved = await this.writeCode(this.editor.text);
|
|
345
|
+
if (!saved) return false;
|
|
346
|
+
const staging = unstaged && this.fileDirty === true;
|
|
347
|
+
if (staging) await this.stageEditedFile(rel);
|
|
348
|
+
this.finishNote(staging ? 'staged' : 'saved');
|
|
349
|
+
return true;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
saveEdit() {
|
|
353
|
+
return this.composeKind === 'file' ? this.saveFile() : this.saveCode();
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// One save at a time; `after` runs once the edit is safely on disk
|
|
357
|
+
async saveToDisk(after) {
|
|
358
|
+
if (this.saving) return;
|
|
359
|
+
this.saving = true;
|
|
360
|
+
const settle = () => {
|
|
361
|
+
this.saving = false;
|
|
362
|
+
};
|
|
363
|
+
const saved = await this.saveEdit().finally(settle);
|
|
364
|
+
if (saved && after) after();
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Leaves the editor whether or not the edit could be written
|
|
368
|
+
async writeAndClose() {
|
|
369
|
+
const text = this.editor.text;
|
|
370
|
+
if (this.composeKind === 'file') await this.saveUnitFile(text);
|
|
371
|
+
else await this.writeCode(text);
|
|
372
|
+
this.closeCompose();
|
|
317
373
|
}
|
|
318
374
|
|
|
319
375
|
commitCompose() {
|
|
320
376
|
const kind = this.composeKind;
|
|
321
|
-
if (!kind || !this.editor) return
|
|
377
|
+
if (!kind || !this.editor) return;
|
|
322
378
|
const text = this.editor.text;
|
|
323
|
-
if (kind === 'feedback')
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
}
|
|
327
|
-
if (kind === 'tasks') {
|
|
328
|
-
this.tasks.commitTask(text);
|
|
329
|
-
return true;
|
|
330
|
-
}
|
|
331
|
-
if (kind === 'code') return this.commitCode(text);
|
|
332
|
-
if (kind === 'file') return this.saveUnitFile(text);
|
|
333
|
-
return true;
|
|
379
|
+
if (kind === 'feedback' && this.notes) this.commitFeedback(text);
|
|
380
|
+
if (kind === 'tasks') this.tasks.commitTask(text);
|
|
381
|
+
if (kind === 'code') this.commitCode(text);
|
|
334
382
|
}
|
|
335
383
|
|
|
384
|
+
// Notes save at once. An edit to a file answers `saving` and finishes later
|
|
336
385
|
saveCompose(opts = {}) {
|
|
337
386
|
const ui = this.ui;
|
|
338
387
|
const kind = this.composeKind;
|
|
339
388
|
if (COMMAND_KINDS.includes(kind)) return { kind };
|
|
340
|
-
if (
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
const rel = itemPath(item);
|
|
389
|
+
if (this.writesDisk()) {
|
|
390
|
+
ui.ops.hold(this.saveToDisk(opts.after));
|
|
391
|
+
return { kind: 'saving' };
|
|
392
|
+
}
|
|
345
393
|
const baseline = this.composeBaseline;
|
|
346
|
-
|
|
347
|
-
const reload = this.fileDirty === true;
|
|
394
|
+
this.commitCompose();
|
|
348
395
|
if (kind === 'feedback' && this.notes) {
|
|
349
396
|
const next = this.editor ? this.editor.text : '';
|
|
350
|
-
|
|
351
|
-
}
|
|
352
|
-
const staging = shouldStage && reload;
|
|
353
|
-
if (staging) this.stageEditedFile(rel);
|
|
354
|
-
const doneStatus = staging ? 'staged' : 'saved';
|
|
355
|
-
if (reload) {
|
|
356
|
-
this.finishSave(true, doneStatus);
|
|
357
|
-
} else {
|
|
358
|
-
ui.flushReview();
|
|
359
|
-
this.closeCompose();
|
|
360
|
-
ui.status = doneStatus;
|
|
397
|
+
this.notes.rememberTemplate(baseline, next);
|
|
361
398
|
}
|
|
399
|
+
this.finishNote('saved');
|
|
362
400
|
if (opts.editNext === true) this.tasks.editNextTask();
|
|
363
401
|
return { kind: 'saved' };
|
|
364
402
|
}
|
|
365
403
|
|
|
404
|
+
autosaveFile(text) {
|
|
405
|
+
if (this.saving || text === this.composeBaseline) return;
|
|
406
|
+
this.ui.ops.track(this.saveUnitFile(text));
|
|
407
|
+
}
|
|
408
|
+
|
|
366
409
|
autosave() {
|
|
367
410
|
const kind = this.composeKind;
|
|
368
411
|
const text = this.editor ? this.editor.text : '';
|
|
369
|
-
if (kind === 'file' && this.editor)
|
|
370
|
-
if (text === this.composeBaseline) return;
|
|
371
|
-
return void this.saveUnitFile(text);
|
|
372
|
-
}
|
|
373
|
-
const editingCode = kind === 'code';
|
|
412
|
+
if (kind === 'file' && this.editor) return void this.autosaveFile(text);
|
|
374
413
|
const reviewKind = kind !== null && !COMMAND_KINDS.includes(kind);
|
|
375
|
-
const
|
|
376
|
-
if (
|
|
377
|
-
this.commitCompose();
|
|
378
|
-
}
|
|
414
|
+
const noted = reviewKind && !this.writesDisk();
|
|
415
|
+
if (noted && (kind === 'code' || text.trim())) this.commitCompose();
|
|
379
416
|
if (this.notes && this.notes.dirty) this.ui.flushReview();
|
|
380
417
|
}
|
|
381
418
|
|
|
@@ -536,6 +573,12 @@ class Composer {
|
|
|
536
573
|
ui.status = 'read only';
|
|
537
574
|
return;
|
|
538
575
|
}
|
|
576
|
+
ui.ops.hold(this.editViewed());
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
async editViewed() {
|
|
580
|
+
const ui = this.ui;
|
|
581
|
+
await ui.reads.loadText(ui.nav.reviewPath);
|
|
539
582
|
const at = unit.editCursor(ui);
|
|
540
583
|
this.openCompose('file', at.text);
|
|
541
584
|
this.fileEditRows = at.rows ?? null;
|
|
@@ -544,11 +587,24 @@ class Composer {
|
|
|
544
587
|
this.followFileEdit();
|
|
545
588
|
}
|
|
546
589
|
|
|
590
|
+
onFileEdit() {
|
|
591
|
+
const ui = this.ui;
|
|
592
|
+
const list = ui.fileList();
|
|
593
|
+
const entry = list[ui.nav.fileCursor];
|
|
594
|
+
if (!entry || isTasksEntry(entry)) {
|
|
595
|
+
ui.status = 'not a diff block';
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const edit = () => this.onUnitEdit();
|
|
599
|
+
ui.ops.hold(unit.showFile(ui, entry).then(edit));
|
|
600
|
+
}
|
|
601
|
+
|
|
547
602
|
onCode() {
|
|
548
603
|
const ui = this.ui;
|
|
604
|
+
if (ui.nav.pane === 'files') return void this.onFileEdit();
|
|
549
605
|
if (ui.nav.pane === 'unit') return void this.onUnitEdit();
|
|
550
606
|
const item = ui.current();
|
|
551
|
-
if (
|
|
607
|
+
if (!canEditCode(item)) {
|
|
552
608
|
ui.status = 'not a diff block';
|
|
553
609
|
return;
|
|
554
610
|
}
|
|
@@ -594,16 +650,11 @@ class Composer {
|
|
|
594
650
|
}
|
|
595
651
|
|
|
596
652
|
copy() {
|
|
597
|
-
|
|
598
|
-
if (!text) return;
|
|
599
|
-
const ok = clipboard.copyText(text, this.ui.stdout);
|
|
600
|
-
this.ui.status = ok ? 'copied' : 'copy failed';
|
|
653
|
+
copyLine(this.editor, this.ui);
|
|
601
654
|
}
|
|
602
655
|
|
|
603
656
|
cut() {
|
|
604
|
-
|
|
605
|
-
this.copy();
|
|
606
|
-
this.editor.removeSpan();
|
|
657
|
+
cutLine(this.editor, this.ui);
|
|
607
658
|
}
|
|
608
659
|
|
|
609
660
|
paste() {
|
|
@@ -631,10 +682,7 @@ class Composer {
|
|
|
631
682
|
this.afterEdit();
|
|
632
683
|
return result ?? null;
|
|
633
684
|
}
|
|
634
|
-
if (
|
|
635
|
-
return null;
|
|
636
|
-
}
|
|
637
|
-
if (key.charCodeAt(0) >= 32) {
|
|
685
|
+
if (isTextKey(key)) {
|
|
638
686
|
this.templates.clearTemplatePick();
|
|
639
687
|
this.editor.insert(key);
|
|
640
688
|
this.afterEdit();
|
|
@@ -645,12 +693,15 @@ class Composer {
|
|
|
645
693
|
applyDiskText(text) {
|
|
646
694
|
const editor = this.editor;
|
|
647
695
|
if (this.composeKind !== 'file' || !editor) return;
|
|
648
|
-
|
|
696
|
+
// The baseline is the last text known on disk: no news, keep the typing
|
|
697
|
+
if (text === editor.text || text === this.composeBaseline) return;
|
|
649
698
|
const cursor = editor.cursor;
|
|
650
699
|
editor.text = text;
|
|
651
700
|
editor.cursor = Math.min(cursor, text.length);
|
|
652
701
|
editor.anchor = null;
|
|
653
702
|
this.composeBaseline = text;
|
|
703
|
+
// An outside change is the new starting point, not an edit made here
|
|
704
|
+
this.fileOpened = text;
|
|
654
705
|
this.fileEditRows = unit.linesFor(this.ui, this.ui.nav.reviewPath, null);
|
|
655
706
|
this.syncCodeScroll();
|
|
656
707
|
this.followFileEdit();
|