reslop 0.2.1 → 0.2.4
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 +19 -4
- package/bin/reslop.js +4 -2
- package/lib/ansi.js +120 -4
- package/lib/api.js +2 -1
- package/lib/cli.js +21 -9
- package/lib/clipboard.js +6 -3
- package/lib/deps-diff.js +61 -115
- package/lib/deps.js +57 -54
- package/lib/detect.js +4 -2
- package/lib/diff/align.js +2 -1
- package/lib/diff/diff.js +6 -3
- package/lib/diff/display.js +2 -1
- package/lib/diff/inline.js +2 -1
- package/lib/diff/patch.js +2 -1
- package/lib/diff/unit.js +4 -2
- package/lib/editor.js +22 -4
- package/lib/files.js +38 -12
- package/lib/git-branches.js +150 -165
- package/lib/git-deps.js +20 -60
- package/lib/git-worktree.js +508 -0
- package/lib/git.js +23 -580
- package/lib/github.js +2 -1
- package/lib/gitlab.js +2 -1
- package/lib/highlight/bash.js +2 -1
- package/lib/highlight/css.js +2 -1
- package/lib/highlight/csv.js +2 -1
- package/lib/highlight/dot.js +2 -1
- package/lib/highlight/html.js +2 -1
- package/lib/highlight/json.js +2 -1
- package/lib/highlight/log.js +2 -1
- package/lib/highlight/txt.js +2 -1
- package/lib/highlight.js +2 -1
- package/lib/keys.js +12 -6
- package/lib/manifest.js +4 -2
- package/lib/npm-commands.js +592 -0
- package/lib/remote.js +20 -13
- package/lib/render/branches.js +25 -41
- package/lib/render/chrome.js +163 -128
- package/lib/render/commits.js +727 -65
- package/lib/render/diff.js +130 -47
- package/lib/render/files.js +4 -12
- package/lib/render/frame.js +41 -13
- package/lib/render/notes.js +4 -11
- package/lib/render/npm.js +216 -0
- package/lib/render/present.js +2 -1
- package/lib/render/primitives.js +34 -50
- package/lib/render/render.js +2 -1
- package/lib/render/unit.js +14 -11
- package/lib/report-model.js +129 -0
- package/lib/report-parse.js +89 -0
- package/lib/report-render.js +104 -0
- package/lib/report-run.js +166 -0
- package/lib/report-test.js +507 -0
- package/lib/report-tools.js +337 -0
- package/lib/review-merge.js +2 -1
- package/lib/review.js +6 -2
- package/lib/session/actions.js +77 -44
- package/lib/session/branches.js +13 -49
- package/lib/session/change-actions.js +2 -1
- package/lib/session/commit.js +113 -53
- package/lib/session/compose.js +83 -14
- package/lib/session/dispatch.js +42 -22
- package/lib/session/files-pane.js +16 -21
- package/lib/session/items.js +6 -3
- package/lib/session/lifecycle.js +12 -5
- package/lib/session/load.js +4 -2
- package/lib/session/navigation.js +2 -0
- package/lib/session/npm.js +388 -0
- package/lib/session/ops.js +15 -1
- package/lib/session/pointer.js +30 -3
- package/lib/session/review.js +2 -1
- package/lib/session/terminal.js +4 -2
- package/lib/session/update.js +2 -1
- package/lib/session/view.js +41 -4
- package/lib/session.js +44 -22
- package/lib/source.js +6 -3
- package/lib/update.js +5 -9
- package/lib/utilities.js +4 -2
- package/lib/wrap.js +3 -1
- package/package.json +6 -6
package/lib/remote.js
CHANGED
|
@@ -1,10 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
5
|
-
const
|
|
6
|
-
const {
|
|
7
|
-
const
|
|
3
|
+
const metautil = require('metautil');
|
|
4
|
+
const { delay, jsonParse, isHashObject } = metautil;
|
|
5
|
+
const utilities = require('./utilities.js');
|
|
6
|
+
const { oneLine } = utilities;
|
|
7
|
+
const files = require('./files.js');
|
|
8
|
+
const { itemPath, isPathInScope } = files;
|
|
9
|
+
const diff = require('./diff/diff.js');
|
|
10
|
+
const { parseDiff, itemsFromFiles, isCtxType } = diff;
|
|
11
|
+
const deps = require('./deps.js');
|
|
12
|
+
const { foldDepItems } = deps;
|
|
8
13
|
|
|
9
14
|
const RETRY_ATTEMPTS = 3;
|
|
10
15
|
const RETRY_DELAY_MS = 200;
|
|
@@ -323,13 +328,15 @@ const changeFiles = (items) => {
|
|
|
323
328
|
|
|
324
329
|
const loadChange = async (ref, options, source) => {
|
|
325
330
|
const { client, url, diffUrl = url, diffAccept } = source;
|
|
326
|
-
const
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
331
|
+
const {
|
|
332
|
+
cwd,
|
|
333
|
+
paths = [],
|
|
334
|
+
fetch = globalThis.fetch,
|
|
335
|
+
token = '',
|
|
336
|
+
retry = {},
|
|
337
|
+
signal,
|
|
338
|
+
} = options;
|
|
339
|
+
const request = { fetch, token, retry, signal };
|
|
333
340
|
const jsonPage = await client.request(url, request);
|
|
334
341
|
const data = client.parseJson(jsonPage.body, `${client.subject} payload`);
|
|
335
342
|
const diffPage = await client.request(diffUrl, {
|
|
@@ -348,7 +355,7 @@ const loadChange = async (ref, options, source) => {
|
|
|
348
355
|
if (isAbortError(error, request.signal)) throw error;
|
|
349
356
|
// Discussion import is optional.
|
|
350
357
|
}
|
|
351
|
-
return { top:
|
|
358
|
+
return { top: cwd, items, sourceLabel, change, imported };
|
|
352
359
|
};
|
|
353
360
|
|
|
354
361
|
module.exports = {
|
package/lib/render/branches.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const ansi = require('../ansi.js');
|
|
4
|
+
const files = require('../files.js');
|
|
4
5
|
const primitives = require('./primitives.js');
|
|
6
|
+
const { shortAge } = files;
|
|
5
7
|
const { measureColumns, padColumns, joinColumns, paintColumns } = primitives;
|
|
6
8
|
|
|
7
9
|
const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
|
|
8
|
-
const {
|
|
10
|
+
const { FILE_MARK } = primitives;
|
|
9
11
|
const { paintCursorList, paintListEdit } = primitives;
|
|
10
|
-
const { branchLabel
|
|
12
|
+
const { branchLabel } = primitives;
|
|
11
13
|
|
|
12
14
|
const BRANCH_COL_ALIGN = {
|
|
13
15
|
sha: 'end',
|
|
@@ -31,13 +33,11 @@ const BRANCH_CURRENT_COL_FG = {
|
|
|
31
33
|
};
|
|
32
34
|
|
|
33
35
|
const BRANCH_NAME_MAX = 24;
|
|
34
|
-
const BRANCH_EDGE = 2;
|
|
35
36
|
const BRANCH_TAIL = 1;
|
|
36
37
|
const branchNameChip = (entry, rowBg) => {
|
|
37
38
|
if (entry.current) {
|
|
38
39
|
return {
|
|
39
40
|
nameFg: THEME.mutedFg,
|
|
40
|
-
wrapFg: THEME.mutedFg,
|
|
41
41
|
nameBg: THEME.currentBg,
|
|
42
42
|
nameBold: false,
|
|
43
43
|
};
|
|
@@ -45,14 +45,12 @@ const branchNameChip = (entry, rowBg) => {
|
|
|
45
45
|
if (entry.isDefault) {
|
|
46
46
|
return {
|
|
47
47
|
nameFg: THEME.warnFg,
|
|
48
|
-
wrapFg: THEME.mutedFg,
|
|
49
48
|
nameBg: rowBg,
|
|
50
49
|
nameBold: true,
|
|
51
50
|
};
|
|
52
51
|
}
|
|
53
52
|
return {
|
|
54
53
|
nameFg: THEME.buttonHotFg,
|
|
55
|
-
wrapFg: THEME.mutedFg,
|
|
56
54
|
nameBg: rowBg,
|
|
57
55
|
nameBold: false,
|
|
58
56
|
};
|
|
@@ -65,15 +63,21 @@ const branchRowFg = (entry, selected) => {
|
|
|
65
63
|
|
|
66
64
|
const branchRowFields = (entry) => {
|
|
67
65
|
const sha = entry.sha ?? '';
|
|
68
|
-
const date = entry.date ?? '';
|
|
66
|
+
const date = shortAge(entry.date ?? '');
|
|
69
67
|
const gone = entry.gone ? 'gone' : '';
|
|
70
68
|
const ahead = entry.ahead ? `⇡${entry.ahead}` : '';
|
|
71
69
|
const behind = entry.behind ? `⇣${entry.behind}` : '';
|
|
72
70
|
return { sha, ahead, behind, gone, date };
|
|
73
71
|
};
|
|
74
72
|
|
|
73
|
+
const branchRowBg = (entry, selected) => {
|
|
74
|
+
if (entry.current) return THEME.currentBg;
|
|
75
|
+
if (selected) return THEME.buttonBg;
|
|
76
|
+
return THEME.ctxBg;
|
|
77
|
+
};
|
|
78
|
+
|
|
75
79
|
const paintBranchRow = (entry, width, color, selected, cols) => {
|
|
76
|
-
const inner = Math.max(1, width -
|
|
80
|
+
const inner = Math.max(1, width - BRANCH_TAIL);
|
|
77
81
|
const mark = selected ? FILE_MARK : ' ';
|
|
78
82
|
const prefix = ` ${mark} `;
|
|
79
83
|
const parts = padColumns(branchRowFields(entry), cols, BRANCH_COL_ALIGN);
|
|
@@ -91,35 +95,23 @@ const paintBranchRow = (entry, width, color, selected, cols) => {
|
|
|
91
95
|
const after = `${' '.repeat(namePad)}${gap}${subject}${' '.repeat(pad)}`;
|
|
92
96
|
const left = `${prefix}${label}${after}`;
|
|
93
97
|
const fgRgb = branchRowFg(entry, selected);
|
|
94
|
-
const
|
|
95
|
-
const chip = branchNameChip(entry,
|
|
96
|
-
const
|
|
97
|
-
const markBg = entry.current ? THEME.currentBg : bgRgb;
|
|
98
|
+
const rowBg = branchRowBg(entry, selected);
|
|
99
|
+
const chip = branchNameChip(entry, rowBg);
|
|
100
|
+
const toneFg = entry.current ? THEME.headerFg : fgRgb;
|
|
98
101
|
const tail = ' '.repeat(BRANCH_TAIL);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
out += paint(
|
|
103
|
-
out +=
|
|
104
|
-
label,
|
|
105
|
-
chip.nameFg,
|
|
106
|
-
chip.wrapFg,
|
|
107
|
-
chip.nameBg,
|
|
108
|
-
color,
|
|
109
|
-
chip.nameBold,
|
|
110
|
-
);
|
|
111
|
-
out += paint(after, markFg, markBg, color);
|
|
102
|
+
if (!color) return `${left}${suffix}${tail}`;
|
|
103
|
+
let out = `${seq(toneFg, rowBg)}${EL}`;
|
|
104
|
+
out += paint(prefix, toneFg, rowBg, color);
|
|
105
|
+
out += paint(label, chip.nameFg, chip.nameBg, color, chip.nameBold);
|
|
106
|
+
out += paint(after, toneFg, rowBg, color);
|
|
112
107
|
const tones = entry.current
|
|
113
108
|
? { ...BRANCH_COL_FG, ...BRANCH_CURRENT_COL_FG }
|
|
114
109
|
: BRANCH_COL_FG;
|
|
115
|
-
out += paintColumns(parts, cols,
|
|
116
|
-
out += paint(tail,
|
|
117
|
-
return out
|
|
110
|
+
out += paintColumns(parts, cols, toneFg, rowBg, color, tones);
|
|
111
|
+
out += paint(tail, toneFg, rowBg, color);
|
|
112
|
+
return out;
|
|
118
113
|
};
|
|
119
114
|
|
|
120
|
-
const paintBranchEdit = (compose, width, color) =>
|
|
121
|
-
paintListEdit(compose, width, color, BRANCH_EDGE);
|
|
122
|
-
|
|
123
115
|
const paintBodyBranches = (view, width, color, bodyH, headerLines) => {
|
|
124
116
|
const branches = view.branches ?? [];
|
|
125
117
|
const cols = measureColumns(branches, branchRowFields, BRANCH_COL_ALIGN);
|
|
@@ -131,10 +123,10 @@ const paintBodyBranches = (view, width, color, bodyH, headerLines) => {
|
|
|
131
123
|
const cursor = view.branchCursor ?? 0;
|
|
132
124
|
const compose = view.compose;
|
|
133
125
|
const editing = compose && compose.kind === 'branch';
|
|
134
|
-
const tail = editing ?
|
|
126
|
+
const tail = editing ? paintListEdit(compose, width, color) : null;
|
|
135
127
|
const paintRow = (entry, rowWidth, rowColor, selected) =>
|
|
136
128
|
paintBranchRow(entry, rowWidth, rowColor, selected, cols);
|
|
137
|
-
|
|
129
|
+
return paintCursorList(
|
|
138
130
|
branches,
|
|
139
131
|
cursor,
|
|
140
132
|
width,
|
|
@@ -145,14 +137,6 @@ const paintBodyBranches = (view, width, color, bodyH, headerLines) => {
|
|
|
145
137
|
tail,
|
|
146
138
|
view.listScroll,
|
|
147
139
|
);
|
|
148
|
-
return paneResult({
|
|
149
|
-
body: painted.body,
|
|
150
|
-
fileHits: painted.fileHits,
|
|
151
|
-
todoOwners: [],
|
|
152
|
-
cursor: painted.cursor,
|
|
153
|
-
splitBody: false,
|
|
154
|
-
listScroll: painted.offset,
|
|
155
|
-
});
|
|
156
140
|
};
|
|
157
141
|
|
|
158
142
|
module.exports = { paintBodyBranches };
|
package/lib/render/chrome.js
CHANGED
|
@@ -5,26 +5,38 @@ const keys = require('../keys.js');
|
|
|
5
5
|
const files = require('../files.js');
|
|
6
6
|
const primitives = require('./primitives.js');
|
|
7
7
|
|
|
8
|
-
const { itemPath,
|
|
8
|
+
const { itemPath, REPO_TODOS_LABEL, isTodosEntry, fileTotals } = files;
|
|
9
9
|
const { ACTION_IDS, BRANCH_ACTIONS, COMMIT_ACTIONS, actionLetter } = keys;
|
|
10
10
|
const { buttonWord } = keys;
|
|
11
11
|
const { FILES_HIDDEN, FILES_GIT_DISABLED, BRANCHES_DISABLED } = keys;
|
|
12
12
|
const { COMMITS_DISABLED, TODO_DISABLED, DIFF_DISABLED, UNIT_DISABLED } = keys;
|
|
13
|
+
const { NPM_DISABLED } = keys;
|
|
13
14
|
const { diffGitDisabled, branchGitDisabled, commitGitDisabled } = keys;
|
|
14
15
|
const { CONFIRM, confirmPrefix, promptFromChoices } = keys;
|
|
15
16
|
const { THEME, paint, visibleWidth } = ansi;
|
|
16
17
|
const { truncateVisible, fg, RESET, EL, seq } = ansi;
|
|
17
|
-
const { fill, branchLabel
|
|
18
|
+
const { fill, branchLabel } = primitives;
|
|
18
19
|
|
|
19
20
|
const HEADER_ROLE_FG = {
|
|
20
21
|
repo: THEME.headerRepoFg,
|
|
21
22
|
dir: THEME.headerDirFg,
|
|
22
23
|
slash: THEME.headerSlashFg,
|
|
23
24
|
file: THEME.headerFileFg,
|
|
24
|
-
meta: THEME.headerMetaFg,
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
-
const
|
|
27
|
+
const WAVE = '·•●•·';
|
|
28
|
+
|
|
29
|
+
const busyBars = (wave) => {
|
|
30
|
+
const n = wave.length;
|
|
31
|
+
const frames = new Array(n);
|
|
32
|
+
for (let frame = 0; frame < n; frame++) {
|
|
33
|
+
const start = (n - frame) % n;
|
|
34
|
+
frames[frame] = wave.slice(start) + wave.slice(0, start);
|
|
35
|
+
}
|
|
36
|
+
return frames;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
const BUSY_BAR = busyBars(WAVE);
|
|
28
40
|
const BUSY_PROGRESS = [
|
|
29
41
|
'pulling',
|
|
30
42
|
'pushing',
|
|
@@ -34,6 +46,7 @@ const BUSY_PROGRESS = [
|
|
|
34
46
|
'npm i',
|
|
35
47
|
'npm uninstall',
|
|
36
48
|
'npm audit fix',
|
|
49
|
+
'running',
|
|
37
50
|
'checking out',
|
|
38
51
|
'creating branch',
|
|
39
52
|
'rebasing',
|
|
@@ -105,21 +118,12 @@ const dropPrompt = (name) => ` drop ${name}? y/n`;
|
|
|
105
118
|
const PUSH_REJECT_PREFIX = CONFIRM.confirmPush.prefix;
|
|
106
119
|
const PUSH_PROMPT = `${PUSH_REJECT_PREFIX} force push`;
|
|
107
120
|
|
|
121
|
+
const BUTTON_LEAD = ' ';
|
|
108
122
|
const BUTTON_GAP = ' ';
|
|
123
|
+
const BUTTON_EDGE = 1;
|
|
124
|
+
const buttonGap = (index) => (index ? BUTTON_GAP : BUTTON_LEAD);
|
|
109
125
|
const LOGO = '👁️';
|
|
110
126
|
|
|
111
|
-
const ORIGIN_LABEL = {
|
|
112
|
-
commit: (view) => view.revShort || 'commit',
|
|
113
|
-
pr: (view) => view.sourceLabel || 'pr',
|
|
114
|
-
todo: () => 'todo',
|
|
115
|
-
};
|
|
116
|
-
|
|
117
|
-
const originLabel = (origin, view) => {
|
|
118
|
-
const named = ORIGIN_LABEL[origin];
|
|
119
|
-
if (named) return named(view);
|
|
120
|
-
return origin ?? 'unknown';
|
|
121
|
-
};
|
|
122
|
-
|
|
123
127
|
const pathSegments = (rel) => {
|
|
124
128
|
const segs = [];
|
|
125
129
|
if (!rel) return segs;
|
|
@@ -137,126 +141,59 @@ const pathSegments = (rel) => {
|
|
|
137
141
|
return segs;
|
|
138
142
|
};
|
|
139
143
|
|
|
140
|
-
const headerTarget = (view) => {
|
|
141
|
-
const pane = view.pane ?? 'diff';
|
|
142
|
-
if (pane === 'files') {
|
|
143
|
-
const files = view.files ?? [];
|
|
144
|
-
const cursor = view.fileCursor ?? 0;
|
|
145
|
-
const entry = files[cursor];
|
|
146
|
-
const total = files.length;
|
|
147
|
-
const n = total ? cursor + 1 : 0;
|
|
148
|
-
if (!entry) return { path: '', origin: '', n, total };
|
|
149
|
-
if (isTodosEntry(entry)) {
|
|
150
|
-
return { path: TODO_FILE, origin: 'todo', n, total };
|
|
151
|
-
}
|
|
152
|
-
return { path: entry.path, origin: entry.status, n, total };
|
|
153
|
-
}
|
|
154
|
-
if (pane === 'branches') {
|
|
155
|
-
const compose = view.compose;
|
|
156
|
-
const branches = view.branches ?? [];
|
|
157
|
-
if (compose && compose.kind === 'branch') {
|
|
158
|
-
const total = branches.length + 1;
|
|
159
|
-
return { path: compose.text || '', origin: 'new', n: total, total };
|
|
160
|
-
}
|
|
161
|
-
const cursor = view.branchCursor ?? 0;
|
|
162
|
-
const entry = branches[cursor];
|
|
163
|
-
const total = branches.length;
|
|
164
|
-
const n = total ? cursor + 1 : 0;
|
|
165
|
-
if (!entry) return { path: '', origin: 'branch', n, total };
|
|
166
|
-
const origin = entry.current ? 'current' : 'branch';
|
|
167
|
-
return { path: entry.name, origin, n, total };
|
|
168
|
-
}
|
|
169
|
-
if (pane === 'commits') {
|
|
170
|
-
const compose = view.compose;
|
|
171
|
-
const commits = view.commits ?? [];
|
|
172
|
-
if (compose && compose.kind === 'commit') {
|
|
173
|
-
const path = `${compose.text || ''}`.replaceAll('\n', ' ');
|
|
174
|
-
if (compose.commitKind === 'amend' || compose.commitKind === 'reword') {
|
|
175
|
-
const cursor = view.commitCursor ?? 0;
|
|
176
|
-
const entry = commits[cursor];
|
|
177
|
-
const total = commits.length;
|
|
178
|
-
const n = total ? cursor + 1 : 0;
|
|
179
|
-
if (!entry) return { path, origin: 'commit', n, total };
|
|
180
|
-
const origin = entry.head ? 'HEAD' : 'commit';
|
|
181
|
-
return { path, origin, n, total };
|
|
182
|
-
}
|
|
183
|
-
const total = commits.length + 1;
|
|
184
|
-
return { path, origin: 'new', n: 1, total };
|
|
185
|
-
}
|
|
186
|
-
const cursor = view.commitCursor ?? 0;
|
|
187
|
-
const entry = commits[cursor];
|
|
188
|
-
const total = commits.length;
|
|
189
|
-
const n = total ? cursor + 1 : 0;
|
|
190
|
-
if (!entry) return { path: '', origin: 'commit', n, total };
|
|
191
|
-
const origin = entry.head ? 'HEAD' : 'commit';
|
|
192
|
-
return { path: entry.subject ?? '', origin, n, total };
|
|
193
|
-
}
|
|
194
|
-
if (pane === 'unit') {
|
|
195
|
-
const path = view.reviewPath ?? '';
|
|
196
|
-
const lines = view.unitLines ?? [];
|
|
197
|
-
const top = Math.max(0, (view.scroll ?? 0) - 1);
|
|
198
|
-
const n = lines.length ? Math.min(lines.length, top + 1) : 0;
|
|
199
|
-
const origin = view.item ? originLabel(view.item.origin, view) : 'unit';
|
|
200
|
-
return { path, origin, n, total: lines.length };
|
|
201
|
-
}
|
|
202
|
-
if (!view.item) return { path: '', origin: '', n: 0, total: view.total ?? 0 };
|
|
203
|
-
if (view.item.origin === 'todo') {
|
|
204
|
-
return { path: TODO_FILE, origin: 'todo', n: 1, total: 1 };
|
|
205
|
-
}
|
|
206
|
-
return {
|
|
207
|
-
path: itemPath(view.item),
|
|
208
|
-
origin: originLabel(view.item.origin, view),
|
|
209
|
-
n: view.index + 1,
|
|
210
|
-
total: view.total,
|
|
211
|
-
};
|
|
212
|
-
};
|
|
213
|
-
|
|
214
144
|
const headerScreen = (view) => {
|
|
215
145
|
const pane = view.pane ?? 'diff';
|
|
216
146
|
if (pane === 'branches') return 'branches';
|
|
217
|
-
if (pane === 'commits')
|
|
147
|
+
if (pane === 'commits') {
|
|
148
|
+
const mode = view.commitView === 'full' ? 'full' : 'brief';
|
|
149
|
+
return `commits ${mode}`;
|
|
150
|
+
}
|
|
151
|
+
if (pane === 'npm') return 'npm';
|
|
218
152
|
if (pane === 'files') {
|
|
219
153
|
const files = view.files ?? [];
|
|
220
154
|
const entry = files[view.fileCursor ?? 0];
|
|
221
|
-
if (isTodosEntry(entry)) return
|
|
155
|
+
if (isTodosEntry(entry)) return REPO_TODOS_LABEL;
|
|
222
156
|
return '';
|
|
223
157
|
}
|
|
224
|
-
if (view.item && view.item.origin === 'todo') return
|
|
158
|
+
if (view.item && view.item.origin === 'todo') return REPO_TODOS_LABEL;
|
|
225
159
|
return '';
|
|
226
160
|
};
|
|
227
161
|
|
|
228
|
-
const
|
|
162
|
+
const headerTarget = (view) => {
|
|
163
|
+
const pane = view.pane ?? 'diff';
|
|
164
|
+
if (pane === 'files') {
|
|
165
|
+
const entry = (view.files ?? [])[view.fileCursor ?? 0];
|
|
166
|
+
if (!entry || isTodosEntry(entry)) return '';
|
|
167
|
+
return entry.path;
|
|
168
|
+
}
|
|
169
|
+
if (pane === 'unit') return view.reviewPath ?? '';
|
|
170
|
+
if (!view.item || view.item.origin === 'todo') return '';
|
|
171
|
+
return itemPath(view.item);
|
|
172
|
+
};
|
|
173
|
+
|
|
174
|
+
const headerLabel = (view) => headerScreen(view) || headerTarget(view);
|
|
229
175
|
|
|
230
176
|
const headerSep = (view) => (headerScreen(view) ? ': ' : '/');
|
|
231
177
|
|
|
232
|
-
const headerPrefix = (view) => {
|
|
178
|
+
const headerPrefix = (view, label) => {
|
|
233
179
|
const repo = view.repoName || '';
|
|
234
|
-
const target = headerTarget(view);
|
|
235
|
-
const label = headerLabel(view, target);
|
|
236
180
|
if (repo && label) return ` ${LOGO} reslop: ${repo}${headerSep(view)}`;
|
|
237
181
|
if (repo) return ` ${LOGO} reslop: ${repo} `;
|
|
238
182
|
return ` ${LOGO} reslop: `;
|
|
239
183
|
};
|
|
240
184
|
|
|
241
|
-
const headerSuffix = ({ origin, n, total }) =>
|
|
242
|
-
origin ? ` ${origin} ${n}/${total}` : ` ${n}/${total}`;
|
|
243
|
-
|
|
244
185
|
const headerText = (view) => {
|
|
245
|
-
const
|
|
246
|
-
|
|
247
|
-
const label = headerLabel(view, target);
|
|
248
|
-
return `${prefix}${label}${headerSuffix(target)}`;
|
|
186
|
+
const label = headerLabel(view);
|
|
187
|
+
return `${headerPrefix(view, label)}${label}`;
|
|
249
188
|
};
|
|
250
189
|
|
|
251
190
|
const headerPieces = (view, width) => {
|
|
252
191
|
const repo = view.repoName || '';
|
|
253
|
-
const
|
|
254
|
-
const path = headerLabel(view, target);
|
|
255
|
-
const suffix = headerSuffix(target);
|
|
192
|
+
const path = headerLabel(view);
|
|
256
193
|
const right = ' ';
|
|
257
194
|
const chrome = ` ${LOGO} reslop: `;
|
|
258
|
-
const prefixW = visibleWidth(headerPrefix(view));
|
|
259
|
-
const used = prefixW + visibleWidth(
|
|
195
|
+
const prefixW = visibleWidth(headerPrefix(view, path));
|
|
196
|
+
const used = prefixW + visibleWidth(right);
|
|
260
197
|
const room = Math.max(0, width - used);
|
|
261
198
|
const clipped = truncateVisible(path, room);
|
|
262
199
|
const pad = Math.max(0, room - visibleWidth(clipped));
|
|
@@ -277,7 +214,6 @@ const headerPieces = (view, width) => {
|
|
|
277
214
|
if (pad) {
|
|
278
215
|
pieces.push({ text: ' '.repeat(pad), role: 'chrome' });
|
|
279
216
|
}
|
|
280
|
-
pieces.push({ text: suffix, role: 'meta' });
|
|
281
217
|
pieces.push({ text: right, role: 'chrome' });
|
|
282
218
|
return pieces;
|
|
283
219
|
};
|
|
@@ -340,6 +276,7 @@ const layoutButtons = (width, compact, hidden, extra, disabled) => {
|
|
|
340
276
|
for (const add of extraIds) {
|
|
341
277
|
if (!ids.includes(add)) ids.push(add);
|
|
342
278
|
}
|
|
279
|
+
continue;
|
|
343
280
|
}
|
|
344
281
|
if (hiddenIds.includes(id)) continue;
|
|
345
282
|
ids.push(id);
|
|
@@ -348,13 +285,15 @@ const layoutButtons = (width, compact, hidden, extra, disabled) => {
|
|
|
348
285
|
const hits = [];
|
|
349
286
|
const parts = [];
|
|
350
287
|
let x = 0;
|
|
288
|
+
const limit = Math.max(0, width - BUTTON_EDGE);
|
|
351
289
|
for (const id of ids) {
|
|
352
290
|
const letter = actionLetter(id);
|
|
353
291
|
const word = buttonWord(id);
|
|
354
292
|
const label = short ? letter : word;
|
|
355
|
-
const
|
|
293
|
+
const gap = buttonGap(parts.length);
|
|
294
|
+
const piece = `${gap}${label}`;
|
|
356
295
|
const w = visibleWidth(piece);
|
|
357
|
-
if (x + w >
|
|
296
|
+
if (x + w > limit && parts.length) break;
|
|
358
297
|
const off = disabledIds.includes(id);
|
|
359
298
|
if (!off) hits.push({ id, x0: x, x1: x + w });
|
|
360
299
|
parts.push({ action: { id }, piece, letter, label, disabled: off });
|
|
@@ -373,8 +312,10 @@ const paintButtons = (layout, width, color) => {
|
|
|
373
312
|
let used = 0;
|
|
374
313
|
const fgRgb = THEME.buttonFg;
|
|
375
314
|
const bgRgb = THEME.buttonBg;
|
|
376
|
-
for (
|
|
377
|
-
|
|
315
|
+
for (let i = 0; i < layout.parts.length; i++) {
|
|
316
|
+
const part = layout.parts[i];
|
|
317
|
+
const gap = buttonGap(i);
|
|
318
|
+
out += paint(gap, fgRgb, bgRgb, color);
|
|
378
319
|
if (part.disabled) out += paint(part.label, fgRgb, bgRgb, color);
|
|
379
320
|
else out += paintMarkedWord(part.label, part.letter, color, BUTTON_MARK);
|
|
380
321
|
used += visibleWidth(part.piece);
|
|
@@ -456,7 +397,9 @@ const statusStats = (view) => {
|
|
|
456
397
|
const fallbackN = counts.staged ?? 0;
|
|
457
398
|
const n = hasGit ? total.staged : fallbackN;
|
|
458
399
|
const m = hasGit ? total.remaining : counted;
|
|
459
|
-
|
|
400
|
+
const git = joinStatus([statusDelta(total), `${n}/${m}`]);
|
|
401
|
+
if (!view.npmLogs) return git;
|
|
402
|
+
return joinStatus([view.npmLogs, git]);
|
|
460
403
|
};
|
|
461
404
|
|
|
462
405
|
const statusLayout = (view, status, width) => {
|
|
@@ -512,14 +455,11 @@ const paintChoicePrompt = (choices, width, color, prefix = '') => {
|
|
|
512
455
|
return out + fill(pad, THEME.mutedFg, bgRgb, color);
|
|
513
456
|
};
|
|
514
457
|
|
|
515
|
-
const STATUS_MARK = /(
|
|
458
|
+
const STATUS_MARK = /(\+[0-9]+(?:\/[0-9]+)?|-[0-9]+(?:\/[0-9]+)?)/g;
|
|
459
|
+
const OLD_LOGS = /old logs \S+/;
|
|
516
460
|
|
|
517
461
|
const paintStatusMark = (token, color) => {
|
|
518
462
|
const bgRgb = THEME.chromeBg;
|
|
519
|
-
const muted = THEME.mutedFg;
|
|
520
|
-
if (token.startsWith('[')) {
|
|
521
|
-
return paintBracketName(token, THEME.buttonHotFg, muted, bgRgb, color);
|
|
522
|
-
}
|
|
523
463
|
if (token.startsWith('+')) {
|
|
524
464
|
return paint(token, THEME.addLineFg, bgRgb, color);
|
|
525
465
|
}
|
|
@@ -530,6 +470,18 @@ const paintStatusLeft = (text, color) => {
|
|
|
530
470
|
const bgRgb = THEME.chromeBg;
|
|
531
471
|
const muted = THEME.mutedFg;
|
|
532
472
|
if (!color) return text;
|
|
473
|
+
const found = text.match(OLD_LOGS);
|
|
474
|
+
if (found) {
|
|
475
|
+
const at = found.index;
|
|
476
|
+
const token = found[0];
|
|
477
|
+
const before = text.slice(0, at);
|
|
478
|
+
const after = text.slice(at + token.length);
|
|
479
|
+
return (
|
|
480
|
+
paintStatusLeft(before, color) +
|
|
481
|
+
paint(token, THEME.shaFg, bgRgb, color) +
|
|
482
|
+
paintStatusLeft(after, color)
|
|
483
|
+
);
|
|
484
|
+
}
|
|
533
485
|
let out = '';
|
|
534
486
|
let last = 0;
|
|
535
487
|
for (const match of text.matchAll(STATUS_MARK)) {
|
|
@@ -547,6 +499,68 @@ const paintStatusLeft = (text, color) => {
|
|
|
547
499
|
return out;
|
|
548
500
|
};
|
|
549
501
|
|
|
502
|
+
const paintBranchHead = (text, branch, color) => {
|
|
503
|
+
const name = branchLabel(branch);
|
|
504
|
+
if (!name || !text.startsWith(' ')) return null;
|
|
505
|
+
const head = ` ${name}`;
|
|
506
|
+
const shown = text.startsWith(head) ? name : text.slice(1);
|
|
507
|
+
if (!shown || !name.startsWith(shown)) return null;
|
|
508
|
+
const rest = text.slice(1 + shown.length);
|
|
509
|
+
const bgRgb = THEME.chromeBg;
|
|
510
|
+
return (
|
|
511
|
+
paint(' ', THEME.mutedFg, bgRgb, color) +
|
|
512
|
+
paint(shown, THEME.buttonHotFg, bgRgb, color, true) +
|
|
513
|
+
paintStatusLeft(rest, color)
|
|
514
|
+
);
|
|
515
|
+
};
|
|
516
|
+
|
|
517
|
+
const SAVE_HINT = 'Press **Ctrl-S** or **Enter** at EOF to save';
|
|
518
|
+
const HINT_HOT_FG = 0xff;
|
|
519
|
+
|
|
520
|
+
const hintParts = (source) => {
|
|
521
|
+
const bits = source.split('**');
|
|
522
|
+
const parts = [];
|
|
523
|
+
for (let i = 0; i < bits.length; i++) {
|
|
524
|
+
if (!bits[i]) continue;
|
|
525
|
+
parts.push({ text: bits[i], hot: i % 2 === 1 });
|
|
526
|
+
}
|
|
527
|
+
return parts;
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
const saveHintText = () => SAVE_HINT.replaceAll('**', '');
|
|
531
|
+
|
|
532
|
+
const commitSaveHint = (view) => {
|
|
533
|
+
if (view.mode !== 'compose' || view.commitView !== 'full') return false;
|
|
534
|
+
const compose = view.compose;
|
|
535
|
+
return Boolean(compose && compose.kind === 'commit');
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
const paintSaveHintBar = (width, color) => {
|
|
539
|
+
const bgRgb = THEME.buttonBg;
|
|
540
|
+
const plain = `${BUTTON_LEAD}${saveHintText()}`;
|
|
541
|
+
if (!color) {
|
|
542
|
+
const pad = Math.max(0, width - visibleWidth(plain));
|
|
543
|
+
return plain + ' '.repeat(pad);
|
|
544
|
+
}
|
|
545
|
+
let out = paint(BUTTON_LEAD, THEME.buttonFg, bgRgb, color);
|
|
546
|
+
for (const part of hintParts(SAVE_HINT)) {
|
|
547
|
+
const tone = part.hot ? HINT_HOT_FG : THEME.buttonFg;
|
|
548
|
+
out += paint(part.text, tone, bgRgb, color, part.hot);
|
|
549
|
+
}
|
|
550
|
+
const pad = Math.max(0, width - visibleWidth(plain));
|
|
551
|
+
return out + fill(pad, THEME.buttonFg, bgRgb, color);
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
const paintFooter = (view, layout, width, color) => {
|
|
555
|
+
if (commitSaveHint(view)) return paintSaveHintBar(width, color);
|
|
556
|
+
return paintButtons(layout, width, color);
|
|
557
|
+
};
|
|
558
|
+
|
|
559
|
+
const footerHits = (view, layout) => {
|
|
560
|
+
if (commitSaveHint(view)) return [];
|
|
561
|
+
return layout.hits;
|
|
562
|
+
};
|
|
563
|
+
|
|
550
564
|
const paintStatusLine = (view, status, width, color) => {
|
|
551
565
|
const confirm = CONFIRM[view.mode];
|
|
552
566
|
if (confirm) {
|
|
@@ -560,8 +574,13 @@ const paintStatusLine = (view, status, width, color) => {
|
|
|
560
574
|
return layout.clipped + leftGap + layout.msg + rightGap + layout.right;
|
|
561
575
|
}
|
|
562
576
|
const bgRgb = THEME.chromeBg;
|
|
577
|
+
const worktree = countsKind(view.sourceKind, view.revShort) === 'worktree';
|
|
578
|
+
const head = worktree
|
|
579
|
+
? paintBranchHead(layout.clipped, view.branch, color)
|
|
580
|
+
: null;
|
|
581
|
+
const left = head ?? paintStatusLeft(layout.clipped, color);
|
|
563
582
|
return (
|
|
564
|
-
|
|
583
|
+
left +
|
|
565
584
|
paint(leftGap, THEME.mutedFg, bgRgb, color) +
|
|
566
585
|
paint(layout.msg, layout.msgFg, bgRgb, color) +
|
|
567
586
|
paint(rightGap, THEME.mutedFg, bgRgb, color) +
|
|
@@ -583,10 +602,10 @@ const statusBranchHit = (view, width, y) => {
|
|
|
583
602
|
if (!branchLabel(branch)) return null;
|
|
584
603
|
if (countsKind(view.sourceKind, view.revShort) !== 'worktree') return null;
|
|
585
604
|
const layout = statusLayout(view, view.status ?? '', width);
|
|
586
|
-
const
|
|
587
|
-
const
|
|
588
|
-
if (
|
|
589
|
-
return { id: 'branch', y, x0:
|
|
605
|
+
const name = branchLabel(branch);
|
|
606
|
+
const at = layout.clipped.indexOf(name);
|
|
607
|
+
if (at < 0) return null;
|
|
608
|
+
return { id: 'branch', y, x0: at, x1: at + visibleWidth(name) };
|
|
590
609
|
};
|
|
591
610
|
|
|
592
611
|
const selectedBranch = (view) => (view.branches ?? [])[view.branchCursor ?? 0];
|
|
@@ -599,6 +618,7 @@ const hiddenActions = (view) => {
|
|
|
599
618
|
if (pane === 'unit') return UNIT_DISABLED;
|
|
600
619
|
if (pane === 'branches') return BRANCHES_DISABLED;
|
|
601
620
|
if (pane === 'commits') return COMMITS_DISABLED;
|
|
621
|
+
if (pane === 'npm') return NPM_DISABLED;
|
|
602
622
|
if (view.item && view.item.origin === 'todo') return TODO_DISABLED;
|
|
603
623
|
return DIFF_DISABLED;
|
|
604
624
|
};
|
|
@@ -616,9 +636,18 @@ const dimFooterActions = (view) => {
|
|
|
616
636
|
if (pane === 'commits') {
|
|
617
637
|
const entry = selectedCommit(view);
|
|
618
638
|
const staged = (view.counts?.staged ?? 0) > 0;
|
|
619
|
-
|
|
620
|
-
return commitGitDisabled({
|
|
639
|
+
const unstagedCurrent = !view.rev && !view.revShort;
|
|
640
|
+
return commitGitDisabled({
|
|
641
|
+
...entry,
|
|
642
|
+
canCommit: staged,
|
|
643
|
+
unstagedCurrent,
|
|
644
|
+
});
|
|
645
|
+
}
|
|
646
|
+
if (pane === 'npm' && view.npmView) {
|
|
647
|
+
if (view.npmRunning) return ['npmRerun'];
|
|
648
|
+
return [];
|
|
621
649
|
}
|
|
650
|
+
if (pane === 'npm' && !view.npmLogs) return ['npmLogs'];
|
|
622
651
|
return [];
|
|
623
652
|
};
|
|
624
653
|
|
|
@@ -626,10 +655,14 @@ const extraFooterActions = (view) => {
|
|
|
626
655
|
const pane = view.pane ?? 'diff';
|
|
627
656
|
if (pane === 'files') {
|
|
628
657
|
const scope = view.fileScope === 'file' ? 'diff' : 'file';
|
|
629
|
-
return [scope, 'commit', 'pull', 'push'];
|
|
658
|
+
return ['npm', scope, 'commit', 'pull', 'push'];
|
|
630
659
|
}
|
|
631
660
|
if (pane === 'branches') return Object.keys(BRANCH_ACTIONS);
|
|
632
661
|
if (pane === 'commits') return Object.keys(COMMIT_ACTIONS);
|
|
662
|
+
if (pane === 'npm') {
|
|
663
|
+
if (view.npmView) return ['npmStop', 'npmRerun'];
|
|
664
|
+
return ['npmEdit', 'npmNew', 'npmDrop', 'npmLogs'];
|
|
665
|
+
}
|
|
633
666
|
return [];
|
|
634
667
|
};
|
|
635
668
|
|
|
@@ -643,6 +676,8 @@ module.exports = {
|
|
|
643
676
|
paintHeader,
|
|
644
677
|
layoutButtons,
|
|
645
678
|
paintButtons,
|
|
679
|
+
paintFooter,
|
|
680
|
+
footerHits,
|
|
646
681
|
paintStatusLine,
|
|
647
682
|
statusChoiceHits,
|
|
648
683
|
statusBranchHit,
|