reslop 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +25 -1
- package/LICENSE +1 -1
- package/README.md +12 -13
- package/bin/reslop.js +3 -8
- package/lib/ansi.js +32 -19
- package/lib/capabilities.js +8 -12
- package/lib/cli.js +40 -74
- package/lib/clipboard.js +48 -18
- package/lib/dashboard/git.js +128 -0
- package/lib/dashboard/model.js +332 -0
- package/lib/dashboard/npm.js +193 -0
- package/lib/dashboard/tree.js +278 -0
- package/lib/deps-diff.js +196 -404
- package/lib/deps.js +240 -399
- 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 +138 -141
- 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 +90 -206
- package/lib/editor.js +98 -55
- package/lib/files.js +86 -103
- package/lib/git-branches.js +254 -416
- package/lib/git-deps.js +182 -344
- package/lib/git-reword-todo.js +5 -13
- package/lib/git-worktree.js +141 -212
- 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 +157 -132
- 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 +16 -26
- 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 +22 -35
- package/lib/highlight/txt.js +9 -22
- package/lib/highlight.js +17 -46
- package/lib/keys.js +62 -85
- package/lib/manifest.js +67 -130
- package/lib/npm-commands.js +119 -189
- package/lib/remote.js +141 -197
- package/lib/render/branches.js +66 -73
- package/lib/render/chrome.js +264 -323
- package/lib/render/commits.js +187 -500
- package/lib/render/dash-activity.js +212 -0
- package/lib/render/dash-blocks.js +245 -0
- package/lib/render/dash-table.js +290 -0
- package/lib/render/dashboard.js +62 -0
- package/lib/render/diff.js +123 -228
- package/lib/render/files.js +45 -99
- package/lib/render/frame.js +129 -183
- package/lib/render/notes.js +144 -160
- package/lib/render/npm.js +234 -112
- package/lib/render/present.js +4 -11
- package/lib/render/primitives.js +131 -170
- package/lib/render/render.js +5 -14
- package/lib/render/report.js +43 -0
- package/lib/render/tiles.js +260 -0
- package/lib/render/unit.js +15 -28
- package/lib/report-model.js +110 -33
- package/lib/report-parse.js +56 -38
- package/lib/report-render.js +102 -59
- package/lib/report-run.js +87 -78
- package/lib/report-test.js +256 -141
- package/lib/report-tools.js +96 -161
- package/lib/review-merge.js +114 -152
- package/lib/review.js +115 -189
- package/lib/runs.js +225 -0
- package/lib/select.js +89 -75
- package/lib/session/accessors.js +14 -21
- package/lib/session/actions.js +82 -63
- package/lib/session/branches.js +193 -247
- package/lib/session/change-actions.js +171 -214
- package/lib/session/commit.js +280 -342
- package/lib/session/compose.js +559 -674
- package/lib/session/dashboard.js +385 -0
- package/lib/session/dispatch.js +164 -157
- package/lib/session/edit-click.js +57 -0
- package/lib/session/files-pane.js +85 -53
- package/lib/session/items.js +73 -104
- package/lib/session/lifecycle.js +403 -541
- package/lib/session/load.js +48 -68
- package/lib/session/navigation.js +96 -87
- package/lib/session/npm.js +409 -345
- package/lib/session/ops.js +196 -63
- package/lib/session/pointer.js +23 -15
- package/lib/session/progress.js +33 -21
- package/lib/session/review.js +33 -54
- package/lib/session/tasks.js +333 -0
- package/lib/session/templates.js +51 -59
- package/lib/session/terminal.js +85 -136
- package/lib/session/unit.js +3 -4
- package/lib/session/update.js +76 -97
- package/lib/session/view.js +52 -83
- package/lib/session/watch.js +208 -204
- package/lib/session.js +112 -237
- package/lib/update.js +45 -116
- package/lib/utilities.js +67 -89
- package/lib/wrap.js +33 -44
- package/package.json +1 -1
- package/lib/session/todos.js +0 -293
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const metautil = require('metautil');
|
|
4
|
+
const { bytesToSize } = metautil;
|
|
5
|
+
const files = require('../files.js');
|
|
6
|
+
const { relativeAge } = files;
|
|
7
|
+
const ansi = require('../ansi.js');
|
|
8
|
+
const { visibleWidth, graphemes, graphemeWidth } = ansi;
|
|
9
|
+
const tiles = require('./tiles.js');
|
|
10
|
+
const { seg, segmentsWidth, clipSegments, titleSegments } = tiles;
|
|
11
|
+
const tree = require('../dashboard/tree.js');
|
|
12
|
+
const { HEAT_MS } = tree;
|
|
13
|
+
|
|
14
|
+
const MIN_FLEX = 6;
|
|
15
|
+
const SECONDS_AGO = /^\d+s ago$/;
|
|
16
|
+
|
|
17
|
+
const trimmed = (value) =>
|
|
18
|
+
value >= 10 ? `${Math.round(value)}` : `${Math.round(value * 10) / 10}`;
|
|
19
|
+
|
|
20
|
+
const num = (value) => {
|
|
21
|
+
const n = Math.max(0, Math.round(value));
|
|
22
|
+
if (n >= 1e6) return `${trimmed(n / 1e6)}m`;
|
|
23
|
+
if (n >= 1e4) return `${trimmed(n / 1e3)}k`;
|
|
24
|
+
return `${n}`;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const size = (bytes) => bytesToSize(bytes > 0 ? bytes : 0);
|
|
28
|
+
|
|
29
|
+
const ago = (at, now) => {
|
|
30
|
+
if (!at) return '';
|
|
31
|
+
const text = relativeAge(at, now);
|
|
32
|
+
return SECONDS_AGO.test(text) ? '<1m ago' : text;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const duration = (ms) => {
|
|
36
|
+
const value = Math.max(0, ms);
|
|
37
|
+
if (value < 1000) return `${Math.round(value)}ms`;
|
|
38
|
+
if (value < 60000) return `${(value / 1000).toFixed(1)}s`;
|
|
39
|
+
const minutes = Math.floor(value / 60000);
|
|
40
|
+
const seconds = Math.floor((value % 60000) / 1000);
|
|
41
|
+
return `${minutes}m${`${seconds}`.padStart(2, '0')}s`;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const isHot = (at, now) => at > 0 && now - at < HEAT_MS;
|
|
45
|
+
|
|
46
|
+
const delta = (value, now, at) => {
|
|
47
|
+
if (!at || now - at >= HEAT_MS || !value) return null;
|
|
48
|
+
if (value > 0) return seg(`▲+${num(value)}`, 'add', true);
|
|
49
|
+
return seg(`▼${num(-value)}`, 'del', true);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const waiting = (text) => ({ badge: [], lines: [[seg(text, 'muted')]] });
|
|
53
|
+
|
|
54
|
+
const cell = (text, tone = 'text', align = 'l', bold = false) => ({
|
|
55
|
+
segs: [seg(text, tone, bold)],
|
|
56
|
+
align,
|
|
57
|
+
flex: false,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
const cellSegs = (segs, align = 'l') => ({ segs, align, flex: false });
|
|
61
|
+
|
|
62
|
+
const flexCell = (text, tone = 'muted') => ({
|
|
63
|
+
segs: [seg(text, tone)],
|
|
64
|
+
align: 'l',
|
|
65
|
+
flex: true,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
const blankCell = { segs: [], align: 'l', flex: false };
|
|
69
|
+
|
|
70
|
+
const columnWidths = (rows, count) => {
|
|
71
|
+
const widths = new Array(count).fill(0);
|
|
72
|
+
for (const row of rows) {
|
|
73
|
+
for (let c = 0; c < count; c++) {
|
|
74
|
+
const item = row[c];
|
|
75
|
+
if (!item || (item.flex && !item.keep)) continue;
|
|
76
|
+
widths[c] = Math.max(widths[c], segmentsWidth(item.segs));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return widths;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const flexColumn = (rows, count) => {
|
|
83
|
+
for (let c = 0; c < count; c++) {
|
|
84
|
+
if (rows.some((row) => row[c] && row[c].flex)) return c;
|
|
85
|
+
}
|
|
86
|
+
return -1;
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
const usedWidth = (widths, use, gaps) => {
|
|
90
|
+
let total = 0;
|
|
91
|
+
for (let c = 0; c < use; c++) total += widths[c] + gaps[c];
|
|
92
|
+
return total;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const columnGaps = (rows, count, fallback) => {
|
|
96
|
+
const gaps = new Array(count).fill(fallback);
|
|
97
|
+
const fixed = new Array(count).fill(false);
|
|
98
|
+
gaps[0] = 0;
|
|
99
|
+
fixed[0] = true;
|
|
100
|
+
for (let c = 1; c < count; c++) {
|
|
101
|
+
const owner = rows.find((row) => row[c] && typeof row[c].gap === 'number');
|
|
102
|
+
if (!owner) continue;
|
|
103
|
+
gaps[c] = owner[c].gap;
|
|
104
|
+
fixed[c] = true;
|
|
105
|
+
}
|
|
106
|
+
return { gaps, fixed };
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const closeEmpty = (widths, gaps, fixed) => {
|
|
110
|
+
for (let c = 1; c < widths.length; c++) {
|
|
111
|
+
if (widths[c - 1] !== 0) continue;
|
|
112
|
+
gaps[c] = 0;
|
|
113
|
+
fixed[c] = true;
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
const widenGaps = (gaps, fixed, use, room) => {
|
|
118
|
+
if (room <= 0) return;
|
|
119
|
+
const open = [];
|
|
120
|
+
for (let c = 1; c < use; c++) {
|
|
121
|
+
if (!fixed[c]) open.push(c);
|
|
122
|
+
}
|
|
123
|
+
if (!open.length) return;
|
|
124
|
+
const share = Math.floor(room / open.length);
|
|
125
|
+
let extra = room - share * open.length;
|
|
126
|
+
for (const index of open) {
|
|
127
|
+
gaps[index] += share;
|
|
128
|
+
if (extra <= 0) continue;
|
|
129
|
+
gaps[index] += 1;
|
|
130
|
+
extra -= 1;
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const flexFloor = (rows, index) => {
|
|
135
|
+
let floor = null;
|
|
136
|
+
for (const row of rows) {
|
|
137
|
+
const item = row[index];
|
|
138
|
+
if (!item || !item.flex || typeof item.min !== 'number') continue;
|
|
139
|
+
floor = floor === null ? item.min : Math.min(floor, item.min);
|
|
140
|
+
}
|
|
141
|
+
return floor;
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
const pickColumns = (rows, width, gap, spread) => {
|
|
145
|
+
const count = Math.max(0, ...rows.map((row) => row.length));
|
|
146
|
+
const widths = columnWidths(rows, count);
|
|
147
|
+
const flex = flexColumn(rows, count);
|
|
148
|
+
const keep = flex >= 0 && rows.some((row) => row[flex] && row[flex].keep);
|
|
149
|
+
const floor = flex >= 0 && !keep ? flexFloor(rows, flex) : null;
|
|
150
|
+
const minFlex = floor ?? MIN_FLEX;
|
|
151
|
+
const reserve = floor ?? 0;
|
|
152
|
+
const { gaps, fixed } = columnGaps(rows, count, gap);
|
|
153
|
+
closeEmpty(widths, gaps, fixed);
|
|
154
|
+
let use = count;
|
|
155
|
+
while (use > 1) {
|
|
156
|
+
const shrinks = flex >= 0 && flex < use && !keep;
|
|
157
|
+
const used = usedWidth(widths, use, gaps);
|
|
158
|
+
const need = shrinks ? minFlex : 0;
|
|
159
|
+
if (used + need <= width) break;
|
|
160
|
+
const trailing = shrinks && flex < use - 1;
|
|
161
|
+
if (trailing && used + reserve <= width) break;
|
|
162
|
+
if (keep && flex === use - 1) break;
|
|
163
|
+
use -= 1;
|
|
164
|
+
}
|
|
165
|
+
const room = width - usedWidth(widths, use, gaps);
|
|
166
|
+
if (flex >= 0 && flex < use) widths[flex] = Math.max(0, widths[flex] + room);
|
|
167
|
+
else if (spread) widenGaps(gaps, fixed, use, room);
|
|
168
|
+
return { use, widths, flex, gaps };
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
const flexSegments = (item, room) => {
|
|
172
|
+
const clipped = clipSegments(item.segs, room);
|
|
173
|
+
const pad = Math.max(0, room - segmentsWidth(clipped));
|
|
174
|
+
return [...clipped, seg(' '.repeat(pad))];
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const renderRow = (row, columns) => {
|
|
178
|
+
const { use, widths, flex, gaps } = columns;
|
|
179
|
+
const line = [];
|
|
180
|
+
for (let c = 0; c < use; c++) {
|
|
181
|
+
const item = row[c] ?? blankCell;
|
|
182
|
+
if (c) line.push(seg(' '.repeat(gaps[c])));
|
|
183
|
+
if (c === flex) {
|
|
184
|
+
line.push(...flexSegments(item, widths[c]));
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
const pad = ' '.repeat(Math.max(0, widths[c] - segmentsWidth(item.segs)));
|
|
188
|
+
if (item.align === 'r') line.push(seg(pad), ...item.segs);
|
|
189
|
+
else line.push(...item.segs, seg(pad));
|
|
190
|
+
}
|
|
191
|
+
return line;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const tableLines = (rows, width, gap = 2, spread = false) => {
|
|
195
|
+
if (!rows.length) return [];
|
|
196
|
+
const columns = pickColumns(rows, width, gap, spread);
|
|
197
|
+
return rows.map((row) => renderRow(row, columns));
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
const tailVisible = (text, skip) => {
|
|
201
|
+
let left = skip;
|
|
202
|
+
let col = 0;
|
|
203
|
+
let out = '';
|
|
204
|
+
for (const ch of graphemes(text)) {
|
|
205
|
+
const w = graphemeWidth(ch, col);
|
|
206
|
+
col += w;
|
|
207
|
+
if (left > 0) left -= w;
|
|
208
|
+
else out += ch;
|
|
209
|
+
}
|
|
210
|
+
return out;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const skipSegments = (segs, skip) => {
|
|
214
|
+
const out = [];
|
|
215
|
+
let left = skip;
|
|
216
|
+
for (const part of segs) {
|
|
217
|
+
if (left <= 0) {
|
|
218
|
+
out.push(part);
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const w = visibleWidth(part.text);
|
|
222
|
+
if (w <= left) {
|
|
223
|
+
left -= w;
|
|
224
|
+
continue;
|
|
225
|
+
}
|
|
226
|
+
const text = tailVisible(part.text, left);
|
|
227
|
+
left = 0;
|
|
228
|
+
if (text) out.push(seg(text, part.tone, part.bold));
|
|
229
|
+
}
|
|
230
|
+
return out;
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const captionOver = (tile, line, badge = []) => {
|
|
234
|
+
const caption = titleSegments(tile, badge);
|
|
235
|
+
return [...caption, ...skipSegments(line, segmentsWidth(caption))];
|
|
236
|
+
};
|
|
237
|
+
|
|
238
|
+
const titleAside = (tile, aside, width, badge = []) => {
|
|
239
|
+
const left = titleSegments(tile, badge);
|
|
240
|
+
const gap = width - segmentsWidth(left) - segmentsWidth(aside);
|
|
241
|
+
return [...left, seg(' '.repeat(Math.max(1, gap))), ...aside];
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const stat = (label, value, tone = 'text', bold = false) => ({
|
|
245
|
+
label,
|
|
246
|
+
value: typeof value === 'string' ? [seg(value, tone, bold)] : value,
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
const pairRows = (items, perRow = 2) => {
|
|
250
|
+
const rows = [];
|
|
251
|
+
for (let i = 0; i < items.length; i += perRow) {
|
|
252
|
+
const row = [];
|
|
253
|
+
for (const item of items.slice(i, i + perRow)) {
|
|
254
|
+
row.push(cell(item.label, 'muted'), cellSegs(item.value));
|
|
255
|
+
}
|
|
256
|
+
rows.push(row);
|
|
257
|
+
}
|
|
258
|
+
return rows;
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
const pickGroups = (folders, exts, rows) => {
|
|
262
|
+
const count = Math.max(0, rows);
|
|
263
|
+
const dirs = folders.slice(0, count);
|
|
264
|
+
return { dirs, exts: exts.slice(0, count - dirs.length) };
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const labelOf = (key, isDir) => {
|
|
268
|
+
if (isDir) return key === '.' ? '/' : key;
|
|
269
|
+
return key.startsWith('.') ? `*${key}` : key;
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
module.exports = {
|
|
273
|
+
num,
|
|
274
|
+
size,
|
|
275
|
+
ago,
|
|
276
|
+
duration,
|
|
277
|
+
isHot,
|
|
278
|
+
delta,
|
|
279
|
+
waiting,
|
|
280
|
+
cell,
|
|
281
|
+
cellSegs,
|
|
282
|
+
flexCell,
|
|
283
|
+
tableLines,
|
|
284
|
+
stat,
|
|
285
|
+
pairRows,
|
|
286
|
+
pickGroups,
|
|
287
|
+
labelOf,
|
|
288
|
+
titleAside,
|
|
289
|
+
captionOver,
|
|
290
|
+
};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const primitives = require('./primitives.js');
|
|
4
|
+
const { paintBodyFill, paneResult } = primitives;
|
|
5
|
+
const tiles = require('./tiles.js');
|
|
6
|
+
const { layoutTiles, paintTile, composeBands, tileHits, pageFill } = tiles;
|
|
7
|
+
const blocks = require('./dash-blocks.js');
|
|
8
|
+
const { filesBlock, diffsBlock, npmBlock, tasksBlock } = blocks;
|
|
9
|
+
const activity = require('./dash-activity.js');
|
|
10
|
+
const { commitsBlock, branchesBlock, runsBlock } = activity;
|
|
11
|
+
|
|
12
|
+
const TILES = [
|
|
13
|
+
{ id: 'files', key: 'f', title: 'files', rank: 3, build: filesBlock },
|
|
14
|
+
{ id: 'diffs', key: 'd', title: 'diffs', rank: 0, build: diffsBlock },
|
|
15
|
+
{ id: 'tasks', key: 't', title: 'tasks', rank: 4, build: tasksBlock },
|
|
16
|
+
{
|
|
17
|
+
id: 'branches',
|
|
18
|
+
key: 'b',
|
|
19
|
+
title: 'branches',
|
|
20
|
+
rank: 5,
|
|
21
|
+
build: branchesBlock,
|
|
22
|
+
},
|
|
23
|
+
{ id: 'commits', key: 'c', title: 'commits', rank: 2, build: commitsBlock },
|
|
24
|
+
{ id: 'run', key: 'r', title: 'run', rank: 1, build: runsBlock },
|
|
25
|
+
{ id: 'npm', key: 'n', title: 'npm', rank: 6, build: npmBlock },
|
|
26
|
+
];
|
|
27
|
+
const TILE_BY_ID = new Map(TILES.map((tile) => [tile.id, tile]));
|
|
28
|
+
const MARGIN_MIN_H = 12;
|
|
29
|
+
|
|
30
|
+
const paintTiles = (model, bands, color) => {
|
|
31
|
+
const painted = new Map();
|
|
32
|
+
const ctx = { now: model.now, frame: model.frame };
|
|
33
|
+
for (const band of bands) {
|
|
34
|
+
for (const place of band.tiles) {
|
|
35
|
+
const tile = TILE_BY_ID.get(place.id);
|
|
36
|
+
const inner = Math.max(0, place.w - 2);
|
|
37
|
+
const room = Math.max(0, band.h - 1);
|
|
38
|
+
const content = tile.build(model, inner, room, ctx, tile);
|
|
39
|
+
painted.set(place.id, paintTile(tile, content, place.w, band.h, color));
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return painted;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const paintBodyDashboard = (view, width, color, bodyH, headerLines) => {
|
|
46
|
+
const model = view.dashboard;
|
|
47
|
+
if (!model) {
|
|
48
|
+
const blank = paintBodyFill(width, color, 'files');
|
|
49
|
+
return paneResult({ body: new Array(bodyH).fill(blank) });
|
|
50
|
+
}
|
|
51
|
+
const margin = bodyH >= MARGIN_MIN_H ? 1 : 0;
|
|
52
|
+
const height = bodyH - margin * 2;
|
|
53
|
+
const bands = layoutTiles(width, height, TILES);
|
|
54
|
+
const painted = paintTiles(model, bands, color);
|
|
55
|
+
const rows = composeBands(bands, painted, width, height, color);
|
|
56
|
+
const pad = new Array(margin).fill(pageFill(width, color));
|
|
57
|
+
const body = [...pad, ...rows, ...pad];
|
|
58
|
+
const fileHits = tileHits(bands, headerLines + margin);
|
|
59
|
+
return paneResult({ body, fileHits });
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
module.exports = { TILES, paintBodyDashboard };
|