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
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const files = require('../files.js');
|
|
4
|
+
const { listPath, isTodosEntry } = files;
|
|
5
|
+
const diff = require('../diff/diff.js');
|
|
6
|
+
const { unitLines, itemsForPath, blockLineIndex } = diff;
|
|
7
|
+
const { hunkLineRange, sameHunk, hunkKey } = diff;
|
|
8
|
+
const { fileOffset, splitEditor, mergeEditRows, fileLineIndex } = diff;
|
|
9
|
+
|
|
10
|
+
const fileText = (ui, rel) => {
|
|
11
|
+
if (!rel) return '';
|
|
12
|
+
const repo = ui.repo;
|
|
13
|
+
if (repo && typeof repo.fileText === 'function') {
|
|
14
|
+
return repo.fileText(ui.top, rel, ui.rev);
|
|
15
|
+
}
|
|
16
|
+
return '';
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const pathItems = (ui, rel) => itemsForPath(ui.items ?? [], rel);
|
|
20
|
+
|
|
21
|
+
const linesFor = (
|
|
22
|
+
ui,
|
|
23
|
+
rel,
|
|
24
|
+
overlay = ui.composer.liveFileText(),
|
|
25
|
+
base = null,
|
|
26
|
+
) => {
|
|
27
|
+
const text = overlay === null ? fileText(ui, rel) : overlay;
|
|
28
|
+
const group = pathItems(ui, rel);
|
|
29
|
+
if (overlay !== null && base && base.length) {
|
|
30
|
+
return mergeEditRows(base, splitEditor(text));
|
|
31
|
+
}
|
|
32
|
+
return unitLines(text, group);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const BODY_SHIFT = 1;
|
|
36
|
+
|
|
37
|
+
const clampUnitLine = (nav, length) => {
|
|
38
|
+
if (!length) {
|
|
39
|
+
nav.unitLine = 0;
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
if (nav.unitLine < 0) nav.unitLine = 0;
|
|
43
|
+
if (nav.unitLine >= length) nav.unitLine = length - 1;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const CONTEXT = 1;
|
|
47
|
+
|
|
48
|
+
const frameBodyH = (frame, ui) => {
|
|
49
|
+
if (frame && frame.bodyH) return frame.bodyH;
|
|
50
|
+
const size = ui?.lastSize ?? ui?.getSize?.();
|
|
51
|
+
const height = size && size.height ? size.height : 24;
|
|
52
|
+
return Math.max(1, height - 4);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const ensureRangeVisible = (nav, firstLine, lastLine, frame, ui) => {
|
|
56
|
+
const bodyH = frameBodyH(frame, ui);
|
|
57
|
+
const diffFirst = firstLine + BODY_SHIFT;
|
|
58
|
+
const diffLast = lastLine + BODY_SHIFT;
|
|
59
|
+
const diffSpan = diffLast - diffFirst + 1;
|
|
60
|
+
let first = diffFirst;
|
|
61
|
+
let last = diffLast;
|
|
62
|
+
if (diffSpan <= bodyH) {
|
|
63
|
+
const padFirst = Math.max(0, firstLine - CONTEXT) + BODY_SHIFT;
|
|
64
|
+
const padLast = lastLine + CONTEXT + BODY_SHIFT;
|
|
65
|
+
if (padLast - padFirst + 1 <= bodyH) {
|
|
66
|
+
first = padFirst;
|
|
67
|
+
last = padLast;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const span = last - first + 1;
|
|
71
|
+
let scroll = nav.scroll ?? 0;
|
|
72
|
+
if (span > bodyH) {
|
|
73
|
+
scroll = first;
|
|
74
|
+
} else {
|
|
75
|
+
if (first < scroll) scroll = first;
|
|
76
|
+
const viewLast = scroll + bodyH - 1;
|
|
77
|
+
if (last > viewLast) scroll = last - bodyH + 1;
|
|
78
|
+
}
|
|
79
|
+
nav.scroll = Math.max(0, scroll);
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const ensureVisible = (nav, length, frame) => {
|
|
83
|
+
clampUnitLine(nav, length);
|
|
84
|
+
ensureRangeVisible(nav, nav.unitLine, nav.unitLine, frame);
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const followFileCursor = (nav, editor, frame, rows) => {
|
|
88
|
+
if (!editor) return;
|
|
89
|
+
const fileLine = editor.linePos().line;
|
|
90
|
+
if (rows && rows.length) {
|
|
91
|
+
nav.unitLine = fileLineIndex(rows, fileLine);
|
|
92
|
+
ensureVisible(nav, rows.length, frame);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const length = `${editor.text}`.split('\n').length;
|
|
96
|
+
nav.unitLine = fileLine;
|
|
97
|
+
ensureVisible(nav, length, frame);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const jumpToItem = (ui, item) => {
|
|
101
|
+
const nav = ui.nav;
|
|
102
|
+
const rel = nav.reviewPath;
|
|
103
|
+
const lines = linesFor(ui, rel);
|
|
104
|
+
const items = ui.items ?? [];
|
|
105
|
+
const idx = items.indexOf(item);
|
|
106
|
+
if (idx >= 0) nav.index = idx;
|
|
107
|
+
const range = hunkLineRange(lines, item);
|
|
108
|
+
nav.unitLine = range.start;
|
|
109
|
+
ensureRangeVisible(nav, range.start, range.end, ui.lastFrame, ui);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
const viewLine = (nav, length) => {
|
|
113
|
+
if (!length) return 0;
|
|
114
|
+
const top = Math.max(0, (nav.scroll ?? 0) - 1);
|
|
115
|
+
return Math.min(top, length - 1);
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const moveUnitBlock = (ui, step) => {
|
|
119
|
+
const nav = ui.nav;
|
|
120
|
+
const rel = nav.reviewPath;
|
|
121
|
+
if (!rel) return;
|
|
122
|
+
const items = ui.items ?? [];
|
|
123
|
+
const indexes = [];
|
|
124
|
+
const seen = new Set();
|
|
125
|
+
for (let i = 0; i < items.length; i++) {
|
|
126
|
+
if (listPath(items[i]) !== rel) continue;
|
|
127
|
+
const key = hunkKey(items[i]);
|
|
128
|
+
if (seen.has(key)) continue;
|
|
129
|
+
seen.add(key);
|
|
130
|
+
indexes.push(i);
|
|
131
|
+
}
|
|
132
|
+
if (!indexes.length) {
|
|
133
|
+
ui.status = 'not a diff block';
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
let at = -1;
|
|
137
|
+
const current = items[nav.index];
|
|
138
|
+
for (let i = 0; i < indexes.length; i++) {
|
|
139
|
+
if (!sameHunk(items[indexes[i]], current)) continue;
|
|
140
|
+
at = i;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
if (at < 0) at = step > 0 ? -1 : 0;
|
|
144
|
+
const next = at + step;
|
|
145
|
+
if (next < 0 || next >= indexes.length) return;
|
|
146
|
+
const item = items[indexes[next]];
|
|
147
|
+
jumpToItem(ui, item);
|
|
148
|
+
ui.status = '';
|
|
149
|
+
nav.clearSelection();
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const openUnitFile = (ui, entry) => {
|
|
153
|
+
if (!entry || isTodosEntry(entry)) return;
|
|
154
|
+
const nav = ui.nav;
|
|
155
|
+
nav.reviewPath = entry.path;
|
|
156
|
+
nav.todoOpen = false;
|
|
157
|
+
nav.pane = 'unit';
|
|
158
|
+
nav.scroll = 0;
|
|
159
|
+
nav.unitLine = 0;
|
|
160
|
+
nav.clearSelection();
|
|
161
|
+
const items = ui.items ?? [];
|
|
162
|
+
const group = pathItems(ui, entry.path);
|
|
163
|
+
if (group.length) {
|
|
164
|
+
const want = items[entry.openIndex] ?? items[entry.firstIndex] ?? group[0];
|
|
165
|
+
const item = group.includes(want) ? want : group[0];
|
|
166
|
+
jumpToItem(ui, item);
|
|
167
|
+
} else {
|
|
168
|
+
nav.unitLine = 0;
|
|
169
|
+
}
|
|
170
|
+
ui.status = '';
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const setFileScope = (ui, next) => {
|
|
174
|
+
if (ui.nav.pane !== 'files') return;
|
|
175
|
+
if (ui.nav.fileScope === next) return;
|
|
176
|
+
ui.nav.fileScope = next;
|
|
177
|
+
ui.nav.restoreFileCursor(ui.fileList());
|
|
178
|
+
ui.status = next === 'file' ? 'file scope' : 'diff mode';
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const editCursor = (ui) => {
|
|
182
|
+
const rel = ui.nav.reviewPath;
|
|
183
|
+
const text = fileText(ui, rel);
|
|
184
|
+
const group = pathItems(ui, rel);
|
|
185
|
+
const lines = unitLines(text, group);
|
|
186
|
+
const items = ui.items ?? [];
|
|
187
|
+
const item = items[ui.nav.index];
|
|
188
|
+
const atBlock = item && listPath(item) === rel;
|
|
189
|
+
const at = atBlock
|
|
190
|
+
? blockLineIndex(lines, item)
|
|
191
|
+
: viewLine(ui.nav, lines.length);
|
|
192
|
+
return {
|
|
193
|
+
text,
|
|
194
|
+
cursor: fileOffset(lines, at),
|
|
195
|
+
rows: lines,
|
|
196
|
+
};
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
module.exports = {
|
|
200
|
+
fileText,
|
|
201
|
+
linesFor,
|
|
202
|
+
moveUnitBlock,
|
|
203
|
+
openUnitFile,
|
|
204
|
+
setFileScope,
|
|
205
|
+
editCursor,
|
|
206
|
+
ensureVisible,
|
|
207
|
+
followFileCursor,
|
|
208
|
+
};
|
package/lib/session/update.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const { stateView } = require('./accessors.js');
|
|
4
|
+
|
|
3
5
|
const update = require('../update.js');
|
|
4
6
|
const { checkUpdate, installUpdate, markSkipped } = update;
|
|
5
7
|
|
|
6
8
|
const optsOf = (api) => {
|
|
7
|
-
const value = api.
|
|
9
|
+
const value = api.ui.updateOpts;
|
|
8
10
|
if (!value || value === true) return {};
|
|
9
11
|
return value;
|
|
10
12
|
};
|
|
@@ -19,15 +21,16 @@ const reset = (state) => {
|
|
|
19
21
|
|
|
20
22
|
const maybePrompt = (api) => {
|
|
21
23
|
if (!api.state.offer) return;
|
|
22
|
-
if (!api.
|
|
23
|
-
if (api.
|
|
24
|
-
api.
|
|
24
|
+
if (!api.ui.loader.didLoad) return;
|
|
25
|
+
if (api.ui.mode !== 'review') return;
|
|
26
|
+
api.ui.mode = 'confirmUpdate';
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
const runInstall = async (api, version) => {
|
|
28
|
-
api.
|
|
29
|
-
api.
|
|
30
|
-
api.
|
|
30
|
+
api.ui.status = 'updating reslop';
|
|
31
|
+
api.ui.progressFrame = 0;
|
|
32
|
+
api.ui.progress.start('install');
|
|
33
|
+
api.ui.paint();
|
|
31
34
|
let ok = false;
|
|
32
35
|
try {
|
|
33
36
|
await installUpdate(version, optsOf(api));
|
|
@@ -35,23 +38,23 @@ const runInstall = async (api, version) => {
|
|
|
35
38
|
} catch {
|
|
36
39
|
// ignore install errors
|
|
37
40
|
}
|
|
38
|
-
api.
|
|
39
|
-
if (api.
|
|
40
|
-
api.
|
|
41
|
-
api.
|
|
41
|
+
api.ui.progress.stop('install');
|
|
42
|
+
if (api.ui.done || !api.ui.uiOpen) return;
|
|
43
|
+
api.ui.status = ok ? 'updated' : 'update failed';
|
|
44
|
+
api.ui.paint();
|
|
42
45
|
};
|
|
43
46
|
|
|
44
47
|
const acceptUpdate = (api) => {
|
|
45
|
-
api.
|
|
48
|
+
api.ui.mode = 'review';
|
|
46
49
|
api.state.offer = false;
|
|
47
50
|
const version = api.state.to;
|
|
48
51
|
api.state.installPromise = runInstall(api, version);
|
|
49
52
|
};
|
|
50
53
|
|
|
51
54
|
const declineUpdate = (api) => {
|
|
52
|
-
api.
|
|
55
|
+
api.ui.mode = 'review';
|
|
53
56
|
api.state.offer = false;
|
|
54
|
-
api.
|
|
57
|
+
api.ui.status = '';
|
|
55
58
|
markSkipped(api.state.to, optsOf(api));
|
|
56
59
|
};
|
|
57
60
|
|
|
@@ -62,7 +65,7 @@ const runOpenUpdate = async (api) => {
|
|
|
62
65
|
} catch {
|
|
63
66
|
return;
|
|
64
67
|
}
|
|
65
|
-
if (api.
|
|
68
|
+
if (api.ui.done) return;
|
|
66
69
|
if (plan.action === 'install') {
|
|
67
70
|
api.state.installPromise = runInstall(api, plan.latest);
|
|
68
71
|
await api.state.installPromise;
|
|
@@ -73,17 +76,17 @@ const runOpenUpdate = async (api) => {
|
|
|
73
76
|
api.state.to = plan.latest;
|
|
74
77
|
api.state.offer = true;
|
|
75
78
|
maybePrompt(api);
|
|
76
|
-
api.
|
|
79
|
+
api.ui.paint();
|
|
77
80
|
};
|
|
78
81
|
|
|
79
82
|
const openUpdate = (api) => {
|
|
80
|
-
if (api.
|
|
83
|
+
if (api.ui.updateOpts === false) return Promise.resolve();
|
|
81
84
|
if (api.state.checkPromise) return api.state.checkPromise;
|
|
82
85
|
api.state.checkPromise = runOpenUpdate(api);
|
|
83
86
|
return api.state.checkPromise;
|
|
84
87
|
};
|
|
85
88
|
|
|
86
|
-
const createUpdateCoordinator = (
|
|
89
|
+
const createUpdateCoordinator = (ui) => {
|
|
87
90
|
const state = {
|
|
88
91
|
offer: false,
|
|
89
92
|
from: '',
|
|
@@ -91,35 +94,19 @@ const createUpdateCoordinator = (options) => {
|
|
|
91
94
|
checkPromise: null,
|
|
92
95
|
installPromise: null,
|
|
93
96
|
};
|
|
94
|
-
const api = {
|
|
95
|
-
return
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
return state.from;
|
|
104
|
-
},
|
|
105
|
-
get to() {
|
|
106
|
-
return state.to;
|
|
97
|
+
const api = { ui, state };
|
|
98
|
+
return stateView(
|
|
99
|
+
state,
|
|
100
|
+
{
|
|
101
|
+
reset: () => reset(state),
|
|
102
|
+
maybePrompt: () => maybePrompt(api),
|
|
103
|
+
accept: () => acceptUpdate(api),
|
|
104
|
+
decline: () => declineUpdate(api),
|
|
105
|
+
open: () => openUpdate(api),
|
|
107
106
|
},
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
get installPromise() {
|
|
112
|
-
return state.installPromise;
|
|
113
|
-
},
|
|
114
|
-
set installPromise(value) {
|
|
115
|
-
state.installPromise = value;
|
|
116
|
-
},
|
|
117
|
-
reset: () => reset(state),
|
|
118
|
-
maybePrompt: () => maybePrompt(api),
|
|
119
|
-
accept: () => acceptUpdate(api),
|
|
120
|
-
decline: () => declineUpdate(api),
|
|
121
|
-
open: () => openUpdate(api),
|
|
122
|
-
};
|
|
107
|
+
['offer', 'installPromise'],
|
|
108
|
+
['from', 'to', 'checkPromise'],
|
|
109
|
+
);
|
|
123
110
|
};
|
|
124
111
|
|
|
125
112
|
module.exports = { createUpdateCoordinator };
|
package/lib/session/view.js
CHANGED
|
@@ -6,11 +6,8 @@ const files = require('../files.js');
|
|
|
6
6
|
const { fileEntries, REPO_TODOS_LABEL } = files;
|
|
7
7
|
const review = require('../review.js');
|
|
8
8
|
const { noteCounts } = review;
|
|
9
|
-
|
|
10
|
-
const
|
|
11
|
-
'github-pr': 'pr',
|
|
12
|
-
'gitlab-mr': 'mr',
|
|
13
|
-
};
|
|
9
|
+
const diff = require('../diff/diff.js');
|
|
10
|
+
const { unitLines, itemsForPath, splitEditor, mergeEditRows } = diff;
|
|
14
11
|
|
|
15
12
|
const relabelStatus = (entries, from, to) => {
|
|
16
13
|
if (!to) return entries;
|
|
@@ -43,14 +40,60 @@ const todosEntry = (counts) => ({
|
|
|
43
40
|
unstaged: 0,
|
|
44
41
|
});
|
|
45
42
|
|
|
43
|
+
const blankEntry = (rel) => ({
|
|
44
|
+
path: rel,
|
|
45
|
+
origins: [],
|
|
46
|
+
firstIndex: 0,
|
|
47
|
+
openIndex: 0,
|
|
48
|
+
remaining: 0,
|
|
49
|
+
added: 0,
|
|
50
|
+
removed: 0,
|
|
51
|
+
stagedAdded: 0,
|
|
52
|
+
stagedRemoved: 0,
|
|
53
|
+
unstagedAdded: 0,
|
|
54
|
+
unstagedRemoved: 0,
|
|
55
|
+
staged: 0,
|
|
56
|
+
unstaged: 0,
|
|
57
|
+
date: '',
|
|
58
|
+
status: '',
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const listedNames = (api) => {
|
|
62
|
+
const repo = api.ui.repo;
|
|
63
|
+
if (!repo || typeof repo.listFiles !== 'function') return [];
|
|
64
|
+
try {
|
|
65
|
+
return repo.listFiles(api.ui.top || api.ui.cwd, api.ui.paths ?? [], {
|
|
66
|
+
commit: api.ui.rev,
|
|
67
|
+
});
|
|
68
|
+
} catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
const mergeUnitFiles = (entries, names) => {
|
|
74
|
+
const have = new Set();
|
|
75
|
+
for (const entry of entries) have.add(entry.path);
|
|
76
|
+
const extra = [];
|
|
77
|
+
for (const rel of names) {
|
|
78
|
+
if (!rel || have.has(rel)) continue;
|
|
79
|
+
extra.push(blankEntry(rel));
|
|
80
|
+
}
|
|
81
|
+
const merged = [...entries, ...extra];
|
|
82
|
+
merged.sort((left, right) => left.path.localeCompare(right.path, 'en'));
|
|
83
|
+
return merged;
|
|
84
|
+
};
|
|
85
|
+
|
|
46
86
|
const fileList = (api, notes = noteCounts(api.review.store)) => {
|
|
47
87
|
const entries = fileEntries(api.collection.items);
|
|
48
88
|
const sourceLabel = api.ui.sourceLabel;
|
|
49
89
|
const from = sourceLabel ? 'pr' : 'commit';
|
|
50
90
|
const to = sourceLabel || api.ui.revShort;
|
|
51
91
|
const labeled = relabelStatus(entries, from, to);
|
|
52
|
-
|
|
53
|
-
|
|
92
|
+
const names = listedNames(api);
|
|
93
|
+
const scoped =
|
|
94
|
+
api.nav.fileScope === 'file' ? mergeUnitFiles(labeled, names) : labeled;
|
|
95
|
+
scoped.unshift(todosEntry(notes));
|
|
96
|
+
return scoped;
|
|
54
97
|
};
|
|
55
98
|
|
|
56
99
|
const counts = (api, notes = noteCounts(api.review.store)) => {
|
|
@@ -76,11 +119,34 @@ const counts = (api, notes = noteCounts(api.review.store)) => {
|
|
|
76
119
|
|
|
77
120
|
const viewStatus = (api) => api.ui.status || api.ui.busy;
|
|
78
121
|
|
|
122
|
+
const unitFileText = (api) => {
|
|
123
|
+
const rel = api.nav.reviewPath;
|
|
124
|
+
if (!rel) return '';
|
|
125
|
+
const repo = api.ui.repo;
|
|
126
|
+
if (repo && typeof repo.fileText === 'function') {
|
|
127
|
+
return repo.fileText(api.ui.top, rel, api.ui.rev);
|
|
128
|
+
}
|
|
129
|
+
return '';
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const unitBodyLines = (api) => {
|
|
133
|
+
if (api.nav.pane !== 'unit') return [];
|
|
134
|
+
const rel = api.nav.reviewPath;
|
|
135
|
+
const group = itemsForPath(api.collection.items, rel);
|
|
136
|
+
const live = api.composer.liveFileText();
|
|
137
|
+
if (live !== null) {
|
|
138
|
+
const base = api.composer.fileEditRows();
|
|
139
|
+
if (base && base.length) return mergeEditRows(base, splitEditor(live));
|
|
140
|
+
return mergeEditRows(unitLines(live, group), splitEditor(live));
|
|
141
|
+
}
|
|
142
|
+
return unitLines(unitFileText(api), group);
|
|
143
|
+
};
|
|
144
|
+
|
|
79
145
|
const view = (api) => {
|
|
80
|
-
const { composer, nav, ui, gitBranches, updater } = api;
|
|
146
|
+
const { composer, nav, ui, gitBranches, commits, updater } = api;
|
|
81
147
|
const templates = composer.shownTemplates();
|
|
82
148
|
const change = ui.change;
|
|
83
|
-
const sourceKind = (change &&
|
|
149
|
+
const sourceKind = (change && change.source) || '';
|
|
84
150
|
const notes = noteCounts(api.review.store);
|
|
85
151
|
return {
|
|
86
152
|
item: ui.current(),
|
|
@@ -94,12 +160,17 @@ const view = (api) => {
|
|
|
94
160
|
counts: counts(api, notes),
|
|
95
161
|
selection: nav.selection,
|
|
96
162
|
pane: nav.pane,
|
|
163
|
+
fileScope: nav.fileScope,
|
|
97
164
|
files: fileList(api, notes),
|
|
98
165
|
fileCursor: nav.fileCursor,
|
|
99
166
|
reviewPath: nav.reviewPath,
|
|
167
|
+
unitLine: nav.unitLine,
|
|
168
|
+
unitLines: unitBodyLines(api),
|
|
100
169
|
branch: ui.branch,
|
|
101
170
|
branches: gitBranches.branches,
|
|
102
171
|
branchCursor: nav.branchCursor,
|
|
172
|
+
commits: commits.commits,
|
|
173
|
+
commitCursor: nav.commitCursor,
|
|
103
174
|
repoName: repoNameOf(api),
|
|
104
175
|
revShort: ui.revShort || '',
|
|
105
176
|
sourceLabel: ui.sourceLabel || '',
|
|
@@ -115,7 +186,7 @@ const view = (api) => {
|
|
|
115
186
|
templateIndex: composer.clampedTemplateIndex(templates),
|
|
116
187
|
updateFrom: updater.from,
|
|
117
188
|
updateTo: updater.to,
|
|
118
|
-
dropName: gitBranches.dropName,
|
|
189
|
+
dropName: gitBranches.dropName || commits.dropName,
|
|
119
190
|
};
|
|
120
191
|
};
|
|
121
192
|
|
|
@@ -126,4 +197,4 @@ const createView = (api) => ({
|
|
|
126
197
|
view: () => view(api),
|
|
127
198
|
});
|
|
128
199
|
|
|
129
|
-
module.exports = {
|
|
200
|
+
module.exports = { relabelStatus, createView };
|
package/lib/session/watch.js
CHANGED
|
@@ -74,12 +74,13 @@ const closeWatcher = (watcher) => {
|
|
|
74
74
|
|
|
75
75
|
const schedule = (state) => {
|
|
76
76
|
if (state.closed) return;
|
|
77
|
-
if (state.timer !== null)
|
|
78
|
-
state.timer =
|
|
77
|
+
if (state.timer !== null) clearTimeout(state.timer);
|
|
78
|
+
state.timer = setTimeout(() => {
|
|
79
79
|
state.timer = null;
|
|
80
80
|
if (state.closed) return;
|
|
81
81
|
state.onChange();
|
|
82
82
|
}, state.debounceMs);
|
|
83
|
+
if (typeof state.timer.unref === 'function') state.timer.unref();
|
|
83
84
|
};
|
|
84
85
|
|
|
85
86
|
const handleWatchEvent = (state, dir, eventType, filename) => {
|
|
@@ -100,9 +101,9 @@ const watchOne = (state, target, extra = {}) => {
|
|
|
100
101
|
const onEvent = (eventType, filename) => {
|
|
101
102
|
handleWatchEvent(state, target, eventType, filename);
|
|
102
103
|
};
|
|
103
|
-
const watcher =
|
|
104
|
+
const watcher = fs.watch(
|
|
104
105
|
target,
|
|
105
|
-
{ persistent:
|
|
106
|
+
{ persistent: false, encoding: 'utf8', ...extra },
|
|
106
107
|
onEvent,
|
|
107
108
|
);
|
|
108
109
|
if (typeof watcher.on === 'function') {
|
|
@@ -120,7 +121,7 @@ const watchTree = (state, dir) => {
|
|
|
120
121
|
if (state.recursive) return;
|
|
121
122
|
let entries;
|
|
122
123
|
try {
|
|
123
|
-
entries =
|
|
124
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
124
125
|
} catch {
|
|
125
126
|
return;
|
|
126
127
|
}
|
|
@@ -135,7 +136,7 @@ const maybeWatchNewDir = (state, full) => {
|
|
|
135
136
|
if (state.recursive) return;
|
|
136
137
|
let stat;
|
|
137
138
|
try {
|
|
138
|
-
stat =
|
|
139
|
+
stat = fs.statSync(full);
|
|
139
140
|
} catch {
|
|
140
141
|
return;
|
|
141
142
|
}
|
|
@@ -148,15 +149,11 @@ const watchScope = (state, spec) => {
|
|
|
148
149
|
const target = path.resolve(state.root, spec);
|
|
149
150
|
let stat;
|
|
150
151
|
try {
|
|
151
|
-
stat =
|
|
152
|
+
stat = fs.statSync(target);
|
|
152
153
|
} catch {
|
|
153
|
-
watchOne(state, path.dirname(target));
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
if (stat.isDirectory()) {
|
|
157
|
-
watchTree(state, target);
|
|
158
|
-
return;
|
|
154
|
+
return void watchOne(state, path.dirname(target));
|
|
159
155
|
}
|
|
156
|
+
if (stat.isDirectory()) return void watchTree(state, target);
|
|
160
157
|
watchOne(state, path.dirname(target));
|
|
161
158
|
};
|
|
162
159
|
|
|
@@ -168,7 +165,7 @@ const watchGit = (state) => {
|
|
|
168
165
|
|
|
169
166
|
const stopState = (state) => {
|
|
170
167
|
state.closed = true;
|
|
171
|
-
if (state.timer !== null)
|
|
168
|
+
if (state.timer !== null) clearTimeout(state.timer);
|
|
172
169
|
state.timer = null;
|
|
173
170
|
while (state.watchers.length) closeWatcher(state.watchers.pop());
|
|
174
171
|
state.watched.clear();
|
|
@@ -186,11 +183,6 @@ const createDiskWatcher = (options) => {
|
|
|
186
183
|
const recursive = options.recursive ?? canWatchRecursive();
|
|
187
184
|
const state = {
|
|
188
185
|
root: path.resolve(options.root),
|
|
189
|
-
watchFn: options.watch ?? fs.watch,
|
|
190
|
-
readdirSync: options.readdirSync ?? fs.readdirSync,
|
|
191
|
-
statSync: options.statSync ?? fs.statSync,
|
|
192
|
-
setTimeoutFn: options.setTimeout ?? setTimeout,
|
|
193
|
-
clearTimeoutFn: options.clearTimeout ?? clearTimeout,
|
|
194
186
|
debounceMs: options.debounceMs ?? DEBOUNCE_MS,
|
|
195
187
|
onChange: options.onChange,
|
|
196
188
|
recursive,
|