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/files.js
CHANGED
|
@@ -6,9 +6,44 @@ const { listFilePath } = manifest;
|
|
|
6
6
|
const REVIEW_DIR = '.review';
|
|
7
7
|
const ORIGIN_ORDER = ['staged', 'unstaged', 'untracked', 'commit', 'pr'];
|
|
8
8
|
const TASKS_FILE = 'TODOs';
|
|
9
|
-
const REPO_TASKS_LABEL =
|
|
10
|
-
'Project Tasks:
|
|
9
|
+
const REPO_TASKS_LABEL = [
|
|
10
|
+
'Project Tasks: Feature requests, Bug reports,',
|
|
11
|
+
'Technical debt, Research, Security',
|
|
12
|
+
].join(' ');
|
|
13
|
+
const TASK_KINDS = [
|
|
14
|
+
{ id: 'features', title: 'Feature requests' },
|
|
15
|
+
{ id: 'bugs', title: 'Bug reports' },
|
|
16
|
+
{ id: 'debt', title: 'Technical debt' },
|
|
17
|
+
{ id: 'research', title: 'Research' },
|
|
18
|
+
{ id: 'security', title: 'Security' },
|
|
19
|
+
];
|
|
20
|
+
const TASK_KIND_IDS = new Set(TASK_KINDS.map((kind) => kind.id));
|
|
21
|
+
const TASK_KIND_ALIAS = { backlog: 'debt', issues: 'debt' };
|
|
22
|
+
|
|
23
|
+
const taskKind = (todo) => {
|
|
24
|
+
const kind = todo && todo.kind;
|
|
25
|
+
if (TASK_KIND_IDS.has(kind)) return kind;
|
|
26
|
+
return TASK_KIND_ALIAS[kind] ?? 'debt';
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const taskTitle = (id) => {
|
|
30
|
+
for (const kind of TASK_KINDS) {
|
|
31
|
+
if (kind.id === id) return kind.title;
|
|
32
|
+
}
|
|
33
|
+
return TASK_KINDS[0].title;
|
|
34
|
+
};
|
|
11
35
|
const TOTAL_LABEL = 'total';
|
|
36
|
+
const AGE_UNIT = {
|
|
37
|
+
second: 's',
|
|
38
|
+
minute: 'm',
|
|
39
|
+
hour: 'h',
|
|
40
|
+
day: 'd',
|
|
41
|
+
week: 'w',
|
|
42
|
+
month: 'mo',
|
|
43
|
+
year: 'y',
|
|
44
|
+
};
|
|
45
|
+
const AGE_AGO = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
|
|
46
|
+
const AGE_SHORT = /^(\d+)(mo|[smhdwy])(?: ago)?$/i;
|
|
12
47
|
|
|
13
48
|
const isPathInScope = (rel, paths) => {
|
|
14
49
|
if (!paths.length) return true;
|
|
@@ -20,20 +55,17 @@ const isPathInScope = (rel, paths) => {
|
|
|
20
55
|
return false;
|
|
21
56
|
};
|
|
22
57
|
|
|
23
|
-
const itemPath = (item) =>
|
|
24
|
-
if (!item || !item.file) return '';
|
|
25
|
-
return item.file.newPath || item.file.oldPath || '';
|
|
26
|
-
};
|
|
58
|
+
const itemPath = (item) => item?.file?.newPath || item?.file?.oldPath || '';
|
|
27
59
|
|
|
28
60
|
const listPath = (item) => listFilePath(itemPath(item));
|
|
29
61
|
|
|
30
|
-
const isTaskItem = (item) =>
|
|
62
|
+
const isTaskItem = (item) => item?.origin === 'task';
|
|
31
63
|
|
|
32
64
|
const isReadOnlyOrigin = (origin) => origin === 'commit' || origin === 'pr';
|
|
33
65
|
|
|
34
|
-
const isTasksEntry = (entry) =>
|
|
66
|
+
const isTasksEntry = (entry) => entry?.kind === 'tasks';
|
|
35
67
|
|
|
36
|
-
const isTotalEntry = (entry) =>
|
|
68
|
+
const isTotalEntry = (entry) => entry?.kind === 'total';
|
|
37
69
|
|
|
38
70
|
const makeTaskItem = () => ({
|
|
39
71
|
origin: 'task',
|
|
@@ -53,52 +85,30 @@ const makeTaskItem = () => ({
|
|
|
53
85
|
});
|
|
54
86
|
|
|
55
87
|
const fileStatus = (origins) => {
|
|
56
|
-
const
|
|
57
|
-
const seen = ORIGIN_ORDER.filter((origin) => present.has(origin));
|
|
88
|
+
const seen = ORIGIN_ORDER.filter((origin) => origins.includes(origin));
|
|
58
89
|
const key = seen.join('+');
|
|
59
|
-
|
|
60
|
-
if (key === 'staged+unstaged') return 'partial';
|
|
61
|
-
return key;
|
|
90
|
+
return key === 'staged+unstaged' ? 'partial' : key;
|
|
62
91
|
};
|
|
63
92
|
|
|
64
93
|
const lineDelta = ({ hunk, blockId }) => {
|
|
65
94
|
const delta = { added: 0, removed: 0 };
|
|
66
|
-
if (!hunk) return delta;
|
|
67
|
-
const { lines } = hunk;
|
|
68
|
-
if (!lines) return delta;
|
|
95
|
+
if (!hunk || !hunk.lines) return delta;
|
|
69
96
|
const scoped = typeof blockId === 'number';
|
|
70
|
-
for (const
|
|
71
|
-
if (scoped &&
|
|
72
|
-
if (type === 'add') delta.added += 1;
|
|
73
|
-
else if (type === 'del') delta.removed += 1;
|
|
97
|
+
for (const line of hunk.lines) {
|
|
98
|
+
if (scoped && line.blockId !== blockId) continue;
|
|
99
|
+
if (line.type === 'add') delta.added += 1;
|
|
100
|
+
else if (line.type === 'del') delta.removed += 1;
|
|
74
101
|
}
|
|
75
102
|
return delta;
|
|
76
103
|
};
|
|
77
104
|
|
|
78
|
-
const AGE_UNIT = {
|
|
79
|
-
second: 's',
|
|
80
|
-
minute: 'm',
|
|
81
|
-
hour: 'h',
|
|
82
|
-
day: 'd',
|
|
83
|
-
week: 'w',
|
|
84
|
-
month: 'mo',
|
|
85
|
-
year: 'y',
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const AGE_AGO = /^(\d+)\s+(second|minute|hour|day|week|month|year)s?\s+ago$/i;
|
|
89
|
-
const AGE_SHORT = /^(\d+)(mo|[smhdwy])(?: ago)?$/i;
|
|
90
|
-
|
|
91
105
|
const shortUnit = (n, unit) => `${n}${AGE_UNIT[unit]} ago`;
|
|
92
106
|
|
|
93
107
|
const shortAge = (text) => {
|
|
94
108
|
const raw = `${text ?? ''}`.trim();
|
|
95
|
-
if (!raw) return '';
|
|
96
109
|
if (/^yesterday$/i.test(raw)) return '1d ago';
|
|
97
110
|
const match = AGE_AGO.exec(raw);
|
|
98
|
-
if (match)
|
|
99
|
-
const unit = AGE_UNIT[match[2].toLowerCase()];
|
|
100
|
-
if (unit) return `${match[1]}${unit} ago`;
|
|
101
|
-
}
|
|
111
|
+
if (match) return shortUnit(match[1], match[2].toLowerCase());
|
|
102
112
|
const brief = AGE_SHORT.exec(raw);
|
|
103
113
|
if (brief) return `${brief[1]}${brief[2].toLowerCase()} ago`;
|
|
104
114
|
return raw;
|
|
@@ -118,6 +128,18 @@ const relativeAge = (thenMs, nowMs = Date.now()) => {
|
|
|
118
128
|
return shortUnit(Math.round(days / 365), 'year');
|
|
119
129
|
};
|
|
120
130
|
|
|
131
|
+
const emptyCounts = () => ({
|
|
132
|
+
remaining: 0,
|
|
133
|
+
added: 0,
|
|
134
|
+
removed: 0,
|
|
135
|
+
stagedAdded: 0,
|
|
136
|
+
stagedRemoved: 0,
|
|
137
|
+
unstagedAdded: 0,
|
|
138
|
+
unstagedRemoved: 0,
|
|
139
|
+
staged: 0,
|
|
140
|
+
unstaged: 0,
|
|
141
|
+
});
|
|
142
|
+
|
|
121
143
|
const addLineDelta = (group, origin, delta) => {
|
|
122
144
|
group.added += delta.added;
|
|
123
145
|
group.removed += delta.removed;
|
|
@@ -141,19 +163,12 @@ const originLineDelta = (entry) => {
|
|
|
141
163
|
}
|
|
142
164
|
const added = entry.added ?? 0;
|
|
143
165
|
const removed = entry.removed ?? 0;
|
|
144
|
-
|
|
145
|
-
return {
|
|
146
|
-
stagedAdded: added,
|
|
147
|
-
stagedRemoved: removed,
|
|
148
|
-
unstagedAdded: 0,
|
|
149
|
-
unstagedRemoved: 0,
|
|
150
|
-
};
|
|
151
|
-
}
|
|
166
|
+
const staged = entry.status === 'staged';
|
|
152
167
|
return {
|
|
153
|
-
stagedAdded: 0,
|
|
154
|
-
stagedRemoved: 0,
|
|
155
|
-
unstagedAdded: added,
|
|
156
|
-
unstagedRemoved: removed,
|
|
168
|
+
stagedAdded: staged ? added : 0,
|
|
169
|
+
stagedRemoved: staged ? removed : 0,
|
|
170
|
+
unstagedAdded: staged ? 0 : added,
|
|
171
|
+
unstagedRemoved: staged ? 0 : removed,
|
|
157
172
|
};
|
|
158
173
|
};
|
|
159
174
|
|
|
@@ -165,22 +180,8 @@ const fileEntries = (items) => {
|
|
|
165
180
|
const rel = listPath(item);
|
|
166
181
|
if (!rel) continue;
|
|
167
182
|
if (!groups.has(rel)) {
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
origins: [],
|
|
171
|
-
firstIndex: i,
|
|
172
|
-
openIndex: i,
|
|
173
|
-
remaining: 0,
|
|
174
|
-
added: 0,
|
|
175
|
-
removed: 0,
|
|
176
|
-
stagedAdded: 0,
|
|
177
|
-
stagedRemoved: 0,
|
|
178
|
-
unstagedAdded: 0,
|
|
179
|
-
unstagedRemoved: 0,
|
|
180
|
-
staged: 0,
|
|
181
|
-
unstaged: 0,
|
|
182
|
-
date: '',
|
|
183
|
-
});
|
|
183
|
+
const fields = { origins: [], firstIndex: i, openIndex: i };
|
|
184
|
+
groups.set(rel, { path: rel, ...fields, ...emptyCounts(), date: '' });
|
|
184
185
|
}
|
|
185
186
|
const group = groups.get(rel);
|
|
186
187
|
const { origin } = item;
|
|
@@ -195,47 +196,30 @@ const fileEntries = (items) => {
|
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
const entries = [...groups.values()];
|
|
198
|
-
for (const group of entries)
|
|
199
|
-
group.status = fileStatus(group.origins);
|
|
200
|
-
}
|
|
199
|
+
for (const group of entries) group.status = fileStatus(group.origins);
|
|
201
200
|
entries.sort((left, right) => left.path.localeCompare(right.path, 'en'));
|
|
202
201
|
return entries;
|
|
203
202
|
};
|
|
204
203
|
|
|
205
204
|
const fileTotals = (files) => {
|
|
206
|
-
const total = {
|
|
207
|
-
|
|
208
|
-
kind: 'total',
|
|
209
|
-
status: '',
|
|
210
|
-
remaining: 0,
|
|
211
|
-
added: 0,
|
|
212
|
-
removed: 0,
|
|
213
|
-
stagedAdded: 0,
|
|
214
|
-
stagedRemoved: 0,
|
|
215
|
-
unstagedAdded: 0,
|
|
216
|
-
unstagedRemoved: 0,
|
|
217
|
-
staged: 0,
|
|
218
|
-
unstaged: 0,
|
|
219
|
-
};
|
|
205
|
+
const total = { path: TOTAL_LABEL, kind: 'total', status: '' };
|
|
206
|
+
Object.assign(total, emptyCounts());
|
|
220
207
|
for (const entry of files) {
|
|
221
208
|
if (isTasksEntry(entry) || isTotalEntry(entry)) continue;
|
|
222
209
|
const remaining = entry.remaining ?? 0;
|
|
223
210
|
const staged = entry.staged ?? 0;
|
|
224
|
-
const added = entry.added ?? 0;
|
|
225
|
-
const removed = entry.removed ?? 0;
|
|
226
211
|
const lines = originLineDelta(entry);
|
|
227
212
|
const listed = entry.unstaged ?? Math.max(0, remaining - staged);
|
|
228
|
-
const untracked =
|
|
229
|
-
const unstaged = untracked && listed === 0 ? remaining : listed;
|
|
213
|
+
const untracked = entry.status === 'untracked';
|
|
230
214
|
total.remaining += remaining;
|
|
231
|
-
total.added += added;
|
|
232
|
-
total.removed += removed;
|
|
215
|
+
total.added += entry.added ?? 0;
|
|
216
|
+
total.removed += entry.removed ?? 0;
|
|
233
217
|
total.stagedAdded += lines.stagedAdded;
|
|
234
218
|
total.stagedRemoved += lines.stagedRemoved;
|
|
235
219
|
total.unstagedAdded += lines.unstagedAdded;
|
|
236
220
|
total.unstagedRemoved += lines.unstagedRemoved;
|
|
237
221
|
total.staged += staged;
|
|
238
|
-
total.unstaged +=
|
|
222
|
+
total.unstaged += untracked && listed === 0 ? remaining : listed;
|
|
239
223
|
}
|
|
240
224
|
return total;
|
|
241
225
|
};
|
|
@@ -244,6 +228,9 @@ module.exports = {
|
|
|
244
228
|
REVIEW_DIR,
|
|
245
229
|
TASKS_FILE,
|
|
246
230
|
REPO_TASKS_LABEL,
|
|
231
|
+
TASK_KINDS,
|
|
232
|
+
taskKind,
|
|
233
|
+
taskTitle,
|
|
247
234
|
TOTAL_LABEL,
|
|
248
235
|
isPathInScope,
|
|
249
236
|
itemPath,
|
|
@@ -254,6 +241,7 @@ module.exports = {
|
|
|
254
241
|
isTotalEntry,
|
|
255
242
|
makeTaskItem,
|
|
256
243
|
fileStatus,
|
|
244
|
+
lineDelta,
|
|
257
245
|
fileEntries,
|
|
258
246
|
fileTotals,
|
|
259
247
|
relativeAge,
|
package/lib/find.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const matchesIn = (text, query) => {
|
|
4
|
+
const spans = [];
|
|
5
|
+
if (!query || !text) return spans;
|
|
6
|
+
const hay = text.toLowerCase();
|
|
7
|
+
const needle = query.toLowerCase();
|
|
8
|
+
const step = needle.length;
|
|
9
|
+
let from = 0;
|
|
10
|
+
while (from <= hay.length - step) {
|
|
11
|
+
const at = hay.indexOf(needle, from);
|
|
12
|
+
if (at < 0) break;
|
|
13
|
+
spans.push({ start: at, end: at + step });
|
|
14
|
+
from = at + step;
|
|
15
|
+
}
|
|
16
|
+
return spans;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const fileMatches = (entries, query) => {
|
|
20
|
+
const hits = [];
|
|
21
|
+
if (!query) return hits;
|
|
22
|
+
for (let index = 0; index < entries.length; index++) {
|
|
23
|
+
const entry = entries[index];
|
|
24
|
+
const path = entry && entry.path ? entry.path : '';
|
|
25
|
+
if (!path) continue;
|
|
26
|
+
for (const span of matchesIn(path, query)) {
|
|
27
|
+
hits.push({ index, path, start: span.start, end: span.end });
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return hits;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const activeHit = (entries, find) => {
|
|
34
|
+
if (!find || !find.query) return null;
|
|
35
|
+
const hits = fileMatches(entries, find.query);
|
|
36
|
+
if (!hits.length) return null;
|
|
37
|
+
const count = hits.length;
|
|
38
|
+
let at = find.at % count;
|
|
39
|
+
if (at < 0) at += count;
|
|
40
|
+
return hits[at];
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const keptPrefix = (shown, full) => {
|
|
44
|
+
const mark = shown.endsWith('…') && shown !== full ? 1 : 0;
|
|
45
|
+
const keep = shown.length - mark;
|
|
46
|
+
if (!full.startsWith(shown.slice(0, keep))) return -1;
|
|
47
|
+
return keep;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const spansFor = (shown, full, query, hit) => {
|
|
51
|
+
const spans = matchesIn(shown, query);
|
|
52
|
+
if (!hit || hit.path !== full) return spans;
|
|
53
|
+
const keep = keptPrefix(shown, full);
|
|
54
|
+
if (keep < 0 || hit.start >= keep) return spans;
|
|
55
|
+
return spans.map((span) => ({
|
|
56
|
+
...span,
|
|
57
|
+
on: span.start === hit.start,
|
|
58
|
+
}));
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
module.exports = { matchesIn, fileMatches, activeHit, spansFor };
|