reslop 0.2.0 → 0.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -1
- package/README.md +13 -2
- package/lib/ansi.js +263 -6
- package/lib/api.js +2 -0
- package/lib/capabilities.js +6 -0
- package/lib/cli.js +13 -2
- package/lib/deps.js +43 -3
- package/lib/git-branches.js +228 -17
- package/lib/git-reword-todo.js +20 -0
- package/lib/git.js +20 -3
- package/lib/keys.js +10 -4
- package/lib/npm-commands.js +513 -0
- package/lib/render/branches.js +23 -58
- package/lib/render/chrome.js +156 -99
- package/lib/render/commits.js +643 -31
- package/lib/render/diff.js +4 -4
- package/lib/render/frame.js +51 -5
- package/lib/render/notes.js +22 -5
- package/lib/render/npm.js +228 -0
- package/lib/render/primitives.js +48 -34
- package/lib/render/render.js +3 -3
- package/lib/report-model.js +127 -0
- package/lib/report-parse.js +94 -0
- package/lib/report-render.js +87 -0
- package/lib/report-run.js +164 -0
- package/lib/report-test.js +506 -0
- package/lib/report-tools.js +311 -0
- package/lib/review-merge.js +270 -0
- package/lib/review.js +38 -6
- package/lib/session/actions.js +81 -5
- package/lib/session/commit.js +187 -19
- package/lib/session/compose.js +96 -32
- package/lib/session/dispatch.js +36 -2
- package/lib/session/files-pane.js +36 -6
- package/lib/session/lifecycle.js +116 -16
- package/lib/session/navigation.js +1 -0
- package/lib/session/npm.js +335 -0
- package/lib/session/pointer.js +12 -1
- package/lib/session/todos.js +29 -1
- package/lib/session/view.js +14 -3
- package/lib/session/watch.js +120 -8
- package/lib/session.js +11 -2
- package/package.json +7 -7
package/lib/render/diff.js
CHANGED
|
@@ -78,8 +78,8 @@ const STAGED_STYLES = {
|
|
|
78
78
|
};
|
|
79
79
|
|
|
80
80
|
const SOFT_WRAP = ['warn', 'note'];
|
|
81
|
-
const
|
|
82
|
-
const
|
|
81
|
+
const delCharBg = () => ansi.bg(THEME.delCharBg);
|
|
82
|
+
const addCharBg = () => ansi.bg(THEME.addCharBg);
|
|
83
83
|
|
|
84
84
|
const paintSpans = (pieces, lineStyle, budget, color) => {
|
|
85
85
|
let out = '';
|
|
@@ -298,8 +298,8 @@ const paintBodyDiff = (view, width, color) => {
|
|
|
298
298
|
};
|
|
299
299
|
|
|
300
300
|
module.exports = {
|
|
301
|
-
|
|
302
|
-
|
|
301
|
+
delCharBg,
|
|
302
|
+
addCharBg,
|
|
303
303
|
paintDiffLine,
|
|
304
304
|
codeInnerWidth,
|
|
305
305
|
paintBodyDiff,
|
package/lib/render/frame.js
CHANGED
|
@@ -8,18 +8,20 @@ const files = require('./files.js');
|
|
|
8
8
|
const branches = require('./branches.js');
|
|
9
9
|
const commits = require('./commits.js');
|
|
10
10
|
const notes = require('./notes.js');
|
|
11
|
+
const npm = require('./npm.js');
|
|
11
12
|
const diff = require('./diff.js');
|
|
12
13
|
const unit = require('./unit.js');
|
|
13
14
|
|
|
14
15
|
const { RESET } = ansi;
|
|
15
16
|
const { LIST_PANES, paintBodyFill, paneResult } = primitives;
|
|
16
|
-
const { paintHeader,
|
|
17
|
+
const { paintHeader, paintFooter, footerHits, paintStatusLine } = chrome;
|
|
17
18
|
const { layoutButtons, statusBranchHit, statusChoiceHits } = chrome;
|
|
18
19
|
const { hiddenActions, dimFooterActions, extraFooterActions } = chrome;
|
|
19
20
|
const { paintNotePanel, paintBodyTodo } = notes;
|
|
20
21
|
const { paintBodyFiles } = files;
|
|
21
22
|
const { paintBodyBranches } = branches;
|
|
22
23
|
const { paintBodyCommits } = commits;
|
|
24
|
+
const { paintBodyNpm } = npm;
|
|
23
25
|
const { paintBodyDiff } = diff;
|
|
24
26
|
const { paintBodyUnit } = unit;
|
|
25
27
|
|
|
@@ -28,6 +30,7 @@ const bodyKind = (view) => {
|
|
|
28
30
|
if (pane === 'files' || pane === 'branches' || pane === 'commits') {
|
|
29
31
|
return pane;
|
|
30
32
|
}
|
|
33
|
+
if (pane === 'npm') return 'npm';
|
|
31
34
|
if (pane === 'unit') return 'unit';
|
|
32
35
|
if (!view.item) return 'empty';
|
|
33
36
|
if (view.item.origin === 'todo') return 'todo';
|
|
@@ -47,6 +50,7 @@ const BODY_PAINT = {
|
|
|
47
50
|
files: paintBodyFiles,
|
|
48
51
|
branches: paintBodyBranches,
|
|
49
52
|
commits: paintBodyCommits,
|
|
53
|
+
npm: paintBodyNpm,
|
|
50
54
|
empty: paintBodyEmpty,
|
|
51
55
|
todo: paintBodyTodo,
|
|
52
56
|
diff: paintBodyDiff,
|
|
@@ -75,6 +79,33 @@ const bodyFillKind = (listPane, splitBody) => {
|
|
|
75
79
|
return 'plain';
|
|
76
80
|
};
|
|
77
81
|
|
|
82
|
+
const ownerSpan = (owners, focus) => {
|
|
83
|
+
let first = -1;
|
|
84
|
+
let last = -1;
|
|
85
|
+
for (let i = 0; i < owners.length; i++) {
|
|
86
|
+
if (owners[i] !== focus) continue;
|
|
87
|
+
if (first < 0) first = i;
|
|
88
|
+
last = i;
|
|
89
|
+
}
|
|
90
|
+
return { first, last };
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const revealScroll = (owners, focus, height, prefer) => {
|
|
94
|
+
const span = ownerSpan(owners, focus);
|
|
95
|
+
if (span.first < 0) return prefer;
|
|
96
|
+
const room = Math.max(1, height);
|
|
97
|
+
let start = prefer;
|
|
98
|
+
if (span.first < start) start = span.first;
|
|
99
|
+
if (span.last >= start + room) {
|
|
100
|
+
const fits = span.last - span.first + 1 <= room;
|
|
101
|
+
start = fits ? span.last - room + 1 : span.first;
|
|
102
|
+
}
|
|
103
|
+
const maxStart = Math.max(0, owners.length - room);
|
|
104
|
+
if (start > maxStart) start = maxStart;
|
|
105
|
+
if (start < 0) start = 0;
|
|
106
|
+
return start;
|
|
107
|
+
};
|
|
108
|
+
|
|
78
109
|
const mapBodyCursor = (cursor, shift, scroll, headerLines, shown) => {
|
|
79
110
|
if (!cursor) return null;
|
|
80
111
|
const row = cursor.row + shift;
|
|
@@ -88,6 +119,7 @@ const mapBodyCursor = (cursor, shift, scroll, headerLines, shown) => {
|
|
|
88
119
|
const windowBody = (view, width, color, bodyH, headerLines, paintedBody) => {
|
|
89
120
|
const sourceBody = paintedBody.body;
|
|
90
121
|
const sourceOwners = paintedBody.todoOwners;
|
|
122
|
+
const sourceChecks = paintedBody.todoChecks ?? [];
|
|
91
123
|
const splitBody = paintedBody.splitBody === true;
|
|
92
124
|
const pane = view.pane ?? 'diff';
|
|
93
125
|
const listPane = LIST_PANES.includes(pane);
|
|
@@ -109,9 +141,19 @@ const windowBody = (view, width, color, bodyH, headerLines, paintedBody) => {
|
|
|
109
141
|
for (let i = 0; i < sourceOwners.length; i++) {
|
|
110
142
|
todoOwners[ownerAt++] = sourceOwners[i];
|
|
111
143
|
}
|
|
144
|
+
const checkLead = lead && sourceChecks.length;
|
|
145
|
+
const todoChecks = new Array(sourceChecks.length + (checkLead ? 1 : 0));
|
|
146
|
+
let checkAt = 0;
|
|
147
|
+
if (checkLead) todoChecks[checkAt++] = false;
|
|
148
|
+
for (let i = 0; i < sourceChecks.length; i++) {
|
|
149
|
+
todoChecks[checkAt++] = sourceChecks[i];
|
|
150
|
+
}
|
|
112
151
|
const scrollRaw = view.scroll ?? 0;
|
|
113
152
|
const scrollMax = Math.max(0, body.length - bodyH);
|
|
114
|
-
|
|
153
|
+
let scroll = listPane ? 0 : Math.min(scrollRaw, scrollMax);
|
|
154
|
+
if (view.item && view.item.origin === 'todo') {
|
|
155
|
+
scroll = revealScroll(todoOwners, view.todoFocus ?? 0, bodyH, scroll);
|
|
156
|
+
}
|
|
115
157
|
const end = Math.min(body.length, scroll + bodyH);
|
|
116
158
|
const copied = Math.max(0, end - scroll);
|
|
117
159
|
const slice = new Array(bodyH);
|
|
@@ -120,7 +162,11 @@ const windowBody = (view, width, color, bodyH, headerLines, paintedBody) => {
|
|
|
120
162
|
for (let i = 0; i < copied; i++) {
|
|
121
163
|
const owner = todoOwners[scroll + i];
|
|
122
164
|
if (owner === undefined || owner === null) continue;
|
|
123
|
-
|
|
165
|
+
const y = headerLines + i + 1;
|
|
166
|
+
if (todoChecks[scroll + i]) {
|
|
167
|
+
todoHits.push({ y, cursor: owner, x0: 1, x1: 4, check: true });
|
|
168
|
+
}
|
|
169
|
+
todoHits.push({ y, cursor: owner });
|
|
124
170
|
}
|
|
125
171
|
const cursor = mapBodyCursor(
|
|
126
172
|
paintedBody.cursor,
|
|
@@ -153,7 +199,7 @@ const stackFrame = (view, width, color, rows, layout, windowed, notes) => {
|
|
|
153
199
|
for (const row of notes.noteRows) rows.push(row);
|
|
154
200
|
const status = view.status ?? '';
|
|
155
201
|
rows.push(paintStatusLine(view, status, width, color));
|
|
156
|
-
rows.push(
|
|
202
|
+
rows.push(paintFooter(view, layout, width, color));
|
|
157
203
|
return overlayRows(rows, view.selection);
|
|
158
204
|
};
|
|
159
205
|
|
|
@@ -195,7 +241,7 @@ const renderFrame = (view, options = {}) => {
|
|
|
195
241
|
return {
|
|
196
242
|
text,
|
|
197
243
|
rows: painted,
|
|
198
|
-
buttons: layout
|
|
244
|
+
buttons: footerHits(view, layout),
|
|
199
245
|
fileHits,
|
|
200
246
|
todoHits,
|
|
201
247
|
templateHits,
|
package/lib/render/notes.js
CHANGED
|
@@ -11,7 +11,7 @@ const { THEME, visibleWidth, truncateVisible, seq, EL, RESET, BOLD } = ansi;
|
|
|
11
11
|
const { paintBar, paintCheckBar, windowRows } = primitives;
|
|
12
12
|
const { LIST_PANES, CHECK_LEAD, paneResult } = primitives;
|
|
13
13
|
|
|
14
|
-
const NOTE_COMPOSE = ['feedback'
|
|
14
|
+
const NOTE_COMPOSE = ['feedback'];
|
|
15
15
|
const INPUT_MAX = 4;
|
|
16
16
|
const NOTE_MAX = 4;
|
|
17
17
|
const NOTE_PAD = 1;
|
|
@@ -57,6 +57,7 @@ const paintTodoList = (texts, focus, width, color, edit) => {
|
|
|
57
57
|
const inner = todoInnerWidth(width);
|
|
58
58
|
const rows = [];
|
|
59
59
|
const owners = [];
|
|
60
|
+
const checks = [];
|
|
60
61
|
let cursor = null;
|
|
61
62
|
for (let i = 0; i < texts.length; i++) {
|
|
62
63
|
const selected = i === focus;
|
|
@@ -65,14 +66,22 @@ const paintTodoList = (texts, focus, width, color, edit) => {
|
|
|
65
66
|
const text = texts[i];
|
|
66
67
|
const wrapped = wrapTodoItem(text, inner);
|
|
67
68
|
const rowStart = rows.length;
|
|
68
|
-
for (
|
|
69
|
-
|
|
69
|
+
for (let line = 0; line < wrapped.length; line++) {
|
|
70
|
+
const painted = paintCheckBar(
|
|
71
|
+
` ${wrapped[line]}`,
|
|
72
|
+
width,
|
|
73
|
+
fgRgb,
|
|
74
|
+
bgRgb,
|
|
75
|
+
color,
|
|
76
|
+
);
|
|
77
|
+
rows.push(painted);
|
|
70
78
|
owners.push(i);
|
|
79
|
+
checks.push(line === 0);
|
|
71
80
|
}
|
|
72
81
|
if (!edit || !selected) continue;
|
|
73
82
|
cursor = todoCursor(text, edit, inner, rowStart);
|
|
74
83
|
}
|
|
75
|
-
return { rows, owners, cursor };
|
|
84
|
+
return { rows, owners, checks, cursor };
|
|
76
85
|
};
|
|
77
86
|
|
|
78
87
|
const noteInnerWidth = (width) => Math.max(1, width - NOTE_PAD * 2);
|
|
@@ -184,13 +193,21 @@ const paintBodyTodo = (view, width, color) => {
|
|
|
184
193
|
const caption = paintTodoCaption(width, color);
|
|
185
194
|
const body = [caption, ...painted.rows];
|
|
186
195
|
const todoOwners = [null, ...painted.owners];
|
|
196
|
+
const todoChecks = [false, ...painted.checks];
|
|
187
197
|
let cursor = null;
|
|
188
198
|
if (painted.cursor) {
|
|
189
199
|
cursor = { x: painted.cursor.x, row: painted.cursor.row + 1 };
|
|
190
200
|
}
|
|
191
201
|
const fileHits = [];
|
|
192
202
|
const splitBody = false;
|
|
193
|
-
return paneResult({
|
|
203
|
+
return paneResult({
|
|
204
|
+
body,
|
|
205
|
+
fileHits,
|
|
206
|
+
todoOwners,
|
|
207
|
+
todoChecks,
|
|
208
|
+
cursor,
|
|
209
|
+
splitBody,
|
|
210
|
+
});
|
|
194
211
|
};
|
|
195
212
|
|
|
196
213
|
module.exports = {
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ansi = require('../ansi.js');
|
|
4
|
+
const chrome = require('./chrome.js');
|
|
5
|
+
const primitives = require('./primitives.js');
|
|
6
|
+
const npmCommands = require('../npm-commands.js');
|
|
7
|
+
|
|
8
|
+
const { THEME, paint, visibleWidth, truncateVisible, seq, EL } = ansi;
|
|
9
|
+
const { clipAnsi, stripAnsi, RESET, foregroundOn } = ansi;
|
|
10
|
+
const { formatBusyStatus } = chrome;
|
|
11
|
+
const { TABLE_KEY, TABLE_VAL } = npmCommands;
|
|
12
|
+
const { fill, FILE_MARK, paneResult, paintCursorList } = primitives;
|
|
13
|
+
const { paintListEdit } = primitives;
|
|
14
|
+
|
|
15
|
+
const EDGE = 2;
|
|
16
|
+
const TAIL = 1;
|
|
17
|
+
const LOG_PAD_Y = 1;
|
|
18
|
+
const LOG_PAD_X = 2;
|
|
19
|
+
const NPM_NAME_MAX = 24;
|
|
20
|
+
|
|
21
|
+
const logViewRows = (bodyH) => {
|
|
22
|
+
const height = Math.max(1, bodyH);
|
|
23
|
+
if (height >= LOG_PAD_Y * 2 + 1) return height - LOG_PAD_Y * 2;
|
|
24
|
+
return height;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const rowFg = (selected) => (selected ? THEME.chromeFg : THEME.mutedFg);
|
|
28
|
+
|
|
29
|
+
const npmNameWidth = (commands) => {
|
|
30
|
+
let width = 0;
|
|
31
|
+
for (const entry of commands) {
|
|
32
|
+
width = Math.max(width, visibleWidth(`${entry.name ?? ''}`));
|
|
33
|
+
}
|
|
34
|
+
return Math.min(NPM_NAME_MAX, width);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const paintNpmRow = (entry, width, color, selected, nameCol = 0) => {
|
|
38
|
+
const inner = Math.max(1, width - EDGE - TAIL);
|
|
39
|
+
const mark = selected ? FILE_MARK : ' ';
|
|
40
|
+
const prefix = ` ${mark} `;
|
|
41
|
+
const prefixW = visibleWidth(prefix);
|
|
42
|
+
const rest = Math.max(1, inner - prefixW);
|
|
43
|
+
const detail = entry.kind === 'bin' ? 'bin' : entry.command;
|
|
44
|
+
const nameW = Math.min(Math.max(0, nameCol), rest);
|
|
45
|
+
const name = truncateVisible(entry.name, nameW);
|
|
46
|
+
const gap = rest > nameW + 2 ? ' ' : '';
|
|
47
|
+
const detailW = Math.max(0, rest - nameW - visibleWidth(gap));
|
|
48
|
+
const shown = truncateVisible(detail, detailW);
|
|
49
|
+
const mid = `${name}${' '.repeat(Math.max(0, nameW - visibleWidth(name)))}`;
|
|
50
|
+
const after = `${gap}${shown}`;
|
|
51
|
+
const pad = Math.max(0, rest - visibleWidth(mid + after));
|
|
52
|
+
const left = `${prefix}${mid}${after}${' '.repeat(pad)}`;
|
|
53
|
+
const fgRgb = rowFg(selected);
|
|
54
|
+
const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
|
|
55
|
+
const tail = ' '.repeat(TAIL);
|
|
56
|
+
const edge = fill(EDGE, fgRgb, THEME.ctxBg, color);
|
|
57
|
+
if (!color) return `${left}${tail}${edge}`;
|
|
58
|
+
let out = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
59
|
+
out += paint(prefix, fgRgb, bgRgb, color);
|
|
60
|
+
const bin = entry.kind === 'bin';
|
|
61
|
+
const nameFg = bin ? THEME.shaFg : THEME.buttonHotFg;
|
|
62
|
+
out += paint(mid, nameFg, bgRgb, color, !bin);
|
|
63
|
+
out += paint(`${after}${' '.repeat(pad)}${tail}`, fgRgb, bgRgb, color);
|
|
64
|
+
return out + edge;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const paintNpmList = (view, width, color, bodyH, headerLines) => {
|
|
68
|
+
const commands = view.npmCommands ?? [];
|
|
69
|
+
const nameW = npmNameWidth(commands);
|
|
70
|
+
const cursor = view.npmCursor ?? 0;
|
|
71
|
+
const compose = view.compose;
|
|
72
|
+
const editing = compose && compose.kind === 'npm';
|
|
73
|
+
const paintRow = (entry, rowWidth, rowColor, selected) =>
|
|
74
|
+
paintNpmRow(entry, rowWidth, rowColor, selected, nameW);
|
|
75
|
+
let extra = null;
|
|
76
|
+
if (editing) {
|
|
77
|
+
const painted = paintListEdit(compose, width, color, EDGE);
|
|
78
|
+
const at = view.npmEditName ? 'replace' : 'end';
|
|
79
|
+
extra = { row: painted.row, cursor: painted.cursor, at, index: cursor };
|
|
80
|
+
}
|
|
81
|
+
const painted = paintCursorList(
|
|
82
|
+
commands,
|
|
83
|
+
cursor,
|
|
84
|
+
width,
|
|
85
|
+
color,
|
|
86
|
+
bodyH,
|
|
87
|
+
headerLines,
|
|
88
|
+
paintRow,
|
|
89
|
+
extra,
|
|
90
|
+
view.listScroll,
|
|
91
|
+
);
|
|
92
|
+
return paneResult({
|
|
93
|
+
body: painted.body,
|
|
94
|
+
fileHits: painted.fileHits,
|
|
95
|
+
todoOwners: [],
|
|
96
|
+
cursor: painted.cursor,
|
|
97
|
+
splitBody: false,
|
|
98
|
+
listScroll: painted.offset,
|
|
99
|
+
});
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
const logStart = (view, count, rows) => {
|
|
103
|
+
const maxStart = Math.max(0, count - rows);
|
|
104
|
+
let start = maxStart;
|
|
105
|
+
if (!view.npmFollow) start = view.npmScroll ?? 0;
|
|
106
|
+
if (start > maxStart) start = maxStart;
|
|
107
|
+
if (start < 0) start = 0;
|
|
108
|
+
return start;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
const isTableLine = (line) => `${line ?? ''}`.startsWith(TABLE_KEY);
|
|
112
|
+
|
|
113
|
+
const mixRgb = (from, to, part) => {
|
|
114
|
+
const out = [0, 0, 0];
|
|
115
|
+
for (let i = 0; i < 3; i++) {
|
|
116
|
+
const next = from[i] + (to[i] - from[i]) * part;
|
|
117
|
+
out[i] = Math.round(next);
|
|
118
|
+
}
|
|
119
|
+
return out;
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
const tableTone = (index) => {
|
|
123
|
+
const lift = index % 2 === 0 ? 0.035 : 0.07;
|
|
124
|
+
const valueBg = mixRgb(THEME.ctxBg, THEME.chromeFg, lift);
|
|
125
|
+
const keyBg = mixRgb(THEME.ctxBg, THEME.chromeFg, lift + 0.025);
|
|
126
|
+
const frame = mixRgb(THEME.ctxBg, THEME.chromeFg, lift * 0.4);
|
|
127
|
+
return { valueBg, keyBg, frame };
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
const tableIndex = (lines, index) => {
|
|
131
|
+
let at = index;
|
|
132
|
+
let row = 0;
|
|
133
|
+
while (at > 0 && isTableLine(lines[at - 1])) {
|
|
134
|
+
at -= 1;
|
|
135
|
+
row += 1;
|
|
136
|
+
}
|
|
137
|
+
return row;
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
const paintTableLine = (line, width, index) => {
|
|
141
|
+
const raw = line.slice(TABLE_KEY.length);
|
|
142
|
+
const at = raw.indexOf(TABLE_VAL);
|
|
143
|
+
const key = at < 0 ? raw : raw.slice(0, at);
|
|
144
|
+
const rest = at < 0 ? '' : raw.slice(at + TABLE_VAL.length);
|
|
145
|
+
const gap = rest.startsWith(' ') ? ' ' : '';
|
|
146
|
+
const value = rest.slice(gap.length);
|
|
147
|
+
const edge = 1;
|
|
148
|
+
const inner = Math.max(1, width - edge * 2);
|
|
149
|
+
const keyW = visibleWidth(key);
|
|
150
|
+
const gapW = visibleWidth(gap);
|
|
151
|
+
const valueW = Math.max(0, inner - keyW - gapW);
|
|
152
|
+
const shown = truncateVisible(value, valueW);
|
|
153
|
+
const pad = ' '.repeat(Math.max(0, valueW - visibleWidth(shown)));
|
|
154
|
+
const tone = tableTone(index);
|
|
155
|
+
const ink = THEME.ctxFg;
|
|
156
|
+
let out = fill(edge, ink, tone.frame, true);
|
|
157
|
+
out += paint(key, ink, tone.keyBg, true);
|
|
158
|
+
out += paint(gap, ink, tone.frame, true);
|
|
159
|
+
out += paint(`${shown}${pad}`, ink, tone.valueBg, true);
|
|
160
|
+
return `${out}${fill(edge, ink, tone.frame, true)}`;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const paintLogLine = (line, width, color, fgRgb = THEME.ctxFg) => {
|
|
164
|
+
const inner = Math.max(1, width - LOG_PAD_X * 2);
|
|
165
|
+
const source = color ? `${line ?? ''}` : stripAnsi(line);
|
|
166
|
+
const clip = (value) =>
|
|
167
|
+
color ? clipAnsi(value, inner) : truncateVisible(value, inner);
|
|
168
|
+
const body = color ? foregroundOn(clip(source), THEME.ctxBg) : clip(source);
|
|
169
|
+
const lead = ' '.repeat(LOG_PAD_X);
|
|
170
|
+
const pad = Math.max(0, width - LOG_PAD_X - visibleWidth(body));
|
|
171
|
+
const tail = ' '.repeat(pad);
|
|
172
|
+
if (!color) return `${lead}${body}${tail}`;
|
|
173
|
+
const base = seq(fgRgb, THEME.ctxBg);
|
|
174
|
+
let out = `${base}${EL}${lead}${body}`;
|
|
175
|
+
if (pad) out += `${RESET}${base}${tail}`;
|
|
176
|
+
return `${out}${RESET}`;
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const paintRunBar = (width, color, frame) => {
|
|
180
|
+
const label = formatBusyStatus('running', frame);
|
|
181
|
+
return paintLogLine(label, width, color, THEME.chromeFg);
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const paintNpmLog = (view, width, color, bodyH) => {
|
|
185
|
+
const text = view.npmOutput ?? '';
|
|
186
|
+
const lines = text.split('\n');
|
|
187
|
+
if (lines.length && lines.at(-1) === '') lines.pop();
|
|
188
|
+
const running = view.npmRunning === true;
|
|
189
|
+
const count = lines.length + (running ? 1 : 0);
|
|
190
|
+
const rows = logViewRows(bodyH);
|
|
191
|
+
const padY = bodyH - rows;
|
|
192
|
+
const top = padY ? 1 : 0;
|
|
193
|
+
const start = logStart(view, count, rows);
|
|
194
|
+
const blank = fill(width, THEME.ctxFg, THEME.ctxBg, color);
|
|
195
|
+
const body = [];
|
|
196
|
+
for (let i = 0; i < top; i++) body.push(blank);
|
|
197
|
+
for (let i = 0; i < rows; i++) {
|
|
198
|
+
const index = start + i;
|
|
199
|
+
if (index < lines.length) {
|
|
200
|
+
const line = lines[index];
|
|
201
|
+
if (color && isTableLine(line)) {
|
|
202
|
+
const row = tableIndex(lines, index);
|
|
203
|
+
body.push(paintTableLine(line, width, row));
|
|
204
|
+
} else {
|
|
205
|
+
body.push(paintLogLine(line, width, color));
|
|
206
|
+
}
|
|
207
|
+
} else if (running && index === lines.length) {
|
|
208
|
+
body.push(paintRunBar(width, color, view.progressFrame ?? 0));
|
|
209
|
+
} else {
|
|
210
|
+
body.push(blank);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
for (let i = 0; i < padY - top; i++) body.push(blank);
|
|
214
|
+
return paneResult({
|
|
215
|
+
body,
|
|
216
|
+
fileHits: [],
|
|
217
|
+
todoOwners: [],
|
|
218
|
+
cursor: null,
|
|
219
|
+
splitBody: false,
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const paintBodyNpm = (view, width, color, bodyH, headerLines) => {
|
|
224
|
+
if (view.npmView) return paintNpmLog(view, width, color, bodyH);
|
|
225
|
+
return paintNpmList(view, width, color, bodyH, headerLines);
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
module.exports = { paintBodyNpm, logViewRows };
|
package/lib/render/primitives.js
CHANGED
|
@@ -8,7 +8,7 @@ const GUTTER_W = 2;
|
|
|
8
8
|
const FILE_MARK = '▶';
|
|
9
9
|
const FILE_LIST_PAD = 1;
|
|
10
10
|
const CHECK_DONE = 'xX';
|
|
11
|
-
const LIST_PANES = ['files', 'branches', 'commits'];
|
|
11
|
+
const LIST_PANES = ['files', 'branches', 'commits', 'npm'];
|
|
12
12
|
const CHECK_TOKEN = /\[([ xX])\]/;
|
|
13
13
|
const CHECK_LEAD = /^\[[ xX]\] /;
|
|
14
14
|
|
|
@@ -104,6 +104,13 @@ const listWindowStart = (focus, offset, listH, count) => {
|
|
|
104
104
|
return start;
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
+
const extraIdx = (extraRow, cursor, count) => {
|
|
108
|
+
if (!extraRow) return -1;
|
|
109
|
+
if (extraRow.at === 'start') return 0;
|
|
110
|
+
if (extraRow.at === 'replace') return extraRow.index ?? cursor;
|
|
111
|
+
return count;
|
|
112
|
+
};
|
|
113
|
+
|
|
107
114
|
const paintCursorList = (
|
|
108
115
|
items,
|
|
109
116
|
cursor,
|
|
@@ -112,15 +119,16 @@ const paintCursorList = (
|
|
|
112
119
|
bodyH,
|
|
113
120
|
headerLines,
|
|
114
121
|
paintRow,
|
|
115
|
-
|
|
122
|
+
extraRow,
|
|
116
123
|
offset = 0,
|
|
117
124
|
) => {
|
|
118
125
|
const pad = bodyH >= FILE_LIST_PAD * 2 + 1 ? FILE_LIST_PAD : 0;
|
|
119
126
|
const listH = bodyH - pad * 2;
|
|
120
|
-
const
|
|
127
|
+
const inserting = extraRow && extraRow.at === 'start';
|
|
128
|
+
const extra = extraRow && extraRow.at !== 'replace' ? 1 : 0;
|
|
121
129
|
const count = items.length + extra;
|
|
122
|
-
const
|
|
123
|
-
const focus =
|
|
130
|
+
const extraAt = extraIdx(extraRow, cursor, items.length);
|
|
131
|
+
const focus = extraRow ? extraAt : cursor;
|
|
124
132
|
const start = listWindowStart(focus, offset, listH, count);
|
|
125
133
|
const blank = paintBodyFill(width, color, 'files');
|
|
126
134
|
const body = [];
|
|
@@ -129,52 +137,57 @@ const paintCursorList = (
|
|
|
129
137
|
if (pad) body.push(blank);
|
|
130
138
|
for (let i = 0; i < listH; i++) {
|
|
131
139
|
const idx = start + i;
|
|
132
|
-
if (
|
|
133
|
-
body.push(
|
|
134
|
-
if (
|
|
135
|
-
textCursor = { x:
|
|
140
|
+
if (extraRow && idx === extraAt) {
|
|
141
|
+
body.push(extraRow.row);
|
|
142
|
+
if (extraRow.cursor) {
|
|
143
|
+
textCursor = { x: extraRow.cursor.x, row: body.length - 1 };
|
|
136
144
|
}
|
|
137
145
|
continue;
|
|
138
146
|
}
|
|
139
|
-
const
|
|
147
|
+
const itemIdx = inserting ? idx - 1 : idx;
|
|
148
|
+
const entry = items[itemIdx];
|
|
140
149
|
if (!entry) break;
|
|
141
|
-
const selected =
|
|
150
|
+
const selected = extraRow ? false : itemIdx === cursor;
|
|
142
151
|
body.push(paintRow(entry, width, color, selected));
|
|
143
152
|
const y = headerLines + pad + i + 1;
|
|
144
|
-
fileHits.push({ y, cursor:
|
|
153
|
+
fileHits.push({ y, cursor: itemIdx });
|
|
145
154
|
}
|
|
146
155
|
if (pad) body.push(blank);
|
|
147
156
|
return { body, fileHits, cursor: textCursor, offset: start };
|
|
148
157
|
};
|
|
149
158
|
|
|
150
|
-
const branchLabel = (name) => {
|
|
151
|
-
const branch = `${name ?? ''}`.trim();
|
|
152
|
-
if (!branch) return '';
|
|
153
|
-
return `[${branch}]`;
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const splitBracketName = (label) => {
|
|
157
|
-
const text = `${label ?? ''}`;
|
|
158
|
-
const open = text.startsWith('[') ? '[' : '';
|
|
159
|
-
const rest = open ? text.slice(1) : text;
|
|
160
|
-
const close = rest.endsWith(']') ? ']' : '';
|
|
161
|
-
const name = close ? rest.slice(0, -1) : rest;
|
|
162
|
-
return { open, name, close };
|
|
163
|
-
};
|
|
159
|
+
const branchLabel = (name) => `${name ?? ''}`.trim();
|
|
164
160
|
|
|
165
|
-
const
|
|
166
|
-
const
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
);
|
|
161
|
+
const paintListEdit = (compose, width, color, edgeW = 0) => {
|
|
162
|
+
const inner = Math.max(1, width - edgeW);
|
|
163
|
+
const prefix = ` ${FILE_MARK} `;
|
|
164
|
+
const prefixW = visibleWidth(prefix);
|
|
165
|
+
const rest = Math.max(1, inner - prefixW);
|
|
166
|
+
const text = `${compose.text ?? ''}`.replaceAll('\n', ' ');
|
|
167
|
+
const clipped = truncateVisible(text, rest);
|
|
168
|
+
const pad = Math.max(0, rest - visibleWidth(clipped));
|
|
169
|
+
const left = `${prefix}${clipped}${' '.repeat(pad)}`;
|
|
170
|
+
const fgRgb = THEME.chromeFg;
|
|
171
|
+
const bgRgb = THEME.buttonBg;
|
|
172
|
+
const edge = fill(edgeW, fgRgb, THEME.ctxBg, color);
|
|
173
|
+
let row = `${left}${edge}`;
|
|
174
|
+
if (color) {
|
|
175
|
+
const gap = ' '.repeat(pad);
|
|
176
|
+
row = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
177
|
+
row += paint(prefix, fgRgb, bgRgb, color);
|
|
178
|
+
row += paint(clipped, THEME.buttonHotFg, bgRgb, color);
|
|
179
|
+
row += paint(gap, fgRgb, bgRgb, color);
|
|
180
|
+
row += edge;
|
|
181
|
+
}
|
|
182
|
+
const col = Math.min(compose.cursor ?? 0, visibleWidth(clipped));
|
|
183
|
+
return { row, cursor: { x: prefixW + col + 1 } };
|
|
172
184
|
};
|
|
173
185
|
|
|
174
186
|
const paneResult = (fields) => ({
|
|
175
187
|
body: fields.body,
|
|
176
188
|
fileHits: fields.fileHits,
|
|
177
189
|
todoOwners: fields.todoOwners,
|
|
190
|
+
todoChecks: fields.todoChecks ?? [],
|
|
178
191
|
cursor: fields.cursor ?? null,
|
|
179
192
|
splitBody: fields.splitBody === true,
|
|
180
193
|
listScroll: fields.listScroll ?? 0,
|
|
@@ -238,8 +251,9 @@ module.exports = {
|
|
|
238
251
|
paintSplitRule,
|
|
239
252
|
paintBodyFill,
|
|
240
253
|
windowRows,
|
|
254
|
+
listWindowStart,
|
|
241
255
|
paintCursorList,
|
|
256
|
+
paintListEdit,
|
|
242
257
|
branchLabel,
|
|
243
|
-
paintBracketName,
|
|
244
258
|
paneResult,
|
|
245
259
|
};
|
package/lib/render/render.js
CHANGED
|
@@ -9,7 +9,7 @@ const { renderFrame } = require('./frame.js');
|
|
|
9
9
|
const { QUIT_PROMPT, PUSH_PROMPT } = chrome;
|
|
10
10
|
const { updatePrompt, dropPrompt } = chrome;
|
|
11
11
|
const { formatBusyStatus, headerText, layoutButtons } = chrome;
|
|
12
|
-
const {
|
|
12
|
+
const { delCharBg, addCharBg, paintDiffLine, codeInnerWidth } = diff;
|
|
13
13
|
const { todoInnerWidth, todoBodyWidth, noteInnerWidth } = notes;
|
|
14
14
|
const { presentCursor, presentRows } = present;
|
|
15
15
|
|
|
@@ -18,8 +18,8 @@ module.exports = {
|
|
|
18
18
|
PUSH_PROMPT,
|
|
19
19
|
updatePrompt,
|
|
20
20
|
dropPrompt,
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
delCharBg,
|
|
22
|
+
addCharBg,
|
|
23
23
|
paintDiffLine,
|
|
24
24
|
headerText,
|
|
25
25
|
layoutButtons,
|