reslop 0.2.6 → 0.3.0
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 +16 -14
- package/bin/reslop.js +8 -5
- package/lib/agents/agents.js +504 -0
- package/lib/agents/allow.js +71 -0
- package/lib/agents/allow.json +16 -0
- package/lib/agents/models.js +460 -0
- package/lib/api.js +15 -12
- package/lib/cli.js +61 -66
- package/lib/{files.js → common/files.js} +51 -52
- package/lib/common/find.js +61 -0
- package/lib/common/format.js +101 -0
- package/lib/{utilities.js → common/process.js} +7 -95
- package/lib/common/tasks.json +27 -0
- package/lib/common/utilities.js +112 -0
- package/lib/dashboard/git.js +9 -16
- package/lib/dashboard/model.js +117 -51
- package/lib/dashboard/npm.js +133 -32
- package/lib/dashboard/tree.js +27 -30
- package/lib/{session → dashboard}/watch.js +41 -31
- package/lib/dashboard/workspace.js +151 -0
- package/lib/{deps.js → deps/deps.js} +31 -45
- package/lib/{deps-diff.js → deps/diff.js} +35 -25
- package/lib/{manifest.js → deps/manifest.js} +47 -30
- package/lib/diff/align.js +1 -2
- package/lib/diff/diff.js +2 -2
- package/lib/diff/display.js +29 -5
- package/lib/diff/inline.js +10 -14
- package/lib/diff/items.js +6 -4
- package/lib/diff/patch.js +39 -3
- package/lib/diff/unit.js +45 -16
- package/lib/{git-branches.js → git/branches.js} +53 -48
- package/lib/{git-deps.js → git/deps.js} +123 -95
- package/lib/git/git.js +71 -0
- package/lib/git/worktree.js +475 -0
- package/lib/highlight/bash.js +1 -2
- package/lib/highlight/csv.js +1 -2
- package/lib/highlight/dot.js +1 -2
- package/lib/{highlight.js → highlight/highlight.js} +42 -11
- package/lib/highlight/html.js +20 -18
- package/lib/highlight/json.js +1 -2
- package/lib/highlight/log.js +1 -2
- package/lib/highlight/txt.js +1 -2
- package/lib/{session → input}/actions.js +240 -71
- package/lib/{editor.js → input/editor.js} +7 -8
- package/lib/{keys.js → input/keys.js} +36 -39
- package/lib/input/line-edit.js +77 -0
- package/lib/input/menu-pick.js +70 -0
- package/lib/render/agents.js +640 -0
- package/lib/render/branches.js +4 -5
- package/lib/render/commits.js +54 -64
- package/lib/render/{dash-activity.js → dashboard/activity.js} +25 -40
- package/lib/render/dashboard/blocks.js +490 -0
- package/lib/render/{dashboard.js → dashboard/dashboard.js} +25 -22
- package/lib/render/diff.js +166 -45
- package/lib/render/field.js +131 -0
- package/lib/render/files.js +22 -8
- package/lib/render/footer.js +383 -0
- package/lib/render/frame.js +127 -25
- package/lib/render/header.js +176 -0
- package/lib/render/log.js +319 -0
- package/lib/render/menu.js +60 -0
- package/lib/render/notes.js +29 -13
- package/lib/render/npm.js +322 -196
- package/lib/render/packages.js +89 -0
- package/lib/render/primitives.js +109 -16
- package/lib/render/render.js +7 -11
- package/lib/render/repos.js +214 -0
- package/lib/render/status.js +370 -0
- package/lib/render/status.json +85 -0
- package/lib/render/{dash-table.js → table.js} +19 -61
- package/lib/render/tiles.js +203 -47
- package/lib/render/unit.js +32 -13
- package/lib/{report-model.js → report/model.js} +4 -6
- package/lib/{report-parse.js → report/parse.js} +9 -14
- package/lib/{report-render.js → report/render.js} +46 -14
- package/lib/{report-run.js → report/run.js} +42 -30
- package/lib/{report-test.js → report/test.js} +56 -55
- package/lib/{report-tools.js → report/tools.js} +24 -33
- package/lib/review/agent-instructions.md +9 -0
- package/lib/review/format.js +426 -0
- package/lib/{review-merge.js → review/merge.js} +9 -8
- package/lib/review/review.js +190 -0
- package/lib/review/store.js +281 -0
- package/lib/review/templates.js +83 -0
- package/lib/runs/commands.js +14 -0
- package/lib/runs/logs.js +299 -0
- package/lib/runs/output.js +174 -0
- package/lib/runs/process.js +113 -0
- package/lib/{runs.js → runs/runs.js} +84 -36
- package/lib/runs/scripts.js +150 -0
- package/lib/session/agents/agents.js +958 -0
- package/lib/session/agents/job.js +320 -0
- package/lib/session/agents/jobs.js +355 -0
- package/lib/session/agents/options.js +153 -0
- package/lib/session/agents/plans.js +83 -0
- package/lib/session/branches.js +36 -44
- package/lib/session/change-actions.js +34 -100
- package/lib/session/commit.js +122 -56
- package/lib/session/compose.js +197 -146
- package/lib/session/dashboard.js +183 -37
- package/lib/session/dispatch.js +120 -22
- package/lib/session/files-pane.js +60 -24
- package/lib/session/find.js +81 -0
- package/lib/session/import.js +82 -0
- package/lib/session/items.js +8 -17
- package/lib/session/lifecycle.js +96 -66
- package/lib/session/load.js +2 -4
- package/lib/session/navigation.js +8 -3
- package/lib/session/npm/history.js +249 -0
- package/lib/session/npm/npm.js +815 -0
- package/lib/session/npm/packages.js +150 -0
- package/lib/session/ops.js +125 -34
- package/lib/session/pointer.js +216 -10
- package/lib/session/prefs.js +112 -0
- package/lib/session/reads.js +109 -0
- package/lib/session/review.js +28 -10
- package/lib/session/run-panel.js +40 -0
- package/lib/{session.js → session/session.js} +166 -59
- package/lib/session/tasks.js +321 -66
- package/lib/session/templates.js +1 -1
- package/lib/session/terminal.js +94 -12
- package/lib/session/unit.js +35 -30
- package/lib/session/update.js +194 -6
- package/lib/session/view.js +72 -59
- package/lib/session/workspace.js +416 -0
- package/lib/{capabilities.js → source/capabilities.js} +3 -12
- package/lib/{github.js → source/github.js} +102 -32
- package/lib/{gitlab.js → source/gitlab.js} +87 -10
- package/lib/{remote.js → source/remote.js} +144 -40
- package/lib/source/review-threads.graphql +20 -0
- package/lib/source/source.js +74 -0
- package/lib/term/ansi.js +396 -0
- package/lib/{clipboard.js → term/clipboard.js} +3 -6
- package/lib/{select.js → term/select.js} +2 -2
- package/lib/term/themes/dark.json +122 -0
- package/lib/term/themes/light.json +122 -0
- package/lib/term/wide.txt +21 -0
- package/lib/{wrap.js → term/wrap.js} +4 -4
- package/package.json +2 -2
- package/lib/ansi.js +0 -505
- package/lib/detect.js +0 -34
- package/lib/diff/edit.js +0 -33
- package/lib/git-worktree.js +0 -437
- package/lib/git.js +0 -79
- package/lib/npm-commands.js +0 -522
- package/lib/render/chrome.js +0 -628
- package/lib/render/dash-blocks.js +0 -245
- package/lib/render/present.js +0 -19
- package/lib/render/report.js +0 -43
- package/lib/review.js +0 -744
- package/lib/session/accessors.js +0 -26
- package/lib/session/edit-click.js +0 -57
- package/lib/session/npm.js +0 -452
- package/lib/session/progress.js +0 -41
- package/lib/source.js +0 -36
- package/lib/update.js +0 -192
- /package/lib/{git-reword-todo.js → git/reword-todo.js} +0 -0
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { THEME, paint, visibleWidth } = require('../term/ansi.js');
|
|
4
|
+
const { paintSegments } = require('./tiles.js');
|
|
5
|
+
const { npmContent } = require('./dashboard/blocks.js');
|
|
6
|
+
const primitives = require('./primitives.js');
|
|
7
|
+
const { MARK_W, markPrefix, paintCursorList, fieldWindow, editSpan } =
|
|
8
|
+
primitives;
|
|
9
|
+
|
|
10
|
+
const TILE = { key: 'n', title: 'npm' };
|
|
11
|
+
const TAIL = 1;
|
|
12
|
+
const LIST_PAD = 1;
|
|
13
|
+
|
|
14
|
+
const paintMarginRow = (line, width, color, selected) => {
|
|
15
|
+
const bg = selected ? THEME.buttonBg : THEME.ctxBg;
|
|
16
|
+
const fg = selected ? THEME.chromeFg : THEME.mutedFg;
|
|
17
|
+
const inner = Math.max(1, width - MARK_W - TAIL);
|
|
18
|
+
const body = paintSegments(line, inner, bg, color);
|
|
19
|
+
const prefix = markPrefix(selected);
|
|
20
|
+
const tail = ' '.repeat(TAIL);
|
|
21
|
+
if (!color) return `${prefix}${body}${tail}`;
|
|
22
|
+
return paint(prefix, fg, bg, color) + body + paint(tail, fg, bg, color);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const paintPackageEdit = (compose, width, color) => {
|
|
26
|
+
const prefix = markPrefix(true);
|
|
27
|
+
const rest = Math.max(1, width - MARK_W - TAIL);
|
|
28
|
+
const text = `${compose.text ?? ''}`.replaceAll('\n', ' ');
|
|
29
|
+
const win = fieldWindow(text, compose.cursor, compose.scrollCol, rest);
|
|
30
|
+
const gap = ' '.repeat(Math.max(0, rest - visibleWidth(win.text)));
|
|
31
|
+
const tail = ' '.repeat(TAIL);
|
|
32
|
+
const fg = THEME.chromeFg;
|
|
33
|
+
const bg = THEME.buttonBg;
|
|
34
|
+
let row = `${prefix}${win.text}${gap}${tail}`;
|
|
35
|
+
if (color) {
|
|
36
|
+
row = paint(prefix, fg, bg, color);
|
|
37
|
+
row += paint(win.text, THEME.buttonHotFg, bg, color);
|
|
38
|
+
row += paint(`${gap}${tail}`, fg, bg, color);
|
|
39
|
+
}
|
|
40
|
+
const textX = MARK_W + 1;
|
|
41
|
+
const edit = editSpan(1, textX, textX + rest, 0, text);
|
|
42
|
+
const cursor = { x: textX + win.col, scroll: win.scroll };
|
|
43
|
+
return { row, cursor, edits: [{ ...edit, scroll: win.scroll }] };
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const paintBodyPackages = (view, width, color, bodyH, headerLines) => {
|
|
47
|
+
const ctx = { frame: view.progressFrame ?? 0 };
|
|
48
|
+
const inner = Math.max(1, width - MARK_W - TAIL);
|
|
49
|
+
const content = npmContent(view.packages, inner, null, ctx, TILE);
|
|
50
|
+
const headed = Boolean(content.titleLine) && content.lines.length > 0;
|
|
51
|
+
const rows = headed ? content.lines.slice(1) : content.lines;
|
|
52
|
+
const reserved = headed ? 1 : 0;
|
|
53
|
+
const last = Math.max(0, rows.length - 1);
|
|
54
|
+
const cursor = Math.min(view.packagesCursor ?? 0, last);
|
|
55
|
+
const pane = {
|
|
56
|
+
width,
|
|
57
|
+
color,
|
|
58
|
+
bodyH: Math.max(1, bodyH - reserved),
|
|
59
|
+
headerLines: headerLines + reserved,
|
|
60
|
+
offset: view.listScroll,
|
|
61
|
+
};
|
|
62
|
+
const paintRow = (line, selected) =>
|
|
63
|
+
paintMarginRow(line, width, color, selected);
|
|
64
|
+
const editing = view.compose && view.compose.kind === 'package';
|
|
65
|
+
let extra = null;
|
|
66
|
+
if (editing) {
|
|
67
|
+
const painted = paintPackageEdit(view.compose, width, color);
|
|
68
|
+
const at = 'index';
|
|
69
|
+
extra = { ...painted, at, index: cursor };
|
|
70
|
+
}
|
|
71
|
+
const painted = paintCursorList(rows, cursor, pane, paintRow, extra);
|
|
72
|
+
if (!headed) return painted;
|
|
73
|
+
const head = paintMarginRow(content.lines[0], width, color, false);
|
|
74
|
+
const body = painted.body.slice();
|
|
75
|
+
const pad = pane.bodyH >= LIST_PAD * 2 + 1 ? LIST_PAD : 0;
|
|
76
|
+
body.splice(pad, 0, head);
|
|
77
|
+
const nudge = (point) => {
|
|
78
|
+
if (!point || point.row < pad) return point;
|
|
79
|
+
return { ...point, row: point.row + 1 };
|
|
80
|
+
};
|
|
81
|
+
return {
|
|
82
|
+
...painted,
|
|
83
|
+
body,
|
|
84
|
+
editHits: painted.editHits.map(nudge),
|
|
85
|
+
cursor: nudge(painted.cursor),
|
|
86
|
+
};
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
module.exports = { paintBodyPackages };
|
package/lib/render/primitives.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
3
|
+
const { clamp, trimText } = require('../common/utilities.js');
|
|
4
|
+
const ansi = require('../term/ansi.js');
|
|
5
|
+
const { LIST_PANES } = require('../input/actions.js');
|
|
5
6
|
|
|
6
7
|
const { THEME, paint, visibleWidth, graphemes, graphemeWidth } = ansi;
|
|
7
|
-
const { LIST_PANES } = actions;
|
|
8
8
|
|
|
9
9
|
const GUTTER_W = 2;
|
|
10
10
|
const FILE_MARK = '▶';
|
|
@@ -17,11 +17,14 @@ const fill = (width, fgRgb, bgRgb, color) =>
|
|
|
17
17
|
|
|
18
18
|
const markPrefix = (selected) => ` ${selected ? FILE_MARK : ' '} `;
|
|
19
19
|
|
|
20
|
-
const isListPane = (pane) =>
|
|
20
|
+
const isListPane = (pane) => {
|
|
21
|
+
if (LIST_PANES.includes(pane)) return true;
|
|
22
|
+
return pane === 'dashboard' || pane === 'repos';
|
|
23
|
+
};
|
|
21
24
|
|
|
22
25
|
const isTaskView = (view) => view.item?.origin === 'task';
|
|
23
26
|
|
|
24
|
-
const branchLabel = (name) =>
|
|
27
|
+
const branchLabel = (name) => trimText(name);
|
|
25
28
|
|
|
26
29
|
const padVisible = (text, width, align) => {
|
|
27
30
|
const gap = ' '.repeat(Math.max(0, width - visibleWidth(text)));
|
|
@@ -45,6 +48,11 @@ const paintBodyFill = (width, color, kind) => {
|
|
|
45
48
|
return ctx(leftW) + paintSplitRule(color) + ctx(rightW);
|
|
46
49
|
};
|
|
47
50
|
|
|
51
|
+
const lazyRows = (length, paint) => ({ length, paint });
|
|
52
|
+
|
|
53
|
+
const bodyRow = (body, index) =>
|
|
54
|
+
Array.isArray(body) ? body[index] : body.paint(index);
|
|
55
|
+
|
|
48
56
|
const paneResult = (fields = {}) => ({
|
|
49
57
|
body: fields.body ?? [],
|
|
50
58
|
fileHits: fields.fileHits ?? [],
|
|
@@ -54,6 +62,7 @@ const paneResult = (fields = {}) => ({
|
|
|
54
62
|
splitBody: fields.splitBody === true,
|
|
55
63
|
listScroll: fields.listScroll ?? 0,
|
|
56
64
|
editHits: fields.editHits ?? [],
|
|
65
|
+
scrollBar: fields.scrollBar ?? null,
|
|
57
66
|
});
|
|
58
67
|
|
|
59
68
|
const editSpan = (x0, textX, x1, start, text) => ({
|
|
@@ -78,7 +87,7 @@ const listWindowStart = (focus, offset, listH, count) => {
|
|
|
78
87
|
let start = offset ?? 0;
|
|
79
88
|
if (focus < start) start = focus;
|
|
80
89
|
if (focus >= start + listH) start = focus - listH + 1;
|
|
81
|
-
return
|
|
90
|
+
return clamp(start, 0, count - listH);
|
|
82
91
|
};
|
|
83
92
|
|
|
84
93
|
const rowCursor = (cursor, row) => {
|
|
@@ -89,10 +98,11 @@ const rowCursor = (cursor, row) => {
|
|
|
89
98
|
|
|
90
99
|
const paintListWindow = (count, focus, pane, lineAt) => {
|
|
91
100
|
const { width, color, bodyH, headerLines, offset } = pane;
|
|
92
|
-
const
|
|
101
|
+
const padded = bodyH >= LIST_PAD * 2 + 1 ? LIST_PAD : 0;
|
|
102
|
+
const pad = pane.pad === 0 ? 0 : padded;
|
|
93
103
|
const listH = bodyH - pad * 2;
|
|
94
104
|
const start = listWindowStart(focus, offset, listH, count);
|
|
95
|
-
const blank = paintBodyFill(width, color, 'files');
|
|
105
|
+
const blank = pane.blank ?? paintBodyFill(width, color, 'files');
|
|
96
106
|
const body = pad ? [blank] : [];
|
|
97
107
|
const fileHits = [];
|
|
98
108
|
const editHits = [];
|
|
@@ -104,18 +114,38 @@ const paintListWindow = (count, focus, pane, lineAt) => {
|
|
|
104
114
|
if (line.cursor) cursor = rowCursor(line.cursor, row);
|
|
105
115
|
for (const edit of line.edits) editHits.push({ ...edit, row });
|
|
106
116
|
if (line.hit) {
|
|
107
|
-
|
|
117
|
+
const y = headerLines + pad + i + 1;
|
|
118
|
+
for (const span of line.spans ?? []) {
|
|
119
|
+
fileHits.push({ y, cursor: line.index, ...span });
|
|
120
|
+
}
|
|
121
|
+
fileHits.push({ y, cursor: line.index });
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
if (pad) body.push(blank);
|
|
111
125
|
return paneResult({ body, fileHits, editHits, cursor, listScroll: start });
|
|
112
126
|
};
|
|
113
127
|
|
|
128
|
+
const insertedAt = (extra, count) => {
|
|
129
|
+
if (extra.at === 'start') return 0;
|
|
130
|
+
if (extra.at !== 'index') return count;
|
|
131
|
+
const index = extra.index ?? count;
|
|
132
|
+
return clamp(index, 0, count);
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const lineFromPaint = (painted, index) => {
|
|
136
|
+
if (typeof painted === 'string') return listLine(painted, index);
|
|
137
|
+
const line = listLine(painted.row, index);
|
|
138
|
+
if (painted.hit === false) line.hit = false;
|
|
139
|
+
if (painted.spans) line.spans = painted.spans;
|
|
140
|
+
return line;
|
|
141
|
+
};
|
|
142
|
+
|
|
114
143
|
const paintCursorList = (items, cursor, pane, paintRow, extra = null) => {
|
|
115
144
|
const lineAt = (index) => {
|
|
116
145
|
const entry = items[index];
|
|
117
146
|
if (!entry) return null;
|
|
118
|
-
|
|
147
|
+
const selected = !extra && index === cursor;
|
|
148
|
+
return lineFromPaint(paintRow(entry, selected, index), index);
|
|
119
149
|
};
|
|
120
150
|
if (!extra) return paintListWindow(items.length, cursor, pane, lineAt);
|
|
121
151
|
const edit = listLine(extra.row, -1, extra.cursor, extra.edits);
|
|
@@ -124,10 +154,12 @@ const paintCursorList = (items, cursor, pane, paintRow, extra = null) => {
|
|
|
124
154
|
const replaced = (i) => (i === at ? edit : lineAt(i));
|
|
125
155
|
return paintListWindow(items.length, at, pane, replaced);
|
|
126
156
|
}
|
|
127
|
-
const
|
|
128
|
-
const
|
|
129
|
-
|
|
130
|
-
|
|
157
|
+
const at = insertedAt(extra, items.length);
|
|
158
|
+
const inserted = (i) => {
|
|
159
|
+
if (i === at) return edit;
|
|
160
|
+
const index = i < at ? i : i - 1;
|
|
161
|
+
return lineAt(index);
|
|
162
|
+
};
|
|
131
163
|
return paintListWindow(items.length + 1, at, pane, inserted);
|
|
132
164
|
};
|
|
133
165
|
|
|
@@ -156,7 +188,7 @@ const sliceVisible = (text, skip, width) => {
|
|
|
156
188
|
|
|
157
189
|
const fieldWindow = (text, cursor, scroll, width) => {
|
|
158
190
|
if (width <= 0) return { text: '', col: 0, scroll: 0 };
|
|
159
|
-
const at =
|
|
191
|
+
const at = clamp(cursor ?? 0, 0, text.length);
|
|
160
192
|
const col = visibleWidth(text.slice(0, at));
|
|
161
193
|
let next = Math.max(0, scroll ?? 0);
|
|
162
194
|
if (col < next) next = col;
|
|
@@ -206,10 +238,67 @@ const paintColumns = (parts, widths, fgRgb, bgRgb, color, tones) => {
|
|
|
206
238
|
return text;
|
|
207
239
|
};
|
|
208
240
|
|
|
241
|
+
const paintPathText = (text, fg, bg, color, bold, spans) => {
|
|
242
|
+
if (!spans.length) return paint(text, fg, bg, color, bold);
|
|
243
|
+
let out = '';
|
|
244
|
+
let at = 0;
|
|
245
|
+
for (const span of spans) {
|
|
246
|
+
if (span.start > at) {
|
|
247
|
+
out += paint(text.slice(at, span.start), fg, bg, color, bold);
|
|
248
|
+
}
|
|
249
|
+
const on = span.on === true;
|
|
250
|
+
const markFg = on ? THEME.searchOnFg : THEME.searchFg;
|
|
251
|
+
const markBg = on ? THEME.searchOnBg : THEME.searchBg;
|
|
252
|
+
const slice = text.slice(span.start, span.end);
|
|
253
|
+
out += paint(slice, markFg, markBg, color, true);
|
|
254
|
+
at = span.end;
|
|
255
|
+
}
|
|
256
|
+
if (at < text.length) out += paint(text.slice(at), fg, bg, color, bold);
|
|
257
|
+
return out;
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const scrollThumb = (count, inner, start) => {
|
|
261
|
+
if (count <= inner || inner < 1) return null;
|
|
262
|
+
let size = Math.max(1, Math.round((inner * inner) / count));
|
|
263
|
+
if (inner > 1) size = Math.min(size, inner - 1);
|
|
264
|
+
const travel = inner - size;
|
|
265
|
+
const maxStart = count - inner;
|
|
266
|
+
const at = maxStart <= 0 ? 0 : Math.round((start * travel) / maxStart);
|
|
267
|
+
return { size, travel, maxStart, at };
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
const scrollMark = (index, count, inner, start) => {
|
|
271
|
+
const thumb = scrollThumb(count, inner, start);
|
|
272
|
+
if (!thumb) return '';
|
|
273
|
+
const end = thumb.at + thumb.size;
|
|
274
|
+
if (index >= thumb.at && index < end) return 'thumb';
|
|
275
|
+
return 'track';
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const scrollAtThumb = (top, thumb) => {
|
|
279
|
+
if (!thumb || thumb.travel <= 0) return 0;
|
|
280
|
+
const at = clamp(top, 0, thumb.travel);
|
|
281
|
+
return Math.round((at * thumb.maxStart) / thumb.travel);
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
const placeHits = (hits, origin, width, side) => {
|
|
285
|
+
const placed = [];
|
|
286
|
+
for (const hit of hits) {
|
|
287
|
+
const x0 = (hit.x0 ?? 0) + origin;
|
|
288
|
+
const x1 = (hit.x1 ?? width) + origin;
|
|
289
|
+
placed.push({ ...hit, x0, x1, side });
|
|
290
|
+
}
|
|
291
|
+
return placed;
|
|
292
|
+
};
|
|
293
|
+
|
|
209
294
|
module.exports = {
|
|
295
|
+
scrollThumb,
|
|
296
|
+
scrollMark,
|
|
297
|
+
scrollAtThumb,
|
|
298
|
+
placeHits,
|
|
210
299
|
GUTTER_W,
|
|
211
|
-
FILE_MARK,
|
|
212
300
|
MARK_W,
|
|
301
|
+
LIST_PAD,
|
|
213
302
|
fill,
|
|
214
303
|
markPrefix,
|
|
215
304
|
isListPane,
|
|
@@ -220,8 +309,11 @@ module.exports = {
|
|
|
220
309
|
paintSplitRule,
|
|
221
310
|
paintBodyFill,
|
|
222
311
|
paneResult,
|
|
312
|
+
lazyRows,
|
|
313
|
+
bodyRow,
|
|
223
314
|
editSpan,
|
|
224
315
|
listLine,
|
|
316
|
+
listWindowStart,
|
|
225
317
|
paintListWindow,
|
|
226
318
|
paintCursorList,
|
|
227
319
|
sliceVisible,
|
|
@@ -230,4 +322,5 @@ module.exports = {
|
|
|
230
322
|
padColumns,
|
|
231
323
|
joinColumns,
|
|
232
324
|
paintColumns,
|
|
325
|
+
paintPathText,
|
|
233
326
|
};
|
package/lib/render/render.js
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const
|
|
5
|
-
const
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
const { formatBusyStatus, headerText, layoutButtons } = chrome;
|
|
10
|
-
const { todoBodyWidth, noteInnerWidth } = notes;
|
|
11
|
-
const { paintDiffLine, codeInnerWidth } = diff;
|
|
12
|
-
const { presentCursor, presentRows } = present;
|
|
13
|
-
const { renderFrame } = frame;
|
|
3
|
+
const { formatBusyStatus } = require('./status.js');
|
|
4
|
+
const { headerText } = require('./header.js');
|
|
5
|
+
const { layoutButtons } = require('./footer.js');
|
|
6
|
+
const { todoBodyWidth, noteInnerWidth } = require('./notes.js');
|
|
7
|
+
const { paintDiffLine, codeInnerWidth, activeDigits } = require('./diff.js');
|
|
8
|
+
const { renderFrame, presentCursor, presentRows } = require('./frame.js');
|
|
14
9
|
|
|
15
10
|
module.exports = {
|
|
16
11
|
formatBusyStatus,
|
|
@@ -20,6 +15,7 @@ module.exports = {
|
|
|
20
15
|
noteInnerWidth,
|
|
21
16
|
paintDiffLine,
|
|
22
17
|
codeInnerWidth,
|
|
18
|
+
activeDigits,
|
|
23
19
|
renderFrame,
|
|
24
20
|
presentCursor,
|
|
25
21
|
presentRows,
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { THEME } = require('../term/ansi.js');
|
|
4
|
+
const { paintBodyFill, paneResult } = require('./primitives.js');
|
|
5
|
+
const tiles = require('./tiles.js');
|
|
6
|
+
const { RUN_ICON } = tiles;
|
|
7
|
+
const { seg, spinner, equalGrid, pageWindow, placeEqualTiles } = tiles;
|
|
8
|
+
const { paintTile, composeBands, tileHits, pageFill } = tiles;
|
|
9
|
+
const { segmentsWidth, clipSegments, blinkTone } = tiles;
|
|
10
|
+
const { size } = require('../common/format.js');
|
|
11
|
+
const { isHot } = require('./table.js');
|
|
12
|
+
|
|
13
|
+
const REPO_MIN_H = 6;
|
|
14
|
+
const MARGIN_MIN_H = 12;
|
|
15
|
+
const EXT_LIMIT = 3;
|
|
16
|
+
const DIM_BACKS = {
|
|
17
|
+
head: THEME.dashDimHeadBg,
|
|
18
|
+
tile: THEME.dashDimTileBg,
|
|
19
|
+
row: THEME.dashDimRowBg,
|
|
20
|
+
};
|
|
21
|
+
const extName = (key) => {
|
|
22
|
+
const text = `${key ?? ''}`;
|
|
23
|
+
if (!text || text === '(none)') return 'none';
|
|
24
|
+
return text.startsWith('.') ? text.slice(1) : text;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const spin = (frame) => seg(spinner(frame), 'warn', true);
|
|
28
|
+
|
|
29
|
+
const rowPending = (row) => {
|
|
30
|
+
if (!row.filesReady || !row.commitsReady) return true;
|
|
31
|
+
if (!row.diffReady || !row.tasksReady) return true;
|
|
32
|
+
return !row.runsReady || !row.npmReady;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const fitAside = (left, right, width) => {
|
|
36
|
+
const rightW = segmentsWidth(right);
|
|
37
|
+
const room = Math.max(0, width - rightW - 1);
|
|
38
|
+
const clipped = clipSegments(left, room);
|
|
39
|
+
const gap = Math.max(1, width - segmentsWidth(clipped) - rightW);
|
|
40
|
+
return [...clipped, seg(' '.repeat(gap)), ...right];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const titleLine = (row, ctx, selected, width) => {
|
|
44
|
+
const hot = isHot(row.activityAt, ctx.now);
|
|
45
|
+
const tone = hot ? blinkTone(ctx.frame) : 'text';
|
|
46
|
+
const left = [seg(row.name, tone, selected || hot)];
|
|
47
|
+
if (hot) left.push(seg(' ●', tone, true));
|
|
48
|
+
const right = [];
|
|
49
|
+
if (rowPending(row)) right.push(spin(ctx.frame), seg(' '));
|
|
50
|
+
const label = row.filesReady ? size(row.bytes) : '…';
|
|
51
|
+
const sizeTone = row.filesReady ? 'text' : 'muted';
|
|
52
|
+
right.push(seg(label, sizeTone, row.filesReady));
|
|
53
|
+
return fitAside(left, right, width);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const extLine = (row, ctx) => {
|
|
57
|
+
if (!row.filesReady) return [spin(ctx.frame)];
|
|
58
|
+
const exts = row.exts.slice(0, EXT_LIMIT);
|
|
59
|
+
if (!exts.length) return [seg('no files', 'muted')];
|
|
60
|
+
const segs = [];
|
|
61
|
+
for (let i = 0; i < exts.length; i++) {
|
|
62
|
+
if (i) segs.push(seg(' '));
|
|
63
|
+
segs.push(seg(extName(exts[i].key), 'sha'));
|
|
64
|
+
segs.push(seg(` ${size(exts[i].bytes)}`, 'text'));
|
|
65
|
+
}
|
|
66
|
+
return segs;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const countValue = (ready, text, frame) =>
|
|
70
|
+
ready ? seg(text, 'text', true) : spin(frame);
|
|
71
|
+
|
|
72
|
+
const countLine = (row, ctx) => [
|
|
73
|
+
seg('tasks: ', 'muted'),
|
|
74
|
+
countValue(row.tasksReady, `${row.tasksDone}/${row.tasks}`, ctx.frame),
|
|
75
|
+
seg(' commits: ', 'muted'),
|
|
76
|
+
countValue(row.commitsReady, `${row.commits}`, ctx.frame),
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const resultNote = (run) => {
|
|
80
|
+
const result = run.result;
|
|
81
|
+
if (!result) return [];
|
|
82
|
+
if (typeof result.tests === 'number') {
|
|
83
|
+
const note = [seg(` ${result.tests}`, 'muted')];
|
|
84
|
+
if (result.failed) note.push(seg(` ${result.failed} fail`, 'error'));
|
|
85
|
+
return note;
|
|
86
|
+
}
|
|
87
|
+
if (typeof result.errors === 'number') {
|
|
88
|
+
return [seg(` ${result.errors} err`, result.errors ? 'error' : 'muted')];
|
|
89
|
+
}
|
|
90
|
+
return [];
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const runLine = (row, ctx) => {
|
|
94
|
+
if (!row.runsReady) return [seg('run: ', 'muted'), spin(ctx.frame)];
|
|
95
|
+
const run = row.run;
|
|
96
|
+
if (!run) return [seg('run: ', 'muted'), seg('idle', 'muted')];
|
|
97
|
+
if (run.status === 'running') {
|
|
98
|
+
const seen = run.done + run.failed;
|
|
99
|
+
const total = run.expected > 0 ? `/${run.expected}` : '';
|
|
100
|
+
const fail = run.failed ? ` ${run.failed} fail` : '';
|
|
101
|
+
return [
|
|
102
|
+
seg(`${spinner(ctx.frame)} `, 'warn', true),
|
|
103
|
+
seg(run.name || 'run', 'text'),
|
|
104
|
+
seg(` ${seen}${total}${fail}`, 'warn'),
|
|
105
|
+
];
|
|
106
|
+
}
|
|
107
|
+
const icon = RUN_ICON[run.status] ?? ['?', 'muted'];
|
|
108
|
+
return [
|
|
109
|
+
seg(`${icon[0]} `, icon[1], true),
|
|
110
|
+
seg(run.name || 'run', 'text'),
|
|
111
|
+
seg(` ${run.status}`, icon[1]),
|
|
112
|
+
...resultNote(run),
|
|
113
|
+
];
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const npmLine = (row, ctx) => {
|
|
117
|
+
if (!row.npmReady) return [seg('deps: ', 'muted'), spin(ctx.frame)];
|
|
118
|
+
const npm = row.npm;
|
|
119
|
+
if (!npm || !npm.hasManifest) return [seg('no package.json', 'muted')];
|
|
120
|
+
const modules = npm.modules || { count: 0, bytes: 0 };
|
|
121
|
+
const segs = [
|
|
122
|
+
seg('deps: ', 'muted'),
|
|
123
|
+
seg(`${npm.deps}`, 'text'),
|
|
124
|
+
seg(' dev: ', 'muted'),
|
|
125
|
+
seg(`${npm.dev}`, 'text'),
|
|
126
|
+
seg(' all: ', 'muted'),
|
|
127
|
+
seg(`${modules.count}`, 'text'),
|
|
128
|
+
seg(' (', 'muted'),
|
|
129
|
+
seg(size(modules.bytes), 'text'),
|
|
130
|
+
seg(')', 'muted'),
|
|
131
|
+
];
|
|
132
|
+
if (npm.audit) {
|
|
133
|
+
segs.push(
|
|
134
|
+
seg(' '),
|
|
135
|
+
seg('🚨 ', 'error'),
|
|
136
|
+
seg(`${npm.audit}`, 'error', true),
|
|
137
|
+
);
|
|
138
|
+
}
|
|
139
|
+
if (npm.outdated) {
|
|
140
|
+
segs.push(
|
|
141
|
+
seg(' '),
|
|
142
|
+
seg('⚠️ ', 'warn'),
|
|
143
|
+
seg(`${npm.outdated}`, 'warn', true),
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return segs;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
const diffLine = (row, ctx) => {
|
|
150
|
+
if (!row.diffReady) return [seg('diff: ', 'muted'), spin(ctx.frame)];
|
|
151
|
+
const diff = row.diff;
|
|
152
|
+
const added = diff.stagedAdded + diff.unstagedAdded;
|
|
153
|
+
const removed = diff.stagedRemoved + diff.unstagedRemoved;
|
|
154
|
+
return [
|
|
155
|
+
seg('diff: ', 'muted'),
|
|
156
|
+
seg(`+${diff.stagedAdded}/${added}`, 'add'),
|
|
157
|
+
seg(' '),
|
|
158
|
+
seg(`-${diff.stagedRemoved}/${removed}`, 'del'),
|
|
159
|
+
];
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const repoContent = (row, ctx, selected, width) => ({
|
|
163
|
+
titleLine: titleLine(row, ctx, selected, width),
|
|
164
|
+
lines: [
|
|
165
|
+
extLine(row, ctx),
|
|
166
|
+
countLine(row, ctx),
|
|
167
|
+
runLine(row, ctx),
|
|
168
|
+
diffLine(row, ctx),
|
|
169
|
+
npmLine(row, ctx),
|
|
170
|
+
],
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
const paintRepos = (model, bands, color) => {
|
|
174
|
+
const painted = new Map();
|
|
175
|
+
const ctx = { now: model.now, frame: model.frame };
|
|
176
|
+
const selected = model.tiles[model.cursor];
|
|
177
|
+
const selectedId = selected ? selected.id : '';
|
|
178
|
+
for (const band of bands) {
|
|
179
|
+
for (const place of band.tiles) {
|
|
180
|
+
const row = model.tiles.find((tile) => tile.id === place.id);
|
|
181
|
+
if (!row) continue;
|
|
182
|
+
const tile = { id: row.id, key: '', title: row.name };
|
|
183
|
+
const inner = Math.max(0, place.w - 2);
|
|
184
|
+
const picked = row.id === selectedId;
|
|
185
|
+
const content = repoContent(row, ctx, picked, inner);
|
|
186
|
+
const backs = picked ? null : DIM_BACKS;
|
|
187
|
+
const rows = paintTile(tile, content, place.w, band.h, color, backs);
|
|
188
|
+
painted.set(place.id, rows);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return painted;
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
const paintBodyRepos = (view, width, color, bodyH, headerLines) => {
|
|
195
|
+
const model = view.repos;
|
|
196
|
+
if (!model || !model.tiles.length) {
|
|
197
|
+
const blank = paintBodyFill(width, color, 'files');
|
|
198
|
+
return paneResult({ body: new Array(bodyH).fill(blank) });
|
|
199
|
+
}
|
|
200
|
+
const margin = bodyH >= MARGIN_MIN_H ? 1 : 0;
|
|
201
|
+
const height = Math.max(1, bodyH - margin * 2);
|
|
202
|
+
const grid = equalGrid(width, height, model.tiles.length, 34, REPO_MIN_H);
|
|
203
|
+
const page = pageWindow(model.tiles.length, model.cursor, grid.cap);
|
|
204
|
+
const shown = model.tiles.slice(page.start, page.end);
|
|
205
|
+
const bands = placeEqualTiles(shown, grid);
|
|
206
|
+
const painted = paintRepos(model, bands, color);
|
|
207
|
+
const rows = composeBands(bands, painted, width, height, color);
|
|
208
|
+
const pad = new Array(margin).fill(pageFill(width, color));
|
|
209
|
+
const body = [...pad, ...rows, ...pad];
|
|
210
|
+
const fileHits = tileHits(bands, headerLines + margin);
|
|
211
|
+
return { ...paneResult({ body, fileHits }), gridCols: grid.cols };
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
module.exports = { paintBodyRepos };
|