reslop 0.1.8 → 0.2.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.
- package/CHANGELOG.md +25 -1
- package/README.md +27 -38
- package/bin/reslop.js +2 -2
- package/lib/ansi.js +6 -2
- package/lib/api.js +2 -2
- package/lib/capabilities.js +10 -14
- package/lib/cli.js +28 -47
- package/lib/clipboard.js +1 -1
- package/lib/deps-diff.js +712 -0
- package/lib/deps.js +556 -32
- package/lib/detect.js +1 -7
- package/lib/diff/align.js +7 -10
- package/lib/diff/diff.js +53 -0
- package/lib/diff/display.js +5 -4
- package/lib/diff/inline.js +26 -21
- package/lib/diff/items.js +6 -4
- package/lib/diff/patch.js +1 -3
- package/lib/diff/unit.js +392 -0
- package/lib/files.js +34 -8
- package/lib/{git/branches.js → git-branches.js} +103 -10
- package/lib/{git/dependencies.js → git-deps.js} +111 -38
- package/lib/git.js +592 -65
- package/lib/github.js +28 -197
- package/lib/gitlab.js +24 -195
- package/lib/highlight/bash.js +1 -3
- package/lib/highlight/css.js +1 -3
- package/lib/highlight/js.js +11 -14
- package/lib/highlight.js +6 -9
- package/lib/keys.js +22 -9
- package/lib/manifest.js +330 -0
- package/lib/remote.js +374 -0
- package/lib/render/branches.js +34 -66
- package/lib/render/chrome.js +57 -22
- package/lib/render/commits.js +85 -0
- package/lib/render/diff.js +16 -16
- package/lib/render/files.js +29 -37
- package/lib/render/frame.js +29 -13
- package/lib/render/notes.js +4 -3
- package/lib/render/primitives.js +46 -1
- package/lib/{render.js → render/render.js} +6 -7
- package/lib/render/unit.js +41 -0
- package/lib/review.js +750 -14
- package/lib/select.js +5 -10
- package/lib/session/accessors.js +9 -1
- package/lib/session/actions.js +93 -36
- package/lib/session/branches.js +130 -144
- package/lib/session/change-actions.js +206 -124
- package/lib/session/commit.js +217 -62
- package/lib/session/compose.js +158 -13
- package/lib/session/dispatch.js +109 -55
- package/lib/session/files-pane.js +145 -142
- package/lib/session/items.js +131 -34
- package/lib/session/lifecycle.js +82 -42
- package/lib/session/load.js +19 -33
- package/lib/session/navigation.js +15 -3
- package/lib/session/ops.js +28 -41
- package/lib/session/pointer.js +58 -54
- package/lib/session/review.js +23 -37
- package/lib/session/terminal.js +6 -12
- package/lib/session/todos.js +8 -12
- package/lib/session/unit.js +208 -0
- package/lib/session/update.js +33 -46
- package/lib/session/view.js +82 -11
- package/lib/session/watch.js +11 -19
- package/lib/session.js +29 -226
- package/lib/source.js +3 -3
- package/lib/update.js +40 -75
- package/lib/{git/proc.js → utilities.js} +75 -11
- package/lib/wrap.js +1 -3
- package/package.json +2 -2
- package/lib/deps/changes.js +0 -331
- package/lib/deps/items.js +0 -131
- package/lib/deps/manifest.js +0 -145
- package/lib/deps/merge.js +0 -120
- package/lib/deps/proposals.js +0 -164
- package/lib/deps/reports.js +0 -180
- package/lib/deps/rows.js +0 -177
- package/lib/deps/scan.js +0 -138
- package/lib/deps/usage.js +0 -68
- package/lib/deps/versions.js +0 -119
- package/lib/diff.js +0 -33
- package/lib/git/changes.js +0 -164
- package/lib/git/read.js +0 -253
- package/lib/git/run.js +0 -100
- package/lib/npm/commands.js +0 -100
- package/lib/remote/changes.js +0 -37
- package/lib/remote/headers.js +0 -36
- package/lib/remote/http.js +0 -122
- package/lib/remote/notes.js +0 -94
- package/lib/review/format.js +0 -434
- package/lib/review/storage.js +0 -150
- package/lib/review/store.js +0 -178
- package/lib/review/templates.js +0 -49
- package/lib/session/origins.js +0 -7
- package/lib/sys.js +0 -84
package/lib/session/items.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { stateView } = require('./accessors.js');
|
|
4
|
+
|
|
3
5
|
const files = require('../files.js');
|
|
4
6
|
const { itemPath, TODO_FILE, isTodoItem } = files;
|
|
5
7
|
const { feedbackKey } = require('../review.js');
|
|
6
|
-
const { refreshIndexPatches } = require('../diff.js');
|
|
8
|
+
const { refreshIndexPatches } = require('../diff/diff.js');
|
|
7
9
|
|
|
8
10
|
const depChangeKey = (item) => {
|
|
9
11
|
const change = item.dep && item.dep.change;
|
|
@@ -21,25 +23,125 @@ const npmBusyKind = (item) => {
|
|
|
21
23
|
return 'npm i';
|
|
22
24
|
};
|
|
23
25
|
|
|
24
|
-
const
|
|
26
|
+
const blockKey = (item) => {
|
|
25
27
|
if (!item || !item.file) return '';
|
|
26
28
|
const rel = itemPath(item);
|
|
27
29
|
if (isTodoItem(item)) return `todo:${TODO_FILE}`;
|
|
28
|
-
const origin = item.origin ?? '';
|
|
29
30
|
const depId = depChangeKey(item);
|
|
30
|
-
if (depId) return `${
|
|
31
|
+
if (depId) return `${rel}:dep:${depId}`;
|
|
31
32
|
if (!item.hunk) {
|
|
32
33
|
const block = item.blockId ?? '-';
|
|
33
|
-
return `${
|
|
34
|
+
return `${rel}:file:${block}`;
|
|
34
35
|
}
|
|
35
36
|
const oldStart = item.hunk.oldStart;
|
|
36
37
|
const newStart = item.hunk.newStart;
|
|
37
38
|
const block = item.blockId ?? '-';
|
|
38
|
-
return `${
|
|
39
|
+
return `${rel}:${oldStart}:${newStart}:${block}`;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const itemKey = (item) => {
|
|
43
|
+
const key = blockKey(item);
|
|
44
|
+
if (!key || isTodoItem(item)) return key;
|
|
45
|
+
const origin = item.origin ?? '';
|
|
46
|
+
return `${origin}:${key}`;
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const blockLinesKey = (item) => {
|
|
50
|
+
if (!item || !item.file || isTodoItem(item)) return '';
|
|
51
|
+
if (!item.hunk) return '';
|
|
52
|
+
const parts = [itemPath(item)];
|
|
53
|
+
for (const line of item.hunk.lines) {
|
|
54
|
+
if (line.blockId !== item.blockId) continue;
|
|
55
|
+
if (line.type === 'ctx') continue;
|
|
56
|
+
parts.push(`${line.type}:${line.text}`);
|
|
57
|
+
}
|
|
58
|
+
return parts.join('\0');
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
const pushBucket = (map, key, item) => {
|
|
62
|
+
if (!key) return;
|
|
63
|
+
const list = map.get(key) ?? [];
|
|
64
|
+
list.push(item);
|
|
65
|
+
map.set(key, list);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const takeBucket = (map, key, used) => {
|
|
69
|
+
if (!key) return null;
|
|
70
|
+
const list = map.get(key);
|
|
71
|
+
if (!list || !list.length) return null;
|
|
72
|
+
while (list.length) {
|
|
73
|
+
const item = list.shift();
|
|
74
|
+
if (!used.has(item)) return item;
|
|
75
|
+
}
|
|
76
|
+
return null;
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const groupByPath = (items) => {
|
|
80
|
+
const groups = new Map();
|
|
81
|
+
for (const item of items) {
|
|
82
|
+
const rel = itemPath(item) || '';
|
|
83
|
+
const list = groups.get(rel) ?? [];
|
|
84
|
+
list.push(item);
|
|
85
|
+
groups.set(rel, list);
|
|
86
|
+
}
|
|
87
|
+
return groups;
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const alignGroup = (prev, next) => {
|
|
91
|
+
if (!prev.length) return next;
|
|
92
|
+
if (!next.length) return [];
|
|
93
|
+
const byBlock = new Map();
|
|
94
|
+
const byLines = new Map();
|
|
95
|
+
for (const item of next) {
|
|
96
|
+
pushBucket(byBlock, blockKey(item), item);
|
|
97
|
+
pushBucket(byLines, blockLinesKey(item), item);
|
|
98
|
+
}
|
|
99
|
+
const used = new Set();
|
|
100
|
+
const ordered = [];
|
|
101
|
+
for (const item of prev) {
|
|
102
|
+
const byText = takeBucket(byLines, blockLinesKey(item), used);
|
|
103
|
+
const taken = byText ?? takeBucket(byBlock, blockKey(item), used);
|
|
104
|
+
if (!taken) continue;
|
|
105
|
+
used.add(taken);
|
|
106
|
+
ordered.push(taken);
|
|
107
|
+
}
|
|
108
|
+
for (const item of next) {
|
|
109
|
+
if (used.has(item)) continue;
|
|
110
|
+
ordered.push(item);
|
|
111
|
+
}
|
|
112
|
+
return ordered;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const alignLoadedItems = (prev, next) => {
|
|
116
|
+
if (!prev.length || !next.length) return next;
|
|
117
|
+
const nextGroups = groupByPath(next);
|
|
118
|
+
const seen = new Set();
|
|
119
|
+
const ordered = [];
|
|
120
|
+
for (const [rel, list] of groupByPath(prev)) {
|
|
121
|
+
seen.add(rel);
|
|
122
|
+
const aligned = alignGroup(list, nextGroups.get(rel) ?? []);
|
|
123
|
+
for (const item of aligned) ordered.push(item);
|
|
124
|
+
}
|
|
125
|
+
for (const [rel, list] of nextGroups) {
|
|
126
|
+
if (seen.has(rel)) continue;
|
|
127
|
+
for (const item of list) ordered.push(item);
|
|
128
|
+
}
|
|
129
|
+
return ordered;
|
|
39
130
|
};
|
|
40
131
|
|
|
41
132
|
const restoredIndex = (items, item) => {
|
|
42
133
|
if (!item || !item.file) return -1;
|
|
134
|
+
const origin = item.origin ?? '';
|
|
135
|
+
const lines = blockLinesKey(item);
|
|
136
|
+
if (lines) {
|
|
137
|
+
let any = -1;
|
|
138
|
+
for (let i = 0; i < items.length; i++) {
|
|
139
|
+
if (blockLinesKey(items[i]) !== lines) continue;
|
|
140
|
+
if (any < 0) any = i;
|
|
141
|
+
if ((items[i].origin ?? '') === origin) return i;
|
|
142
|
+
}
|
|
143
|
+
if (any >= 0) return any;
|
|
144
|
+
}
|
|
43
145
|
const key = itemKey(item);
|
|
44
146
|
if (key) {
|
|
45
147
|
const exact = items.findIndex((entry) => itemKey(entry) === key);
|
|
@@ -47,7 +149,6 @@ const restoredIndex = (items, item) => {
|
|
|
47
149
|
}
|
|
48
150
|
const rel = itemPath(item);
|
|
49
151
|
if (!rel) return -1;
|
|
50
|
-
const origin = item.origin ?? '';
|
|
51
152
|
const start = item.hunk ? item.hunk.newStart : 0;
|
|
52
153
|
let fileMatch = -1;
|
|
53
154
|
let best = -1;
|
|
@@ -106,8 +207,9 @@ const createItemCollection = () => {
|
|
|
106
207
|
const isDismissed = (item) => state.dismissed.has(itemKey(item));
|
|
107
208
|
|
|
108
209
|
const replace = (items) => {
|
|
210
|
+
const aligned = alignLoadedItems(state.items, items);
|
|
109
211
|
const next = [];
|
|
110
|
-
for (const item of
|
|
212
|
+
for (const item of aligned) {
|
|
111
213
|
if (isDismissed(item)) continue;
|
|
112
214
|
next.push(item);
|
|
113
215
|
}
|
|
@@ -123,18 +225,15 @@ const createItemCollection = () => {
|
|
|
123
225
|
state.dismissed.clear();
|
|
124
226
|
};
|
|
125
227
|
|
|
126
|
-
const findIndexByKey = (key) => {
|
|
127
|
-
if (!key) return -1;
|
|
128
|
-
return state.items.findIndex((item) => itemKey(item) === key);
|
|
129
|
-
};
|
|
130
|
-
|
|
131
228
|
const findRestoredIndex = (item) => restoredIndex(state.items, item);
|
|
132
229
|
|
|
133
230
|
const keepOrigin = (item, origin) => {
|
|
134
231
|
const next = { ...item, origin };
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
232
|
+
const { length } = state.items;
|
|
233
|
+
const replaced = new Array(length);
|
|
234
|
+
for (let i = 0; i < length; i++) {
|
|
235
|
+
const entry = state.items[i];
|
|
236
|
+
replaced[i] = sameBlock(entry, item) ? next : entry;
|
|
138
237
|
}
|
|
139
238
|
if (next.dep) {
|
|
140
239
|
state.items = replaced;
|
|
@@ -143,33 +242,31 @@ const createItemCollection = () => {
|
|
|
143
242
|
state.items = refreshIndexPatches(replaced, next);
|
|
144
243
|
};
|
|
145
244
|
|
|
146
|
-
return
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
245
|
+
return stateView(
|
|
246
|
+
state,
|
|
247
|
+
{
|
|
248
|
+
reset,
|
|
249
|
+
replace,
|
|
250
|
+
dismiss,
|
|
251
|
+
clearDismissed,
|
|
252
|
+
isDismissed,
|
|
253
|
+
findRestoredIndex,
|
|
254
|
+
keepOrigin,
|
|
152
255
|
},
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
reset,
|
|
157
|
-
replace,
|
|
158
|
-
dismiss,
|
|
159
|
-
clearDismissed,
|
|
160
|
-
isDismissed,
|
|
161
|
-
findIndexByKey,
|
|
162
|
-
findRestoredIndex,
|
|
163
|
-
keepOrigin,
|
|
164
|
-
};
|
|
256
|
+
['items'],
|
|
257
|
+
['dismissed'],
|
|
258
|
+
);
|
|
165
259
|
};
|
|
166
260
|
|
|
167
261
|
module.exports = {
|
|
168
262
|
depChangeKey,
|
|
169
263
|
needsReload,
|
|
170
264
|
npmBusyKind,
|
|
265
|
+
blockKey,
|
|
171
266
|
itemKey,
|
|
172
267
|
itemFeedKey,
|
|
268
|
+
blockLinesKey,
|
|
269
|
+
alignLoadedItems,
|
|
173
270
|
restoredIndex,
|
|
174
271
|
sameBlock,
|
|
175
272
|
canEditCode,
|
package/lib/session/lifecycle.js
CHANGED
|
@@ -4,10 +4,10 @@ const fs = require('node:fs');
|
|
|
4
4
|
const path = require('node:path');
|
|
5
5
|
|
|
6
6
|
const { isAbortError } = require('./load.js');
|
|
7
|
-
const { createDiskWatcher, POLL_MS } = require('./watch.js');
|
|
7
|
+
const { createDiskWatcher, POLL_MS, DEBOUNCE_MS } = require('./watch.js');
|
|
8
8
|
const files = require('../files.js');
|
|
9
|
-
const { itemPath } = files;
|
|
10
|
-
const { MANIFEST, LOCKFILE } = require('../deps
|
|
9
|
+
const { itemPath, relativeAge } = files;
|
|
10
|
+
const { MANIFEST, LOCKFILE } = require('../deps.js');
|
|
11
11
|
const review = require('../review.js');
|
|
12
12
|
const { hasNotes } = review;
|
|
13
13
|
|
|
@@ -30,8 +30,6 @@ const shouldDeferExtras = (api) => {
|
|
|
30
30
|
return audit && (waitingOutdated || waitingAudit);
|
|
31
31
|
};
|
|
32
32
|
|
|
33
|
-
const watchOpts = (api) => api.watch ?? {};
|
|
34
|
-
|
|
35
33
|
const endReadLoop = (api) => {
|
|
36
34
|
const finish = api.ui.finishReadLoop;
|
|
37
35
|
if (finish) finish();
|
|
@@ -50,8 +48,6 @@ const kindFromExtra = (extra = {}) => {
|
|
|
50
48
|
return 'reload';
|
|
51
49
|
};
|
|
52
50
|
|
|
53
|
-
const shouldResetNav = (kind) => kind !== 'open' && kind !== 'watch';
|
|
54
|
-
|
|
55
51
|
const finishIfEmpty = (api, kind) => {
|
|
56
52
|
const { collection, review: notes, ui, nav } = api;
|
|
57
53
|
if (collection.items.length) return;
|
|
@@ -103,9 +99,47 @@ const itemRels = (collection) => {
|
|
|
103
99
|
return rels;
|
|
104
100
|
};
|
|
105
101
|
|
|
102
|
+
const fileDatesFromDisk = (top, rels) => {
|
|
103
|
+
const dates = new Map();
|
|
104
|
+
if (!top) return dates;
|
|
105
|
+
const now = Date.now();
|
|
106
|
+
for (const rel of rels) {
|
|
107
|
+
try {
|
|
108
|
+
const st = fs.statSync(path.join(top, rel));
|
|
109
|
+
dates.set(rel, relativeAge(st.mtimeMs, now));
|
|
110
|
+
} catch {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return dates;
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const stampItemDates = (items, dates) => {
|
|
118
|
+
for (const item of items) {
|
|
119
|
+
const date = dates.get(itemPath(item));
|
|
120
|
+
if (date) item.date = date;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
const syncUnitEditor = (api) => {
|
|
125
|
+
const composer = api.ui.composer;
|
|
126
|
+
if (!composer || typeof composer.applyDiskText !== 'function') return;
|
|
127
|
+
if (api.nav.pane !== 'unit') return;
|
|
128
|
+
const rel = api.nav.reviewPath;
|
|
129
|
+
if (!rel) return;
|
|
130
|
+
const repo = api.ui.repo;
|
|
131
|
+
if (!repo || typeof repo.fileText !== 'function') return;
|
|
132
|
+
const text = repo.fileText(api.ui.top || api.loadConfig.cwd, rel, api.ui.rev);
|
|
133
|
+
composer.applyDiskText(text);
|
|
134
|
+
};
|
|
135
|
+
|
|
106
136
|
const syncItemWatches = (api) => {
|
|
107
137
|
if (!api.diskWatcher) return;
|
|
108
|
-
|
|
138
|
+
const rels = itemRels(api.collection);
|
|
139
|
+
if (api.nav.pane === 'unit' && api.nav.reviewPath) {
|
|
140
|
+
if (!rels.includes(api.nav.reviewPath)) rels.push(api.nav.reviewPath);
|
|
141
|
+
}
|
|
142
|
+
api.diskWatcher.watchFiles(rels);
|
|
109
143
|
};
|
|
110
144
|
|
|
111
145
|
const applyLoaded = (api, loaded) => {
|
|
@@ -117,6 +151,10 @@ const applyLoaded = (api, loaded) => {
|
|
|
117
151
|
const here = ui.current();
|
|
118
152
|
ui.top = loaded.top ?? api.loadConfig.cwd;
|
|
119
153
|
collection.replace(loaded.items);
|
|
154
|
+
stampItemDates(
|
|
155
|
+
collection.items,
|
|
156
|
+
fileDatesFromDisk(ui.top, itemRels(collection)),
|
|
157
|
+
);
|
|
120
158
|
if (loaded.rev) ui.rev = loaded.rev;
|
|
121
159
|
if (loaded.revShort) {
|
|
122
160
|
ui.revShort = loaded.revShort;
|
|
@@ -134,6 +172,7 @@ const applyLoaded = (api, loaded) => {
|
|
|
134
172
|
markLoaded(api);
|
|
135
173
|
loader.pendingExtras = loaded.pending === true;
|
|
136
174
|
syncItemWatches(api);
|
|
175
|
+
syncUnitEditor(api);
|
|
137
176
|
return true;
|
|
138
177
|
};
|
|
139
178
|
|
|
@@ -147,10 +186,10 @@ const load = (api) => {
|
|
|
147
186
|
applyLoaded(api, loaded);
|
|
148
187
|
};
|
|
149
188
|
|
|
150
|
-
const fileStamp = (top, rel
|
|
189
|
+
const fileStamp = (top, rel) => {
|
|
151
190
|
const full = path.join(top, rel);
|
|
152
191
|
try {
|
|
153
|
-
const stat = statSync(full);
|
|
192
|
+
const stat = fs.statSync(full);
|
|
154
193
|
return `${stat.mtimeMs}:${stat.size}`;
|
|
155
194
|
} catch {
|
|
156
195
|
return 'missing';
|
|
@@ -158,11 +197,9 @@ const fileStamp = (top, rel, statSync) => {
|
|
|
158
197
|
};
|
|
159
198
|
|
|
160
199
|
const npmStamp = (api) => {
|
|
161
|
-
const extra = watchOpts(api);
|
|
162
|
-
const statSync = extra.statSync ?? fs.statSync;
|
|
163
200
|
const top = api.ui.top || api.loadConfig.cwd;
|
|
164
|
-
const pkg = fileStamp(top, MANIFEST
|
|
165
|
-
const lock = fileStamp(top, LOCKFILE
|
|
201
|
+
const pkg = fileStamp(top, MANIFEST);
|
|
202
|
+
const lock = fileStamp(top, LOCKFILE);
|
|
166
203
|
return `${pkg}|${lock}`;
|
|
167
204
|
};
|
|
168
205
|
|
|
@@ -190,8 +227,7 @@ const followExtras = async (api, loaded, gen, kind) => {
|
|
|
190
227
|
next = await api.loader.fetchExtras(api.loadConfig.repo, loaded, extra);
|
|
191
228
|
} catch (error) {
|
|
192
229
|
if (!api.loader.isCurrent(gen)) return;
|
|
193
|
-
failOpen(api, error);
|
|
194
|
-
return;
|
|
230
|
+
return void failOpen(api, error);
|
|
195
231
|
}
|
|
196
232
|
if (!api.loader.isCurrent(gen)) return;
|
|
197
233
|
if (api.ui.done) {
|
|
@@ -241,9 +277,8 @@ const resetNavAfterLoad = (api, extra = {}) => {
|
|
|
241
277
|
};
|
|
242
278
|
|
|
243
279
|
const afterSnapshot = (api, kind, extra = {}) => {
|
|
244
|
-
if (
|
|
245
|
-
resetNavAfterLoad(api, extra);
|
|
246
|
-
return;
|
|
280
|
+
if (kind !== 'open' && kind !== 'watch') {
|
|
281
|
+
return void resetNavAfterLoad(api, extra);
|
|
247
282
|
}
|
|
248
283
|
if (extra.afterLoad) extra.afterLoad();
|
|
249
284
|
};
|
|
@@ -270,8 +305,7 @@ const runSnapshot = async (api, gen, kind, extra = {}) => {
|
|
|
270
305
|
);
|
|
271
306
|
} catch (error) {
|
|
272
307
|
if (!api.loader.isCurrent(gen)) return;
|
|
273
|
-
failOpen(api, error);
|
|
274
|
-
return;
|
|
308
|
+
return void failOpen(api, error);
|
|
275
309
|
}
|
|
276
310
|
if (!api.loader.isCurrent(gen)) return;
|
|
277
311
|
if (api.ui.done) {
|
|
@@ -328,8 +362,7 @@ const refreshFromRepo = (api, extra = {}) => {
|
|
|
328
362
|
afterSnapshot(api, kind, extra);
|
|
329
363
|
if (loaded.pending) {
|
|
330
364
|
api.ops.setBusy('checking npm');
|
|
331
|
-
followExtras(api, loaded, gen, kind);
|
|
332
|
-
return;
|
|
365
|
+
return void followExtras(api, loaded, gen, kind);
|
|
333
366
|
}
|
|
334
367
|
if (kind === 'keep') return;
|
|
335
368
|
finishIfEmpty(api, kind);
|
|
@@ -340,17 +373,18 @@ const refreshFromRepo = (api, extra = {}) => {
|
|
|
340
373
|
};
|
|
341
374
|
|
|
342
375
|
const reloadAfterChange = (api, afterLoad) => {
|
|
343
|
-
const
|
|
376
|
+
const hereItem = api.ui.current();
|
|
377
|
+
const hereIndex = api.nav.index;
|
|
344
378
|
const restore = () => {
|
|
345
|
-
api.
|
|
379
|
+
const idx = api.collection.findRestoredIndex(hereItem);
|
|
380
|
+
api.nav.index = idx >= 0 ? idx : hereIndex;
|
|
346
381
|
api.ui.clampIndex();
|
|
347
382
|
api.ui.syncReviewPath();
|
|
348
383
|
api.ui.syncFileCursor();
|
|
349
384
|
if (afterLoad) afterLoad();
|
|
350
385
|
};
|
|
351
386
|
if (api.ui.uiOpen && api.loadConfig.repo.loadAsync) {
|
|
352
|
-
refreshFromRepo(api, { afterLoad: restore });
|
|
353
|
-
return;
|
|
387
|
+
return void refreshFromRepo(api, { afterLoad: restore });
|
|
354
388
|
}
|
|
355
389
|
refreshFromRepo(api);
|
|
356
390
|
restore();
|
|
@@ -364,9 +398,7 @@ const onReload = (api) => {
|
|
|
364
398
|
|
|
365
399
|
const clearWatchRetry = (api) => {
|
|
366
400
|
if (api.watchRetry === null) return;
|
|
367
|
-
|
|
368
|
-
const clearTimeoutFn = extra.clearTimeout ?? clearTimeout;
|
|
369
|
-
clearTimeoutFn(api.watchRetry);
|
|
401
|
+
clearTimeout(api.watchRetry);
|
|
370
402
|
api.watchRetry = null;
|
|
371
403
|
};
|
|
372
404
|
|
|
@@ -376,35 +408,43 @@ const shouldDeferWatch = (api) => {
|
|
|
376
408
|
if (!ui.uiOpen) return true;
|
|
377
409
|
if (!api.loader.didLoad) return true;
|
|
378
410
|
if (ops.gitBusy) return true;
|
|
411
|
+
if (ui.mode === 'compose') {
|
|
412
|
+
const kind = ui.composer && ui.composer.state.composeKind;
|
|
413
|
+
if (kind === 'file') return false;
|
|
414
|
+
}
|
|
379
415
|
if (ui.mode !== 'review') return true;
|
|
380
416
|
return false;
|
|
381
417
|
};
|
|
382
418
|
|
|
419
|
+
const WATCH_IGNORE_MS = 500;
|
|
420
|
+
|
|
421
|
+
const ignoreWatch = (api, ms = WATCH_IGNORE_MS) => {
|
|
422
|
+
api.ignoreWatchUntil = Date.now() + ms;
|
|
423
|
+
};
|
|
424
|
+
|
|
383
425
|
const onDiskChange = (api) => {
|
|
426
|
+
if (Date.now() < (api.ignoreWatchUntil ?? 0)) return;
|
|
384
427
|
if (shouldDeferWatch(api)) {
|
|
385
428
|
if (api.watchRetry !== null) return;
|
|
386
|
-
|
|
387
|
-
const setTimeoutFn = extra.setTimeout ?? setTimeout;
|
|
388
|
-
const delay = extra.debounceMs ?? 200;
|
|
389
|
-
api.watchRetry = setTimeoutFn(() => {
|
|
429
|
+
api.watchRetry = setTimeout(() => {
|
|
390
430
|
api.watchRetry = null;
|
|
391
431
|
onDiskChange(api);
|
|
392
|
-
},
|
|
432
|
+
}, DEBOUNCE_MS);
|
|
433
|
+
if (typeof api.watchRetry.unref === 'function') api.watchRetry.unref();
|
|
393
434
|
return;
|
|
394
435
|
}
|
|
395
436
|
refreshFromRepo(api, { keepView: true, quiet: true });
|
|
396
437
|
};
|
|
397
438
|
|
|
398
439
|
const currentFileStamp = (api) => {
|
|
399
|
-
|
|
400
|
-
|
|
440
|
+
const pane = api.nav.pane;
|
|
441
|
+
if (pane !== 'diff' && pane !== 'unit') return '';
|
|
442
|
+
const rel = pane === 'unit' ? api.nav.reviewPath : itemPath(api.ui.current());
|
|
401
443
|
if (!rel) return '';
|
|
402
|
-
const extra = watchOpts(api);
|
|
403
|
-
const statSync = extra.statSync ?? fs.statSync;
|
|
404
444
|
const full = path.join(api.ui.top || api.loadConfig.cwd, rel);
|
|
405
445
|
let stat;
|
|
406
446
|
try {
|
|
407
|
-
stat = statSync(full);
|
|
447
|
+
stat = fs.statSync(full);
|
|
408
448
|
} catch {
|
|
409
449
|
return `${rel}:missing`;
|
|
410
450
|
}
|
|
@@ -440,12 +480,10 @@ const startWatch = (api) => {
|
|
|
440
480
|
if (!isWorktree(api.ui)) return;
|
|
441
481
|
const root = api.ui.top || api.loadConfig.cwd;
|
|
442
482
|
if (!root) return;
|
|
443
|
-
const extra = watchOpts(api);
|
|
444
483
|
api.diskWatcher = createDiskWatcher({
|
|
445
484
|
root,
|
|
446
485
|
paths: api.loadConfig.paths,
|
|
447
486
|
onChange: () => onDiskChange(api),
|
|
448
|
-
...extra,
|
|
449
487
|
});
|
|
450
488
|
syncItemWatches(api);
|
|
451
489
|
if (!api.ui.term) return;
|
|
@@ -456,6 +494,7 @@ const createLifecycle = (api) => {
|
|
|
456
494
|
api.diskWatcher = null;
|
|
457
495
|
api.watchRetry = null;
|
|
458
496
|
api.watchStamp = '';
|
|
497
|
+
api.ignoreWatchUntil = 0;
|
|
459
498
|
api.npmExtras = null;
|
|
460
499
|
return {
|
|
461
500
|
applyLoaded: (loaded) => applyLoaded(api, loaded),
|
|
@@ -469,6 +508,7 @@ const createLifecycle = (api) => {
|
|
|
469
508
|
pollCurrentFile: () => pollCurrentFile(api),
|
|
470
509
|
startWatch: () => startWatch(api),
|
|
471
510
|
stopWatch: () => stopWatch(api),
|
|
511
|
+
ignoreWatch: (ms) => ignoreWatch(api, ms),
|
|
472
512
|
clearNpmExtras: () => clearNpmExtras(api),
|
|
473
513
|
};
|
|
474
514
|
};
|
package/lib/session/load.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { stateView } = require('./accessors.js');
|
|
4
|
+
|
|
3
5
|
const isAbortError = (error) => !!(error && error.code === 'ABORT');
|
|
4
6
|
|
|
5
7
|
const createLoadCoordinator = () => {
|
|
@@ -46,40 +48,24 @@ const createLoadCoordinator = () => {
|
|
|
46
48
|
return repo.loadExtras(loaded, extra);
|
|
47
49
|
};
|
|
48
50
|
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
state.pendingExtras = value;
|
|
51
|
+
return stateView(
|
|
52
|
+
state,
|
|
53
|
+
{
|
|
54
|
+
get signal() {
|
|
55
|
+
return state.abort?.signal;
|
|
56
|
+
},
|
|
57
|
+
bump,
|
|
58
|
+
isCurrent,
|
|
59
|
+
accept,
|
|
60
|
+
abort,
|
|
61
|
+
reset,
|
|
62
|
+
tag,
|
|
63
|
+
fetchSnapshot,
|
|
64
|
+
fetchExtras,
|
|
64
65
|
},
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
set didLoad(value) {
|
|
69
|
-
state.didLoad = value;
|
|
70
|
-
},
|
|
71
|
-
get signal() {
|
|
72
|
-
return state.abort?.signal;
|
|
73
|
-
},
|
|
74
|
-
bump,
|
|
75
|
-
isCurrent,
|
|
76
|
-
accept,
|
|
77
|
-
abort,
|
|
78
|
-
reset,
|
|
79
|
-
tag,
|
|
80
|
-
fetchSnapshot,
|
|
81
|
-
fetchExtras,
|
|
82
|
-
};
|
|
66
|
+
['promise', 'pendingExtras', 'didLoad'],
|
|
67
|
+
['gen'],
|
|
68
|
+
);
|
|
83
69
|
};
|
|
84
70
|
|
|
85
71
|
module.exports = { isAbortError, createLoadCoordinator };
|
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const files = require('../files.js');
|
|
4
|
-
const {
|
|
4
|
+
const { listPath, isTodoItem, isTodosEntry, TODO_FILE } = files;
|
|
5
5
|
|
|
6
6
|
const initialState = (options = {}) => ({
|
|
7
7
|
pane: options.startPane ?? 'files',
|
|
8
8
|
index: 0,
|
|
9
9
|
fileCursor: 0,
|
|
10
10
|
branchCursor: 0,
|
|
11
|
+
commitCursor: 0,
|
|
11
12
|
scroll: 0,
|
|
12
13
|
listScroll: 0,
|
|
13
14
|
reviewPath: null,
|
|
15
|
+
fileScope: 'diff',
|
|
16
|
+
unitLine: 0,
|
|
14
17
|
selection: null,
|
|
15
18
|
mouseAnchor: null,
|
|
16
19
|
pendingClick: null,
|
|
@@ -29,12 +32,21 @@ const clampIndex = (nav, length) => {
|
|
|
29
32
|
|
|
30
33
|
const current = (nav, items, todoItem) => {
|
|
31
34
|
if (nav.todoOpen) return todoItem;
|
|
35
|
+
if (nav.pane === 'unit' && nav.reviewPath) {
|
|
36
|
+
const item = items[nav.index];
|
|
37
|
+
if (item && listPath(item) === nav.reviewPath) return item;
|
|
38
|
+
for (const entry of items) {
|
|
39
|
+
if (listPath(entry) === nav.reviewPath) return entry;
|
|
40
|
+
}
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
32
43
|
return items[nav.index] ?? null;
|
|
33
44
|
};
|
|
34
45
|
|
|
35
46
|
const syncReviewPath = (nav, item) => {
|
|
36
|
-
if (nav.pane !== 'diff') return;
|
|
37
|
-
nav.
|
|
47
|
+
if (nav.pane !== 'diff' && nav.pane !== 'unit') return;
|
|
48
|
+
if (nav.pane === 'unit' && nav.reviewPath) return;
|
|
49
|
+
nav.reviewPath = item ? listPath(item) : null;
|
|
38
50
|
};
|
|
39
51
|
|
|
40
52
|
const clampFileCursor = (nav, length) => {
|