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/primitives.js
CHANGED
|
@@ -2,68 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
const ansi = require('../ansi.js');
|
|
4
4
|
const actions = require('../session/actions.js');
|
|
5
|
-
const { LIST_PANES } = actions;
|
|
6
5
|
|
|
7
|
-
const { THEME, paint, visibleWidth,
|
|
8
|
-
const {
|
|
6
|
+
const { THEME, paint, visibleWidth, graphemes, graphemeWidth } = ansi;
|
|
7
|
+
const { LIST_PANES } = actions;
|
|
9
8
|
|
|
10
9
|
const GUTTER_W = 2;
|
|
11
10
|
const FILE_MARK = '▶';
|
|
11
|
+
const MARK_W = visibleWidth(` ${FILE_MARK} `);
|
|
12
12
|
const LIST_PAD = 1;
|
|
13
|
-
const
|
|
14
|
-
const CHECK_TOKEN = /\[([ xX])\]/;
|
|
15
|
-
const CHECK_LEAD = /^\[[ xX]\] /;
|
|
13
|
+
const NO_EDITS = [];
|
|
16
14
|
|
|
17
15
|
const fill = (width, fgRgb, bgRgb, color) =>
|
|
18
16
|
paint(' '.repeat(Math.max(0, width)), fgRgb, bgRgb, color);
|
|
19
17
|
|
|
20
|
-
const
|
|
21
|
-
const clipped = truncateVisible(text, width);
|
|
22
|
-
const pad = Math.max(0, width - visibleWidth(clipped));
|
|
23
|
-
const filled = clipped + ' '.repeat(pad);
|
|
24
|
-
if (!color) return filled;
|
|
25
|
-
return `${seq(fgRgb, bgRgb)}${EL}${filled}${RESET}`;
|
|
26
|
-
};
|
|
18
|
+
const markPrefix = (selected) => ` ${selected ? FILE_MARK : ' '} `;
|
|
27
19
|
|
|
28
|
-
const
|
|
29
|
-
|
|
30
|
-
if (!match) return null;
|
|
31
|
-
const start = match.index;
|
|
32
|
-
const token = match[0];
|
|
33
|
-
const before = text.slice(0, start);
|
|
34
|
-
const mark = match[1];
|
|
35
|
-
const after = text.slice(start + token.length);
|
|
36
|
-
return { before, mark, token, after };
|
|
37
|
-
};
|
|
20
|
+
const isListPane = (pane) =>
|
|
21
|
+
LIST_PANES.includes(pane) || pane === 'dashboard' || pane === 'repos';
|
|
38
22
|
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
if (!parts) return paintBar(text, width, fgRgb, bgRgb, color);
|
|
43
|
-
const pad = Math.max(0, width - visibleWidth(clipped));
|
|
44
|
-
const done = CHECK_DONE.includes(parts.mark);
|
|
45
|
-
let chipFg = THEME.checkFg;
|
|
46
|
-
let chipBg = THEME.checkBg;
|
|
47
|
-
if (plain) {
|
|
48
|
-
chipFg = fgRgb;
|
|
49
|
-
chipBg = bgRgb;
|
|
50
|
-
} else if (done) {
|
|
51
|
-
chipFg = THEME.checkDoneFg;
|
|
52
|
-
chipBg = THEME.checkDoneBg;
|
|
53
|
-
}
|
|
54
|
-
const after = `${parts.after}${' '.repeat(pad)}`;
|
|
55
|
-
let out = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
56
|
-
if (parts.before) out += paint(parts.before, fgRgb, bgRgb, color);
|
|
57
|
-
out += paint(parts.token, chipFg, chipBg, color);
|
|
58
|
-
if (after) out += paint(after, fgRgb, bgRgb, color);
|
|
59
|
-
return out;
|
|
60
|
-
};
|
|
23
|
+
const isTaskView = (view) => view.item?.origin === 'task';
|
|
24
|
+
|
|
25
|
+
const branchLabel = (name) => `${name ?? ''}`.trim();
|
|
61
26
|
|
|
62
27
|
const padVisible = (text, width, align) => {
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
if (align === 'end') return `${text}${gap}`;
|
|
66
|
-
return `${gap}${text}`;
|
|
28
|
+
const gap = ' '.repeat(Math.max(0, width - visibleWidth(text)));
|
|
29
|
+
return align === 'end' ? `${text}${gap}` : `${gap}${text}`;
|
|
67
30
|
};
|
|
68
31
|
|
|
69
32
|
const splitWidths = (width) => {
|
|
@@ -75,49 +38,12 @@ const splitWidths = (width) => {
|
|
|
75
38
|
const paintSplitRule = (color) =>
|
|
76
39
|
fill(1, THEME.splitGutterFg, THEME.splitGutterBg, color);
|
|
77
40
|
|
|
78
|
-
const paintSplitBlank = (width, color) => {
|
|
79
|
-
const { leftW, rightW } = splitWidths(width);
|
|
80
|
-
return (
|
|
81
|
-
fill(leftW, THEME.ctxFg, THEME.ctxBg, color) +
|
|
82
|
-
paintSplitRule(color) +
|
|
83
|
-
fill(rightW, THEME.ctxFg, THEME.ctxBg, color)
|
|
84
|
-
);
|
|
85
|
-
};
|
|
86
|
-
|
|
87
41
|
const paintBodyFill = (width, color, kind) => {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
if (kind === 'split') return paintSplitBlank(width, color);
|
|
95
|
-
return fill(width, THEME.ctxFg, THEME.ctxBg, color);
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
const windowRows = (rows, focus, cap) => {
|
|
99
|
-
if (rows.length <= cap) return { rows, offset: 0 };
|
|
100
|
-
let start = focus - cap + 1;
|
|
101
|
-
if (start < 0) start = 0;
|
|
102
|
-
if (start + cap > rows.length) start = rows.length - cap;
|
|
103
|
-
return { rows: rows.slice(start, start + cap), offset: start };
|
|
104
|
-
};
|
|
105
|
-
|
|
106
|
-
const listWindowStart = (focus, offset, listH, count) => {
|
|
107
|
-
let start = offset ?? 0;
|
|
108
|
-
if (focus < start) start = focus;
|
|
109
|
-
if (focus >= start + listH) start = focus - listH + 1;
|
|
110
|
-
const maxStart = Math.max(0, count - listH);
|
|
111
|
-
if (start > maxStart) start = maxStart;
|
|
112
|
-
if (start < 0) start = 0;
|
|
113
|
-
return start;
|
|
114
|
-
};
|
|
115
|
-
|
|
116
|
-
const extraIdx = (extraRow, cursor, count) => {
|
|
117
|
-
if (!extraRow) return -1;
|
|
118
|
-
if (extraRow.at === 'start') return 0;
|
|
119
|
-
if (extraRow.at === 'replace') return extraRow.index ?? cursor;
|
|
120
|
-
return count;
|
|
42
|
+
const ctx = (n) => fill(n, THEME.ctxFg, THEME.ctxBg, color);
|
|
43
|
+
if (kind === 'files') return ctx(Math.max(1, width - 1)) + ctx(1);
|
|
44
|
+
if (kind !== 'split') return ctx(width);
|
|
45
|
+
const { leftW, rightW } = splitWidths(width);
|
|
46
|
+
return ctx(leftW) + paintSplitRule(color) + ctx(rightW);
|
|
121
47
|
};
|
|
122
48
|
|
|
123
49
|
const paneResult = (fields = {}) => ({
|
|
@@ -131,11 +57,29 @@ const paneResult = (fields = {}) => ({
|
|
|
131
57
|
editHits: fields.editHits ?? [],
|
|
132
58
|
});
|
|
133
59
|
|
|
134
|
-
const
|
|
60
|
+
const editSpan = (x0, textX, x1, start, text) => ({
|
|
61
|
+
x0,
|
|
62
|
+
textX,
|
|
63
|
+
x1,
|
|
64
|
+
start,
|
|
65
|
+
text,
|
|
66
|
+
limit: start + text.length,
|
|
67
|
+
live: true,
|
|
68
|
+
});
|
|
135
69
|
|
|
136
|
-
const listLine = (row, index, cursor = null, edits = NO_EDITS) => {
|
|
137
|
-
|
|
138
|
-
|
|
70
|
+
const listLine = (row, index, cursor = null, edits = NO_EDITS) => ({
|
|
71
|
+
row,
|
|
72
|
+
index,
|
|
73
|
+
hit: index >= 0,
|
|
74
|
+
cursor,
|
|
75
|
+
edits,
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
const listWindowStart = (focus, offset, listH, count) => {
|
|
79
|
+
let start = offset ?? 0;
|
|
80
|
+
if (focus < start) start = focus;
|
|
81
|
+
if (focus >= start + listH) start = focus - listH + 1;
|
|
82
|
+
return Math.max(0, Math.min(start, count - listH));
|
|
139
83
|
};
|
|
140
84
|
|
|
141
85
|
const rowCursor = (cursor, row) => {
|
|
@@ -150,11 +94,10 @@ const paintListWindow = (count, focus, pane, lineAt) => {
|
|
|
150
94
|
const listH = bodyH - pad * 2;
|
|
151
95
|
const start = listWindowStart(focus, offset, listH, count);
|
|
152
96
|
const blank = paintBodyFill(width, color, 'files');
|
|
153
|
-
const body = [];
|
|
97
|
+
const body = pad ? [blank] : [];
|
|
154
98
|
const fileHits = [];
|
|
155
99
|
const editHits = [];
|
|
156
100
|
let cursor = null;
|
|
157
|
-
if (pad) body.push(blank);
|
|
158
101
|
for (let i = 0; i < listH; i++) {
|
|
159
102
|
const line = lineAt(start + i);
|
|
160
103
|
if (!line) break;
|
|
@@ -169,30 +112,35 @@ const paintListWindow = (count, focus, pane, lineAt) => {
|
|
|
169
112
|
return paneResult({ body, fileHits, editHits, cursor, listScroll: start });
|
|
170
113
|
};
|
|
171
114
|
|
|
172
|
-
const
|
|
173
|
-
|
|
174
|
-
|
|
115
|
+
const insertedAt = (extra, count) => {
|
|
116
|
+
if (extra.at === 'start') return 0;
|
|
117
|
+
if (extra.at !== 'index') return count;
|
|
118
|
+
const index = extra.index ?? count;
|
|
119
|
+
return Math.max(0, Math.min(index, count));
|
|
175
120
|
};
|
|
176
121
|
|
|
177
|
-
const paintCursorList = (items, cursor, pane, paintRow,
|
|
178
|
-
const
|
|
179
|
-
const inserting = edit !== null && extraRow.at === 'start';
|
|
180
|
-
const extra = edit && extraRow.at !== 'replace' ? 1 : 0;
|
|
181
|
-
const extraAt = extraIdx(extraRow, cursor, items.length);
|
|
182
|
-
const lineAt = (idx) => {
|
|
183
|
-
if (edit && idx === extraAt) return edit;
|
|
184
|
-
const index = inserting ? idx - 1 : idx;
|
|
122
|
+
const paintCursorList = (items, cursor, pane, paintRow, extra = null) => {
|
|
123
|
+
const lineAt = (index) => {
|
|
185
124
|
const entry = items[index];
|
|
186
125
|
if (!entry) return null;
|
|
187
|
-
|
|
126
|
+
const selected = !extra && index === cursor;
|
|
127
|
+
return listLine(paintRow(entry, selected, index), index);
|
|
188
128
|
};
|
|
189
|
-
|
|
190
|
-
|
|
129
|
+
if (!extra) return paintListWindow(items.length, cursor, pane, lineAt);
|
|
130
|
+
const edit = listLine(extra.row, -1, extra.cursor, extra.edits);
|
|
131
|
+
if (extra.at === 'replace') {
|
|
132
|
+
const at = extra.index;
|
|
133
|
+
const replaced = (i) => (i === at ? edit : lineAt(i));
|
|
134
|
+
return paintListWindow(items.length, at, pane, replaced);
|
|
135
|
+
}
|
|
136
|
+
const at = insertedAt(extra, items.length);
|
|
137
|
+
const inserted = (i) => (i === at ? edit : lineAt(i < at ? i : i - 1));
|
|
138
|
+
return paintListWindow(items.length + 1, at, pane, inserted);
|
|
191
139
|
};
|
|
192
140
|
|
|
193
141
|
const sliceVisible = (text, skip, width) => {
|
|
194
142
|
if (width <= 0) return '';
|
|
195
|
-
const parts = graphemes(
|
|
143
|
+
const parts = graphemes(text);
|
|
196
144
|
let used = 0;
|
|
197
145
|
let i = 0;
|
|
198
146
|
const drop = Math.max(0, skip);
|
|
@@ -214,59 +162,16 @@ const sliceVisible = (text, skip, width) => {
|
|
|
214
162
|
};
|
|
215
163
|
|
|
216
164
|
const fieldWindow = (text, cursor, scroll, width) => {
|
|
217
|
-
const raw = `${text ?? ''}`;
|
|
218
165
|
if (width <= 0) return { text: '', col: 0, scroll: 0 };
|
|
219
|
-
const at = Math.max(0, Math.min(cursor ?? 0,
|
|
220
|
-
const col = visibleWidth(
|
|
221
|
-
let next = scroll ?? 0;
|
|
222
|
-
if (next < 0) next = 0;
|
|
166
|
+
const at = Math.max(0, Math.min(cursor ?? 0, text.length));
|
|
167
|
+
const col = visibleWidth(text.slice(0, at));
|
|
168
|
+
let next = Math.max(0, scroll ?? 0);
|
|
223
169
|
if (col < next) next = col;
|
|
224
170
|
if (col >= next + width) next = col - width + 1;
|
|
225
|
-
const shown = sliceVisible(
|
|
171
|
+
const shown = sliceVisible(text, next, width);
|
|
226
172
|
return { text: shown, col: col - next, scroll: next };
|
|
227
173
|
};
|
|
228
174
|
|
|
229
|
-
const branchLabel = (name) => `${name ?? ''}`.trim();
|
|
230
|
-
|
|
231
|
-
const paintListEdit = (compose, width, color, edgeW = 0) => {
|
|
232
|
-
const inner = Math.max(1, width - edgeW);
|
|
233
|
-
const prefix = ` ${FILE_MARK} `;
|
|
234
|
-
const prefixW = visibleWidth(prefix);
|
|
235
|
-
const rest = Math.max(1, inner - prefixW);
|
|
236
|
-
const text = `${compose.text ?? ''}`.replaceAll('\n', ' ');
|
|
237
|
-
const win = fieldWindow(text, compose.cursor, compose.scrollCol, rest);
|
|
238
|
-
const clipped = win.text;
|
|
239
|
-
const pad = Math.max(0, rest - visibleWidth(clipped));
|
|
240
|
-
const left = `${prefix}${clipped}${' '.repeat(pad)}`;
|
|
241
|
-
const fgRgb = THEME.chromeFg;
|
|
242
|
-
const bgRgb = THEME.buttonBg;
|
|
243
|
-
const edge = fill(edgeW, fgRgb, THEME.ctxBg, color);
|
|
244
|
-
let row = `${left}${edge}`;
|
|
245
|
-
if (color) {
|
|
246
|
-
const gap = ' '.repeat(pad);
|
|
247
|
-
row = `${seq(fgRgb, bgRgb)}${EL}`;
|
|
248
|
-
row += paint(prefix, fgRgb, bgRgb, color);
|
|
249
|
-
row += paint(clipped, THEME.buttonHotFg, bgRgb, color);
|
|
250
|
-
row += paint(gap, fgRgb, bgRgb, color);
|
|
251
|
-
row += edge;
|
|
252
|
-
}
|
|
253
|
-
const flat = text;
|
|
254
|
-
return {
|
|
255
|
-
row,
|
|
256
|
-
cursor: { x: prefixW + win.col + 1, scroll: win.scroll },
|
|
257
|
-
edit: {
|
|
258
|
-
x0: 1,
|
|
259
|
-
textX: prefixW + 1,
|
|
260
|
-
x1: prefixW + 1 + rest,
|
|
261
|
-
start: 0,
|
|
262
|
-
text: flat,
|
|
263
|
-
limit: flat.length,
|
|
264
|
-
scroll: win.scroll,
|
|
265
|
-
live: true,
|
|
266
|
-
},
|
|
267
|
-
};
|
|
268
|
-
};
|
|
269
|
-
|
|
270
175
|
const measureColumns = (entries, rowFields, align) => {
|
|
271
176
|
const keys = Object.keys(align);
|
|
272
177
|
const widths = Object.fromEntries(keys.map((key) => [key, 0]));
|
|
@@ -282,7 +187,7 @@ const measureColumns = (entries, rowFields, align) => {
|
|
|
282
187
|
const padColumns = (fields, widths, align) => {
|
|
283
188
|
const parts = {};
|
|
284
189
|
for (const key of Object.keys(align)) {
|
|
285
|
-
const width = widths
|
|
190
|
+
const width = widths[key] ?? visibleWidth(fields[key]);
|
|
286
191
|
parts[key] = padVisible(fields[key], width, align[key]);
|
|
287
192
|
}
|
|
288
193
|
return parts;
|
|
@@ -309,27 +214,27 @@ const paintColumns = (parts, widths, fgRgb, bgRgb, color, tones) => {
|
|
|
309
214
|
};
|
|
310
215
|
|
|
311
216
|
module.exports = {
|
|
312
|
-
measureColumns,
|
|
313
|
-
padColumns,
|
|
314
|
-
joinColumns,
|
|
315
|
-
paintColumns,
|
|
316
217
|
GUTTER_W,
|
|
317
218
|
FILE_MARK,
|
|
318
|
-
|
|
319
|
-
CHECK_LEAD,
|
|
219
|
+
MARK_W,
|
|
320
220
|
fill,
|
|
321
|
-
|
|
322
|
-
|
|
221
|
+
markPrefix,
|
|
222
|
+
isListPane,
|
|
223
|
+
isTaskView,
|
|
224
|
+
branchLabel,
|
|
323
225
|
padVisible,
|
|
324
226
|
splitWidths,
|
|
325
227
|
paintSplitRule,
|
|
326
228
|
paintBodyFill,
|
|
327
|
-
|
|
229
|
+
paneResult,
|
|
230
|
+
editSpan,
|
|
328
231
|
listLine,
|
|
329
232
|
paintListWindow,
|
|
330
233
|
paintCursorList,
|
|
331
|
-
|
|
234
|
+
sliceVisible,
|
|
332
235
|
fieldWindow,
|
|
333
|
-
|
|
334
|
-
|
|
236
|
+
measureColumns,
|
|
237
|
+
padColumns,
|
|
238
|
+
joinColumns,
|
|
239
|
+
paintColumns,
|
|
335
240
|
};
|
package/lib/render/render.js
CHANGED
|
@@ -5,30 +5,21 @@ const notes = require('./notes.js');
|
|
|
5
5
|
const diff = require('./diff.js');
|
|
6
6
|
const present = require('./present.js');
|
|
7
7
|
const frame = require('./frame.js');
|
|
8
|
-
const { renderFrame } = frame;
|
|
9
8
|
|
|
10
|
-
const { QUIT_PROMPT, PUSH_PROMPT } = chrome;
|
|
11
|
-
const { updatePrompt, dropPrompt } = chrome;
|
|
12
9
|
const { formatBusyStatus, headerText, layoutButtons } = chrome;
|
|
13
|
-
const {
|
|
14
|
-
const {
|
|
10
|
+
const { todoBodyWidth, noteInnerWidth } = notes;
|
|
11
|
+
const { paintDiffLine, codeInnerWidth } = diff;
|
|
15
12
|
const { presentCursor, presentRows } = present;
|
|
13
|
+
const { renderFrame } = frame;
|
|
16
14
|
|
|
17
15
|
module.exports = {
|
|
18
|
-
|
|
19
|
-
PUSH_PROMPT,
|
|
20
|
-
updatePrompt,
|
|
21
|
-
dropPrompt,
|
|
22
|
-
delCharBg,
|
|
23
|
-
addCharBg,
|
|
24
|
-
paintDiffLine,
|
|
16
|
+
formatBusyStatus,
|
|
25
17
|
headerText,
|
|
26
18
|
layoutButtons,
|
|
27
|
-
todoInnerWidth,
|
|
28
19
|
todoBodyWidth,
|
|
29
20
|
noteInnerWidth,
|
|
21
|
+
paintDiffLine,
|
|
30
22
|
codeInnerWidth,
|
|
31
|
-
formatBusyStatus,
|
|
32
23
|
renderFrame,
|
|
33
24
|
presentCursor,
|
|
34
25
|
presentRows,
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const commands = require('../npm-commands.js');
|
|
4
|
+
const { formatTable } = commands;
|
|
5
|
+
const reportRender = require('../report-render.js');
|
|
6
|
+
const { summaryRows, problemRows, displayMessage, quoteLines } = reportRender;
|
|
7
|
+
|
|
8
|
+
const reportProblem = (item) => {
|
|
9
|
+
const title = item.name || item.message || item.code || 'problem';
|
|
10
|
+
const mark = item.severity === 'warning' ? '△' : '✖';
|
|
11
|
+
const count = item.count > 1 ? ` × ${item.count}` : '';
|
|
12
|
+
const lines = ['', `${mark} ${title.split('\n')[0]}${count}`, ''];
|
|
13
|
+
lines.push(...formatTable(problemRows(item)));
|
|
14
|
+
const message = displayMessage(item);
|
|
15
|
+
if (message) lines.push('', ...quoteLines(message));
|
|
16
|
+
if (item.trace) {
|
|
17
|
+
lines.push('', 'Stack');
|
|
18
|
+
for (const row of item.trace.split(/\r?\n/)) {
|
|
19
|
+
const frame = row.trim().replace(/^at\s+/, '');
|
|
20
|
+
if (frame) lines.push(` at ${frame}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (item.related.length) {
|
|
24
|
+
lines.push('', 'Related failures');
|
|
25
|
+
for (const row of item.related) lines.push(` ↳ ${row}`);
|
|
26
|
+
}
|
|
27
|
+
return lines;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
const renderReport = (doc) => {
|
|
31
|
+
const lines = [];
|
|
32
|
+
for (const item of doc.reports) {
|
|
33
|
+
if (lines.length) lines.push('');
|
|
34
|
+
const mark = item.ok ? '✔' : '✖';
|
|
35
|
+
lines.push(`${mark} ${item.tool} · exit ${doc.exit}`, '');
|
|
36
|
+
lines.push(...formatTable(summaryRows(item.summary)));
|
|
37
|
+
for (const entry of item.problems) lines.push(...reportProblem(entry));
|
|
38
|
+
}
|
|
39
|
+
if (doc.stderr) lines.push('', 'Stderr', doc.stderr);
|
|
40
|
+
return `${lines.join('\n')}\n`;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
module.exports = { renderReport };
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ansi = require('../ansi.js');
|
|
4
|
+
const { THEME } = ansi;
|
|
5
|
+
const primitives = require('./primitives.js');
|
|
6
|
+
const { paintBodyFill, paneResult } = primitives;
|
|
7
|
+
const tiles = require('./tiles.js');
|
|
8
|
+
const { seg, spinner, equalGrid, pageWindow, placeEqualTiles } = tiles;
|
|
9
|
+
const { paintTile, composeBands, tileHits, pageFill } = tiles;
|
|
10
|
+
const { segmentsWidth, clipSegments } = tiles;
|
|
11
|
+
const table = require('./dash-table.js');
|
|
12
|
+
const { size, isHot } = table;
|
|
13
|
+
|
|
14
|
+
const REPO_MIN_H = 6;
|
|
15
|
+
const MARGIN_MIN_H = 12;
|
|
16
|
+
const EXT_LIMIT = 3;
|
|
17
|
+
const DIM_BACKS = {
|
|
18
|
+
head: THEME.dashDimHeadBg,
|
|
19
|
+
tile: THEME.dashDimTileBg,
|
|
20
|
+
row: THEME.dashDimRowBg,
|
|
21
|
+
};
|
|
22
|
+
const RUN_ICON = {
|
|
23
|
+
passed: ['✔', 'add'],
|
|
24
|
+
failed: ['✖', 'error'],
|
|
25
|
+
aborted: ['⊘', 'muted'],
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const extName = (key) => {
|
|
29
|
+
const text = `${key ?? ''}`;
|
|
30
|
+
if (!text || text === '(none)') return 'none';
|
|
31
|
+
return text.startsWith('.') ? text.slice(1) : text;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const blinkTone = (frame) => (Math.abs(frame) % 2 ? 'hot' : 'warn');
|
|
35
|
+
|
|
36
|
+
const spin = (frame) => seg(spinner(frame), 'warn', true);
|
|
37
|
+
|
|
38
|
+
const rowPending = (row) =>
|
|
39
|
+
!row.filesReady ||
|
|
40
|
+
!row.commitsReady ||
|
|
41
|
+
!row.diffReady ||
|
|
42
|
+
!row.tasksReady ||
|
|
43
|
+
!row.runsReady ||
|
|
44
|
+
!row.npmReady;
|
|
45
|
+
|
|
46
|
+
const fitAside = (left, right, width) => {
|
|
47
|
+
const rightW = segmentsWidth(right);
|
|
48
|
+
const room = Math.max(0, width - rightW - 1);
|
|
49
|
+
const clipped = clipSegments(left, room);
|
|
50
|
+
const gap = Math.max(1, width - segmentsWidth(clipped) - rightW);
|
|
51
|
+
return [...clipped, seg(' '.repeat(gap)), ...right];
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const titleLine = (row, ctx, selected, width) => {
|
|
55
|
+
const hot = isHot(row.activityAt, ctx.now);
|
|
56
|
+
const tone = hot ? blinkTone(ctx.frame) : 'text';
|
|
57
|
+
const left = [seg(row.name, tone, selected || hot)];
|
|
58
|
+
if (hot) left.push(seg(' ●', tone, true));
|
|
59
|
+
const right = [];
|
|
60
|
+
if (rowPending(row)) right.push(spin(ctx.frame), seg(' '));
|
|
61
|
+
const label = row.filesReady ? size(row.bytes) : '…';
|
|
62
|
+
const sizeTone = row.filesReady ? 'text' : 'muted';
|
|
63
|
+
right.push(seg(label, sizeTone, row.filesReady));
|
|
64
|
+
return fitAside(left, right, width);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const extLine = (row, ctx) => {
|
|
68
|
+
if (!row.filesReady) return [spin(ctx.frame)];
|
|
69
|
+
const exts = row.exts.slice(0, EXT_LIMIT);
|
|
70
|
+
if (!exts.length) return [seg('no files', 'muted')];
|
|
71
|
+
const segs = [];
|
|
72
|
+
for (let i = 0; i < exts.length; i++) {
|
|
73
|
+
if (i) segs.push(seg(' '));
|
|
74
|
+
segs.push(seg(extName(exts[i].key), 'sha'));
|
|
75
|
+
segs.push(seg(` ${size(exts[i].bytes)}`, 'text'));
|
|
76
|
+
}
|
|
77
|
+
return segs;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const countValue = (ready, text, frame) =>
|
|
81
|
+
ready ? seg(text, 'text', true) : spin(frame);
|
|
82
|
+
|
|
83
|
+
const countLine = (row, ctx) => [
|
|
84
|
+
seg('tasks: ', 'muted'),
|
|
85
|
+
countValue(row.tasksReady, `${row.tasksDone}/${row.tasks}`, ctx.frame),
|
|
86
|
+
seg(' commits: ', 'muted'),
|
|
87
|
+
countValue(row.commitsReady, `${row.commits}`, ctx.frame),
|
|
88
|
+
];
|
|
89
|
+
|
|
90
|
+
const resultNote = (run) => {
|
|
91
|
+
const result = run.result;
|
|
92
|
+
if (!result) return [];
|
|
93
|
+
if (typeof result.tests === 'number') {
|
|
94
|
+
const note = [seg(` ${result.tests}`, 'muted')];
|
|
95
|
+
if (result.failed) note.push(seg(` ${result.failed} fail`, 'error'));
|
|
96
|
+
return note;
|
|
97
|
+
}
|
|
98
|
+
if (typeof result.errors === 'number') {
|
|
99
|
+
return [seg(` ${result.errors} err`, result.errors ? 'error' : 'muted')];
|
|
100
|
+
}
|
|
101
|
+
return [];
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const runLine = (row, ctx) => {
|
|
105
|
+
if (!row.runsReady) return [seg('run: ', 'muted'), spin(ctx.frame)];
|
|
106
|
+
const run = row.run;
|
|
107
|
+
if (!run) return [seg('run: ', 'muted'), seg('idle', 'muted')];
|
|
108
|
+
if (run.status === 'running') {
|
|
109
|
+
const seen = run.done + run.failed;
|
|
110
|
+
const total = run.expected > 0 ? `/${run.expected}` : '';
|
|
111
|
+
const fail = run.failed ? ` ${run.failed} fail` : '';
|
|
112
|
+
return [
|
|
113
|
+
seg(`${spinner(ctx.frame)} `, 'warn', true),
|
|
114
|
+
seg(run.name || 'run', 'text'),
|
|
115
|
+
seg(` ${seen}${total}${fail}`, 'warn'),
|
|
116
|
+
];
|
|
117
|
+
}
|
|
118
|
+
const icon = RUN_ICON[run.status] ?? ['?', 'muted'];
|
|
119
|
+
return [
|
|
120
|
+
seg(`${icon[0]} `, icon[1], true),
|
|
121
|
+
seg(run.name || 'run', 'text'),
|
|
122
|
+
seg(` ${run.status}`, icon[1]),
|
|
123
|
+
...resultNote(run),
|
|
124
|
+
];
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const npmLine = (row, ctx) => {
|
|
128
|
+
if (!row.npmReady) return [seg('deps: ', 'muted'), spin(ctx.frame)];
|
|
129
|
+
const npm = row.npm;
|
|
130
|
+
if (!npm || !npm.hasManifest) return [seg('no package.json', 'muted')];
|
|
131
|
+
const modules = npm.modules || { count: 0, bytes: 0 };
|
|
132
|
+
const segs = [
|
|
133
|
+
seg('deps: ', 'muted'),
|
|
134
|
+
seg(`${npm.deps}`, 'text'),
|
|
135
|
+
seg(' dev: ', 'muted'),
|
|
136
|
+
seg(`${npm.dev}`, 'text'),
|
|
137
|
+
seg(' all: ', 'muted'),
|
|
138
|
+
seg(`${modules.count}`, 'text'),
|
|
139
|
+
seg(' (', 'muted'),
|
|
140
|
+
seg(size(modules.bytes), 'text'),
|
|
141
|
+
seg(')', 'muted'),
|
|
142
|
+
];
|
|
143
|
+
if (npm.audit) {
|
|
144
|
+
segs.push(
|
|
145
|
+
seg(' '),
|
|
146
|
+
seg('🚨 ', 'error'),
|
|
147
|
+
seg(`${npm.audit}`, 'error', true),
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
if (npm.outdated) {
|
|
151
|
+
segs.push(
|
|
152
|
+
seg(' '),
|
|
153
|
+
seg('⚠️ ', 'warn'),
|
|
154
|
+
seg(`${npm.outdated}`, 'warn', true),
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
return segs;
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const diffLine = (row, ctx) => {
|
|
161
|
+
if (!row.diffReady) return [seg('diff: ', 'muted'), spin(ctx.frame)];
|
|
162
|
+
const diff = row.diff;
|
|
163
|
+
const added = diff.stagedAdded + diff.unstagedAdded;
|
|
164
|
+
const removed = diff.stagedRemoved + diff.unstagedRemoved;
|
|
165
|
+
return [
|
|
166
|
+
seg('diff: ', 'muted'),
|
|
167
|
+
seg(`+${diff.stagedAdded}/${added}`, 'add'),
|
|
168
|
+
seg(' '),
|
|
169
|
+
seg(`-${diff.stagedRemoved}/${removed}`, 'del'),
|
|
170
|
+
];
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
const repoContent = (row, ctx, selected, width) => ({
|
|
174
|
+
titleLine: titleLine(row, ctx, selected, width),
|
|
175
|
+
lines: [
|
|
176
|
+
extLine(row, ctx),
|
|
177
|
+
countLine(row, ctx),
|
|
178
|
+
runLine(row, ctx),
|
|
179
|
+
diffLine(row, ctx),
|
|
180
|
+
npmLine(row, ctx),
|
|
181
|
+
],
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
const paintRepos = (model, bands, color) => {
|
|
185
|
+
const painted = new Map();
|
|
186
|
+
const ctx = { now: model.now, frame: model.frame };
|
|
187
|
+
const selected = model.tiles[model.cursor];
|
|
188
|
+
const selectedId = selected ? selected.id : '';
|
|
189
|
+
for (const band of bands) {
|
|
190
|
+
for (const place of band.tiles) {
|
|
191
|
+
const row = model.tiles.find((tile) => tile.id === place.id);
|
|
192
|
+
if (!row) continue;
|
|
193
|
+
const tile = { id: row.id, key: '', title: row.name };
|
|
194
|
+
const inner = Math.max(0, place.w - 2);
|
|
195
|
+
const picked = row.id === selectedId;
|
|
196
|
+
const content = repoContent(row, ctx, picked, inner);
|
|
197
|
+
const backs = picked ? null : DIM_BACKS;
|
|
198
|
+
const rows = paintTile(tile, content, place.w, band.h, color, backs);
|
|
199
|
+
painted.set(place.id, rows);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return painted;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const paintBodyRepos = (view, width, color, bodyH, headerLines) => {
|
|
206
|
+
const model = view.repos;
|
|
207
|
+
if (!model || !model.tiles.length) {
|
|
208
|
+
const blank = paintBodyFill(width, color, 'files');
|
|
209
|
+
return paneResult({ body: new Array(bodyH).fill(blank) });
|
|
210
|
+
}
|
|
211
|
+
const margin = bodyH >= MARGIN_MIN_H ? 1 : 0;
|
|
212
|
+
const height = Math.max(1, bodyH - margin * 2);
|
|
213
|
+
const grid = equalGrid(width, height, model.tiles.length, 34, REPO_MIN_H);
|
|
214
|
+
const page = pageWindow(model.tiles.length, model.cursor, grid.cap);
|
|
215
|
+
const shown = model.tiles.slice(page.start, page.end);
|
|
216
|
+
const bands = placeEqualTiles(shown, grid);
|
|
217
|
+
model.cols = grid.cols;
|
|
218
|
+
const painted = paintRepos(model, bands, color);
|
|
219
|
+
const rows = composeBands(bands, painted, width, height, color);
|
|
220
|
+
const pad = new Array(margin).fill(pageFill(width, color));
|
|
221
|
+
const body = [...pad, ...rows, ...pad];
|
|
222
|
+
const fileHits = tileHits(bands, headerLines + margin);
|
|
223
|
+
return paneResult({ body, fileHits });
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
module.exports = { REPO_MIN_H, extName, paintBodyRepos };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const ansi = require('../ansi.js');
|
|
4
|
+
const { THEME, paint } = ansi;
|
|
5
|
+
|
|
6
|
+
const paintPathText = (text, fg, bg, color, bold, spans) => {
|
|
7
|
+
if (!spans.length) return paint(text, fg, bg, color, bold);
|
|
8
|
+
let out = '';
|
|
9
|
+
let at = 0;
|
|
10
|
+
for (const span of spans) {
|
|
11
|
+
if (span.start > at) {
|
|
12
|
+
out += paint(text.slice(at, span.start), fg, bg, color, bold);
|
|
13
|
+
}
|
|
14
|
+
const on = span.on === true;
|
|
15
|
+
const markFg = on ? THEME.searchOnFg : THEME.searchFg;
|
|
16
|
+
const markBg = on ? THEME.searchOnBg : THEME.searchBg;
|
|
17
|
+
const slice = text.slice(span.start, span.end);
|
|
18
|
+
out += paint(slice, markFg, markBg, color, true);
|
|
19
|
+
at = span.end;
|
|
20
|
+
}
|
|
21
|
+
if (at < text.length) out += paint(text.slice(at), fg, bg, color, bold);
|
|
22
|
+
return out;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
module.exports = { paintPathText };
|