reslop 0.2.0 → 0.2.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.
- package/CHANGELOG.md +14 -1
- package/README.md +2 -1
- package/lib/ansi.js +144 -3
- package/lib/api.js +2 -0
- package/lib/capabilities.js +6 -0
- package/lib/cli.js +10 -2
- package/lib/git-branches.js +185 -6
- package/lib/git-reword-todo.js +20 -0
- package/lib/git.js +20 -3
- package/lib/render/branches.js +4 -32
- package/lib/render/chrome.js +47 -5
- package/lib/render/commits.js +63 -1
- package/lib/render/diff.js +4 -4
- package/lib/render/frame.js +13 -1
- package/lib/render/notes.js +22 -5
- package/lib/render/primitives.js +54 -11
- package/lib/render/render.js +3 -3
- package/lib/review-merge.js +270 -0
- package/lib/review.js +38 -6
- package/lib/session/actions.js +30 -3
- package/lib/session/commit.js +101 -8
- package/lib/session/compose.js +48 -27
- package/lib/session/dispatch.js +6 -0
- package/lib/session/files-pane.js +17 -3
- package/lib/session/lifecycle.js +113 -16
- package/lib/session/pointer.js +4 -1
- package/lib/session/todos.js +29 -1
- package/lib/session/watch.js +120 -8
- package/lib/session.js +1 -0
- package/package.json +2 -2
package/lib/session/compose.js
CHANGED
|
@@ -9,7 +9,7 @@ const files = require('../files.js');
|
|
|
9
9
|
const { itemPath, isTodoItem, listPath, isReadOnlyOrigin } = files;
|
|
10
10
|
const { blockAddText } = require('../diff/diff.js');
|
|
11
11
|
const items = require('./items.js');
|
|
12
|
-
const { itemFeedKey, canEditCode
|
|
12
|
+
const { itemFeedKey, canEditCode } = items;
|
|
13
13
|
const review = require('../review.js');
|
|
14
14
|
const { setFeedback, setCode, checkLabel, rememberTemplate } = review;
|
|
15
15
|
const { createTemplatePick } = require('./templates.js');
|
|
@@ -81,6 +81,7 @@ const initialState = () => ({
|
|
|
81
81
|
blinkOn: true,
|
|
82
82
|
fileDirty: false,
|
|
83
83
|
fileEditRows: null,
|
|
84
|
+
fileOpened: '',
|
|
84
85
|
});
|
|
85
86
|
|
|
86
87
|
const reset = (state) => {
|
|
@@ -124,6 +125,7 @@ const openCompose = (api, kind, text, todoId) => {
|
|
|
124
125
|
state.composeKind = kind;
|
|
125
126
|
state.composeTodoId = todoId ?? null;
|
|
126
127
|
state.composeBaseline = text;
|
|
128
|
+
state.fileOpened = kind === 'file' ? text : '';
|
|
127
129
|
state.editor = new Editor(text);
|
|
128
130
|
state.templateIndex = -1;
|
|
129
131
|
state.templateFocus = false;
|
|
@@ -140,6 +142,7 @@ const closeCompose = (api) => {
|
|
|
140
142
|
state.commitKind = null;
|
|
141
143
|
state.composeTodoId = null;
|
|
142
144
|
state.composeBaseline = '';
|
|
145
|
+
state.fileOpened = '';
|
|
143
146
|
state.templateIndex = -1;
|
|
144
147
|
state.templateFocus = false;
|
|
145
148
|
state.fileEditRows = null;
|
|
@@ -240,60 +243,66 @@ const saveUnitFile = (api, text) => {
|
|
|
240
243
|
return true;
|
|
241
244
|
};
|
|
242
245
|
|
|
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) => {
|
|
246
|
+
const stageEditedFile = (api, rel) => {
|
|
247
|
+
if (!rel) return false;
|
|
253
248
|
const repo = api.ui.repo;
|
|
254
|
-
if (!
|
|
249
|
+
if (!repo) return false;
|
|
255
250
|
const caps = api.ui.capabilities;
|
|
256
|
-
if (!caps || caps.changes !== true) return
|
|
257
|
-
|
|
251
|
+
if (!caps || caps.changes !== true) return false;
|
|
252
|
+
if (typeof repo.stagePath === 'function') {
|
|
253
|
+
try {
|
|
254
|
+
repo.stagePath(api.ui.top, rel);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
api.ui.status = error.message;
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
if (typeof api.ui.ignoreWatch === 'function') api.ui.ignoreWatch();
|
|
260
|
+
return true;
|
|
261
|
+
}
|
|
262
|
+
if (typeof repo.add !== 'function') return false;
|
|
258
263
|
let staged = 0;
|
|
259
|
-
for (const item of
|
|
264
|
+
for (const item of api.ui.items ?? []) {
|
|
260
265
|
if (listPath(item) !== rel) continue;
|
|
261
266
|
if (isReadOnlyOrigin(item.origin)) continue;
|
|
262
267
|
if (item.origin === 'staged') continue;
|
|
263
|
-
if (beforeKeys.has(blockLinesKey(item))) continue;
|
|
264
268
|
try {
|
|
265
269
|
repo.add(api.ui.top, item);
|
|
266
270
|
} catch (error) {
|
|
267
271
|
api.ui.status = error.message;
|
|
268
|
-
return staged;
|
|
272
|
+
return staged > 0;
|
|
269
273
|
}
|
|
270
274
|
if (api.ui.collection && api.ui.collection.keepOrigin) {
|
|
271
275
|
api.ui.collection.keepOrigin(item, 'staged');
|
|
272
276
|
}
|
|
273
|
-
if (typeof api.ui.ignoreWatch === 'function') api.ui.ignoreWatch();
|
|
274
277
|
staged += 1;
|
|
275
278
|
}
|
|
276
|
-
|
|
279
|
+
if (staged && typeof api.ui.ignoreWatch === 'function') {
|
|
280
|
+
api.ui.ignoreWatch();
|
|
281
|
+
}
|
|
282
|
+
return staged > 0;
|
|
277
283
|
};
|
|
278
284
|
|
|
279
285
|
const saveFileCompose = (api) => {
|
|
280
286
|
const rel = api.nav.reviewPath;
|
|
281
|
-
const
|
|
287
|
+
const opened = api.state.fileOpened;
|
|
288
|
+
const text = api.state.editor ? api.state.editor.text : '';
|
|
282
289
|
if (api.state.editor) {
|
|
283
290
|
api.nav.unitLine = api.state.editor.linePos().line;
|
|
284
291
|
}
|
|
285
|
-
const result = saveUnitFile(api,
|
|
292
|
+
const result = saveUnitFile(api, text);
|
|
286
293
|
if (result === false) return { kind: 'error' };
|
|
294
|
+
const changed = text !== opened;
|
|
287
295
|
const reload = api.state.fileDirty === true;
|
|
296
|
+
if (changed) stageEditedFile(api, rel);
|
|
288
297
|
api.ui.flushReview();
|
|
289
298
|
closeCompose(api);
|
|
299
|
+
const doneStatus = changed ? 'staged' : 'saved';
|
|
290
300
|
if (!reload || typeof api.ui.reloadAfterChange !== 'function') {
|
|
291
|
-
api.ui.status =
|
|
301
|
+
if (!api.ui.status) api.ui.status = doneStatus;
|
|
292
302
|
return { kind: 'saved' };
|
|
293
303
|
}
|
|
294
304
|
api.ui.reloadAfterChange(() => {
|
|
295
|
-
|
|
296
|
-
api.ui.status = n > 0 ? 'staged' : 'saved';
|
|
305
|
+
if (!api.ui.status) api.ui.status = doneStatus;
|
|
297
306
|
});
|
|
298
307
|
return { kind: 'saved' };
|
|
299
308
|
};
|
|
@@ -321,6 +330,13 @@ const saveCompose = (api, extra) => {
|
|
|
321
330
|
if (spec && spec.command) return { kind: spec.command };
|
|
322
331
|
const kind = api.state.composeKind;
|
|
323
332
|
if (kind === 'file') return saveFileCompose(api);
|
|
333
|
+
const item = api.ui.current ? api.ui.current() : null;
|
|
334
|
+
const shouldStage =
|
|
335
|
+
kind === 'code' &&
|
|
336
|
+
writesCodeFile(api, item) &&
|
|
337
|
+
item &&
|
|
338
|
+
item.origin !== 'staged';
|
|
339
|
+
const rel = itemPath(item);
|
|
324
340
|
const baseline = api.state.composeBaseline;
|
|
325
341
|
const result = commitCompose(api);
|
|
326
342
|
if (result === false) return { kind: 'error' };
|
|
@@ -331,12 +347,17 @@ const saveCompose = (api, extra) => {
|
|
|
331
347
|
const next = editor ? editor.text : '';
|
|
332
348
|
if (notes) rememberTemplate(notes, baseline, next);
|
|
333
349
|
}
|
|
350
|
+
if (shouldStage && reload) stageEditedFile(api, rel);
|
|
334
351
|
api.ui.flushReview();
|
|
335
352
|
closeCompose(api);
|
|
353
|
+
const doneStatus = shouldStage && reload ? 'staged' : 'saved';
|
|
336
354
|
if (reload && typeof api.ui.reloadAfterChange === 'function') {
|
|
337
|
-
api.ui.reloadAfterChange()
|
|
355
|
+
api.ui.reloadAfterChange(() => {
|
|
356
|
+
if (!api.ui.status) api.ui.status = doneStatus;
|
|
357
|
+
});
|
|
358
|
+
} else {
|
|
359
|
+
api.ui.status = doneStatus;
|
|
338
360
|
}
|
|
339
|
-
api.ui.status = 'saved';
|
|
340
361
|
if (opts.editNext === true) api.todos.editNextTodo();
|
|
341
362
|
return { kind: 'saved' };
|
|
342
363
|
};
|
|
@@ -369,7 +390,7 @@ const composeView = (api) => {
|
|
|
369
390
|
const kind = state.composeKind;
|
|
370
391
|
const text = state.editor.text;
|
|
371
392
|
const cursor = state.editor.cursor;
|
|
372
|
-
return { kind, text, cursor };
|
|
393
|
+
return { kind, text, cursor, commitKind: state.commitKind };
|
|
373
394
|
};
|
|
374
395
|
|
|
375
396
|
const liveCodeText = (api) => {
|
package/lib/session/dispatch.js
CHANGED
|
@@ -32,6 +32,7 @@ const createDispatcher = (ui) => {
|
|
|
32
32
|
next: ui.filesPane.onNext,
|
|
33
33
|
prev: ui.filesPane.onPrev,
|
|
34
34
|
layout: ui.filesPane.onLayout,
|
|
35
|
+
theme: ui.filesPane.onTheme,
|
|
35
36
|
files: ui.filesPane.onFiles,
|
|
36
37
|
feedback: ui.composer.onFeedback,
|
|
37
38
|
todo: ui.composer.onTodo,
|
|
@@ -39,6 +40,8 @@ const createDispatcher = (ui) => {
|
|
|
39
40
|
reload: ui.lifecycle.onReload,
|
|
40
41
|
commit: ui.commits.onCommit,
|
|
41
42
|
amend: ui.commits.onAmend,
|
|
43
|
+
reword: ui.commits.onReword,
|
|
44
|
+
apply: ui.commits.onApply,
|
|
42
45
|
fixup: ui.commits.onFixup,
|
|
43
46
|
branch: ui.gitBranches.onBranch,
|
|
44
47
|
pull: ui.gitBranches.onPull,
|
|
@@ -54,6 +57,7 @@ const createDispatcher = (ui) => {
|
|
|
54
57
|
open: ui.filesPane.onOpenFile,
|
|
55
58
|
file: ui.filesPane.onFileScope,
|
|
56
59
|
diff: ui.filesPane.onDiffScope,
|
|
60
|
+
check: () => ui.composer.toggleFocusedTodo(),
|
|
57
61
|
removeTodo: ui.composer.removeFocusedTodo,
|
|
58
62
|
quit: () => ui.onQuit(),
|
|
59
63
|
scrollUp: () => ui.filesPane.onScroll(-1),
|
|
@@ -142,6 +146,8 @@ const createInput = (ui) => {
|
|
|
142
146
|
return void ui.runComposeCommand(command);
|
|
143
147
|
}
|
|
144
148
|
if (key === 'escape') return void ui.filesPane.onEscape();
|
|
149
|
+
const toggle = key === 'x' || key === ' ';
|
|
150
|
+
if (toggle && ui.composer.toggleFocusedTodo()) return;
|
|
145
151
|
if (ui.composer.typeIntoTodo(key)) return;
|
|
146
152
|
const action = actionFromKey(key, ui.pane, ui.fileScope);
|
|
147
153
|
if (action) ui.dispatch(action);
|
|
@@ -1,6 +1,8 @@
|
|
|
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;
|
|
5
7
|
const unit = require('./unit.js');
|
|
6
8
|
const { setFileScope, openUnitFile, moveUnitBlock } = unit;
|
|
@@ -24,10 +26,14 @@ const syncFileCursor = (ui) => {
|
|
|
24
26
|
};
|
|
25
27
|
|
|
26
28
|
const showFiles = (ui) => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
+
const nav = ui.nav;
|
|
30
|
+
const held =
|
|
31
|
+
nav.todoOpen === true || nav.pane === 'branches' || nav.pane === 'commits';
|
|
32
|
+
nav.pane = 'files';
|
|
33
|
+
nav.clearSelection();
|
|
29
34
|
followReviewPath(ui);
|
|
30
|
-
|
|
35
|
+
nav.todoOpen = false;
|
|
36
|
+
if (held) ui.lifecycle.catchUpWatch();
|
|
31
37
|
};
|
|
32
38
|
|
|
33
39
|
const onEscape = (ui) => {
|
|
@@ -214,6 +220,13 @@ const onLayout = (ui) => {
|
|
|
214
220
|
ui.nav.clearSelection();
|
|
215
221
|
};
|
|
216
222
|
|
|
223
|
+
const onTheme = (ui) => {
|
|
224
|
+
const i = THEME_NAMES.indexOf(themeName());
|
|
225
|
+
const next = THEME_NAMES[(i + 1) % THEME_NAMES.length];
|
|
226
|
+
setTheme(next);
|
|
227
|
+
ui.status = next;
|
|
228
|
+
};
|
|
229
|
+
|
|
217
230
|
const createFilesPane = (ui) => ({
|
|
218
231
|
clampIndex: () => clampIndex(ui),
|
|
219
232
|
syncReviewPath: () => syncReviewPath(ui),
|
|
@@ -230,6 +243,7 @@ const createFilesPane = (ui) => ({
|
|
|
230
243
|
onHome: () => onJump(ui, false),
|
|
231
244
|
onEnd: () => onJump(ui, true),
|
|
232
245
|
onLayout: () => onLayout(ui),
|
|
246
|
+
onTheme: () => onTheme(ui),
|
|
233
247
|
onFileScope: () => setFileScope(ui, 'file'),
|
|
234
248
|
onDiffScope: () => setFileScope(ui, 'diff'),
|
|
235
249
|
});
|
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,30 @@ 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 || nav.pane === 'branches' || nav.pane === 'commits';
|
|
489
|
+
|
|
490
|
+
const currentFileStamp = (api) => {
|
|
491
|
+
const { nav } = api;
|
|
492
|
+
const root = api.ui.top || api.loadConfig.cwd;
|
|
493
|
+
if (watchesWorktree(nav)) return worktreeStamp(api);
|
|
494
|
+
const pane = nav.pane;
|
|
495
|
+
if (pane !== 'diff' && pane !== 'unit') return '';
|
|
496
|
+
const rel = pane === 'unit' ? nav.reviewPath : itemPath(api.ui.current());
|
|
497
|
+
if (!rel) return '';
|
|
498
|
+
return stampFile(root, rel);
|
|
499
|
+
};
|
|
500
|
+
|
|
454
501
|
const pollCurrentFile = (api) => {
|
|
455
502
|
const stamp = currentFileStamp(api);
|
|
456
503
|
if (!stamp) {
|
|
@@ -466,9 +513,46 @@ const pollCurrentFile = (api) => {
|
|
|
466
513
|
onDiskChange(api);
|
|
467
514
|
};
|
|
468
515
|
|
|
516
|
+
const reviewFileStamp = (api) => {
|
|
517
|
+
const store = api.review && api.review.store;
|
|
518
|
+
if (!store || !store.reviewPath) return '';
|
|
519
|
+
try {
|
|
520
|
+
const stat = fs.statSync(store.reviewPath);
|
|
521
|
+
return `${stat.mtimeMs}:${stat.size}`;
|
|
522
|
+
} catch {
|
|
523
|
+
return 'missing';
|
|
524
|
+
}
|
|
525
|
+
};
|
|
526
|
+
|
|
527
|
+
const noteReviewStamp = (api) => {
|
|
528
|
+
api.reviewStamp = reviewFileStamp(api);
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
const applyReviewChange = (api) => {
|
|
532
|
+
if (!api.ui || api.ui.done) return;
|
|
533
|
+
const stamp = reviewFileStamp(api);
|
|
534
|
+
if (!stamp || stamp === api.reviewStamp) return;
|
|
535
|
+
if (stamp === 'missing') {
|
|
536
|
+
api.reviewStamp = stamp;
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const store = api.review && api.review.store;
|
|
540
|
+
if (!store) return;
|
|
541
|
+
let disk;
|
|
542
|
+
try {
|
|
543
|
+
disk = loadReview(store.reviewPath, store.templates);
|
|
544
|
+
} catch {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
mergeReview(store, disk);
|
|
548
|
+
api.reviewStamp = stamp;
|
|
549
|
+
if (typeof api.ui.paint === 'function') api.ui.paint();
|
|
550
|
+
};
|
|
551
|
+
|
|
469
552
|
const stopWatch = (api) => {
|
|
470
553
|
clearWatchRetry(api);
|
|
471
554
|
api.watchStamp = '';
|
|
555
|
+
api.reviewStamp = '';
|
|
472
556
|
if (api.ui.term) api.ui.term.stopTimer('watch');
|
|
473
557
|
if (!api.diskWatcher) return;
|
|
474
558
|
api.diskWatcher.close();
|
|
@@ -484,16 +568,26 @@ const startWatch = (api) => {
|
|
|
484
568
|
root,
|
|
485
569
|
paths: api.loadConfig.paths,
|
|
486
570
|
onChange: () => onDiskChange(api),
|
|
571
|
+
onReview: () => applyReviewChange(api),
|
|
487
572
|
});
|
|
488
573
|
syncItemWatches(api);
|
|
574
|
+
noteReviewStamp(api);
|
|
489
575
|
if (!api.ui.term) return;
|
|
490
|
-
api.ui.term.startTimer(
|
|
576
|
+
api.ui.term.startTimer(
|
|
577
|
+
'watch',
|
|
578
|
+
() => {
|
|
579
|
+
pollCurrentFile(api);
|
|
580
|
+
applyReviewChange(api);
|
|
581
|
+
},
|
|
582
|
+
POLL_MS,
|
|
583
|
+
);
|
|
491
584
|
};
|
|
492
585
|
|
|
493
586
|
const createLifecycle = (api) => {
|
|
494
587
|
api.diskWatcher = null;
|
|
495
588
|
api.watchRetry = null;
|
|
496
589
|
api.watchStamp = '';
|
|
590
|
+
api.reviewStamp = '';
|
|
497
591
|
api.ignoreWatchUntil = 0;
|
|
498
592
|
api.npmExtras = null;
|
|
499
593
|
return {
|
|
@@ -505,6 +599,9 @@ const createLifecycle = (api) => {
|
|
|
505
599
|
reloadAfterChange: (afterLoad) => reloadAfterChange(api, afterLoad),
|
|
506
600
|
onReload: () => onReload(api),
|
|
507
601
|
onDiskChange: () => onDiskChange(api),
|
|
602
|
+
applyReviewChange: () => applyReviewChange(api),
|
|
603
|
+
noteReviewStamp: () => noteReviewStamp(api),
|
|
604
|
+
catchUpWatch: () => catchUpWatch(api),
|
|
508
605
|
pollCurrentFile: () => pollCurrentFile(api),
|
|
509
606
|
startWatch: () => startWatch(api),
|
|
510
607
|
stopWatch: () => stopWatch(api),
|
package/lib/session/pointer.js
CHANGED
|
@@ -105,7 +105,10 @@ const RELEASE_TARGETS = [
|
|
|
105
105
|
{
|
|
106
106
|
match: (ui) => ui.pane === 'diff',
|
|
107
107
|
hits: 'todoHits',
|
|
108
|
-
onHit: (ui, hit) =>
|
|
108
|
+
onHit: (ui, hit) => {
|
|
109
|
+
if (hit.check) return void ui.composer.onTodoCheck(hit.cursor);
|
|
110
|
+
ui.composer.onTodoHit(hit.cursor);
|
|
111
|
+
},
|
|
109
112
|
},
|
|
110
113
|
{
|
|
111
114
|
match: (ui) => ui.mode === 'compose',
|
package/lib/session/todos.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const files = require('../files.js');
|
|
4
4
|
const { isTodosEntry, TODO_FILE } = files;
|
|
5
5
|
const review = require('../review.js');
|
|
6
|
-
const { addTodo, setTodoText, removeTodo, checkLabel } = review;
|
|
6
|
+
const { addTodo, setTodoText, setTodoDone, removeTodo, checkLabel } = review;
|
|
7
7
|
|
|
8
8
|
const TODO_TYPE_SKIP = [
|
|
9
9
|
'enter',
|
|
@@ -101,6 +101,7 @@ const closeTodoPage = (api) => {
|
|
|
101
101
|
api.nav.todoOpen = false;
|
|
102
102
|
api.ui.syncReviewPath();
|
|
103
103
|
api.ui.syncFileCursor();
|
|
104
|
+
api.ui.lifecycle.catchUpWatch();
|
|
104
105
|
};
|
|
105
106
|
|
|
106
107
|
const focusTodoPage = (api, todoId) => {
|
|
@@ -189,6 +190,17 @@ const removeFocusedTodo = (api) => {
|
|
|
189
190
|
api.ui.flushReview(true);
|
|
190
191
|
};
|
|
191
192
|
|
|
193
|
+
const toggleFocusedTodo = (api) => {
|
|
194
|
+
const nav = api.nav;
|
|
195
|
+
if (!nav.todoOpen || nav.pane !== 'diff') return false;
|
|
196
|
+
if (api.ui.mode !== 'review') return false;
|
|
197
|
+
const todo = listTodos(api)[clampedTodoFocus(api)];
|
|
198
|
+
if (!todo) return false;
|
|
199
|
+
setTodoDone(api.review.store, todo.id, !todo.done);
|
|
200
|
+
api.ui.flushReview(true);
|
|
201
|
+
return true;
|
|
202
|
+
};
|
|
203
|
+
|
|
192
204
|
const onTodoHit = (api, cursor) => {
|
|
193
205
|
if (!api.nav.todoOpen) return;
|
|
194
206
|
const todos = listTodos(api);
|
|
@@ -206,6 +218,20 @@ const onTodoHit = (api, cursor) => {
|
|
|
206
218
|
editFocusedTodo(api);
|
|
207
219
|
};
|
|
208
220
|
|
|
221
|
+
const onTodoCheck = (api, cursor) => {
|
|
222
|
+
if (!api.nav.todoOpen) return;
|
|
223
|
+
const todo = listTodos(api)[cursor];
|
|
224
|
+
if (!todo) return void onTodoHit(api, cursor);
|
|
225
|
+
const { state } = api;
|
|
226
|
+
if (state.composeKind === 'todo' && state.composeTodoId !== todo.id) {
|
|
227
|
+
api.commit();
|
|
228
|
+
api.close();
|
|
229
|
+
}
|
|
230
|
+
setTodoDone(api.review.store, todo.id, !todo.done);
|
|
231
|
+
api.nav.todoFocus = cursor;
|
|
232
|
+
api.ui.flushReview(true);
|
|
233
|
+
};
|
|
234
|
+
|
|
209
235
|
const createTodo = (api) => {
|
|
210
236
|
startDraftCompose(api);
|
|
211
237
|
};
|
|
@@ -253,6 +279,8 @@ const createTodoCompose = (api) => ({
|
|
|
253
279
|
typeIntoTodo: (key) => typeIntoTodo(api, key),
|
|
254
280
|
removeFocusedTodo: () => removeFocusedTodo(api),
|
|
255
281
|
onTodoHit: (cursor) => onTodoHit(api, cursor),
|
|
282
|
+
onTodoCheck: (cursor) => onTodoCheck(api, cursor),
|
|
283
|
+
toggleFocusedTodo: () => toggleFocusedTodo(api),
|
|
256
284
|
onTodo: () => onTodo(api),
|
|
257
285
|
todoEditView: () => todoEditView(api),
|
|
258
286
|
commitTodo: (text) => commitTodo(api, text),
|