reslop 0.2.0 → 0.2.3
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 +13 -2
- package/lib/ansi.js +263 -6
- package/lib/api.js +2 -0
- package/lib/capabilities.js +6 -0
- package/lib/cli.js +13 -2
- package/lib/deps.js +43 -3
- package/lib/git-branches.js +228 -17
- package/lib/git-reword-todo.js +20 -0
- package/lib/git.js +20 -3
- package/lib/keys.js +10 -4
- package/lib/npm-commands.js +513 -0
- package/lib/render/branches.js +23 -58
- package/lib/render/chrome.js +156 -99
- package/lib/render/commits.js +643 -31
- package/lib/render/diff.js +4 -4
- package/lib/render/frame.js +51 -5
- package/lib/render/notes.js +22 -5
- package/lib/render/npm.js +228 -0
- package/lib/render/primitives.js +48 -34
- package/lib/render/render.js +3 -3
- package/lib/report-model.js +127 -0
- package/lib/report-parse.js +94 -0
- package/lib/report-render.js +87 -0
- package/lib/report-run.js +164 -0
- package/lib/report-test.js +506 -0
- package/lib/report-tools.js +311 -0
- package/lib/review-merge.js +270 -0
- package/lib/review.js +38 -6
- package/lib/session/actions.js +81 -5
- package/lib/session/commit.js +187 -19
- package/lib/session/compose.js +96 -32
- package/lib/session/dispatch.js +36 -2
- package/lib/session/files-pane.js +36 -6
- package/lib/session/lifecycle.js +116 -16
- package/lib/session/navigation.js +1 -0
- package/lib/session/npm.js +335 -0
- package/lib/session/pointer.js +12 -1
- package/lib/session/todos.js +29 -1
- package/lib/session/view.js +14 -3
- package/lib/session/watch.js +120 -8
- package/lib/session.js +11 -2
- package/package.json +7 -7
package/lib/session/compose.js
CHANGED
|
@@ -4,12 +4,14 @@ const wrap = require('../wrap.js');
|
|
|
4
4
|
const { cursorInWrap, wrapDoc } = wrap;
|
|
5
5
|
const render = require('../render/render.js');
|
|
6
6
|
const { noteInnerWidth, codeInnerWidth, todoBodyWidth } = render;
|
|
7
|
+
const { commitMessageWidth } = require('../render/commits.js');
|
|
8
|
+
const { listedCommits } = require('./commit.js');
|
|
7
9
|
const { Editor } = require('../editor.js');
|
|
8
10
|
const files = require('../files.js');
|
|
9
11
|
const { itemPath, isTodoItem, listPath, isReadOnlyOrigin } = files;
|
|
10
12
|
const { blockAddText } = require('../diff/diff.js');
|
|
11
13
|
const items = require('./items.js');
|
|
12
|
-
const { itemFeedKey, canEditCode
|
|
14
|
+
const { itemFeedKey, canEditCode } = items;
|
|
13
15
|
const review = require('../review.js');
|
|
14
16
|
const { setFeedback, setCode, checkLabel, rememberTemplate } = review;
|
|
15
17
|
const { createTemplatePick } = require('./templates.js');
|
|
@@ -68,12 +70,21 @@ const KIND = {
|
|
|
68
70
|
command: 'branch',
|
|
69
71
|
view: 'note',
|
|
70
72
|
},
|
|
73
|
+
npm: {
|
|
74
|
+
enter: 'save',
|
|
75
|
+
tab: 'insert-tab',
|
|
76
|
+
escape: 'cancel',
|
|
77
|
+
move: 'note',
|
|
78
|
+
command: 'npm',
|
|
79
|
+
view: 'note',
|
|
80
|
+
},
|
|
71
81
|
};
|
|
72
82
|
|
|
73
83
|
const initialState = () => ({
|
|
74
84
|
editor: null,
|
|
75
85
|
composeKind: null,
|
|
76
86
|
commitKind: null,
|
|
87
|
+
commitBody: '',
|
|
77
88
|
composeTodoId: null,
|
|
78
89
|
composeBaseline: '',
|
|
79
90
|
templateIndex: -1,
|
|
@@ -81,6 +92,7 @@ const initialState = () => ({
|
|
|
81
92
|
blinkOn: true,
|
|
82
93
|
fileDirty: false,
|
|
83
94
|
fileEditRows: null,
|
|
95
|
+
fileOpened: '',
|
|
84
96
|
});
|
|
85
97
|
|
|
86
98
|
const reset = (state) => {
|
|
@@ -124,6 +136,7 @@ const openCompose = (api, kind, text, todoId) => {
|
|
|
124
136
|
state.composeKind = kind;
|
|
125
137
|
state.composeTodoId = todoId ?? null;
|
|
126
138
|
state.composeBaseline = text;
|
|
139
|
+
state.fileOpened = kind === 'file' ? text : '';
|
|
127
140
|
state.editor = new Editor(text);
|
|
128
141
|
state.templateIndex = -1;
|
|
129
142
|
state.templateFocus = false;
|
|
@@ -138,8 +151,10 @@ const closeCompose = (api) => {
|
|
|
138
151
|
state.editor = null;
|
|
139
152
|
state.composeKind = null;
|
|
140
153
|
state.commitKind = null;
|
|
154
|
+
state.commitBody = '';
|
|
141
155
|
state.composeTodoId = null;
|
|
142
156
|
state.composeBaseline = '';
|
|
157
|
+
state.fileOpened = '';
|
|
143
158
|
state.templateIndex = -1;
|
|
144
159
|
state.templateFocus = false;
|
|
145
160
|
state.fileEditRows = null;
|
|
@@ -240,60 +255,66 @@ const saveUnitFile = (api, text) => {
|
|
|
240
255
|
return true;
|
|
241
256
|
};
|
|
242
257
|
|
|
243
|
-
const
|
|
244
|
-
|
|
245
|
-
for (const item of list) {
|
|
246
|
-
if (listPath(item) !== rel) continue;
|
|
247
|
-
keys.add(blockLinesKey(item));
|
|
248
|
-
}
|
|
249
|
-
return keys;
|
|
250
|
-
};
|
|
251
|
-
|
|
252
|
-
const stageNewFileHunks = (api, rel, beforeKeys) => {
|
|
258
|
+
const stageEditedFile = (api, rel) => {
|
|
259
|
+
if (!rel) return false;
|
|
253
260
|
const repo = api.ui.repo;
|
|
254
|
-
if (!
|
|
261
|
+
if (!repo) return false;
|
|
255
262
|
const caps = api.ui.capabilities;
|
|
256
|
-
if (!caps || caps.changes !== true) return
|
|
257
|
-
|
|
263
|
+
if (!caps || caps.changes !== true) return false;
|
|
264
|
+
if (typeof repo.stagePath === 'function') {
|
|
265
|
+
try {
|
|
266
|
+
repo.stagePath(api.ui.top, rel);
|
|
267
|
+
} catch (error) {
|
|
268
|
+
api.ui.status = error.message;
|
|
269
|
+
return false;
|
|
270
|
+
}
|
|
271
|
+
if (typeof api.ui.ignoreWatch === 'function') api.ui.ignoreWatch();
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
if (typeof repo.add !== 'function') return false;
|
|
258
275
|
let staged = 0;
|
|
259
|
-
for (const item of
|
|
276
|
+
for (const item of api.ui.items ?? []) {
|
|
260
277
|
if (listPath(item) !== rel) continue;
|
|
261
278
|
if (isReadOnlyOrigin(item.origin)) continue;
|
|
262
279
|
if (item.origin === 'staged') continue;
|
|
263
|
-
if (beforeKeys.has(blockLinesKey(item))) continue;
|
|
264
280
|
try {
|
|
265
281
|
repo.add(api.ui.top, item);
|
|
266
282
|
} catch (error) {
|
|
267
283
|
api.ui.status = error.message;
|
|
268
|
-
return staged;
|
|
284
|
+
return staged > 0;
|
|
269
285
|
}
|
|
270
286
|
if (api.ui.collection && api.ui.collection.keepOrigin) {
|
|
271
287
|
api.ui.collection.keepOrigin(item, 'staged');
|
|
272
288
|
}
|
|
273
|
-
if (typeof api.ui.ignoreWatch === 'function') api.ui.ignoreWatch();
|
|
274
289
|
staged += 1;
|
|
275
290
|
}
|
|
276
|
-
|
|
291
|
+
if (staged && typeof api.ui.ignoreWatch === 'function') {
|
|
292
|
+
api.ui.ignoreWatch();
|
|
293
|
+
}
|
|
294
|
+
return staged > 0;
|
|
277
295
|
};
|
|
278
296
|
|
|
279
297
|
const saveFileCompose = (api) => {
|
|
280
298
|
const rel = api.nav.reviewPath;
|
|
281
|
-
const
|
|
299
|
+
const opened = api.state.fileOpened;
|
|
300
|
+
const text = api.state.editor ? api.state.editor.text : '';
|
|
282
301
|
if (api.state.editor) {
|
|
283
302
|
api.nav.unitLine = api.state.editor.linePos().line;
|
|
284
303
|
}
|
|
285
|
-
const result = saveUnitFile(api,
|
|
304
|
+
const result = saveUnitFile(api, text);
|
|
286
305
|
if (result === false) return { kind: 'error' };
|
|
306
|
+
const changed = text !== opened;
|
|
287
307
|
const reload = api.state.fileDirty === true;
|
|
308
|
+
if (changed) stageEditedFile(api, rel);
|
|
288
309
|
api.ui.flushReview();
|
|
289
310
|
closeCompose(api);
|
|
311
|
+
const doneStatus = changed ? 'staged' : 'saved';
|
|
290
312
|
if (!reload || typeof api.ui.reloadAfterChange !== 'function') {
|
|
291
|
-
api.ui.status =
|
|
313
|
+
if (!api.ui.status) api.ui.status = doneStatus;
|
|
292
314
|
return { kind: 'saved' };
|
|
293
315
|
}
|
|
294
316
|
api.ui.reloadAfterChange(() => {
|
|
295
|
-
|
|
296
|
-
api.ui.status = n > 0 ? 'staged' : 'saved';
|
|
317
|
+
if (!api.ui.status) api.ui.status = doneStatus;
|
|
297
318
|
});
|
|
298
319
|
return { kind: 'saved' };
|
|
299
320
|
};
|
|
@@ -321,6 +342,13 @@ const saveCompose = (api, extra) => {
|
|
|
321
342
|
if (spec && spec.command) return { kind: spec.command };
|
|
322
343
|
const kind = api.state.composeKind;
|
|
323
344
|
if (kind === 'file') return saveFileCompose(api);
|
|
345
|
+
const item = api.ui.current ? api.ui.current() : null;
|
|
346
|
+
const shouldStage =
|
|
347
|
+
kind === 'code' &&
|
|
348
|
+
writesCodeFile(api, item) &&
|
|
349
|
+
item &&
|
|
350
|
+
item.origin !== 'staged';
|
|
351
|
+
const rel = itemPath(item);
|
|
324
352
|
const baseline = api.state.composeBaseline;
|
|
325
353
|
const result = commitCompose(api);
|
|
326
354
|
if (result === false) return { kind: 'error' };
|
|
@@ -331,12 +359,17 @@ const saveCompose = (api, extra) => {
|
|
|
331
359
|
const next = editor ? editor.text : '';
|
|
332
360
|
if (notes) rememberTemplate(notes, baseline, next);
|
|
333
361
|
}
|
|
362
|
+
if (shouldStage && reload) stageEditedFile(api, rel);
|
|
334
363
|
api.ui.flushReview();
|
|
335
364
|
closeCompose(api);
|
|
365
|
+
const doneStatus = shouldStage && reload ? 'staged' : 'saved';
|
|
336
366
|
if (reload && typeof api.ui.reloadAfterChange === 'function') {
|
|
337
|
-
api.ui.reloadAfterChange()
|
|
367
|
+
api.ui.reloadAfterChange(() => {
|
|
368
|
+
if (!api.ui.status) api.ui.status = doneStatus;
|
|
369
|
+
});
|
|
370
|
+
} else {
|
|
371
|
+
api.ui.status = doneStatus;
|
|
338
372
|
}
|
|
339
|
-
api.ui.status = 'saved';
|
|
340
373
|
if (opts.editNext === true) api.todos.editNextTodo();
|
|
341
374
|
return { kind: 'saved' };
|
|
342
375
|
};
|
|
@@ -369,7 +402,7 @@ const composeView = (api) => {
|
|
|
369
402
|
const kind = state.composeKind;
|
|
370
403
|
const text = state.editor.text;
|
|
371
404
|
const cursor = state.editor.cursor;
|
|
372
|
-
return { kind, text, cursor };
|
|
405
|
+
return { kind, text, cursor, commitKind: state.commitKind };
|
|
373
406
|
};
|
|
374
407
|
|
|
375
408
|
const liveCodeText = (api) => {
|
|
@@ -453,6 +486,22 @@ const moveNoteLine = (api, delta) => {
|
|
|
453
486
|
api.state.editor.moveLine(delta, width);
|
|
454
487
|
};
|
|
455
488
|
|
|
489
|
+
const fullCommitEdit = (api) =>
|
|
490
|
+
api.state.composeKind === 'commit' && api.ui.commits.commitView === 'full';
|
|
491
|
+
|
|
492
|
+
const editedCommitEntry = (api) => {
|
|
493
|
+
const kind = api.state.commitKind;
|
|
494
|
+
if (kind !== 'amend' && kind !== 'reword') return null;
|
|
495
|
+
const list = listedCommits(api.ui.commits.commits);
|
|
496
|
+
return list[api.nav.commitCursor] ?? null;
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
const moveCommitLine = (api, delta) => {
|
|
500
|
+
const size = viewSize(api);
|
|
501
|
+
const width = commitMessageWidth(editedCommitEntry(api), size.width);
|
|
502
|
+
api.state.editor.moveLine(delta, width);
|
|
503
|
+
};
|
|
504
|
+
|
|
456
505
|
const moveComposeLine = (api, delta) => {
|
|
457
506
|
const spec = KIND[api.state.composeKind];
|
|
458
507
|
const move = spec ? spec.move : 'note';
|
|
@@ -466,6 +515,7 @@ const moveComposeLine = (api, delta) => {
|
|
|
466
515
|
const inner = codeInnerWidth(size.width, api.ui.layout);
|
|
467
516
|
return void api.state.editor.moveLine(delta, inner);
|
|
468
517
|
}
|
|
518
|
+
if (fullCommitEdit(api)) return void moveCommitLine(api, delta);
|
|
469
519
|
moveNoteLine(api, delta);
|
|
470
520
|
};
|
|
471
521
|
|
|
@@ -554,17 +604,31 @@ const onCode = (api) => {
|
|
|
554
604
|
openCompose(api, 'code', text);
|
|
555
605
|
};
|
|
556
606
|
|
|
607
|
+
const insertNewline = (api) => {
|
|
608
|
+
api.state.editor.insert('\n');
|
|
609
|
+
resetBlink(api);
|
|
610
|
+
followFileEdit(api);
|
|
611
|
+
return null;
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
const onEmptyLastLine = (editor) => {
|
|
615
|
+
if (!editor) return false;
|
|
616
|
+
const text = editor.text;
|
|
617
|
+
const lastBreak = text.lastIndexOf('\n');
|
|
618
|
+
if (editor.cursor < lastBreak + 1) return false;
|
|
619
|
+
return text.slice(lastBreak + 1).trim() === '';
|
|
620
|
+
};
|
|
621
|
+
|
|
557
622
|
const enterCompose = (api) => {
|
|
558
623
|
const { state } = api;
|
|
559
624
|
if (state.templateFocus && state.composeKind === 'feedback') {
|
|
560
625
|
return api.templates.selectTemplate(state.templateIndex);
|
|
561
626
|
}
|
|
562
627
|
const spec = KIND[state.composeKind];
|
|
563
|
-
if (spec && spec.enter === 'newline')
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
return null;
|
|
628
|
+
if (spec && spec.enter === 'newline') return insertNewline(api);
|
|
629
|
+
if (fullCommitEdit(api)) {
|
|
630
|
+
if (onEmptyLastLine(state.editor)) return saveCompose(api);
|
|
631
|
+
return insertNewline(api);
|
|
568
632
|
}
|
|
569
633
|
if (spec && spec.enter === 'edit-next') {
|
|
570
634
|
return saveCompose(api, { editNext: true });
|
package/lib/session/dispatch.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const compose = require('./compose.js');
|
|
4
|
+
const { listedCommits } = require('./commit.js');
|
|
4
5
|
const { COMPOSE_LEAVE } = compose;
|
|
5
6
|
const actions = require('./actions.js');
|
|
6
7
|
const { CONFIRM_MODE, disabledActions, actionFromKey, applyConfirm } = actions;
|
|
@@ -16,7 +17,7 @@ const currentTarget = (ui) => {
|
|
|
16
17
|
return ui.branches[ui.branchCursor];
|
|
17
18
|
}
|
|
18
19
|
if (ui.pane === 'commits') {
|
|
19
|
-
const entry = ui.commits.commits[ui.commitCursor];
|
|
20
|
+
const entry = listedCommits(ui.commits.commits)[ui.commitCursor];
|
|
20
21
|
const staged = targetHasStaged(ui);
|
|
21
22
|
if (!entry) return { canCommit: staged };
|
|
22
23
|
return { ...entry, canCommit: staged };
|
|
@@ -32,6 +33,7 @@ const createDispatcher = (ui) => {
|
|
|
32
33
|
next: ui.filesPane.onNext,
|
|
33
34
|
prev: ui.filesPane.onPrev,
|
|
34
35
|
layout: ui.filesPane.onLayout,
|
|
36
|
+
theme: ui.filesPane.onTheme,
|
|
35
37
|
files: ui.filesPane.onFiles,
|
|
36
38
|
feedback: ui.composer.onFeedback,
|
|
37
39
|
todo: ui.composer.onTodo,
|
|
@@ -39,7 +41,10 @@ const createDispatcher = (ui) => {
|
|
|
39
41
|
reload: ui.lifecycle.onReload,
|
|
40
42
|
commit: ui.commits.onCommit,
|
|
41
43
|
amend: ui.commits.onAmend,
|
|
44
|
+
reword: ui.commits.onReword,
|
|
45
|
+
apply: ui.commits.onApply,
|
|
42
46
|
fixup: ui.commits.onFixup,
|
|
47
|
+
view: () => ui.commits.onView(),
|
|
43
48
|
branch: ui.gitBranches.onBranch,
|
|
44
49
|
pull: ui.gitBranches.onPull,
|
|
45
50
|
push: ui.gitBranches.onPush,
|
|
@@ -54,7 +59,16 @@ const createDispatcher = (ui) => {
|
|
|
54
59
|
open: ui.filesPane.onOpenFile,
|
|
55
60
|
file: ui.filesPane.onFileScope,
|
|
56
61
|
diff: ui.filesPane.onDiffScope,
|
|
57
|
-
|
|
62
|
+
npm: ui.npm.open,
|
|
63
|
+
npmEdit: ui.npm.edit,
|
|
64
|
+
npmNew: ui.npm.create,
|
|
65
|
+
npmStop: () => ui.npm.closeView(),
|
|
66
|
+
npmRerun: () => ui.npm.rerun(),
|
|
67
|
+
check: () => ui.composer.toggleFocusedTodo(),
|
|
68
|
+
removeTodo: () => {
|
|
69
|
+
if (ui.pane === 'npm') return void ui.npm.askDrop();
|
|
70
|
+
ui.composer.removeFocusedTodo();
|
|
71
|
+
},
|
|
58
72
|
quit: () => ui.onQuit(),
|
|
59
73
|
scrollUp: () => ui.filesPane.onScroll(-1),
|
|
60
74
|
scrollDown: () => ui.filesPane.onScroll(1),
|
|
@@ -86,6 +100,10 @@ const createDispatcher = (ui) => {
|
|
|
86
100
|
if (action === 'next') return void ui.commits.onCommitMove(1);
|
|
87
101
|
if (action === 'prev') return void ui.commits.onCommitMove(-1);
|
|
88
102
|
}
|
|
103
|
+
if (pane === 'npm') {
|
|
104
|
+
if (action === 'next') return void ui.npm.move(1);
|
|
105
|
+
if (action === 'prev') return void ui.npm.move(-1);
|
|
106
|
+
}
|
|
89
107
|
const hidden = disabledActions(pane, currentTarget(ui));
|
|
90
108
|
if (hidden.includes(action)) return;
|
|
91
109
|
const handler = handlers[action];
|
|
@@ -109,12 +127,14 @@ const createInput = (ui) => {
|
|
|
109
127
|
},
|
|
110
128
|
confirmDrop: {
|
|
111
129
|
confirm: () => {
|
|
130
|
+
if (ui.npm.dropName) return void ui.npm.confirmDrop();
|
|
112
131
|
if (ui.commits.dropName) {
|
|
113
132
|
return void ui.commits.confirmDropCommit();
|
|
114
133
|
}
|
|
115
134
|
ui.gitBranches.confirmDropBranch();
|
|
116
135
|
},
|
|
117
136
|
cancel: () => {
|
|
137
|
+
if (ui.npm.dropName) return void ui.npm.cancelDrop();
|
|
118
138
|
if (ui.commits.dropName) {
|
|
119
139
|
return void ui.commits.cancelDropCommit();
|
|
120
140
|
}
|
|
@@ -141,7 +161,21 @@ const createInput = (ui) => {
|
|
|
141
161
|
const command = ui.composer.handleKey(key);
|
|
142
162
|
return void ui.runComposeCommand(command);
|
|
143
163
|
}
|
|
164
|
+
if (ui.pane === 'npm' && ui.npm.viewing) {
|
|
165
|
+
if (key === 'escape') return void ui.npm.closeView();
|
|
166
|
+
if (key === 'r') return void ui.npm.rerun();
|
|
167
|
+
if (key === 'e' || key === 'n') return;
|
|
168
|
+
}
|
|
169
|
+
if (ui.pane === 'npm' && key === 'ctrl-up') return void ui.npm.reorder(-1);
|
|
170
|
+
if (ui.pane === 'npm' && key === 'ctrl-down') {
|
|
171
|
+
return void ui.npm.reorder(1);
|
|
172
|
+
}
|
|
173
|
+
if (ui.pane === 'npm' && (key === 'up' || key === 'down')) {
|
|
174
|
+
return void ui.npm.move(key === 'up' ? -1 : 1);
|
|
175
|
+
}
|
|
144
176
|
if (key === 'escape') return void ui.filesPane.onEscape();
|
|
177
|
+
const toggle = key === 'x' || key === ' ';
|
|
178
|
+
if (toggle && ui.composer.toggleFocusedTodo()) return;
|
|
145
179
|
if (ui.composer.typeIntoTodo(key)) return;
|
|
146
180
|
const action = actionFromKey(key, ui.pane, ui.fileScope);
|
|
147
181
|
if (action) ui.dispatch(action);
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const ansi = require('../ansi.js');
|
|
3
4
|
const files = require('../files.js');
|
|
5
|
+
const { THEME_NAMES, setTheme, themeName } = ansi;
|
|
4
6
|
const { isTodosEntry, TODO_FILE } = files;
|
|
7
|
+
const { logViewRows } = require('../render/npm.js');
|
|
5
8
|
const unit = require('./unit.js');
|
|
6
9
|
const { setFileScope, openUnitFile, moveUnitBlock } = unit;
|
|
7
10
|
|
|
@@ -24,14 +27,25 @@ const syncFileCursor = (ui) => {
|
|
|
24
27
|
};
|
|
25
28
|
|
|
26
29
|
const showFiles = (ui) => {
|
|
27
|
-
|
|
28
|
-
|
|
30
|
+
const nav = ui.nav;
|
|
31
|
+
const held =
|
|
32
|
+
nav.todoOpen === true ||
|
|
33
|
+
nav.pane === 'branches' ||
|
|
34
|
+
nav.pane === 'commits' ||
|
|
35
|
+
nav.pane === 'npm';
|
|
36
|
+
nav.pane = 'files';
|
|
37
|
+
nav.clearSelection();
|
|
29
38
|
followReviewPath(ui);
|
|
30
|
-
|
|
39
|
+
nav.todoOpen = false;
|
|
40
|
+
if (held) ui.lifecycle.catchUpWatch();
|
|
31
41
|
};
|
|
32
42
|
|
|
33
43
|
const onEscape = (ui) => {
|
|
34
|
-
if (
|
|
44
|
+
if (
|
|
45
|
+
ui.nav.pane === 'branches' ||
|
|
46
|
+
ui.nav.pane === 'commits' ||
|
|
47
|
+
ui.nav.pane === 'npm'
|
|
48
|
+
) {
|
|
35
49
|
return void showFiles(ui);
|
|
36
50
|
}
|
|
37
51
|
if (ui.nav.pane === 'files') return void ui.onQuit();
|
|
@@ -59,7 +73,8 @@ const onOpenFile = (ui) => {
|
|
|
59
73
|
if (ui.nav.pane === 'branches') {
|
|
60
74
|
return void ui.gitBranches.onCheckoutBranch();
|
|
61
75
|
}
|
|
62
|
-
if (ui.nav.pane === 'commits') return;
|
|
76
|
+
if (ui.nav.pane === 'commits') return void ui.commits.onShowDiff();
|
|
77
|
+
if (ui.nav.pane === 'npm') return void ui.npm.run();
|
|
63
78
|
if (ui.nav.pane !== 'files') {
|
|
64
79
|
if (ui.nav.todoOpen) ui.composer.editFocusedTodo();
|
|
65
80
|
return;
|
|
@@ -146,7 +161,9 @@ const scrollStep = (ui, fraction) => {
|
|
|
146
161
|
const rows = size.height ?? 24;
|
|
147
162
|
const fallback = Math.max(1, rows - 4);
|
|
148
163
|
const bodyH = frame?.bodyH ?? fallback;
|
|
149
|
-
const
|
|
164
|
+
const viewing = ui.nav.pane === 'npm' && ui.npm && ui.npm.viewing;
|
|
165
|
+
const viewRows = viewing ? logViewRows(bodyH) : bodyH;
|
|
166
|
+
const height = Math.max(1, viewRows);
|
|
150
167
|
const span = Math.floor(height * Math.abs(fraction));
|
|
151
168
|
const n = Math.max(1, span);
|
|
152
169
|
if (fraction < 0) return -n;
|
|
@@ -161,6 +178,7 @@ const onScroll = (ui, delta) => {
|
|
|
161
178
|
if (ui.nav.pane === 'commits') {
|
|
162
179
|
return void ui.commits.onCommitMove(delta);
|
|
163
180
|
}
|
|
181
|
+
if (ui.nav.pane === 'npm') return void ui.npm.move(delta);
|
|
164
182
|
if (ui.nav.todoOpen && ui.composer.todoRowCount() > 1) {
|
|
165
183
|
return void ui.composer.moveTodoFocus(delta);
|
|
166
184
|
}
|
|
@@ -192,6 +210,10 @@ const onJump = (ui, toEnd) => {
|
|
|
192
210
|
const delta = toEnd ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER;
|
|
193
211
|
return void ui.commits.onCommitMove(delta);
|
|
194
212
|
}
|
|
213
|
+
if (ui.nav.pane === 'npm') {
|
|
214
|
+
const delta = toEnd ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER;
|
|
215
|
+
return void ui.npm.move(delta);
|
|
216
|
+
}
|
|
195
217
|
if (ui.nav.todoOpen && ui.composer.todoRowCount() > 0) {
|
|
196
218
|
const last = ui.composer.todoRowCount() - 1;
|
|
197
219
|
const target = toEnd ? last : 0;
|
|
@@ -214,6 +236,13 @@ const onLayout = (ui) => {
|
|
|
214
236
|
ui.nav.clearSelection();
|
|
215
237
|
};
|
|
216
238
|
|
|
239
|
+
const onTheme = (ui) => {
|
|
240
|
+
const i = THEME_NAMES.indexOf(themeName());
|
|
241
|
+
const next = THEME_NAMES[(i + 1) % THEME_NAMES.length];
|
|
242
|
+
setTheme(next);
|
|
243
|
+
ui.status = next;
|
|
244
|
+
};
|
|
245
|
+
|
|
217
246
|
const createFilesPane = (ui) => ({
|
|
218
247
|
clampIndex: () => clampIndex(ui),
|
|
219
248
|
syncReviewPath: () => syncReviewPath(ui),
|
|
@@ -230,6 +259,7 @@ const createFilesPane = (ui) => ({
|
|
|
230
259
|
onHome: () => onJump(ui, false),
|
|
231
260
|
onEnd: () => onJump(ui, true),
|
|
232
261
|
onLayout: () => onLayout(ui),
|
|
262
|
+
onTheme: () => onTheme(ui),
|
|
233
263
|
onFileScope: () => setFileScope(ui, 'file'),
|
|
234
264
|
onDiffScope: () => setFileScope(ui, 'diff'),
|
|
235
265
|
});
|
package/lib/session/lifecycle.js
CHANGED
|
@@ -6,10 +6,11 @@ const path = require('node:path');
|
|
|
6
6
|
const { isAbortError } = require('./load.js');
|
|
7
7
|
const { createDiskWatcher, POLL_MS, DEBOUNCE_MS } = require('./watch.js');
|
|
8
8
|
const files = require('../files.js');
|
|
9
|
-
const { itemPath, relativeAge } = files;
|
|
10
|
-
const { MANIFEST, LOCKFILE } = require('../deps.js');
|
|
9
|
+
const { itemPath, relativeAge, TODO_FILE } = files;
|
|
10
|
+
const { MANIFEST, LOCKFILE, depFileMeta } = require('../deps.js');
|
|
11
11
|
const review = require('../review.js');
|
|
12
|
-
const { hasNotes } = review;
|
|
12
|
+
const { hasNotes, loadReview } = review;
|
|
13
|
+
const { mergeReview } = require('../review-merge.js');
|
|
13
14
|
|
|
14
15
|
const loadOptions = (api, extra = {}) => {
|
|
15
16
|
const { loadConfig, ui } = api;
|
|
@@ -196,11 +197,32 @@ const fileStamp = (top, rel) => {
|
|
|
196
197
|
}
|
|
197
198
|
};
|
|
198
199
|
|
|
200
|
+
const npmRels = (items) => {
|
|
201
|
+
const rels = [MANIFEST, LOCKFILE];
|
|
202
|
+
for (const item of items) {
|
|
203
|
+
const rel = itemPath(item);
|
|
204
|
+
if (!depFileMeta(rel)) continue;
|
|
205
|
+
if (rels.includes(rel)) continue;
|
|
206
|
+
rels.push(rel);
|
|
207
|
+
}
|
|
208
|
+
rels.sort();
|
|
209
|
+
return rels;
|
|
210
|
+
};
|
|
211
|
+
|
|
199
212
|
const npmStamp = (api) => {
|
|
200
213
|
const top = api.ui.top || api.loadConfig.cwd;
|
|
201
|
-
const
|
|
202
|
-
const
|
|
203
|
-
|
|
214
|
+
const rels = npmRels(api.collection.items);
|
|
215
|
+
const parts = new Array(rels.length);
|
|
216
|
+
for (let i = 0; i < rels.length; i++) {
|
|
217
|
+
parts[i] = `${rels[i]}:${fileStamp(top, rels[i])}`;
|
|
218
|
+
}
|
|
219
|
+
return parts.join('|');
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const npmExtrasStale = (api) => {
|
|
223
|
+
const cached = api.npmExtras;
|
|
224
|
+
if (!cached || !api.loadConfig.audit) return false;
|
|
225
|
+
return cached.stamp !== npmStamp(api);
|
|
204
226
|
};
|
|
205
227
|
|
|
206
228
|
const cacheNpmExtras = (api, loaded) => {
|
|
@@ -251,7 +273,6 @@ const followExtras = async (api, loaded, gen, kind) => {
|
|
|
251
273
|
const npmReuseOpts = (api, extra = {}) => {
|
|
252
274
|
const opts = {};
|
|
253
275
|
if (extra.signal) opts.signal = extra.signal;
|
|
254
|
-
if (extra.keepView !== true) return opts;
|
|
255
276
|
const cached = api.npmExtras;
|
|
256
277
|
if (!cached) return opts;
|
|
257
278
|
if (cached.stamp !== npmStamp(api)) return opts;
|
|
@@ -314,7 +335,7 @@ const runSnapshot = async (api, gen, kind, extra = {}) => {
|
|
|
314
335
|
}
|
|
315
336
|
applyLoaded(api, api.loader.tag(loaded, gen));
|
|
316
337
|
afterSnapshot(api, kind, extra);
|
|
317
|
-
if (loaded.pending) {
|
|
338
|
+
if (loaded.pending === true || npmExtrasStale(api)) {
|
|
318
339
|
api.ops.setBusy('checking npm');
|
|
319
340
|
if (kind === 'open' || extra.quiet === true) api.ui.paint();
|
|
320
341
|
await followExtras(api, loaded, gen, kind);
|
|
@@ -360,7 +381,7 @@ const refreshFromRepo = (api, extra = {}) => {
|
|
|
360
381
|
);
|
|
361
382
|
applyLoaded(api, api.loader.tag(loaded, gen));
|
|
362
383
|
afterSnapshot(api, kind, extra);
|
|
363
|
-
if (loaded.pending) {
|
|
384
|
+
if (loaded.pending === true || npmExtrasStale(api)) {
|
|
364
385
|
api.ops.setBusy('checking npm');
|
|
365
386
|
return void followExtras(api, loaded, gen, kind);
|
|
366
387
|
}
|
|
@@ -436,12 +457,14 @@ const onDiskChange = (api) => {
|
|
|
436
457
|
refreshFromRepo(api, { keepView: true, quiet: true });
|
|
437
458
|
};
|
|
438
459
|
|
|
439
|
-
const
|
|
440
|
-
|
|
441
|
-
if (
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
460
|
+
const catchUpWatch = (api) => {
|
|
461
|
+
clearWatchRetry(api);
|
|
462
|
+
if (shouldDeferWatch(api)) return void onDiskChange(api);
|
|
463
|
+
refreshFromRepo(api, { keepView: true, quiet: true });
|
|
464
|
+
};
|
|
465
|
+
|
|
466
|
+
const stampFile = (root, rel) => {
|
|
467
|
+
const full = path.join(root, rel);
|
|
445
468
|
let stat;
|
|
446
469
|
try {
|
|
447
470
|
stat = fs.statSync(full);
|
|
@@ -451,6 +474,33 @@ const currentFileStamp = (api) => {
|
|
|
451
474
|
return `${rel}:${stat.mtimeMs}:${stat.size}`;
|
|
452
475
|
};
|
|
453
476
|
|
|
477
|
+
const worktreeStamp = (api) => {
|
|
478
|
+
const root = api.ui.top || api.loadConfig.cwd;
|
|
479
|
+
const parts = [];
|
|
480
|
+
for (const rel of itemRels(api.collection)) {
|
|
481
|
+
if (!rel || rel === TODO_FILE) continue;
|
|
482
|
+
parts.push(stampFile(root, rel));
|
|
483
|
+
}
|
|
484
|
+
return parts.join('\n');
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
const watchesWorktree = (nav) =>
|
|
488
|
+
nav.todoOpen === true ||
|
|
489
|
+
nav.pane === 'branches' ||
|
|
490
|
+
nav.pane === 'commits' ||
|
|
491
|
+
nav.pane === 'npm';
|
|
492
|
+
|
|
493
|
+
const currentFileStamp = (api) => {
|
|
494
|
+
const { nav } = api;
|
|
495
|
+
const root = api.ui.top || api.loadConfig.cwd;
|
|
496
|
+
if (watchesWorktree(nav)) return worktreeStamp(api);
|
|
497
|
+
const pane = nav.pane;
|
|
498
|
+
if (pane !== 'diff' && pane !== 'unit') return '';
|
|
499
|
+
const rel = pane === 'unit' ? nav.reviewPath : itemPath(api.ui.current());
|
|
500
|
+
if (!rel) return '';
|
|
501
|
+
return stampFile(root, rel);
|
|
502
|
+
};
|
|
503
|
+
|
|
454
504
|
const pollCurrentFile = (api) => {
|
|
455
505
|
const stamp = currentFileStamp(api);
|
|
456
506
|
if (!stamp) {
|
|
@@ -466,9 +516,46 @@ const pollCurrentFile = (api) => {
|
|
|
466
516
|
onDiskChange(api);
|
|
467
517
|
};
|
|
468
518
|
|
|
519
|
+
const reviewFileStamp = (api) => {
|
|
520
|
+
const store = api.review && api.review.store;
|
|
521
|
+
if (!store || !store.reviewPath) return '';
|
|
522
|
+
try {
|
|
523
|
+
const stat = fs.statSync(store.reviewPath);
|
|
524
|
+
return `${stat.mtimeMs}:${stat.size}`;
|
|
525
|
+
} catch {
|
|
526
|
+
return 'missing';
|
|
527
|
+
}
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const noteReviewStamp = (api) => {
|
|
531
|
+
api.reviewStamp = reviewFileStamp(api);
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
const applyReviewChange = (api) => {
|
|
535
|
+
if (!api.ui || api.ui.done) return;
|
|
536
|
+
const stamp = reviewFileStamp(api);
|
|
537
|
+
if (!stamp || stamp === api.reviewStamp) return;
|
|
538
|
+
if (stamp === 'missing') {
|
|
539
|
+
api.reviewStamp = stamp;
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const store = api.review && api.review.store;
|
|
543
|
+
if (!store) return;
|
|
544
|
+
let disk;
|
|
545
|
+
try {
|
|
546
|
+
disk = loadReview(store.reviewPath, store.templates);
|
|
547
|
+
} catch {
|
|
548
|
+
return;
|
|
549
|
+
}
|
|
550
|
+
mergeReview(store, disk);
|
|
551
|
+
api.reviewStamp = stamp;
|
|
552
|
+
if (typeof api.ui.paint === 'function') api.ui.paint();
|
|
553
|
+
};
|
|
554
|
+
|
|
469
555
|
const stopWatch = (api) => {
|
|
470
556
|
clearWatchRetry(api);
|
|
471
557
|
api.watchStamp = '';
|
|
558
|
+
api.reviewStamp = '';
|
|
472
559
|
if (api.ui.term) api.ui.term.stopTimer('watch');
|
|
473
560
|
if (!api.diskWatcher) return;
|
|
474
561
|
api.diskWatcher.close();
|
|
@@ -484,16 +571,26 @@ const startWatch = (api) => {
|
|
|
484
571
|
root,
|
|
485
572
|
paths: api.loadConfig.paths,
|
|
486
573
|
onChange: () => onDiskChange(api),
|
|
574
|
+
onReview: () => applyReviewChange(api),
|
|
487
575
|
});
|
|
488
576
|
syncItemWatches(api);
|
|
577
|
+
noteReviewStamp(api);
|
|
489
578
|
if (!api.ui.term) return;
|
|
490
|
-
api.ui.term.startTimer(
|
|
579
|
+
api.ui.term.startTimer(
|
|
580
|
+
'watch',
|
|
581
|
+
() => {
|
|
582
|
+
pollCurrentFile(api);
|
|
583
|
+
applyReviewChange(api);
|
|
584
|
+
},
|
|
585
|
+
POLL_MS,
|
|
586
|
+
);
|
|
491
587
|
};
|
|
492
588
|
|
|
493
589
|
const createLifecycle = (api) => {
|
|
494
590
|
api.diskWatcher = null;
|
|
495
591
|
api.watchRetry = null;
|
|
496
592
|
api.watchStamp = '';
|
|
593
|
+
api.reviewStamp = '';
|
|
497
594
|
api.ignoreWatchUntil = 0;
|
|
498
595
|
api.npmExtras = null;
|
|
499
596
|
return {
|
|
@@ -505,6 +602,9 @@ const createLifecycle = (api) => {
|
|
|
505
602
|
reloadAfterChange: (afterLoad) => reloadAfterChange(api, afterLoad),
|
|
506
603
|
onReload: () => onReload(api),
|
|
507
604
|
onDiskChange: () => onDiskChange(api),
|
|
605
|
+
applyReviewChange: () => applyReviewChange(api),
|
|
606
|
+
noteReviewStamp: () => noteReviewStamp(api),
|
|
607
|
+
catchUpWatch: () => catchUpWatch(api),
|
|
508
608
|
pollCurrentFile: () => pollCurrentFile(api),
|
|
509
609
|
startWatch: () => startWatch(api),
|
|
510
610
|
stopWatch: () => stopWatch(api),
|