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.
Files changed (158) hide show
  1. package/CHANGELOG.md +30 -1
  2. package/README.md +16 -14
  3. package/bin/reslop.js +8 -5
  4. package/lib/agents/agents.js +573 -0
  5. package/lib/agents/allow.js +71 -0
  6. package/lib/agents/allow.json +17 -0
  7. package/lib/agents/models.js +467 -0
  8. package/lib/api.js +15 -12
  9. package/lib/cli.js +43 -68
  10. package/lib/{files.js → common/files.js} +48 -55
  11. package/lib/{find.js → common/find.js} +1 -1
  12. package/lib/common/format.js +101 -0
  13. package/lib/{utilities.js → common/process.js} +7 -95
  14. package/lib/common/tasks.json +27 -0
  15. package/lib/common/utilities.js +112 -0
  16. package/lib/dashboard/git.js +9 -16
  17. package/lib/dashboard/model.js +90 -49
  18. package/lib/dashboard/npm.js +17 -13
  19. package/lib/dashboard/tree.js +27 -30
  20. package/lib/{session → dashboard}/watch.js +36 -31
  21. package/lib/{workspace.js → dashboard/workspace.js} +23 -38
  22. package/lib/{deps.js → deps/deps.js} +31 -45
  23. package/lib/{deps-diff.js → deps/diff.js} +35 -25
  24. package/lib/{manifest.js → deps/manifest.js} +47 -30
  25. package/lib/diff/align.js +1 -2
  26. package/lib/diff/diff.js +2 -2
  27. package/lib/diff/display.js +29 -5
  28. package/lib/diff/inline.js +10 -14
  29. package/lib/diff/items.js +6 -4
  30. package/lib/diff/patch.js +39 -3
  31. package/lib/diff/unit.js +45 -16
  32. package/lib/{git-branches.js → git/branches.js} +53 -48
  33. package/lib/{git-deps.js → git/deps.js} +151 -95
  34. package/lib/git/git.js +71 -0
  35. package/lib/git/worktree.js +504 -0
  36. package/lib/highlight/bash.js +1 -2
  37. package/lib/highlight/csv.js +1 -2
  38. package/lib/highlight/dot.js +1 -2
  39. package/lib/{highlight.js → highlight/highlight.js} +42 -11
  40. package/lib/highlight/html.js +20 -18
  41. package/lib/highlight/json.js +1 -2
  42. package/lib/highlight/log.js +1 -2
  43. package/lib/highlight/txt.js +1 -2
  44. package/lib/{session → input}/actions.js +114 -48
  45. package/lib/{editor.js → input/editor.js} +16 -8
  46. package/lib/{keys.js → input/keys.js} +24 -45
  47. package/lib/input/line-edit.js +78 -0
  48. package/lib/input/menu-pick.js +70 -0
  49. package/lib/render/agents.js +642 -0
  50. package/lib/render/branches.js +4 -5
  51. package/lib/render/commits.js +54 -64
  52. package/lib/render/{dash-activity.js → dashboard/activity.js} +25 -40
  53. package/lib/render/{dash-blocks.js → dashboard/blocks.js} +131 -137
  54. package/lib/render/{dashboard.js → dashboard/dashboard.js} +25 -22
  55. package/lib/render/diff.js +166 -45
  56. package/lib/render/field.js +131 -0
  57. package/lib/render/files.js +9 -14
  58. package/lib/render/footer.js +385 -0
  59. package/lib/render/frame.js +123 -25
  60. package/lib/render/header.js +176 -0
  61. package/lib/render/log.js +320 -0
  62. package/lib/render/menu.js +60 -0
  63. package/lib/render/notes.js +16 -11
  64. package/lib/render/npm.js +322 -196
  65. package/lib/render/packages.js +9 -13
  66. package/lib/render/primitives.js +101 -15
  67. package/lib/render/render.js +7 -11
  68. package/lib/render/repos.js +13 -25
  69. package/lib/render/status.js +370 -0
  70. package/lib/render/status.json +85 -0
  71. package/lib/render/{dash-table.js → table.js} +13 -60
  72. package/lib/render/tiles.js +109 -48
  73. package/lib/render/unit.js +32 -13
  74. package/lib/{report-model.js → report/model.js} +4 -6
  75. package/lib/{report-parse.js → report/parse.js} +9 -14
  76. package/lib/{report-render.js → report/render.js} +48 -19
  77. package/lib/{report-run.js → report/run.js} +42 -30
  78. package/lib/{report-test.js → report/test.js} +56 -55
  79. package/lib/{report-tools.js → report/tools.js} +24 -33
  80. package/lib/review/agent-instructions.md +14 -0
  81. package/lib/review/format.js +420 -0
  82. package/lib/{review-merge.js → review/merge.js} +9 -8
  83. package/lib/review/review.js +190 -0
  84. package/lib/review/store.js +281 -0
  85. package/lib/review/templates.js +83 -0
  86. package/lib/runs/commands.js +14 -0
  87. package/lib/runs/logs.js +299 -0
  88. package/lib/runs/output.js +174 -0
  89. package/lib/runs/process.js +113 -0
  90. package/lib/{runs.js → runs/runs.js} +84 -36
  91. package/lib/runs/scripts.js +150 -0
  92. package/lib/session/agents/agents.js +976 -0
  93. package/lib/session/agents/job.js +320 -0
  94. package/lib/session/agents/jobs.js +355 -0
  95. package/lib/session/agents/options.js +153 -0
  96. package/lib/session/agents/plans.js +83 -0
  97. package/lib/session/branches.js +33 -40
  98. package/lib/session/change-actions.js +34 -100
  99. package/lib/session/commit.js +91 -52
  100. package/lib/session/compose.js +189 -147
  101. package/lib/session/dashboard.js +23 -22
  102. package/lib/session/dispatch.js +86 -21
  103. package/lib/session/files-pane.js +47 -26
  104. package/lib/session/find.js +16 -29
  105. package/lib/session/import.js +93 -0
  106. package/lib/session/items.js +8 -17
  107. package/lib/session/lifecycle.js +78 -72
  108. package/lib/session/load.js +2 -4
  109. package/lib/session/navigation.js +6 -3
  110. package/lib/session/npm/history.js +250 -0
  111. package/lib/session/npm/npm.js +836 -0
  112. package/lib/session/{packages.js → npm/packages.js} +21 -14
  113. package/lib/session/ops.js +125 -34
  114. package/lib/session/pointer.js +202 -10
  115. package/lib/session/prefs.js +112 -0
  116. package/lib/session/reads.js +109 -0
  117. package/lib/session/review.js +28 -10
  118. package/lib/session/run-panel.js +40 -0
  119. package/lib/{session.js → session/session.js} +158 -63
  120. package/lib/session/tasks.js +321 -66
  121. package/lib/session/templates.js +1 -1
  122. package/lib/session/terminal.js +94 -12
  123. package/lib/session/unit.js +35 -30
  124. package/lib/session/update.js +194 -6
  125. package/lib/session/view.js +65 -65
  126. package/lib/session/workspace.js +39 -78
  127. package/lib/{capabilities.js → source/capabilities.js} +3 -12
  128. package/lib/{github.js → source/github.js} +102 -32
  129. package/lib/{gitlab.js → source/gitlab.js} +87 -10
  130. package/lib/{remote.js → source/remote.js} +144 -40
  131. package/lib/source/review-threads.graphql +20 -0
  132. package/lib/source/source.js +74 -0
  133. package/lib/term/ansi.js +396 -0
  134. package/lib/{clipboard.js → term/clipboard.js} +3 -6
  135. package/lib/{select.js → term/select.js} +4 -3
  136. package/lib/term/themes/dark.json +122 -0
  137. package/lib/term/themes/light.json +122 -0
  138. package/lib/term/wide.txt +21 -0
  139. package/lib/{wrap.js → term/wrap.js} +4 -4
  140. package/package.json +2 -2
  141. package/lib/ansi.js +0 -545
  142. package/lib/detect.js +0 -34
  143. package/lib/diff/edit.js +0 -33
  144. package/lib/git-worktree.js +0 -455
  145. package/lib/git.js +0 -79
  146. package/lib/npm-commands.js +0 -522
  147. package/lib/render/chrome.js +0 -806
  148. package/lib/render/present.js +0 -19
  149. package/lib/render/report.js +0 -43
  150. package/lib/render/search.js +0 -25
  151. package/lib/review.js +0 -745
  152. package/lib/session/accessors.js +0 -26
  153. package/lib/session/edit-click.js +0 -57
  154. package/lib/session/npm.js +0 -452
  155. package/lib/session/progress.js +0 -41
  156. package/lib/source.js +0 -36
  157. package/lib/update.js +0 -192
  158. /package/lib/{git-reword-todo.js → git/reword-todo.js} +0 -0
@@ -1,32 +1,32 @@
1
1
  'use strict';
2
2
 
3
- const wrap = require('../wrap.js');
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 commits = require('../render/commits.js');
8
- const { commitMessageWidth } = commits;
9
- const commit = require('./commit.js');
10
- const { listedCommits } = commit;
11
- const editor = require('../editor.js');
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 diff = require('../diff/diff.js');
17
- const { blockAddText } = diff;
18
- const items = require('./items.js');
19
- const { itemFeedKey, canEditCode } = items;
20
- const review = require('../review.js');
21
- const { setFeedback, setCode, checkLabel, rememberTemplate } = review;
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
- const COMPOSE_EDITS = ['backspace', 'delete', 'ctrl-z', 'ctrl-y'];
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
- backspace: (c) => c.editor.backspace(),
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
- this.editor.reveal(codeInnerWidth(width, this.ui.layout));
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
- setFeedback(this.notes, itemFeedKey(item), itemNote(item, text));
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(notes, key, { ...itemNote(item, text), clear: true });
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
- const item = ui.current();
222
- if (!canEditCode(item)) return true;
223
- const original = blockAddText(item.hunk, item.blockId);
224
- if (!this.writesCodeFile(item)) {
225
- const note = itemNote(item, text);
226
- const clear = text === original;
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
- if (text !== original) {
231
- try {
232
- ui.repo.edit(ui.top, item, text);
233
- } catch (error) {
234
- ui.status = error.message;
235
- return false;
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
- try {
252
- ui.repo.writeFile(ui.top, rel, text);
253
- } catch (error) {
254
- ui.status = error.message;
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
- stageEditedFile(rel) {
284
+ stageItems(rel) {
264
285
  const ui = this.ui;
265
286
  const repo = ui.repo;
266
- if (!rel || !this.canChange()) return false;
267
- if (repo.stagePath) {
268
- try {
269
- repo.stagePath(ui.top, rel);
270
- } catch (error) {
271
- ui.status = error.message;
272
- return false;
273
- }
274
- ui.ignoreWatch();
275
- return true;
276
- }
277
- if (!repo.add) return false;
278
- let staged = 0;
279
- for (const item of ui.items) {
280
- if (listPath(item) !== rel) continue;
281
- if (isReadOnlyOrigin(item.origin) || item.origin === 'staged') continue;
282
- try {
283
- repo.add(ui.top, item);
284
- } catch (error) {
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
- saveFileCompose() {
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 ? this.editor.text : '';
312
- if (this.editor) this.ui.nav.unitLine = this.editor.linePos().line;
313
- if (!this.saveUnitFile(text)) return { kind: 'error' };
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 { kind: 'saved' };
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 true;
383
+ if (!kind || !this.editor) return;
324
384
  const text = this.editor.text;
325
- if (kind === 'feedback') {
326
- if (this.notes) this.commitFeedback(text);
327
- return true;
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 (kind === 'file') return this.saveFileCompose();
343
- const item = ui.current();
344
- const shouldStage =
345
- kind === 'code' && this.writesCodeFile(item) && item.origin !== 'staged';
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
- if (this.commitCompose() === false) return { kind: 'error' };
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
- rememberTemplate(this.notes, baseline, next);
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 fileEdit = editingCode && this.writesCodeFile(this.ui.current());
378
- if (reviewKind && !fileEdit && (editingCode || text.trim())) {
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
- unit.openUnitFile(ui, entry);
558
- this.onUnitEdit();
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 (ui.nav.pane === 'files' || !canEditCode(item)) {
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
- const text = this.editor.selectedText();
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
- if (!this.editor.hasSelect()) return;
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 (!key.length || key.startsWith('ctrl-') || key.startsWith('alt-')) {
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
- if (text === editor.text) return;
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();
@@ -1,22 +1,15 @@
1
1
  'use strict';
2
2
 
3
- const files = require('../files.js');
4
- const { fileEntries, fileTotals } = files;
5
- const review = require('../review.js');
6
- const { noteCounts } = review;
7
- const runs = require('../runs.js');
8
- const { readRuns, settleRecord, isRunsRel } = runs;
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 watch = require('./watch.js');
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 = new Set([
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 = new Set(['package.json', 'package-lock.json', MODULES_STAMP]);
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
- if (GIT_NAMES.has(rel.split('/')[1])) found.git = true;
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.has(rel)) found.npm = true;
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;