reslop 0.2.5 → 0.2.7
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 +22 -1
- package/LICENSE +1 -1
- package/README.md +11 -11
- package/bin/reslop.js +3 -8
- package/lib/ansi.js +72 -19
- package/lib/api.js +1 -1
- package/lib/capabilities.js +8 -8
- package/lib/cli.js +61 -75
- package/lib/clipboard.js +39 -47
- package/lib/dashboard/git.js +128 -0
- package/lib/dashboard/model.js +357 -0
- package/lib/dashboard/npm.js +290 -0
- package/lib/dashboard/tree.js +278 -0
- package/lib/deps-diff.js +197 -402
- package/lib/deps.js +113 -240
- package/lib/detect.js +8 -18
- package/lib/diff/align.js +41 -113
- package/lib/diff/diff.js +12 -43
- package/lib/diff/display.js +25 -36
- package/lib/diff/edit.js +6 -20
- package/lib/diff/inline.js +76 -144
- package/lib/diff/items.js +33 -47
- package/lib/diff/parse.js +28 -56
- package/lib/diff/patch.js +12 -33
- package/lib/diff/unit.js +79 -178
- package/lib/editor.js +52 -69
- package/lib/files.js +79 -91
- package/lib/find.js +61 -0
- package/lib/git-branches.js +254 -416
- package/lib/git-deps.js +148 -272
- package/lib/git-reword-todo.js +5 -13
- package/lib/git-worktree.js +161 -214
- package/lib/git.js +21 -49
- package/lib/github.js +67 -181
- package/lib/gitlab.js +40 -90
- package/lib/highlight/bash.js +55 -177
- package/lib/highlight/core.js +155 -141
- package/lib/highlight/css.js +25 -58
- package/lib/highlight/csv.js +19 -37
- package/lib/highlight/dart.js +72 -213
- package/lib/highlight/dot.js +14 -14
- package/lib/highlight/html.js +42 -63
- package/lib/highlight/js.js +177 -506
- package/lib/highlight/json.js +17 -37
- package/lib/highlight/log.js +19 -25
- package/lib/highlight/txt.js +7 -10
- package/lib/highlight.js +17 -46
- package/lib/keys.js +75 -83
- package/lib/manifest.js +67 -126
- package/lib/npm-commands.js +120 -169
- package/lib/remote.js +140 -190
- package/lib/render/branches.js +63 -61
- package/lib/render/chrome.js +412 -347
- package/lib/render/commits.js +160 -361
- package/lib/render/dash-activity.js +212 -0
- package/lib/render/dash-blocks.js +496 -0
- package/lib/render/dash-table.js +295 -0
- package/lib/render/dashboard.js +62 -0
- package/lib/render/diff.js +123 -258
- package/lib/render/files.js +60 -86
- package/lib/render/frame.js +82 -117
- package/lib/render/notes.js +150 -210
- package/lib/render/npm.js +213 -177
- package/lib/render/packages.js +93 -0
- package/lib/render/present.js +4 -11
- package/lib/render/primitives.js +77 -172
- package/lib/render/render.js +5 -14
- package/lib/render/report.js +43 -0
- package/lib/render/repos.js +226 -0
- package/lib/render/search.js +25 -0
- package/lib/render/tiles.js +355 -0
- package/lib/render/unit.js +15 -31
- package/lib/report-model.js +110 -33
- package/lib/report-parse.js +56 -37
- package/lib/report-render.js +102 -59
- package/lib/report-run.js +87 -77
- package/lib/report-test.js +221 -99
- package/lib/report-tools.js +96 -154
- package/lib/review-merge.js +33 -41
- package/lib/review.js +69 -129
- package/lib/runs.js +225 -0
- package/lib/select.js +53 -91
- package/lib/session/accessors.js +14 -21
- package/lib/session/actions.js +189 -65
- package/lib/session/branches.js +164 -222
- package/lib/session/change-actions.js +49 -119
- package/lib/session/commit.js +298 -352
- package/lib/session/compose.js +572 -715
- package/lib/session/dashboard.js +530 -0
- package/lib/session/dispatch.js +197 -171
- package/lib/session/files-pane.js +92 -45
- package/lib/session/find.js +94 -0
- package/lib/session/items.js +6 -17
- package/lib/session/lifecycle.js +420 -466
- package/lib/session/load.js +1 -14
- package/lib/session/navigation.js +4 -2
- package/lib/session/npm.js +403 -420
- package/lib/session/ops.js +5 -13
- package/lib/session/packages.js +143 -0
- package/lib/session/pointer.js +30 -14
- package/lib/session/progress.js +39 -30
- package/lib/session/review.js +1 -3
- package/lib/session/tasks.js +277 -236
- package/lib/session/templates.js +51 -59
- package/lib/session/terminal.js +85 -132
- package/lib/session/view.js +59 -80
- package/lib/session/watch.js +201 -212
- package/lib/session/workspace.js +455 -0
- package/lib/session.js +95 -161
- package/lib/update.js +44 -111
- package/lib/utilities.js +46 -88
- package/lib/workspace.js +166 -0
- package/lib/wrap.js +29 -55
- package/package.json +1 -1
package/lib/render/npm.js
CHANGED
|
@@ -1,16 +1,19 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const ansi = require('../ansi.js');
|
|
4
|
+
const npmCommands = require('../npm-commands.js');
|
|
5
|
+
const wrap = require('../wrap.js');
|
|
4
6
|
const chrome = require('./chrome.js');
|
|
5
7
|
const primitives = require('./primitives.js');
|
|
6
|
-
const npmCommands = require('../npm-commands.js');
|
|
7
8
|
|
|
8
|
-
const { THEME, paint, visibleWidth, truncateVisible
|
|
9
|
-
const {
|
|
10
|
-
const {
|
|
9
|
+
const { THEME, CODE_FG, paint, visibleWidth, truncateVisible } = ansi;
|
|
10
|
+
const { trimVisible, seq, EL, RESET, fg } = ansi;
|
|
11
|
+
const { clipAnsi, stripAnsi, foregroundOn } = ansi;
|
|
11
12
|
const { TABLE_KEY, TABLE_VAL } = npmCommands;
|
|
12
|
-
const {
|
|
13
|
-
const {
|
|
13
|
+
const { wrapPlain } = wrap;
|
|
14
|
+
const { formatBusyStatus } = chrome;
|
|
15
|
+
const { fill, MARK_W, markPrefix, editSpan } = primitives;
|
|
16
|
+
const { paneResult, paintCursorList, fieldWindow } = primitives;
|
|
14
17
|
|
|
15
18
|
const EDGE = 2;
|
|
16
19
|
const TAIL = 1;
|
|
@@ -18,152 +21,117 @@ const LOG_PAD_Y = 1;
|
|
|
18
21
|
const LOG_PAD_X = 2;
|
|
19
22
|
const NPM_NAME_MAX = 24;
|
|
20
23
|
|
|
24
|
+
const TABLE_INK = {
|
|
25
|
+
actual: THEME.errorFg,
|
|
26
|
+
expected: THEME.addLineFg,
|
|
27
|
+
code: THEME.shaFg,
|
|
28
|
+
type: CODE_FG.className,
|
|
29
|
+
count: THEME.warnFg,
|
|
30
|
+
omitted: THEME.warnFg,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const COUNT_INK = {
|
|
34
|
+
failed: THEME.errorFg,
|
|
35
|
+
errors: THEME.errorFg,
|
|
36
|
+
passed: THEME.addLineFg,
|
|
37
|
+
};
|
|
38
|
+
|
|
21
39
|
const logViewRows = (bodyH) => {
|
|
22
40
|
const height = Math.max(1, bodyH);
|
|
23
41
|
if (height >= LOG_PAD_Y * 2 + 1) return height - LOG_PAD_Y * 2;
|
|
24
42
|
return height;
|
|
25
43
|
};
|
|
26
44
|
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
const npmNameWidth = (commands) => {
|
|
30
|
-
let width = 0;
|
|
45
|
+
const npmNameWidth = (commands, typed = '') => {
|
|
46
|
+
let width = visibleWidth(typed);
|
|
31
47
|
for (const entry of commands) {
|
|
32
48
|
width = Math.max(width, visibleWidth(`${entry.name ?? ''}`));
|
|
33
49
|
}
|
|
34
50
|
return Math.min(NPM_NAME_MAX, width);
|
|
35
51
|
};
|
|
36
52
|
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
const
|
|
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);
|
|
53
|
+
const npmColumns = (width, nameCol) => {
|
|
54
|
+
const rest = Math.max(1, width - EDGE - TAIL - MARK_W);
|
|
55
|
+
const nameW = Math.min(nameCol, rest);
|
|
46
56
|
const gap = rest > nameW + 2 ? ' ' : '';
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
const
|
|
53
|
-
const
|
|
54
|
-
const
|
|
57
|
+
return { rest, nameW, gap, detailW: rest - nameW - gap.length };
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const paintNpmLine = (cols, name, detail, color, selected, bin) => {
|
|
61
|
+
const prefix = markPrefix(selected);
|
|
62
|
+
const namePad = ' '.repeat(Math.max(0, cols.nameW - visibleWidth(name)));
|
|
63
|
+
const mid = `${name}${namePad}`;
|
|
64
|
+
const after = `${cols.gap}${detail}`;
|
|
65
|
+
const pad = ' '.repeat(Math.max(0, cols.rest - visibleWidth(mid + after)));
|
|
55
66
|
const tail = ' '.repeat(TAIL);
|
|
67
|
+
const fgRgb = selected ? THEME.chromeFg : THEME.mutedFg;
|
|
68
|
+
const bgRgb = selected ? THEME.buttonBg : THEME.ctxBg;
|
|
56
69
|
const edge = fill(EDGE, fgRgb, THEME.ctxBg, color);
|
|
57
|
-
if (!color) return `${
|
|
70
|
+
if (!color) return `${prefix}${mid}${after}${pad}${tail}${edge}`;
|
|
71
|
+
const nameFg = bin ? THEME.shaFg : THEME.buttonHotFg;
|
|
58
72
|
let out = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
59
73
|
out += paint(prefix, fgRgb, bgRgb, color);
|
|
60
|
-
const bin = entry.kind === 'bin';
|
|
61
|
-
const nameFg = bin ? THEME.shaFg : THEME.buttonHotFg;
|
|
62
74
|
out += paint(mid, nameFg, bgRgb, color, !bin);
|
|
63
|
-
out += paint(`${after}${
|
|
75
|
+
out += paint(`${after}${pad}${tail}`, fgRgb, bgRgb, color);
|
|
64
76
|
return out + edge;
|
|
65
77
|
};
|
|
66
78
|
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
79
|
+
const paintNpmRow = (entry, width, color, selected, nameCol) => {
|
|
80
|
+
const cols = npmColumns(width, nameCol);
|
|
81
|
+
const bin = entry.kind === 'bin';
|
|
82
|
+
const name = trimVisible(entry.name, cols.nameW);
|
|
83
|
+
const detail = trimVisible(bin ? 'bin' : entry.command, cols.detailW);
|
|
84
|
+
return paintNpmLine(cols, name, detail, color, selected, bin);
|
|
70
85
|
};
|
|
71
86
|
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
87
|
+
const editingName = (view) => view.npmEditField !== 'command';
|
|
88
|
+
|
|
89
|
+
const editWindow = (compose, live, draft, width) => {
|
|
90
|
+
const value = `${(live ? compose.text : draft) ?? ''}`.replaceAll('\n', ' ');
|
|
91
|
+
const win = live
|
|
92
|
+
? fieldWindow(value, compose.cursor, compose.scrollCol, width)
|
|
93
|
+
: fieldWindow(value, 0, 0, width);
|
|
94
|
+
return { ...win, value };
|
|
75
95
|
};
|
|
76
96
|
|
|
77
|
-
const paintNpmEdit = (view, width, color,
|
|
78
|
-
const compose = view
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
const
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
const name = nameWin.text;
|
|
93
|
-
const gap = rest > colW + 2 ? ' ' : '';
|
|
94
|
-
const gapW = visibleWidth(gap);
|
|
95
|
-
const detailW = Math.max(0, rest - colW - gapW);
|
|
96
|
-
const flatCommand = `${commandSource ?? ''}`.replaceAll('\n', ' ');
|
|
97
|
-
const commandAt = field === 'command' ? compose.cursor : 0;
|
|
98
|
-
const commandScroll = field === 'command' ? compose.scrollCol : 0;
|
|
99
|
-
const commandWin = fieldWindow(
|
|
100
|
-
flatCommand,
|
|
101
|
-
commandAt,
|
|
102
|
-
commandScroll,
|
|
103
|
-
detailW,
|
|
104
|
-
);
|
|
105
|
-
const command = commandWin.text;
|
|
106
|
-
const namePad = ' '.repeat(Math.max(0, colW - visibleWidth(name)));
|
|
107
|
-
const mid = `${name}${namePad}`;
|
|
108
|
-
const after = `${gap}${command}`;
|
|
109
|
-
const pad = Math.max(0, rest - visibleWidth(mid + after));
|
|
110
|
-
const tail = ' '.repeat(TAIL);
|
|
111
|
-
const left = `${prefix}${mid}${after}${' '.repeat(pad)}`;
|
|
112
|
-
const fgRgb = THEME.chromeFg;
|
|
113
|
-
const bgRgb = THEME.buttonBg;
|
|
114
|
-
const edge = fill(EDGE, fgRgb, THEME.ctxBg, color);
|
|
115
|
-
let row = `${left}${tail}${edge}`;
|
|
116
|
-
if (color) {
|
|
117
|
-
row = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
118
|
-
row += paint(prefix, fgRgb, bgRgb, color);
|
|
119
|
-
row += paint(mid, THEME.buttonHotFg, bgRgb, color, true);
|
|
120
|
-
row += paint(`${after}${' '.repeat(pad)}${tail}`, fgRgb, bgRgb, color);
|
|
121
|
-
row += edge;
|
|
122
|
-
}
|
|
123
|
-
const active = field === 'name' ? nameWin : commandWin;
|
|
124
|
-
const lead = field === 'name' ? 0 : colW + gapW;
|
|
125
|
-
const nameX = prefixW + 1;
|
|
126
|
-
const commandX = prefixW + colW + gapW + 1;
|
|
97
|
+
const paintNpmEdit = (view, width, color, nameCol) => {
|
|
98
|
+
const { compose } = view;
|
|
99
|
+
const isName = editingName(view);
|
|
100
|
+
const cols = npmColumns(width, nameCol);
|
|
101
|
+
const { npmDraftName: draftName, npmDraftCommand: draftCommand } = view;
|
|
102
|
+
const name = editWindow(compose, isName, draftName, cols.nameW);
|
|
103
|
+
const command = editWindow(compose, !isName, draftCommand, cols.detailW);
|
|
104
|
+
const row = paintNpmLine(cols, name.text, command.text, color, true, false);
|
|
105
|
+
const nameX = MARK_W + 1;
|
|
106
|
+
const commandX = nameX + cols.nameW + cols.gap.length;
|
|
107
|
+
const active = isName ? name : command;
|
|
108
|
+
const cursor = {
|
|
109
|
+
x: (isName ? nameX : commandX) + active.col,
|
|
110
|
+
scroll: active.scroll,
|
|
111
|
+
};
|
|
127
112
|
const edits = [
|
|
128
113
|
{
|
|
129
114
|
field: 'name',
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
start: 0,
|
|
134
|
-
text: flatName,
|
|
135
|
-
limit: flatName.length,
|
|
136
|
-
scroll: nameWin.scroll,
|
|
137
|
-
live: field === 'name',
|
|
115
|
+
...editSpan(1, nameX, commandX, 0, name.value),
|
|
116
|
+
scroll: name.scroll,
|
|
117
|
+
live: isName,
|
|
138
118
|
},
|
|
139
119
|
{
|
|
140
120
|
field: 'command',
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
start: 0,
|
|
145
|
-
text: flatCommand,
|
|
146
|
-
limit: flatCommand.length,
|
|
147
|
-
scroll: commandWin.scroll,
|
|
148
|
-
live: field === 'command',
|
|
121
|
+
...editSpan(commandX, commandX, width + 1, 0, command.value),
|
|
122
|
+
scroll: command.scroll,
|
|
123
|
+
live: !isName,
|
|
149
124
|
},
|
|
150
125
|
];
|
|
151
|
-
const cursor = {
|
|
152
|
-
x: prefixW + lead + active.col + 1,
|
|
153
|
-
scroll: active.scroll,
|
|
154
|
-
};
|
|
155
126
|
return { row, cursor, edits };
|
|
156
127
|
};
|
|
157
128
|
|
|
158
129
|
const paintNpmList = (view, width, color, bodyH, headerLines) => {
|
|
159
130
|
const commands = view.npmCommands ?? [];
|
|
160
|
-
const compose = view
|
|
161
|
-
const editing = compose
|
|
162
|
-
const
|
|
163
|
-
const
|
|
164
|
-
const nameW = editing
|
|
165
|
-
? editNameWidth(commands, live)
|
|
166
|
-
: npmNameWidth(commands);
|
|
131
|
+
const { compose } = view;
|
|
132
|
+
const editing = compose?.kind === 'npm';
|
|
133
|
+
const typed = editingName(view) ? compose?.text : view.npmDraftName;
|
|
134
|
+
const nameW = npmNameWidth(commands, editing ? `${typed ?? ''}` : '');
|
|
167
135
|
const cursor = view.npmCursor ?? 0;
|
|
168
136
|
const pane = { width, color, bodyH, headerLines, offset: view.listScroll };
|
|
169
137
|
const paintRow = (entry, selected) =>
|
|
@@ -179,23 +147,14 @@ const paintNpmList = (view, width, color, bodyH, headerLines) => {
|
|
|
179
147
|
|
|
180
148
|
const logStart = (view, count, rows) => {
|
|
181
149
|
const maxStart = Math.max(0, count - rows);
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
if (start > maxStart) start = maxStart;
|
|
185
|
-
if (start < 0) start = 0;
|
|
186
|
-
return start;
|
|
150
|
+
const start = view.npmFollow ? maxStart : view.npmScroll;
|
|
151
|
+
return Math.max(0, Math.min(start ?? 0, maxStart));
|
|
187
152
|
};
|
|
188
153
|
|
|
189
154
|
const isTableLine = (line) => `${line ?? ''}`.startsWith(TABLE_KEY);
|
|
190
155
|
|
|
191
|
-
const mixRgb = (from, to, part) =>
|
|
192
|
-
|
|
193
|
-
for (let i = 0; i < 3; i++) {
|
|
194
|
-
const next = from[i] + (to[i] - from[i]) * part;
|
|
195
|
-
out[i] = Math.round(next);
|
|
196
|
-
}
|
|
197
|
-
return out;
|
|
198
|
-
};
|
|
156
|
+
const mixRgb = (from, to, part) =>
|
|
157
|
+
from.map((value, i) => Math.round(value + (to[i] - value) * part));
|
|
199
158
|
|
|
200
159
|
const tableTone = (index) => {
|
|
201
160
|
const lift = index % 2 === 0 ? 0.035 : 0.07;
|
|
@@ -206,65 +165,157 @@ const tableTone = (index) => {
|
|
|
206
165
|
};
|
|
207
166
|
|
|
208
167
|
const tableIndex = (lines, index) => {
|
|
209
|
-
let at = index;
|
|
210
168
|
let row = 0;
|
|
211
|
-
while (
|
|
212
|
-
at -= 1;
|
|
213
|
-
row += 1;
|
|
214
|
-
}
|
|
169
|
+
while (index - row > 0 && isTableLine(lines[index - row - 1])) row += 1;
|
|
215
170
|
return row;
|
|
216
171
|
};
|
|
217
172
|
|
|
218
|
-
const
|
|
173
|
+
const tableInk = (key, value) => {
|
|
174
|
+
const name = key.trim();
|
|
175
|
+
if (Object.hasOwn(COUNT_INK, name)) {
|
|
176
|
+
return Number(value) > 0 ? COUNT_INK[name] : THEME.mutedFg;
|
|
177
|
+
}
|
|
178
|
+
return Object.hasOwn(TABLE_INK, name) ? TABLE_INK[name] : THEME.chromeFg;
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
const tableParts = (line, width) => {
|
|
219
182
|
const raw = line.slice(TABLE_KEY.length);
|
|
220
183
|
const at = raw.indexOf(TABLE_VAL);
|
|
221
184
|
const key = at < 0 ? raw : raw.slice(0, at);
|
|
222
185
|
const rest = at < 0 ? '' : raw.slice(at + TABLE_VAL.length);
|
|
223
186
|
const gap = rest.startsWith(' ') ? ' ' : '';
|
|
224
187
|
const value = rest.slice(gap.length);
|
|
225
|
-
const edge = 1;
|
|
226
|
-
const inner = Math.max(
|
|
227
|
-
const keyW = visibleWidth(key);
|
|
228
|
-
const gapW = visibleWidth(gap);
|
|
188
|
+
const edge = Math.min(1, Math.floor(width / 2));
|
|
189
|
+
const inner = Math.max(0, width - edge * 2);
|
|
190
|
+
const keyW = Math.min(visibleWidth(key), inner);
|
|
191
|
+
const gapW = Math.min(visibleWidth(gap), Math.max(0, inner - keyW));
|
|
229
192
|
const valueW = Math.max(0, inner - keyW - gapW);
|
|
230
|
-
|
|
231
|
-
|
|
193
|
+
return { key, gap, value, edge, keyW, gapW, valueW };
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
const wrapTableLine = (line, width) => {
|
|
197
|
+
const part = tableParts(line, width);
|
|
198
|
+
if (part.valueW < 1) return [line];
|
|
199
|
+
const trimmed = part.value.trimEnd();
|
|
200
|
+
const lead = trimmed.startsWith(' ') ? ' ' : '';
|
|
201
|
+
const body = trimmed.slice(lead.length);
|
|
202
|
+
const budget = Math.max(1, part.valueW - visibleWidth(lead));
|
|
203
|
+
if (visibleWidth(body) <= budget) return [line];
|
|
204
|
+
const blank = ' '.repeat(visibleWidth(part.key));
|
|
205
|
+
return wrapPlain(body, budget).map((piece, index) => {
|
|
206
|
+
const name = index === 0 ? part.key : blank;
|
|
207
|
+
return `${TABLE_KEY}${name}${TABLE_VAL}${part.gap}${lead}${piece}`;
|
|
208
|
+
});
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
const wrapLogText = (line, inner) => {
|
|
212
|
+
const mark = line.startsWith('│ ') ? '│ ' : '';
|
|
213
|
+
const rest = line.slice(mark.length);
|
|
214
|
+
const budget = Math.max(1, inner - visibleWidth(mark));
|
|
215
|
+
if (visibleWidth(rest) <= budget) return [line];
|
|
216
|
+
return wrapPlain(rest, budget).map((piece) => `${mark}${piece}`);
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const expandLogLines = (text, width) => {
|
|
220
|
+
const lines = `${text ?? ''}`.split('\n');
|
|
221
|
+
if (lines.at(-1) === '') lines.pop();
|
|
222
|
+
const inner = Math.max(1, width - LOG_PAD_X * 2);
|
|
223
|
+
return lines.flatMap((line) => {
|
|
224
|
+
if (isTableLine(line)) return wrapTableLine(line, width);
|
|
225
|
+
return wrapLogText(line, inner);
|
|
226
|
+
});
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const paintPlainTable = (line, width) => {
|
|
230
|
+
const part = tableParts(line, width);
|
|
231
|
+
const name = trimVisible(part.key, part.keyW);
|
|
232
|
+
const namePad = ' '.repeat(Math.max(0, part.keyW - visibleWidth(name)));
|
|
233
|
+
const shown = trimVisible(part.value, part.valueW);
|
|
234
|
+
const pad = ' '.repeat(Math.max(0, part.valueW - visibleWidth(shown)));
|
|
235
|
+
const edge = ' '.repeat(part.edge);
|
|
236
|
+
const gap = part.gap.slice(0, part.gapW);
|
|
237
|
+
return `${edge}${name}${namePad}${gap}${shown}${pad}${edge}`;
|
|
238
|
+
};
|
|
239
|
+
|
|
240
|
+
const paintTableLine = (line, width, index) => {
|
|
241
|
+
const part = tableParts(line, width);
|
|
242
|
+
const shownKey = trimVisible(part.key, part.keyW);
|
|
243
|
+
const shown = trimVisible(part.value, part.valueW);
|
|
244
|
+
const pad = ' '.repeat(Math.max(0, part.valueW - visibleWidth(shown)));
|
|
232
245
|
const tone = tableTone(index);
|
|
233
246
|
const ink = THEME.ctxFg;
|
|
234
|
-
|
|
235
|
-
out
|
|
236
|
-
out += paint(
|
|
237
|
-
out += paint(
|
|
238
|
-
|
|
247
|
+
const valueFg = tableInk(part.key, part.value);
|
|
248
|
+
let out = fill(part.edge, ink, tone.frame, true);
|
|
249
|
+
out += paint(shownKey, ink, tone.keyBg, true);
|
|
250
|
+
out += paint(part.gap.slice(0, part.gapW), ink, tone.frame, true);
|
|
251
|
+
out += paint(`${shown}${pad}`, valueFg, tone.valueBg, true);
|
|
252
|
+
return out + fill(part.edge, ink, tone.frame, true);
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
const paintLogSource = (line) => {
|
|
256
|
+
if (line !== stripAnsi(line)) return line;
|
|
257
|
+
if (/^[✖✔△] /.test(line)) {
|
|
258
|
+
const ink = line[0] === '✔' ? THEME.addLineFg : THEME.errorFg;
|
|
259
|
+
const color = line[0] === '△' ? THEME.warnFg : ink;
|
|
260
|
+
return paint(line, color, THEME.ctxBg, true, true);
|
|
261
|
+
}
|
|
262
|
+
if (line.startsWith('│')) {
|
|
263
|
+
return `${fg(THEME.shaFg)}│${fg(THEME.ctxFg)}${line.slice(1)}`;
|
|
264
|
+
}
|
|
265
|
+
if (/^(Stack|Related failures|Stderr)$/.test(line)) {
|
|
266
|
+
return paint(line, THEME.chromeFg, THEME.ctxBg, true, true);
|
|
267
|
+
}
|
|
268
|
+
if (!/^\s+(at|↳)\s/.test(line)) return line;
|
|
269
|
+
const source = line.replace(
|
|
270
|
+
/([^\s()|]+)(:\d+:\d+)(?=[\s)|]|$)/g,
|
|
271
|
+
(match, file, position) => {
|
|
272
|
+
const path = `${fg(THEME.shaFg)}${file}`;
|
|
273
|
+
const point = `${fg(THEME.mutedFg)}${position}`;
|
|
274
|
+
return `${path}${point}${fg(THEME.ctxFg)}`;
|
|
275
|
+
},
|
|
276
|
+
);
|
|
277
|
+
const frame = /^(\s+at\s+)(.*?)(\s+\(.+\))$/.exec(source);
|
|
278
|
+
if (!frame) return source;
|
|
279
|
+
return (
|
|
280
|
+
`${fg(THEME.mutedFg)}${frame[1]}${fg(CODE_FG.function)}${frame[2]}` +
|
|
281
|
+
`${fg(THEME.ctxFg)}${frame[3]}`
|
|
282
|
+
);
|
|
239
283
|
};
|
|
240
284
|
|
|
241
285
|
const paintLogLine = (line, width, color, fgRgb = THEME.ctxFg) => {
|
|
242
286
|
const inner = Math.max(1, width - LOG_PAD_X * 2);
|
|
243
|
-
const source = color ? `${line ?? ''}` : stripAnsi(line);
|
|
244
|
-
const clip = (value) =>
|
|
245
|
-
color ? clipAnsi(value, inner) : truncateVisible(value, inner);
|
|
246
|
-
const body = color ? foregroundOn(clip(source), THEME.ctxBg) : clip(source);
|
|
247
287
|
const lead = ' '.repeat(LOG_PAD_X);
|
|
288
|
+
if (!color) {
|
|
289
|
+
const body = truncateVisible(stripAnsi(line), inner);
|
|
290
|
+
const pad = Math.max(0, width - LOG_PAD_X - visibleWidth(body));
|
|
291
|
+
return `${lead}${body}${' '.repeat(pad)}`;
|
|
292
|
+
}
|
|
293
|
+
const source = paintLogSource(`${line ?? ''}`);
|
|
294
|
+
const body = foregroundOn(clipAnsi(source, inner), THEME.ctxBg);
|
|
248
295
|
const pad = Math.max(0, width - LOG_PAD_X - visibleWidth(body));
|
|
249
|
-
const tail = ' '.repeat(pad);
|
|
250
|
-
if (!color) return `${lead}${body}${tail}`;
|
|
251
296
|
const base = seq(fgRgb, THEME.ctxBg);
|
|
252
297
|
let out = `${base}${EL}${lead}${body}`;
|
|
253
|
-
if (pad) out += `${RESET}${base}${
|
|
298
|
+
if (pad) out += `${RESET}${base}${' '.repeat(pad)}`;
|
|
254
299
|
return `${out}${RESET}`;
|
|
255
300
|
};
|
|
256
301
|
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
302
|
+
const paintLogRow = (view, lines, index, width, color) => {
|
|
303
|
+
if (index < lines.length) {
|
|
304
|
+
const line = lines[index];
|
|
305
|
+
if (!isTableLine(line)) return paintLogLine(line, width, color);
|
|
306
|
+
if (!color) return paintPlainTable(line, width);
|
|
307
|
+
return paintTableLine(line, width, tableIndex(lines, index));
|
|
308
|
+
}
|
|
309
|
+
if (view.npmRunning === true && index === lines.length) {
|
|
310
|
+
const label = formatBusyStatus('running', view.progressFrame ?? 0);
|
|
311
|
+
return paintLogLine(label, width, color, THEME.chromeFg);
|
|
312
|
+
}
|
|
313
|
+
return fill(width, THEME.ctxFg, THEME.ctxBg, color);
|
|
260
314
|
};
|
|
261
315
|
|
|
262
316
|
const paintNpmLog = (view, width, color, bodyH) => {
|
|
263
|
-
const
|
|
264
|
-
const
|
|
265
|
-
if (lines.length && lines.at(-1) === '') lines.pop();
|
|
266
|
-
const running = view.npmRunning === true;
|
|
267
|
-
const count = lines.length + (running ? 1 : 0);
|
|
317
|
+
const lines = expandLogLines(view.npmOutput ?? '', width);
|
|
318
|
+
const count = lines.length + (view.npmRunning === true ? 1 : 0);
|
|
268
319
|
const rows = logViewRows(bodyH);
|
|
269
320
|
const padY = bodyH - rows;
|
|
270
321
|
const top = padY ? 1 : 0;
|
|
@@ -273,25 +324,10 @@ const paintNpmLog = (view, width, color, bodyH) => {
|
|
|
273
324
|
const body = [];
|
|
274
325
|
for (let i = 0; i < top; i++) body.push(blank);
|
|
275
326
|
for (let i = 0; i < rows; i++) {
|
|
276
|
-
|
|
277
|
-
if (index < lines.length) {
|
|
278
|
-
const line = lines[index];
|
|
279
|
-
if (color && isTableLine(line)) {
|
|
280
|
-
const row = tableIndex(lines, index);
|
|
281
|
-
body.push(paintTableLine(line, width, row));
|
|
282
|
-
} else {
|
|
283
|
-
body.push(paintLogLine(line, width, color));
|
|
284
|
-
}
|
|
285
|
-
} else if (running && index === lines.length) {
|
|
286
|
-
body.push(paintRunBar(width, color, view.progressFrame ?? 0));
|
|
287
|
-
} else {
|
|
288
|
-
body.push(blank);
|
|
289
|
-
}
|
|
327
|
+
body.push(paintLogRow(view, lines, start + i, width, color));
|
|
290
328
|
}
|
|
291
329
|
for (let i = 0; i < padY - top; i++) body.push(blank);
|
|
292
|
-
return paneResult({
|
|
293
|
-
body,
|
|
294
|
-
});
|
|
330
|
+
return paneResult({ body });
|
|
295
331
|
};
|
|
296
332
|
|
|
297
333
|
const paintBodyNpm = (view, width, color, bodyH, headerLines) => {
|
|
@@ -299,4 +335,4 @@ const paintBodyNpm = (view, width, color, bodyH, headerLines) => {
|
|
|
299
335
|
return paintNpmList(view, width, color, bodyH, headerLines);
|
|
300
336
|
};
|
|
301
337
|
|
|
302
|
-
module.exports = { paintBodyNpm, logViewRows };
|
|
338
|
+
module.exports = { paintBodyNpm, logViewRows, expandLogLines };
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ansi = require('../ansi.js');
|
|
4
|
+
const { THEME, paint, visibleWidth } = ansi;
|
|
5
|
+
const tiles = require('./tiles.js');
|
|
6
|
+
const { paintSegments } = tiles;
|
|
7
|
+
const blocks = require('./dash-blocks.js');
|
|
8
|
+
const { npmContent } = blocks;
|
|
9
|
+
const primitives = require('./primitives.js');
|
|
10
|
+
const { MARK_W, markPrefix, paintCursorList, fieldWindow, editSpan } =
|
|
11
|
+
primitives;
|
|
12
|
+
|
|
13
|
+
const TILE = { key: 'n', title: 'npm' };
|
|
14
|
+
const TAIL = 1;
|
|
15
|
+
const LIST_PAD = 1;
|
|
16
|
+
|
|
17
|
+
const paintMarginRow = (line, width, color, selected) => {
|
|
18
|
+
const bg = selected ? THEME.buttonBg : THEME.ctxBg;
|
|
19
|
+
const fg = selected ? THEME.chromeFg : THEME.mutedFg;
|
|
20
|
+
const inner = Math.max(1, width - MARK_W - TAIL);
|
|
21
|
+
const body = paintSegments(line, inner, bg, color);
|
|
22
|
+
const prefix = markPrefix(selected);
|
|
23
|
+
const tail = ' '.repeat(TAIL);
|
|
24
|
+
if (!color) return `${prefix}${body}${tail}`;
|
|
25
|
+
return paint(prefix, fg, bg, color) + body + paint(tail, fg, bg, color);
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const paintPackageEdit = (compose, width, color) => {
|
|
29
|
+
const prefix = markPrefix(true);
|
|
30
|
+
const rest = Math.max(1, width - MARK_W - TAIL);
|
|
31
|
+
const text = `${compose.text ?? ''}`.replaceAll('\n', ' ');
|
|
32
|
+
const win = fieldWindow(text, compose.cursor, compose.scrollCol, rest);
|
|
33
|
+
const gap = ' '.repeat(Math.max(0, rest - visibleWidth(win.text)));
|
|
34
|
+
const tail = ' '.repeat(TAIL);
|
|
35
|
+
const fg = THEME.chromeFg;
|
|
36
|
+
const bg = THEME.buttonBg;
|
|
37
|
+
let row = `${prefix}${win.text}${gap}${tail}`;
|
|
38
|
+
if (color) {
|
|
39
|
+
row = paint(prefix, fg, bg, color);
|
|
40
|
+
row += paint(win.text, THEME.buttonHotFg, bg, color);
|
|
41
|
+
row += paint(`${gap}${tail}`, fg, bg, color);
|
|
42
|
+
}
|
|
43
|
+
const textX = MARK_W + 1;
|
|
44
|
+
const edit = editSpan(1, textX, textX + rest, 0, text);
|
|
45
|
+
const cursor = { x: textX + win.col, scroll: win.scroll };
|
|
46
|
+
return { row, cursor, edits: [{ ...edit, scroll: win.scroll }] };
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const paintBodyPackages = (view, width, color, bodyH, headerLines) => {
|
|
50
|
+
const ctx = { frame: view.progressFrame ?? 0 };
|
|
51
|
+
const inner = Math.max(1, width - MARK_W - TAIL);
|
|
52
|
+
const content = npmContent(view.packages, inner, null, ctx, TILE);
|
|
53
|
+
const headed = Boolean(content.titleLine) && content.lines.length > 0;
|
|
54
|
+
const rows = headed ? content.lines.slice(1) : content.lines;
|
|
55
|
+
const reserved = headed ? 1 : 0;
|
|
56
|
+
const last = Math.max(0, rows.length - 1);
|
|
57
|
+
const cursor = Math.min(view.packagesCursor ?? 0, last);
|
|
58
|
+
const pane = {
|
|
59
|
+
width,
|
|
60
|
+
color,
|
|
61
|
+
bodyH: Math.max(1, bodyH - reserved),
|
|
62
|
+
headerLines: headerLines + reserved,
|
|
63
|
+
offset: view.listScroll,
|
|
64
|
+
};
|
|
65
|
+
const paintRow = (line, selected) =>
|
|
66
|
+
paintMarginRow(line, width, color, selected);
|
|
67
|
+
const editing = view.compose && view.compose.kind === 'package';
|
|
68
|
+
const extra = editing
|
|
69
|
+
? {
|
|
70
|
+
...paintPackageEdit(view.compose, width, color),
|
|
71
|
+
at: 'index',
|
|
72
|
+
index: cursor,
|
|
73
|
+
}
|
|
74
|
+
: null;
|
|
75
|
+
const painted = paintCursorList(rows, cursor, pane, paintRow, extra);
|
|
76
|
+
if (!headed) return painted;
|
|
77
|
+
const head = paintMarginRow(content.lines[0], width, color, false);
|
|
78
|
+
const body = painted.body.slice();
|
|
79
|
+
const pad = pane.bodyH >= LIST_PAD * 2 + 1 ? LIST_PAD : 0;
|
|
80
|
+
body.splice(pad, 0, head);
|
|
81
|
+
const nudge = (point) => {
|
|
82
|
+
if (!point || point.row < pad) return point;
|
|
83
|
+
return { ...point, row: point.row + 1 };
|
|
84
|
+
};
|
|
85
|
+
return {
|
|
86
|
+
...painted,
|
|
87
|
+
body,
|
|
88
|
+
editHits: painted.editHits.map(nudge),
|
|
89
|
+
cursor: nudge(painted.cursor),
|
|
90
|
+
};
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
module.exports = { paintBodyPackages };
|
package/lib/render/present.js
CHANGED
|
@@ -10,17 +10,10 @@ const presentCursor = (cursor) => {
|
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
const presentRows = (rows, options = {}) => {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
for (let i = 0; i < rows.length; i++) {
|
|
18
|
-
parts.push(`${ESC}[${i + 1};1H`);
|
|
19
|
-
parts.push(rows[i]);
|
|
20
|
-
}
|
|
21
|
-
parts.push(`${ESC}[?2026l`);
|
|
22
|
-
parts.push(presentCursor(cursor));
|
|
23
|
-
return parts.join('');
|
|
13
|
+
let out = `${ESC}[?25l${ESC}[?2026h`;
|
|
14
|
+
if (options.clear === true) out += `${ESC}[H${ESC}[2J`;
|
|
15
|
+
for (let i = 0; i < rows.length; i++) out += `${ESC}[${i + 1};1H${rows[i]}`;
|
|
16
|
+
return `${out}${ESC}[?2026l${presentCursor(options.cursor)}`;
|
|
24
17
|
};
|
|
25
18
|
|
|
26
19
|
module.exports = { presentCursor, presentRows };
|