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.
Files changed (158) hide show
  1. package/CHANGELOG.md +27 -1
  2. package/README.md +16 -14
  3. package/bin/reslop.js +8 -5
  4. package/lib/agents/agents.js +504 -0
  5. package/lib/agents/allow.js +71 -0
  6. package/lib/agents/allow.json +16 -0
  7. package/lib/agents/models.js +460 -0
  8. package/lib/api.js +15 -12
  9. package/lib/cli.js +61 -66
  10. package/lib/{files.js → common/files.js} +51 -52
  11. package/lib/common/find.js +61 -0
  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 +117 -51
  18. package/lib/dashboard/npm.js +133 -32
  19. package/lib/dashboard/tree.js +27 -30
  20. package/lib/{session → dashboard}/watch.js +41 -31
  21. package/lib/dashboard/workspace.js +151 -0
  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} +123 -95
  34. package/lib/git/git.js +71 -0
  35. package/lib/git/worktree.js +475 -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 +240 -71
  45. package/lib/{editor.js → input/editor.js} +7 -8
  46. package/lib/{keys.js → input/keys.js} +36 -39
  47. package/lib/input/line-edit.js +77 -0
  48. package/lib/input/menu-pick.js +70 -0
  49. package/lib/render/agents.js +640 -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/dashboard/blocks.js +490 -0
  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 +22 -8
  58. package/lib/render/footer.js +383 -0
  59. package/lib/render/frame.js +127 -25
  60. package/lib/render/header.js +176 -0
  61. package/lib/render/log.js +319 -0
  62. package/lib/render/menu.js +60 -0
  63. package/lib/render/notes.js +29 -13
  64. package/lib/render/npm.js +322 -196
  65. package/lib/render/packages.js +89 -0
  66. package/lib/render/primitives.js +109 -16
  67. package/lib/render/render.js +7 -11
  68. package/lib/render/repos.js +214 -0
  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} +19 -61
  72. package/lib/render/tiles.js +203 -47
  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} +46 -14
  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 +9 -0
  81. package/lib/review/format.js +426 -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 +958 -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 +36 -44
  98. package/lib/session/change-actions.js +34 -100
  99. package/lib/session/commit.js +122 -56
  100. package/lib/session/compose.js +197 -146
  101. package/lib/session/dashboard.js +183 -37
  102. package/lib/session/dispatch.js +120 -22
  103. package/lib/session/files-pane.js +60 -24
  104. package/lib/session/find.js +81 -0
  105. package/lib/session/import.js +82 -0
  106. package/lib/session/items.js +8 -17
  107. package/lib/session/lifecycle.js +96 -66
  108. package/lib/session/load.js +2 -4
  109. package/lib/session/navigation.js +8 -3
  110. package/lib/session/npm/history.js +249 -0
  111. package/lib/session/npm/npm.js +815 -0
  112. package/lib/session/npm/packages.js +150 -0
  113. package/lib/session/ops.js +125 -34
  114. package/lib/session/pointer.js +216 -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} +166 -59
  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 +72 -59
  126. package/lib/session/workspace.js +416 -0
  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} +2 -2
  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 -505
  142. package/lib/detect.js +0 -34
  143. package/lib/diff/edit.js +0 -33
  144. package/lib/git-worktree.js +0 -437
  145. package/lib/git.js +0 -79
  146. package/lib/npm-commands.js +0 -522
  147. package/lib/render/chrome.js +0 -628
  148. package/lib/render/dash-blocks.js +0 -245
  149. package/lib/render/present.js +0 -19
  150. package/lib/render/report.js +0 -43
  151. package/lib/review.js +0 -744
  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,29 +1,26 @@
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
- const { buildModel } = model;
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';
23
16
  const AGE_TIMER = 'dashboard-age';
24
- const SAMPLES = 40;
17
+ const MINUTE_TIMER = 'dashboard-minute';
18
+ const BLINK_TIMER = 'dashboard-blink';
19
+ const BLINK_MS = 3000;
20
+ const MINUTE_KEEP = 360;
25
21
  const SWITCHES = 4;
26
- const GIT_NAMES = new Set([
22
+ const RUN_MARK = { failed: 'fail', passed: 'pass', aborted: 'abort' };
23
+ const GIT_NAMES = [
27
24
  'HEAD',
28
25
  'packed-refs',
29
26
  'FETCH_HEAD',
@@ -32,8 +29,8 @@ const GIT_NAMES = new Set([
32
29
  'logs',
33
30
  'rebase-merge',
34
31
  'rebase-apply',
35
- ]);
36
- const NPM_FILES = new Set(['package.json', 'package-lock.json', MODULES_STAMP]);
32
+ ];
33
+ const NPM_FILES = ['package.json', 'package-lock.json', MODULES_STAMP];
37
34
  const AGE_FAST_MS = 1000;
38
35
  const AGE_MID_MS = 30000;
39
36
  const AGE_SLOW_MS = 600000;
@@ -81,11 +78,12 @@ const classify = (paths) => {
81
78
  if (rel === UNKNOWN_PATH) {
82
79
  found.all = true;
83
80
  } else if (rel.startsWith('.git/')) {
84
- 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;
85
83
  } else if (isRunsRel(rel)) {
86
84
  found.runs = true;
87
85
  } else {
88
- if (NPM_FILES.has(rel)) found.npm = true;
86
+ if (NPM_FILES.includes(rel)) found.npm = true;
89
87
  found.files.push(rel);
90
88
  }
91
89
  }
@@ -124,7 +122,9 @@ class Dashboard {
124
122
  this.totals = null;
125
123
  this.totalsKey = '';
126
124
  this.diffDelta = null;
127
- this.samples = [];
125
+ this.minutes = new Map();
126
+ this.activityAt = 0;
127
+ this.rebaseOn = false;
128
128
  this.switches = [];
129
129
  this.branchAt = new Map();
130
130
  this.branchSha = new Map();
@@ -177,6 +177,101 @@ class Dashboard {
177
177
  progress.stop(TICK_ID);
178
178
  term.cancel(HEAT_TIMER);
179
179
  term.cancel(AGE_TIMER);
180
+ term.cancel(MINUTE_TIMER);
181
+ term.cancel(BLINK_TIMER);
182
+ }
183
+
184
+ bucket(at) {
185
+ const minute = minuteOf(at);
186
+ let newest = minute;
187
+ for (const key of this.minutes.keys()) {
188
+ if (key > newest) newest = key;
189
+ }
190
+ if (minute < newest - MINUTE_KEEP) return null;
191
+ let row = this.minutes.get(minute);
192
+ if (!row) {
193
+ row = { level: 0, added: 0, removed: 0, marks: [] };
194
+ this.minutes.set(minute, row);
195
+ }
196
+ const oldest = newest - MINUTE_KEEP;
197
+ for (const key of this.minutes.keys()) {
198
+ if (key < oldest) this.minutes.delete(key);
199
+ }
200
+ return row;
201
+ }
202
+
203
+ touchActivity(at) {
204
+ if (at > this.activityAt) this.activityAt = at;
205
+ }
206
+
207
+ heat(at, added, removed = 0) {
208
+ const plus = Math.max(0, added || 0);
209
+ const minus = Math.max(0, removed || 0);
210
+ if (!at || plus + minus <= 0) return;
211
+ const row = this.bucket(at);
212
+ if (!row) return;
213
+ row.added += plus;
214
+ row.removed += minus;
215
+ row.level += plus + minus;
216
+ this.touchActivity(at);
217
+ }
218
+
219
+ mark(at, kind) {
220
+ if (!at || !kind) return;
221
+ const row = this.bucket(at);
222
+ if (!row || row.marks.includes(kind)) return;
223
+ row.marks.push(kind);
224
+ this.touchActivity(at);
225
+ }
226
+
227
+ noteCommits(next) {
228
+ const recent = next.commits.recent;
229
+ for (const entry of recent) this.mark(entry.at, 'commit');
230
+ }
231
+
232
+ noteRebase(next, now) {
233
+ const rebasing = Boolean(next.rebase);
234
+ if (rebasing && !this.rebaseOn) this.mark(now, 'rebase');
235
+ this.rebaseOn = rebasing;
236
+ }
237
+
238
+ noteRuns(runs) {
239
+ for (const run of runs) {
240
+ const kind = RUN_MARK[run.status];
241
+ if (kind) this.mark(run.endedAt || run.startedAt, kind);
242
+ }
243
+ }
244
+
245
+ isRunLive() {
246
+ for (const run of this.liveRuns()) {
247
+ if (run.status === 'running') return true;
248
+ }
249
+ const npm = this.ui.npm.lastRun;
250
+ return Boolean(npm && npm.running);
251
+ }
252
+
253
+ isBusy() {
254
+ if (this.git && this.git.rebase) return true;
255
+ return this.isRunLive();
256
+ }
257
+
258
+ liveActivity(now) {
259
+ if (this.isBusy()) return true;
260
+ return now - this.activityAt < BLINK_MS;
261
+ }
262
+
263
+ activityView(now) {
264
+ const minutes = [];
265
+ for (const [at, row] of this.minutes) {
266
+ minutes.push({
267
+ at,
268
+ level: row.level,
269
+ added: row.added,
270
+ removed: row.removed,
271
+ marks: row.marks.slice(),
272
+ });
273
+ }
274
+ return { minutes, live: this.liveActivity(now) };
180
275
  }
181
276
 
182
277
  afterDraw() {
@@ -184,17 +279,21 @@ class Dashboard {
184
279
  if (!this.visible()) return void this.cancelTimers();
185
280
  const { progress, term } = this.ui;
186
281
  const now = Date.now();
282
+ if (this.isBusy()) this.touchActivity(now);
187
283
  const until = this.hotUntil();
188
284
  const hot = now < until;
189
- const runs = this.liveRuns();
190
- const running = runs.some((run) => run.status === 'running');
191
- if (running || hot) progress.start(TICK_ID);
285
+ const live = this.liveActivity(now);
286
+ if (live || hot) progress.start(TICK_ID);
192
287
  else progress.stop(TICK_ID);
193
288
  const repaint = () => this.paintIfVisible();
194
289
  if (hot) term.later(HEAT_TIMER, repaint, until - now + 20);
195
- const at = this.youngest(runs);
290
+ const at = this.youngest(this.liveRuns());
196
291
  const delay = at ? ageDelay(now - at) : AGE_SLOW_MS;
197
292
  term.later(AGE_TIMER, repaint, delay);
293
+ const minuteLeft = MINUTE_MS - (now % MINUTE_MS) + 20;
294
+ term.later(MINUTE_TIMER, repaint, minuteLeft);
295
+ const blinkLeft = this.activityAt + BLINK_MS - now;
296
+ if (blinkLeft > 0) term.later(BLINK_TIMER, repaint, blinkLeft + 20);
198
297
  }
199
298
 
200
299
  async runFiles() {
@@ -209,7 +308,12 @@ class Dashboard {
209
308
  const after = index.summary().total;
210
309
  const lines = after.lines - before.lines;
211
310
  const bytes = after.bytes - before.bytes;
212
- if (lines || bytes) this.fileDelta = { lines, at: Date.now() };
311
+ if (!lines && !bytes) return;
312
+ const at = Date.now();
313
+ this.fileDelta = { lines, at };
314
+ const plus = lines > 0 ? lines : 0;
315
+ const minus = lines < 0 ? -lines : 0;
316
+ this.heat(at, plus || (minus ? 0 : 1), minus);
213
317
  }
214
318
 
215
319
  noteShas(next, now) {
@@ -243,9 +347,12 @@ class Dashboard {
243
347
  const moved = this.noteShas(next, now);
244
348
  const switched = this.noteSwitch(next, now);
245
349
  if (moved || switched) this.marks.branches = now;
350
+ if (switched) this.mark(now, 'branch');
246
351
  const sha = next.commits.last ? next.commits.last.sha : '';
247
352
  if (this.lastSha && sha !== this.lastSha) this.marks.commit = now;
248
353
  this.lastSha = sha;
354
+ this.noteCommits(next);
355
+ this.noteRebase(next, now);
249
356
  this.git = next;
250
357
  }
251
358
 
@@ -259,12 +366,17 @@ class Dashboard {
259
366
  (prev.deps !== next.deps ||
260
367
  prev.dev !== next.dev ||
261
368
  prev.modules.stamp !== next.modules.stamp);
262
- if (changed) this.marks.npm = Date.now();
369
+ if (changed) {
370
+ const now = Date.now();
371
+ this.marks.npm = now;
372
+ this.mark(now, 'npm');
373
+ }
263
374
  this.npm = next;
264
375
  }
265
376
 
266
377
  runRuns() {
267
378
  this.runs = readRuns(this.root());
379
+ this.noteRuns(this.runs);
268
380
  }
269
381
 
270
382
  noteLoaded() {
@@ -276,12 +388,14 @@ class Dashboard {
276
388
  if (prev) {
277
389
  const added = totals.added - prev.added;
278
390
  const removed = totals.removed - prev.removed;
279
- this.diffDelta = { added, removed, at: Date.now() };
391
+ const at = Date.now();
392
+ this.diffDelta = { added, removed, at };
393
+ const plus = Math.max(0, added);
394
+ const minus = Math.max(0, removed);
395
+ this.heat(at, plus || (minus ? 0 : 1), minus);
280
396
  }
281
397
  this.totals = totals;
282
398
  this.totalsKey = key;
283
- this.samples.push(totals.added + totals.removed);
284
- if (this.samples.length > SAMPLES) this.samples.shift();
285
399
  }
286
400
 
287
401
  requestAll() {
@@ -290,8 +404,12 @@ class Dashboard {
290
404
  }
291
405
 
292
406
  noteDisk(paths) {
293
- if (!this.active) return;
294
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;
295
413
  if (found.all) return void this.requestAll();
296
414
  const tasks = this.tasks;
297
415
  if (found.files.length) {
@@ -325,17 +443,18 @@ class Dashboard {
325
443
 
326
444
  view() {
327
445
  const ui = this.ui;
446
+ const now = Date.now();
328
447
  const entries = fileEntries(ui.items);
329
448
  const store = ui.review.store;
330
449
  return buildModel({
331
- now: Date.now(),
450
+ now,
332
451
  frame: ui.progressFrame,
333
452
  busy: ui.ops.busy,
334
453
  index: this.index,
335
454
  fileDelta: this.fileDelta,
336
455
  entries,
337
456
  totals: this.totals ?? fileTotals(entries),
338
- samples: this.samples,
457
+ activity: this.activityView(now),
339
458
  diffDelta: this.diffDelta,
340
459
  git: this.git,
341
460
  switches: this.switches,
@@ -344,17 +463,43 @@ class Dashboard {
344
463
  npm: this.npm,
345
464
  npmExtras: ui.lifecycle.npmExtras(),
346
465
  npmRun: ui.npm.lastRun,
466
+ npmRuns: ui.npm.dashboardRuns(),
347
467
  runs: this.liveRuns(),
348
468
  notes: noteCounts(store),
349
469
  store,
470
+ agents: ui.agents.summary(),
350
471
  });
351
472
  }
352
473
 
474
+ packagesView() {
475
+ return npmModel({
476
+ npm: this.npm,
477
+ npmExtras: this.ui.lifecycle.npmExtras(),
478
+ npmRun: this.ui.npm.lastRun,
479
+ });
480
+ }
481
+
482
+ packageCount() {
483
+ const data = this.packagesView();
484
+ if (!data.ready || !data.hasManifest) return 1;
485
+ const listed = data.modules && data.modules.packages;
486
+ return listed ? listed.length : 0;
487
+ }
488
+
489
+ openPackages() {
490
+ const ui = this.ui;
491
+ ui.nav.pane = 'packages';
492
+ ui.nav.packagesCursor = 0;
493
+ ui.nav.resetListScroll('packages');
494
+ ui.nav.clearSelection();
495
+ ui.status = '';
496
+ }
497
+
353
498
  showFiles(scope) {
354
499
  const ui = this.ui;
355
500
  ui.showFiles();
356
- if (scope === 'file') filesPane.onFileScope(ui);
357
- else filesPane.onDiffScope(ui);
501
+ if (scope === 'file') filesPane.onFileScope(ui, true);
502
+ else filesPane.onDiffScope(ui, true);
358
503
  ui.status = '';
359
504
  }
360
505
 
@@ -365,9 +510,10 @@ class Dashboard {
365
510
  diffs: () => this.showFiles('diff'),
366
511
  commits: () => ui.commits.onCommit(),
367
512
  branches: () => ui.gitBranches.onBranch(),
368
- npm: () => ui.npm.open(),
513
+ npm: () => this.openPackages(),
369
514
  run: () => ui.npm.open(),
370
515
  tasks: () => ui.composer.tasks.openTasksPage(),
516
+ agents: () => ui.agents.open(),
371
517
  };
372
518
  const opener = openers[id];
373
519
  if (!opener || ui.mode !== 'review') return;
@@ -1,28 +1,35 @@
1
1
  'use strict';
2
2
 
3
- const compose = require('./compose.js');
4
- const { COMPOSE_LEAVE } = compose;
5
- const commit = require('./commit.js');
6
- const { hasStagedChanges, listedCommits } = commit;
7
- const actions = require('./actions.js');
3
+ const { COMPOSE_LEAVE } = require('./compose.js');
4
+ const { listedCommits } = require('./commit.js');
5
+ const actions = require('../input/actions.js');
8
6
  const { LIST_PANES, DASH_BLOCKS, DASH_ALLOWED, CONFIRM_MODE } = actions;
9
- const { disabledActions, actionFromKey, confirmKey } = actions;
10
- const changeActions = require('./change-actions.js');
11
- const { onCommand } = changeActions;
7
+ const { REPOS_ALLOWED } = actions;
8
+ const { disabledActions, actionFromKey, confirmKey, lineHasDiff } = actions;
9
+ const { onCommand } = require('./change-actions.js');
12
10
  const filesPane = require('./files-pane.js');
13
- const pointer = require('./pointer.js');
14
- const { handlePointer } = pointer;
11
+ const { handlePointer } = require('./pointer.js');
12
+ const { FIND_PANES, startFind, onFindKey } = require('./find.js');
13
+ const { startImport, stopImport, onImportKey } = require('./import.js');
14
+ const { unitBodyLines } = require('./view.js');
15
15
 
16
16
  const currentTarget = (ui) => {
17
17
  if (ui.pane === 'files') return ui.fileCursorEntry();
18
18
  if (ui.pane === 'branches') return ui.branches[ui.branchCursor];
19
19
  if (ui.pane === 'commits') {
20
20
  const entry = listedCommits(ui.commits.commits)[ui.commitCursor];
21
- const canCommit = hasStagedChanges(ui);
21
+ const canCommit = ui.commits.staged;
22
22
  const unstagedCurrent = !ui.rev && !ui.revShort;
23
23
  return { ...entry, canCommit, unstagedCurrent };
24
24
  }
25
- return ui.current();
25
+ if (ui.pane !== 'unit') return ui.current();
26
+ const item = ui.current();
27
+ const lines = unitBodyLines(ui);
28
+ if (!lines.length) return item;
29
+ const line = lines[ui.nav.unitLine] ?? null;
30
+ const lineDiff = lineHasDiff(line);
31
+ if (!item) return { lineDiff };
32
+ return { ...item, lineDiff };
26
33
  };
27
34
 
28
35
  const scrollBy = (fraction) => (ui) =>
@@ -43,9 +50,13 @@ const HANDLERS = {
43
50
  next: filesPane.onNext,
44
51
  prev: filesPane.onPrev,
45
52
  layout: filesPane.onLayout,
53
+ lines: filesPane.onLineNumbers,
46
54
  theme: filesPane.onTheme,
47
55
  files: filesPane.onFiles,
48
- open: filesPane.onOpenFile,
56
+ open: (ui) => {
57
+ if (ui.pane === 'repos') return void ui.workspace.openSelected();
58
+ filesPane.onOpenFile(ui);
59
+ },
49
60
  file: filesPane.onFileScope,
50
61
  diff: filesPane.onDiffScope,
51
62
  back: filesPane.onEscape,
@@ -63,10 +74,12 @@ const HANDLERS = {
63
74
  check: (ui) => ui.composer.tasks.toggleFocusedTask(),
64
75
  removeTask: (ui) => {
65
76
  if (ui.pane === 'npm') return void ui.npm.askDrop();
77
+ if (ui.pane === 'packages') return;
66
78
  ui.composer.tasks.removeFocusedTask();
67
79
  },
68
80
  reload: (ui) => ui.lifecycle.onReload(),
69
81
  commit: (ui) => ui.commits.onCommit(),
82
+ newCommit: (ui) => ui.commits.onInsert(),
70
83
  amend: (ui) => ui.commits.onAmend(),
71
84
  reword: (ui) => ui.commits.onReword(),
72
85
  apply: (ui) => ui.commits.onApply(),
@@ -82,26 +95,51 @@ const HANDLERS = {
82
95
  if (ui.pane === 'tasks') return void ui.composer.tasks.removeFocusedTask();
83
96
  ui.gitBranches.onDropBranch();
84
97
  },
98
+ taskUp: (ui) => ui.composer.tasks.moveIssue(-1),
99
+ taskDown: (ui) => ui.composer.tasks.moveIssue(1),
100
+ plan: (ui) => ui.composer.tasks.togglePlanMenu(),
101
+ import: (ui) => startImport(ui),
85
102
  npm: (ui) => ui.npm.open(),
86
103
  npmEdit: (ui) => ui.npm.edit(),
87
104
  npmNew: (ui) => ui.npm.create(),
88
105
  npmDrop: (ui) => ui.npm.askDrop(),
106
+ packageNew: (ui) => ui.packages.create(),
107
+ packageDrop: (ui) => ui.packages.askDrop(),
108
+ packageWanted: (ui) => ui.packages.updateWanted(),
109
+ packageLatest: (ui) => ui.packages.updateLatest(),
89
110
  npmLogs: (ui) => ui.npm.askLogs(),
90
- npmStop: (ui) => ui.npm.closeView(),
111
+ npmStop: (ui) => ui.npm.stop(),
91
112
  npmVerbose: (ui) => ui.npm.toggleVerbose(),
92
113
  npmRerun: (ui) => ui.npm.rerun(),
114
+ agentModel: (ui) => ui.agents.openPick('model'),
115
+ agentEffort: (ui) => ui.agents.openPick('effort'),
116
+ agentFast: (ui) => ui.agents.toggleFast(),
117
+ agentContext: (ui) => ui.agents.openPick('context'),
118
+ agentReview: (ui) => ui.agents.openPick('plan'),
119
+ agentStop: (ui) => ui.agents.stop(),
120
+ agentRerun: (ui) => ui.agents.rerun(),
93
121
  quit: (ui) => ui.onQuit(),
122
+ repoLeft: (ui) => ui.workspace.move(-1, 0),
123
+ repoRight: (ui) => ui.workspace.move(1, 0),
124
+ repoUp: (ui) => ui.workspace.move(0, -1),
125
+ repoDown: (ui) => ui.workspace.move(0, 1),
94
126
  ...DASH_HANDLERS,
95
127
  };
96
128
 
97
129
  const dispatchAction = (ui, action) => {
98
130
  if (CONFIRM_MODE.includes(ui.mode)) return;
131
+ if (ui.mode === 'import') stopImport(ui);
132
+ if (action === 'lines' && (ui.pane === 'diff' || ui.pane === 'unit')) {
133
+ return void filesPane.onLineNumbers(ui);
134
+ }
99
135
  if (ui.mode === 'compose') {
100
136
  if (!COMPOSE_LEAVE.includes(action)) return;
101
- const result = ui.composer.saveCompose();
102
- if (result && result.kind === 'error') return;
137
+ const after = () => dispatchAction(ui, action);
138
+ const result = ui.composer.saveCompose({ after });
139
+ if (result.kind === 'saving') return;
103
140
  }
104
141
  const pane = ui.pane;
142
+ if (pane === 'repos' && !REPOS_ALLOWED.includes(action)) return;
105
143
  if (pane === 'dashboard' && !DASH_ALLOWED.includes(action)) return;
106
144
  if (LIST_PANES.includes(pane) && (action === 'next' || action === 'prev')) {
107
145
  return void filesPane.onScroll(ui, action === 'next' ? 1 : -1);
@@ -113,8 +151,7 @@ const dispatchAction = (ui, action) => {
113
151
 
114
152
  const CONFIRMATIONS = {
115
153
  confirmQuit: {
116
- ready: (ui) => ui.finishQuit('ready'),
117
- editing: (ui) => ui.finishQuit('editing'),
154
+ confirm: (ui) => ui.confirmQuit(),
118
155
  cancel: (ui) => ui.cancelQuit(),
119
156
  },
120
157
  confirmUpdate: {
@@ -123,11 +160,13 @@ const CONFIRMATIONS = {
123
160
  },
124
161
  confirmDrop: {
125
162
  confirm: (ui) => {
163
+ if (ui.packages.dropName) return void ui.packages.confirmDrop();
126
164
  if (ui.npm.dropName) return void ui.npm.confirmDrop();
127
165
  if (ui.commits.dropName) return void ui.commits.confirmDropCommit();
128
166
  ui.gitBranches.confirmDropBranch();
129
167
  },
130
168
  cancel: (ui) => {
169
+ if (ui.packages.dropName) return void ui.packages.cancelDrop();
131
170
  if (ui.npm.dropName) return void ui.npm.cancelDrop();
132
171
  if (ui.commits.dropName) return void ui.commits.cancelDropCommit();
133
172
  ui.gitBranches.cancelDropBranch();
@@ -137,6 +176,13 @@ const CONFIRMATIONS = {
137
176
  force: (ui) => ui.gitBranches.onForcePush(),
138
177
  cancel: (ui) => ui.gitBranches.cancelPush(),
139
178
  },
179
+ confirmCommit: {
180
+ update: (ui) => ui.commits.chooseCommit('update'),
181
+ commit: (ui) => ui.commits.chooseCommit('commit'),
182
+ amend: (ui) => ui.commits.chooseCommit('amend'),
183
+ fixup: (ui) => ui.commits.chooseCommit('fixup'),
184
+ cancel: (ui) => ui.commits.cancelCommitChoice(),
185
+ },
140
186
  };
141
187
 
142
188
  const confirmChoice = (ui, key) => {
@@ -147,6 +193,7 @@ const confirmChoice = (ui, key) => {
147
193
 
148
194
  const NPM_VIEW_KEYS = {
149
195
  escape: (ui) => ui.npm.closeView(),
196
+ s: (ui) => ui.npm.stop(),
150
197
  r: (ui) => ui.npm.rerun(),
151
198
  v: (ui) => ui.npm.toggleVerbose(),
152
199
  e: () => {},
@@ -158,29 +205,80 @@ const NPM_LIST_KEYS = {
158
205
  'ctrl-down': (ui) => ui.npm.reorder(1),
159
206
  up: (ui) => ui.npm.move(-1),
160
207
  down: (ui) => ui.npm.move(1),
208
+ right: (ui) => ui.npm.focusRuns(),
209
+ left: (ui) => ui.npm.focusCommands(),
210
+ tab: (ui) => ui.npm.toggleFocus(),
211
+ 'shift-tab': (ui) => ui.npm.toggleFocus(),
212
+ };
213
+
214
+ const AGENT_VIEW_KEYS = {
215
+ escape: (ui) => ui.agents.closeView(),
216
+ s: (ui) => ui.agents.stop(),
217
+ r: (ui) => ui.agents.rerun(),
218
+ e: () => {},
219
+ m: () => {},
220
+ a: () => {},
221
+ t: () => {},
222
+ f: () => {},
223
+ };
224
+
225
+ const AGENT_LIST_KEYS = {
226
+ right: (ui) => ui.agents.focusRuns(),
227
+ left: (ui) => ui.agents.focusCli(),
228
+ tab: (ui) => ui.agents.toggleFocus(),
229
+ 'shift-tab': (ui) => ui.agents.toggleFocus(),
161
230
  };
162
231
 
163
232
  const npmKey = (ui, key) => {
164
233
  if (ui.pane !== 'npm') return null;
165
- if (ui.npm.viewing && Object.hasOwn(NPM_VIEW_KEYS, key)) {
166
- return NPM_VIEW_KEYS[key];
234
+ if (ui.npm.viewing) {
235
+ if (Object.hasOwn(NPM_VIEW_KEYS, key)) return NPM_VIEW_KEYS[key];
236
+ if (key === 'up' || key === 'down') return NPM_LIST_KEYS[key];
237
+ return null;
167
238
  }
168
239
  return Object.hasOwn(NPM_LIST_KEYS, key) ? NPM_LIST_KEYS[key] : null;
169
240
  };
170
241
 
242
+ const agentKey = (ui, key) => {
243
+ if (ui.pane !== 'agents') return null;
244
+ if (ui.agents.pick) return () => ui.agents.onPickKey(key);
245
+ const keys = ui.agents.viewing ? AGENT_VIEW_KEYS : AGENT_LIST_KEYS;
246
+ return Object.hasOwn(keys, key) ? keys[key] : null;
247
+ };
248
+
249
+ const planMenuKey = (ui, key) => {
250
+ if (!ui.composer.tasks.planOpen || ui.pane !== 'tasks') return false;
251
+ ui.composer.tasks.onPlanKey(key);
252
+ return true;
253
+ };
254
+
171
255
  const handleKey = (ui, key) => {
172
256
  if (CONFIRM_MODE.includes(ui.mode)) return void confirmChoice(ui, key);
257
+ if (planMenuKey(ui, key)) return;
258
+ if (ui.pane === 'tasks' && (key === 'ctrl-up' || key === 'ctrl-down')) {
259
+ if (ui.mode === 'import') stopImport(ui);
260
+ const delta = key === 'ctrl-up' ? -1 : 1;
261
+ return void ui.composer.tasks.moveIssue(delta);
262
+ }
263
+ if (key === 'ctrl-l' && (ui.pane === 'diff' || ui.pane === 'unit')) {
264
+ return void filesPane.onLineNumbers(ui);
265
+ }
173
266
  if (ui.mode === 'compose') {
174
267
  return void ui.runComposeCommand(ui.composer.handleKey(key));
175
268
  }
269
+ if (ui.mode === 'find') return void onFindKey(ui, key);
270
+ if (ui.mode === 'import') return void onImportKey(ui, key);
271
+ if (key === '/' && FIND_PANES.includes(ui.pane)) return void startFind(ui);
176
272
  const onNpmKey = npmKey(ui, key);
177
273
  if (onNpmKey) return void onNpmKey(ui);
274
+ const onAgentKey = agentKey(ui, key);
275
+ if (onAgentKey) return void onAgentKey(ui);
178
276
  if (key === 'escape') return void filesPane.onEscape(ui);
179
277
  const tasks = ui.composer.tasks;
180
- const toggle = key === 'x' || key === ' ';
278
+ const toggle = key === ' ';
181
279
  if (toggle && tasks.toggleFocusedTask()) return;
182
280
  if (tasks.typeIntoTask(key)) return;
183
- const action = actionFromKey(key, ui.pane, ui.fileScope);
281
+ const action = actionFromKey(key, ui.pane);
184
282
  if (action) ui.dispatch(action);
185
283
  };
186
284