reslop 0.2.6 → 0.2.7
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 +11 -1
- package/lib/ansi.js +42 -2
- package/lib/api.js +1 -1
- package/lib/cli.js +21 -1
- package/lib/dashboard/model.js +28 -3
- package/lib/dashboard/npm.js +116 -19
- package/lib/files.js +13 -7
- package/lib/find.js +61 -0
- package/lib/git-deps.js +1 -0
- package/lib/git-worktree.js +22 -4
- package/lib/keys.js +26 -1
- package/lib/render/chrome.js +257 -79
- package/lib/render/dash-blocks.js +297 -46
- package/lib/render/dash-table.js +6 -1
- package/lib/render/files.js +23 -4
- package/lib/render/frame.js +4 -0
- package/lib/render/notes.js +13 -2
- package/lib/render/packages.js +93 -0
- package/lib/render/primitives.js +13 -6
- package/lib/render/repos.js +226 -0
- package/lib/render/search.js +25 -0
- package/lib/render/tiles.js +107 -12
- package/lib/review.js +5 -4
- package/lib/session/actions.js +136 -30
- package/lib/session/branches.js +3 -5
- package/lib/session/commit.js +31 -4
- package/lib/session/compose.js +19 -4
- package/lib/session/dashboard.js +160 -15
- package/lib/session/dispatch.js +38 -4
- package/lib/session/files-pane.js +17 -2
- package/lib/session/find.js +94 -0
- package/lib/session/lifecycle.js +27 -0
- package/lib/session/navigation.js +2 -0
- package/lib/session/packages.js +143 -0
- package/lib/session/pointer.js +14 -0
- package/lib/session/tasks.js +3 -3
- package/lib/session/view.js +15 -2
- package/lib/session/watch.js +5 -0
- package/lib/session/workspace.js +455 -0
- package/lib/session.js +14 -0
- package/lib/workspace.js +166 -0
- package/package.json +1 -1
package/lib/session/dashboard.js
CHANGED
|
@@ -13,7 +13,7 @@ const { readGitSummary } = gitInfo;
|
|
|
13
13
|
const npmInfo = require('../dashboard/npm.js');
|
|
14
14
|
const { readNpmSummary } = npmInfo;
|
|
15
15
|
const model = require('../dashboard/model.js');
|
|
16
|
-
const { buildModel } = model;
|
|
16
|
+
const { buildModel, minuteOf, MINUTE_MS, npmModel } = model;
|
|
17
17
|
const filesPane = require('./files-pane.js');
|
|
18
18
|
const watch = require('./watch.js');
|
|
19
19
|
const { UNKNOWN_PATH, MODULES_STAMP } = watch;
|
|
@@ -21,8 +21,12 @@ const { UNKNOWN_PATH, MODULES_STAMP } = watch;
|
|
|
21
21
|
const TICK_ID = 'dashboard';
|
|
22
22
|
const HEAT_TIMER = 'dashboard-heat';
|
|
23
23
|
const AGE_TIMER = 'dashboard-age';
|
|
24
|
-
const
|
|
24
|
+
const MINUTE_TIMER = 'dashboard-minute';
|
|
25
|
+
const BLINK_TIMER = 'dashboard-blink';
|
|
26
|
+
const BLINK_MS = 3000;
|
|
27
|
+
const MINUTE_KEEP = 360;
|
|
25
28
|
const SWITCHES = 4;
|
|
29
|
+
const RUN_MARK = { failed: 'fail', passed: 'pass', aborted: 'abort' };
|
|
26
30
|
const GIT_NAMES = new Set([
|
|
27
31
|
'HEAD',
|
|
28
32
|
'packed-refs',
|
|
@@ -124,7 +128,9 @@ class Dashboard {
|
|
|
124
128
|
this.totals = null;
|
|
125
129
|
this.totalsKey = '';
|
|
126
130
|
this.diffDelta = null;
|
|
127
|
-
this.
|
|
131
|
+
this.minutes = new Map();
|
|
132
|
+
this.activityAt = 0;
|
|
133
|
+
this.rebaseOn = false;
|
|
128
134
|
this.switches = [];
|
|
129
135
|
this.branchAt = new Map();
|
|
130
136
|
this.branchSha = new Map();
|
|
@@ -177,6 +183,101 @@ class Dashboard {
|
|
|
177
183
|
progress.stop(TICK_ID);
|
|
178
184
|
term.cancel(HEAT_TIMER);
|
|
179
185
|
term.cancel(AGE_TIMER);
|
|
186
|
+
term.cancel(MINUTE_TIMER);
|
|
187
|
+
term.cancel(BLINK_TIMER);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
bucket(at) {
|
|
191
|
+
const minute = minuteOf(at);
|
|
192
|
+
let newest = minute;
|
|
193
|
+
for (const key of this.minutes.keys()) {
|
|
194
|
+
if (key > newest) newest = key;
|
|
195
|
+
}
|
|
196
|
+
if (minute < newest - MINUTE_KEEP) return null;
|
|
197
|
+
let row = this.minutes.get(minute);
|
|
198
|
+
if (!row) {
|
|
199
|
+
row = { level: 0, added: 0, removed: 0, marks: [] };
|
|
200
|
+
this.minutes.set(minute, row);
|
|
201
|
+
}
|
|
202
|
+
const oldest = newest - MINUTE_KEEP;
|
|
203
|
+
for (const key of this.minutes.keys()) {
|
|
204
|
+
if (key < oldest) this.minutes.delete(key);
|
|
205
|
+
}
|
|
206
|
+
return row;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
touchActivity(at) {
|
|
210
|
+
if (at > this.activityAt) this.activityAt = at;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
heat(at, added, removed = 0) {
|
|
214
|
+
const plus = Math.max(0, added || 0);
|
|
215
|
+
const minus = Math.max(0, removed || 0);
|
|
216
|
+
if (!at || plus + minus <= 0) return;
|
|
217
|
+
const row = this.bucket(at);
|
|
218
|
+
if (!row) return;
|
|
219
|
+
row.added += plus;
|
|
220
|
+
row.removed += minus;
|
|
221
|
+
row.level += plus + minus;
|
|
222
|
+
this.touchActivity(at);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
mark(at, kind) {
|
|
226
|
+
if (!at || !kind) return;
|
|
227
|
+
const row = this.bucket(at);
|
|
228
|
+
if (!row || row.marks.includes(kind)) return;
|
|
229
|
+
row.marks.push(kind);
|
|
230
|
+
this.touchActivity(at);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
noteCommits(next) {
|
|
234
|
+
const recent = next.commits.recent;
|
|
235
|
+
for (const entry of recent) this.mark(entry.at, 'commit');
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
noteRebase(next, now) {
|
|
239
|
+
const rebasing = Boolean(next.rebase);
|
|
240
|
+
if (rebasing && !this.rebaseOn) this.mark(now, 'rebase');
|
|
241
|
+
this.rebaseOn = rebasing;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
noteRuns(runs) {
|
|
245
|
+
for (const run of runs) {
|
|
246
|
+
const kind = RUN_MARK[run.status];
|
|
247
|
+
if (kind) this.mark(run.endedAt || run.startedAt, kind);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
isRunLive() {
|
|
252
|
+
for (const run of this.liveRuns()) {
|
|
253
|
+
if (run.status === 'running') return true;
|
|
254
|
+
}
|
|
255
|
+
const npm = this.ui.npm.lastRun;
|
|
256
|
+
return Boolean(npm && npm.running);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
isBusy() {
|
|
260
|
+
if (this.git && this.git.rebase) return true;
|
|
261
|
+
return this.isRunLive();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
liveActivity(now) {
|
|
265
|
+
if (this.isBusy()) return true;
|
|
266
|
+
return now - this.activityAt < BLINK_MS;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
activityView(now) {
|
|
270
|
+
const minutes = [];
|
|
271
|
+
for (const [at, row] of this.minutes) {
|
|
272
|
+
minutes.push({
|
|
273
|
+
at,
|
|
274
|
+
level: row.level,
|
|
275
|
+
added: row.added,
|
|
276
|
+
removed: row.removed,
|
|
277
|
+
marks: row.marks.slice(),
|
|
278
|
+
});
|
|
279
|
+
}
|
|
280
|
+
return { minutes, live: this.liveActivity(now) };
|
|
180
281
|
}
|
|
181
282
|
|
|
182
283
|
afterDraw() {
|
|
@@ -184,17 +285,21 @@ class Dashboard {
|
|
|
184
285
|
if (!this.visible()) return void this.cancelTimers();
|
|
185
286
|
const { progress, term } = this.ui;
|
|
186
287
|
const now = Date.now();
|
|
288
|
+
if (this.isBusy()) this.touchActivity(now);
|
|
187
289
|
const until = this.hotUntil();
|
|
188
290
|
const hot = now < until;
|
|
189
|
-
const
|
|
190
|
-
|
|
191
|
-
if (running || hot) progress.start(TICK_ID);
|
|
291
|
+
const live = this.liveActivity(now);
|
|
292
|
+
if (live || hot) progress.start(TICK_ID);
|
|
192
293
|
else progress.stop(TICK_ID);
|
|
193
294
|
const repaint = () => this.paintIfVisible();
|
|
194
295
|
if (hot) term.later(HEAT_TIMER, repaint, until - now + 20);
|
|
195
|
-
const at = this.youngest(
|
|
296
|
+
const at = this.youngest(this.liveRuns());
|
|
196
297
|
const delay = at ? ageDelay(now - at) : AGE_SLOW_MS;
|
|
197
298
|
term.later(AGE_TIMER, repaint, delay);
|
|
299
|
+
const minuteLeft = MINUTE_MS - (now % MINUTE_MS) + 20;
|
|
300
|
+
term.later(MINUTE_TIMER, repaint, minuteLeft);
|
|
301
|
+
const blinkLeft = this.activityAt + BLINK_MS - now;
|
|
302
|
+
if (blinkLeft > 0) term.later(BLINK_TIMER, repaint, blinkLeft + 20);
|
|
198
303
|
}
|
|
199
304
|
|
|
200
305
|
async runFiles() {
|
|
@@ -209,7 +314,12 @@ class Dashboard {
|
|
|
209
314
|
const after = index.summary().total;
|
|
210
315
|
const lines = after.lines - before.lines;
|
|
211
316
|
const bytes = after.bytes - before.bytes;
|
|
212
|
-
if (lines
|
|
317
|
+
if (!lines && !bytes) return;
|
|
318
|
+
const at = Date.now();
|
|
319
|
+
this.fileDelta = { lines, at };
|
|
320
|
+
const plus = lines > 0 ? lines : 0;
|
|
321
|
+
const minus = lines < 0 ? -lines : 0;
|
|
322
|
+
this.heat(at, plus || (minus ? 0 : 1), minus);
|
|
213
323
|
}
|
|
214
324
|
|
|
215
325
|
noteShas(next, now) {
|
|
@@ -243,9 +353,12 @@ class Dashboard {
|
|
|
243
353
|
const moved = this.noteShas(next, now);
|
|
244
354
|
const switched = this.noteSwitch(next, now);
|
|
245
355
|
if (moved || switched) this.marks.branches = now;
|
|
356
|
+
if (switched) this.mark(now, 'branch');
|
|
246
357
|
const sha = next.commits.last ? next.commits.last.sha : '';
|
|
247
358
|
if (this.lastSha && sha !== this.lastSha) this.marks.commit = now;
|
|
248
359
|
this.lastSha = sha;
|
|
360
|
+
this.noteCommits(next);
|
|
361
|
+
this.noteRebase(next, now);
|
|
249
362
|
this.git = next;
|
|
250
363
|
}
|
|
251
364
|
|
|
@@ -259,12 +372,17 @@ class Dashboard {
|
|
|
259
372
|
(prev.deps !== next.deps ||
|
|
260
373
|
prev.dev !== next.dev ||
|
|
261
374
|
prev.modules.stamp !== next.modules.stamp);
|
|
262
|
-
if (changed)
|
|
375
|
+
if (changed) {
|
|
376
|
+
const now = Date.now();
|
|
377
|
+
this.marks.npm = now;
|
|
378
|
+
this.mark(now, 'npm');
|
|
379
|
+
}
|
|
263
380
|
this.npm = next;
|
|
264
381
|
}
|
|
265
382
|
|
|
266
383
|
runRuns() {
|
|
267
384
|
this.runs = readRuns(this.root());
|
|
385
|
+
this.noteRuns(this.runs);
|
|
268
386
|
}
|
|
269
387
|
|
|
270
388
|
noteLoaded() {
|
|
@@ -276,12 +394,14 @@ class Dashboard {
|
|
|
276
394
|
if (prev) {
|
|
277
395
|
const added = totals.added - prev.added;
|
|
278
396
|
const removed = totals.removed - prev.removed;
|
|
279
|
-
|
|
397
|
+
const at = Date.now();
|
|
398
|
+
this.diffDelta = { added, removed, at };
|
|
399
|
+
const plus = Math.max(0, added);
|
|
400
|
+
const minus = Math.max(0, removed);
|
|
401
|
+
this.heat(at, plus || (minus ? 0 : 1), minus);
|
|
280
402
|
}
|
|
281
403
|
this.totals = totals;
|
|
282
404
|
this.totalsKey = key;
|
|
283
|
-
this.samples.push(totals.added + totals.removed);
|
|
284
|
-
if (this.samples.length > SAMPLES) this.samples.shift();
|
|
285
405
|
}
|
|
286
406
|
|
|
287
407
|
requestAll() {
|
|
@@ -325,17 +445,18 @@ class Dashboard {
|
|
|
325
445
|
|
|
326
446
|
view() {
|
|
327
447
|
const ui = this.ui;
|
|
448
|
+
const now = Date.now();
|
|
328
449
|
const entries = fileEntries(ui.items);
|
|
329
450
|
const store = ui.review.store;
|
|
330
451
|
return buildModel({
|
|
331
|
-
now
|
|
452
|
+
now,
|
|
332
453
|
frame: ui.progressFrame,
|
|
333
454
|
busy: ui.ops.busy,
|
|
334
455
|
index: this.index,
|
|
335
456
|
fileDelta: this.fileDelta,
|
|
336
457
|
entries,
|
|
337
458
|
totals: this.totals ?? fileTotals(entries),
|
|
338
|
-
|
|
459
|
+
activity: this.activityView(now),
|
|
339
460
|
diffDelta: this.diffDelta,
|
|
340
461
|
git: this.git,
|
|
341
462
|
switches: this.switches,
|
|
@@ -350,6 +471,30 @@ class Dashboard {
|
|
|
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();
|
|
@@ -365,7 +510,7 @@ class Dashboard {
|
|
|
365
510
|
diffs: () => this.showFiles('diff'),
|
|
366
511
|
commits: () => ui.commits.onCommit(),
|
|
367
512
|
branches: () => ui.gitBranches.onBranch(),
|
|
368
|
-
npm: () =>
|
|
513
|
+
npm: () => this.openPackages(),
|
|
369
514
|
run: () => ui.npm.open(),
|
|
370
515
|
tasks: () => ui.composer.tasks.openTasksPage(),
|
|
371
516
|
};
|
package/lib/session/dispatch.js
CHANGED
|
@@ -6,12 +6,15 @@ const commit = require('./commit.js');
|
|
|
6
6
|
const { hasStagedChanges, listedCommits } = commit;
|
|
7
7
|
const actions = require('./actions.js');
|
|
8
8
|
const { LIST_PANES, DASH_BLOCKS, DASH_ALLOWED, CONFIRM_MODE } = actions;
|
|
9
|
-
const {
|
|
9
|
+
const { REPOS_ALLOWED } = actions;
|
|
10
|
+
const { disabledActions, actionFromKey, confirmKey, lineHasDiff } = actions;
|
|
10
11
|
const changeActions = require('./change-actions.js');
|
|
11
12
|
const { onCommand } = changeActions;
|
|
12
13
|
const filesPane = require('./files-pane.js');
|
|
13
14
|
const pointer = require('./pointer.js');
|
|
14
15
|
const { handlePointer } = pointer;
|
|
16
|
+
const finder = require('./find.js');
|
|
17
|
+
const { FIND_PANES, startFind, onFindKey } = finder;
|
|
15
18
|
|
|
16
19
|
const currentTarget = (ui) => {
|
|
17
20
|
if (ui.pane === 'files') return ui.fileCursorEntry();
|
|
@@ -22,7 +25,14 @@ const currentTarget = (ui) => {
|
|
|
22
25
|
const unstagedCurrent = !ui.rev && !ui.revShort;
|
|
23
26
|
return { ...entry, canCommit, unstagedCurrent };
|
|
24
27
|
}
|
|
25
|
-
return ui.current();
|
|
28
|
+
if (ui.pane !== 'unit') return ui.current();
|
|
29
|
+
const item = ui.current();
|
|
30
|
+
const lines = ui.view().unitLines ?? [];
|
|
31
|
+
if (!lines.length) return item;
|
|
32
|
+
const line = lines[ui.nav.unitLine] ?? null;
|
|
33
|
+
const lineDiff = lineHasDiff(line);
|
|
34
|
+
if (!item) return { lineDiff };
|
|
35
|
+
return { ...item, lineDiff };
|
|
26
36
|
};
|
|
27
37
|
|
|
28
38
|
const scrollBy = (fraction) => (ui) =>
|
|
@@ -45,7 +55,10 @@ const HANDLERS = {
|
|
|
45
55
|
layout: filesPane.onLayout,
|
|
46
56
|
theme: filesPane.onTheme,
|
|
47
57
|
files: filesPane.onFiles,
|
|
48
|
-
open:
|
|
58
|
+
open: (ui) => {
|
|
59
|
+
if (ui.pane === 'repos') return void ui.workspace.openSelected();
|
|
60
|
+
filesPane.onOpenFile(ui);
|
|
61
|
+
},
|
|
49
62
|
file: filesPane.onFileScope,
|
|
50
63
|
diff: filesPane.onDiffScope,
|
|
51
64
|
back: filesPane.onEscape,
|
|
@@ -63,10 +76,12 @@ const HANDLERS = {
|
|
|
63
76
|
check: (ui) => ui.composer.tasks.toggleFocusedTask(),
|
|
64
77
|
removeTask: (ui) => {
|
|
65
78
|
if (ui.pane === 'npm') return void ui.npm.askDrop();
|
|
79
|
+
if (ui.pane === 'packages') return;
|
|
66
80
|
ui.composer.tasks.removeFocusedTask();
|
|
67
81
|
},
|
|
68
82
|
reload: (ui) => ui.lifecycle.onReload(),
|
|
69
83
|
commit: (ui) => ui.commits.onCommit(),
|
|
84
|
+
newCommit: (ui) => ui.commits.onInsert(),
|
|
70
85
|
amend: (ui) => ui.commits.onAmend(),
|
|
71
86
|
reword: (ui) => ui.commits.onReword(),
|
|
72
87
|
apply: (ui) => ui.commits.onApply(),
|
|
@@ -86,11 +101,19 @@ const HANDLERS = {
|
|
|
86
101
|
npmEdit: (ui) => ui.npm.edit(),
|
|
87
102
|
npmNew: (ui) => ui.npm.create(),
|
|
88
103
|
npmDrop: (ui) => ui.npm.askDrop(),
|
|
104
|
+
packageNew: (ui) => ui.packages.create(),
|
|
105
|
+
packageDrop: (ui) => ui.packages.askDrop(),
|
|
106
|
+
packageWanted: (ui) => ui.packages.updateWanted(),
|
|
107
|
+
packageLatest: (ui) => ui.packages.updateLatest(),
|
|
89
108
|
npmLogs: (ui) => ui.npm.askLogs(),
|
|
90
109
|
npmStop: (ui) => ui.npm.closeView(),
|
|
91
110
|
npmVerbose: (ui) => ui.npm.toggleVerbose(),
|
|
92
111
|
npmRerun: (ui) => ui.npm.rerun(),
|
|
93
112
|
quit: (ui) => ui.onQuit(),
|
|
113
|
+
repoLeft: (ui) => ui.workspace.move(-1, 0),
|
|
114
|
+
repoRight: (ui) => ui.workspace.move(1, 0),
|
|
115
|
+
repoUp: (ui) => ui.workspace.move(0, -1),
|
|
116
|
+
repoDown: (ui) => ui.workspace.move(0, 1),
|
|
94
117
|
...DASH_HANDLERS,
|
|
95
118
|
};
|
|
96
119
|
|
|
@@ -102,6 +125,7 @@ const dispatchAction = (ui, action) => {
|
|
|
102
125
|
if (result && result.kind === 'error') return;
|
|
103
126
|
}
|
|
104
127
|
const pane = ui.pane;
|
|
128
|
+
if (pane === 'repos' && !REPOS_ALLOWED.includes(action)) return;
|
|
105
129
|
if (pane === 'dashboard' && !DASH_ALLOWED.includes(action)) return;
|
|
106
130
|
if (LIST_PANES.includes(pane) && (action === 'next' || action === 'prev')) {
|
|
107
131
|
return void filesPane.onScroll(ui, action === 'next' ? 1 : -1);
|
|
@@ -123,11 +147,13 @@ const CONFIRMATIONS = {
|
|
|
123
147
|
},
|
|
124
148
|
confirmDrop: {
|
|
125
149
|
confirm: (ui) => {
|
|
150
|
+
if (ui.packages.dropName) return void ui.packages.confirmDrop();
|
|
126
151
|
if (ui.npm.dropName) return void ui.npm.confirmDrop();
|
|
127
152
|
if (ui.commits.dropName) return void ui.commits.confirmDropCommit();
|
|
128
153
|
ui.gitBranches.confirmDropBranch();
|
|
129
154
|
},
|
|
130
155
|
cancel: (ui) => {
|
|
156
|
+
if (ui.packages.dropName) return void ui.packages.cancelDrop();
|
|
131
157
|
if (ui.npm.dropName) return void ui.npm.cancelDrop();
|
|
132
158
|
if (ui.commits.dropName) return void ui.commits.cancelDropCommit();
|
|
133
159
|
ui.gitBranches.cancelDropBranch();
|
|
@@ -137,6 +163,12 @@ const CONFIRMATIONS = {
|
|
|
137
163
|
force: (ui) => ui.gitBranches.onForcePush(),
|
|
138
164
|
cancel: (ui) => ui.gitBranches.cancelPush(),
|
|
139
165
|
},
|
|
166
|
+
confirmCommit: {
|
|
167
|
+
commit: (ui) => ui.commits.chooseCommit('commit'),
|
|
168
|
+
amend: (ui) => ui.commits.chooseCommit('amend'),
|
|
169
|
+
fixup: (ui) => ui.commits.chooseCommit('fixup'),
|
|
170
|
+
cancel: (ui) => ui.commits.cancelCommitChoice(),
|
|
171
|
+
},
|
|
140
172
|
};
|
|
141
173
|
|
|
142
174
|
const confirmChoice = (ui, key) => {
|
|
@@ -173,11 +205,13 @@ const handleKey = (ui, key) => {
|
|
|
173
205
|
if (ui.mode === 'compose') {
|
|
174
206
|
return void ui.runComposeCommand(ui.composer.handleKey(key));
|
|
175
207
|
}
|
|
208
|
+
if (ui.mode === 'find') return void onFindKey(ui, key);
|
|
209
|
+
if (key === '/' && FIND_PANES.includes(ui.pane)) return void startFind(ui);
|
|
176
210
|
const onNpmKey = npmKey(ui, key);
|
|
177
211
|
if (onNpmKey) return void onNpmKey(ui);
|
|
178
212
|
if (key === 'escape') return void filesPane.onEscape(ui);
|
|
179
213
|
const tasks = ui.composer.tasks;
|
|
180
|
-
const toggle = key === '
|
|
214
|
+
const toggle = key === ' ';
|
|
181
215
|
if (toggle && tasks.toggleFocusedTask()) return;
|
|
182
216
|
if (tasks.typeIntoTask(key)) return;
|
|
183
217
|
const action = actionFromKey(key, ui.pane, ui.fileScope);
|
|
@@ -35,7 +35,8 @@ const showFiles = (ui) => {
|
|
|
35
35
|
nav.tasksOpen === true ||
|
|
36
36
|
nav.pane === 'branches' ||
|
|
37
37
|
nav.pane === 'commits' ||
|
|
38
|
-
nav.pane === 'npm'
|
|
38
|
+
nav.pane === 'npm' ||
|
|
39
|
+
nav.pane === 'packages';
|
|
39
40
|
nav.pane = 'files';
|
|
40
41
|
nav.resetListScroll('files');
|
|
41
42
|
nav.clearSelection();
|
|
@@ -66,7 +67,7 @@ const showDashboard = (ui) => {
|
|
|
66
67
|
ui.dashboard.enter();
|
|
67
68
|
};
|
|
68
69
|
|
|
69
|
-
const HOME_CHILDREN = ['files', 'branches', 'commits', 'npm'];
|
|
70
|
+
const HOME_CHILDREN = ['files', 'branches', 'commits', 'npm', 'packages'];
|
|
70
71
|
|
|
71
72
|
const returnsHome = (ui) => {
|
|
72
73
|
if (!ui.dashboardHome) return false;
|
|
@@ -75,6 +76,10 @@ const returnsHome = (ui) => {
|
|
|
75
76
|
};
|
|
76
77
|
|
|
77
78
|
const onEscape = (ui) => {
|
|
79
|
+
if (ui.nav.pane === 'repos') return void ui.onQuit();
|
|
80
|
+
if (ui.nav.pane === 'dashboard' && ui.workspaceRoot) {
|
|
81
|
+
return void ui.workspace.show();
|
|
82
|
+
}
|
|
78
83
|
if (ui.nav.pane === 'dashboard') return void ui.onQuit();
|
|
79
84
|
const commitDiff = ui.nav.pane === 'diff' && viewingCommit(ui);
|
|
80
85
|
if (returnsHome(ui) || (ui.dashboardHome && commitDiff)) {
|
|
@@ -101,6 +106,7 @@ const onFiles = (ui) => {
|
|
|
101
106
|
};
|
|
102
107
|
|
|
103
108
|
const onOpenFile = (ui) => {
|
|
109
|
+
if (ui.nav.pane === 'packages') return;
|
|
104
110
|
if (ui.nav.pane === 'branches') {
|
|
105
111
|
return void ui.gitBranches.onCheckoutBranch();
|
|
106
112
|
}
|
|
@@ -203,6 +209,15 @@ const onScroll = (ui, delta) => {
|
|
|
203
209
|
return void ui.commits.onCommitMove(delta);
|
|
204
210
|
}
|
|
205
211
|
if (ui.nav.pane === 'npm') return void ui.npm.move(delta);
|
|
212
|
+
if (ui.nav.pane === 'packages') {
|
|
213
|
+
const count = ui.dashboard.packageCount();
|
|
214
|
+
const nav = ui.nav;
|
|
215
|
+
const next = nav.packagesCursor + delta;
|
|
216
|
+
const last = Math.max(0, count - 1);
|
|
217
|
+
nav.packagesCursor = Math.max(0, Math.min(last, next));
|
|
218
|
+
ui.status = '';
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
206
221
|
if (ui.nav.tasksOpen && ui.composer.tasks.taskRowCount() > 1) {
|
|
207
222
|
return void ui.composer.tasks.moveTaskFocus(delta);
|
|
208
223
|
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const files = require('../files.js');
|
|
4
|
+
const { isTasksEntry, TASKS_FILE } = files;
|
|
5
|
+
const find = require('../find.js');
|
|
6
|
+
const { fileMatches } = find;
|
|
7
|
+
|
|
8
|
+
const FIND_PANES = ['files', 'diff'];
|
|
9
|
+
|
|
10
|
+
const isTypeKey = (key) =>
|
|
11
|
+
Boolean(key) && key.length === 1 && key.charCodeAt(0) >= 32;
|
|
12
|
+
|
|
13
|
+
const startFind = (ui) => {
|
|
14
|
+
if (ui.mode !== 'review') return;
|
|
15
|
+
if (!FIND_PANES.includes(ui.pane)) return;
|
|
16
|
+
ui.mode = 'find';
|
|
17
|
+
ui.nav.find = { query: '', at: 0 };
|
|
18
|
+
ui.status = '';
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const stopFind = (ui) => {
|
|
22
|
+
ui.mode = 'review';
|
|
23
|
+
ui.nav.find = null;
|
|
24
|
+
ui.status = '';
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const focusMatch = (ui, hit, jump) => {
|
|
28
|
+
const entry = ui.fileList()[hit.index];
|
|
29
|
+
if (!entry) return;
|
|
30
|
+
const moved = ui.nav.fileCursor !== hit.index;
|
|
31
|
+
ui.nav.fileCursor = hit.index;
|
|
32
|
+
if (isTasksEntry(entry)) {
|
|
33
|
+
ui.nav.reviewPath = TASKS_FILE;
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
ui.nav.reviewPath = entry.path;
|
|
37
|
+
if (ui.nav.pane !== 'diff') {
|
|
38
|
+
ui.nav.index = entry.firstIndex;
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (!moved && !jump) return;
|
|
42
|
+
ui.nav.index = entry.openIndex ?? entry.firstIndex;
|
|
43
|
+
ui.nav.scroll = 0;
|
|
44
|
+
ui.nav.clearSelection();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const showHit = (ui, jump) => {
|
|
48
|
+
const state = ui.nav.find;
|
|
49
|
+
if (!state) return;
|
|
50
|
+
const hits = fileMatches(ui.fileList(), state.query);
|
|
51
|
+
if (!hits.length) return;
|
|
52
|
+
const count = hits.length;
|
|
53
|
+
let at = state.at % count;
|
|
54
|
+
if (at < 0) at += count;
|
|
55
|
+
state.at = at;
|
|
56
|
+
focusMatch(ui, hits[at], jump);
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
const typeFind = (ui, key) => {
|
|
60
|
+
const state = ui.nav.find;
|
|
61
|
+
state.query += key;
|
|
62
|
+
state.at = 0;
|
|
63
|
+
showHit(ui, true);
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const eraseFind = (ui) => {
|
|
67
|
+
const state = ui.nav.find;
|
|
68
|
+
if (!state.query) return void stopFind(ui);
|
|
69
|
+
state.query = state.query.slice(0, -1);
|
|
70
|
+
state.at = 0;
|
|
71
|
+
showHit(ui, true);
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
const moveFind = (ui, delta) => {
|
|
75
|
+
const state = ui.nav.find;
|
|
76
|
+
if (!state.query) return;
|
|
77
|
+
state.at += delta;
|
|
78
|
+
showHit(ui, false);
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
const onFindKey = (ui, key) => {
|
|
82
|
+
if (!ui.nav.find) {
|
|
83
|
+
ui.mode = 'review';
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (key === 'ctrl-c') return void ui.onQuit();
|
|
87
|
+
if (key === 'escape' || key === 'enter') return void stopFind(ui);
|
|
88
|
+
if (key === 'backspace') return void eraseFind(ui);
|
|
89
|
+
if (key === 'up' || key === 'left') return void moveFind(ui, -1);
|
|
90
|
+
if (key === 'down' || key === 'right') return void moveFind(ui, 1);
|
|
91
|
+
if (isTypeKey(key)) typeFind(ui, key);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
module.exports = { FIND_PANES, startFind, stopFind, onFindKey };
|
package/lib/session/lifecycle.js
CHANGED
|
@@ -163,6 +163,7 @@ class Lifecycle {
|
|
|
163
163
|
|
|
164
164
|
applyLoaded(loaded) {
|
|
165
165
|
const ui = this.ui;
|
|
166
|
+
if (ui.nav.pane === 'repos') return false;
|
|
166
167
|
const { loader, collection, nav } = ui;
|
|
167
168
|
if (loaded.generation !== undefined) {
|
|
168
169
|
if (!loader.isCurrent(loaded.generation, ui.done)) return false;
|
|
@@ -325,6 +326,11 @@ class Lifecycle {
|
|
|
325
326
|
|
|
326
327
|
openLoad() {
|
|
327
328
|
const loader = this.ui.loader;
|
|
329
|
+
if (this.ui.nav.pane === 'repos') {
|
|
330
|
+
loader.didLoad = true;
|
|
331
|
+
loader.promise = Promise.resolve();
|
|
332
|
+
return loader.promise;
|
|
333
|
+
}
|
|
328
334
|
if (loader.promise) return loader.promise;
|
|
329
335
|
if (loader.didLoad && !loader.pendingExtras) {
|
|
330
336
|
loader.promise = Promise.resolve();
|
|
@@ -335,6 +341,10 @@ class Lifecycle {
|
|
|
335
341
|
}
|
|
336
342
|
|
|
337
343
|
async loadReady() {
|
|
344
|
+
if (this.ui.nav.pane === 'repos') {
|
|
345
|
+
this.ui.loader.didLoad = true;
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
338
348
|
this.ui.ensureRepo();
|
|
339
349
|
await this.openLoad();
|
|
340
350
|
if (this.ui.loadError) throw this.ui.loadError;
|
|
@@ -407,6 +417,7 @@ class Lifecycle {
|
|
|
407
417
|
}
|
|
408
418
|
|
|
409
419
|
onDiskChange() {
|
|
420
|
+
if (this.ui.nav.pane === 'repos') return;
|
|
410
421
|
if (Date.now() < this.ignoreWatchUntil) return;
|
|
411
422
|
if (!this.shouldDeferWatch()) {
|
|
412
423
|
return void this.refreshFromRepo({ keepView: true, quiet: true });
|
|
@@ -465,6 +476,22 @@ class Lifecycle {
|
|
|
465
476
|
|
|
466
477
|
startWatch() {
|
|
467
478
|
this.stopWatch();
|
|
479
|
+
const ui = this.ui;
|
|
480
|
+
if (ui.nav.pane === 'repos') {
|
|
481
|
+
const root = ui.workspaceRoot;
|
|
482
|
+
if (!root) return;
|
|
483
|
+
const info = ui.workspaceInfo;
|
|
484
|
+
const repos = info ? info.repos : [];
|
|
485
|
+
const paths = ['.'];
|
|
486
|
+
for (const repo of repos) paths.push(path.join(repo.name, '.git'));
|
|
487
|
+
this.diskWatcher = new DiskWatcher({
|
|
488
|
+
root,
|
|
489
|
+
paths,
|
|
490
|
+
matchReview: (rel) => rel.includes('/.review/'),
|
|
491
|
+
onPaths: (rels) => ui.workspace.notePaths(rels),
|
|
492
|
+
});
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
468
495
|
const root = this.root;
|
|
469
496
|
if (!isWorktree(this.ui) || !root) return;
|
|
470
497
|
this.diskWatcher = new DiskWatcher({
|
|
@@ -20,6 +20,7 @@ class Navigation {
|
|
|
20
20
|
this.branchCursor = 0;
|
|
21
21
|
this.commitCursor = 0;
|
|
22
22
|
this.npmCursor = 0;
|
|
23
|
+
this.packagesCursor = 0;
|
|
23
24
|
this.scroll = 0;
|
|
24
25
|
this.listScrollByPane = freshListScroll();
|
|
25
26
|
this.reviewPath = null;
|
|
@@ -31,6 +32,7 @@ class Navigation {
|
|
|
31
32
|
this.lastClick = null;
|
|
32
33
|
this.tasksOpen = false;
|
|
33
34
|
this.tasksFocus = 0;
|
|
35
|
+
this.find = null;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
38
|
clampIndex(length) {
|