reslop 0.2.6 → 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 +11 -1
- package/lib/ansi.js +42 -2
- package/lib/api.js +1 -1
- package/lib/cli.js +21 -1
- package/lib/dashboard/model.js +28 -3
- package/lib/dashboard/npm.js +116 -19
- package/lib/files.js +13 -7
- package/lib/find.js +61 -0
- package/lib/git-deps.js +1 -0
- package/lib/git-worktree.js +22 -4
- package/lib/keys.js +26 -1
- package/lib/render/chrome.js +257 -79
- package/lib/render/dash-blocks.js +297 -46
- package/lib/render/dash-table.js +6 -1
- package/lib/render/files.js +23 -4
- package/lib/render/frame.js +4 -0
- package/lib/render/notes.js +13 -2
- package/lib/render/packages.js +93 -0
- package/lib/render/primitives.js +13 -6
- package/lib/render/repos.js +226 -0
- package/lib/render/search.js +25 -0
- package/lib/render/tiles.js +107 -12
- package/lib/review.js +5 -4
- package/lib/session/actions.js +136 -30
- package/lib/session/branches.js +3 -5
- package/lib/session/commit.js +31 -4
- package/lib/session/compose.js +19 -4
- package/lib/session/dashboard.js +160 -15
- package/lib/session/dispatch.js +38 -4
- package/lib/session/files-pane.js +17 -2
- package/lib/session/find.js +94 -0
- package/lib/session/lifecycle.js +27 -0
- package/lib/session/navigation.js +2 -0
- package/lib/session/packages.js +143 -0
- package/lib/session/pointer.js +14 -0
- package/lib/session/tasks.js +3 -3
- package/lib/session/view.js +15 -2
- package/lib/session/watch.js +5 -0
- package/lib/session/workspace.js +455 -0
- package/lib/session.js +14 -0
- package/lib/workspace.js +166 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased][unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.2.7][] - 2026-10-03
|
|
6
|
+
|
|
7
|
+
- Add tiling view for multi-repository environment
|
|
8
|
+
- Improve navigation and hotkeys
|
|
9
|
+
- Add npm and dependencies screen
|
|
10
|
+
- Rework tasks: regroup into feature, bug, debt, research, and security
|
|
11
|
+
- Show tasks progress
|
|
12
|
+
- Search file paths in the files and diff screens
|
|
13
|
+
|
|
5
14
|
## [0.2.6][] - 2026-10-02
|
|
6
15
|
|
|
7
16
|
- Add a live dashboard as the start screen
|
|
@@ -163,7 +172,8 @@
|
|
|
163
172
|
- Feedback and todos that produce a repair plan for an agent
|
|
164
173
|
- Intra-line highlighting in unified, mixed, and side-by-side layouts
|
|
165
174
|
|
|
166
|
-
[unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.2.
|
|
175
|
+
[unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.2.7...HEAD
|
|
176
|
+
[0.2.7]: https://github.com/tshemsedinov/reslop/compare/v0.2.6...v0.2.7
|
|
167
177
|
[0.2.6]: https://github.com/tshemsedinov/reslop/compare/v0.2.5...v0.2.6
|
|
168
178
|
[0.2.5]: https://github.com/tshemsedinov/reslop/compare/v0.2.4...v0.2.5
|
|
169
179
|
[0.2.4]: https://github.com/tshemsedinov/reslop/compare/v0.2.3...v0.2.4
|
package/lib/ansi.js
CHANGED
|
@@ -75,6 +75,10 @@ const DARK_UI = {
|
|
|
75
75
|
warnFg: [0xe8, 0xc8, 0x4a],
|
|
76
76
|
mutedFg: [0x7a, 0x7a, 0x8a],
|
|
77
77
|
shaFg: [0x56, 0x9c, 0xd6],
|
|
78
|
+
pkgDepFg: [0xaf, 0xe0, 0xb9],
|
|
79
|
+
pkgDevFg: [0x9a, 0xc4, 0xe6],
|
|
80
|
+
pkgFg: [0xc8, 0xc8, 0xd2],
|
|
81
|
+
pkgChainFg: [0x55, 0x55, 0x62],
|
|
78
82
|
shaDarkFg: [0x15, 0x4a, 0x8c],
|
|
79
83
|
stagedDelLineFg: [0xa0, 0xa0, 0xa8],
|
|
80
84
|
stagedDelLineBg: [0x20, 0x20, 0x26],
|
|
@@ -88,10 +92,26 @@ const DARK_UI = {
|
|
|
88
92
|
dashTileBg: [0x26, 0x26, 0x2a],
|
|
89
93
|
dashRowBg: [0x1c, 0x1c, 0x1f],
|
|
90
94
|
dashHeadBg: [0x70, 0x70, 0x7c],
|
|
95
|
+
dashDimHeadBg: [0x30, 0x30, 0x38],
|
|
96
|
+
dashDimTileBg: [0x12, 0x12, 0x14],
|
|
97
|
+
dashDimRowBg: [0x0c, 0x0c, 0x0e],
|
|
91
98
|
dashHeadMuted: [0xf2, 0xf2, 0xfa],
|
|
92
99
|
dashDelFg: [0xff, 0xd2, 0xd2],
|
|
93
|
-
|
|
100
|
+
heatTrack: [0x3a, 0x3c, 0x48],
|
|
101
|
+
heat1: [0x1c, 0x3a, 0x32],
|
|
102
|
+
heat2: [0x2a, 0x5a, 0x38],
|
|
103
|
+
heat3: [0x8a, 0x6a, 0x18],
|
|
104
|
+
heat4: [0xa0, 0x3a, 0x28],
|
|
105
|
+
heatFg: [0xf4, 0xf4, 0xf8],
|
|
106
|
+
heatBlink: [0xf2, 0xe2, 0x8a],
|
|
107
|
+
heatBlinkFg: [0x1a, 0x16, 0x08],
|
|
94
108
|
dashKeyFg: [0xff, 0xff, 0xff],
|
|
109
|
+
taskHeadFg: [0xff, 0xff, 0xff],
|
|
110
|
+
taskHeadBg: [0x3c, 0x3c, 0x48],
|
|
111
|
+
searchFg: [0x1a, 0x16, 0x08],
|
|
112
|
+
searchBg: [0x8a, 0x6a, 0x18],
|
|
113
|
+
searchOnFg: [0x1a, 0x16, 0x08],
|
|
114
|
+
searchOnBg: [0xf2, 0xe2, 0x8a],
|
|
95
115
|
};
|
|
96
116
|
|
|
97
117
|
const DARK_CODE = {
|
|
@@ -175,6 +195,10 @@ const LIGHT_UI = {
|
|
|
175
195
|
warnFg: [0x9a, 0x67, 0x00],
|
|
176
196
|
mutedFg: [0x6e, 0x77, 0x81],
|
|
177
197
|
shaFg: [0x05, 0x50, 0xae],
|
|
198
|
+
pkgDepFg: [0x3c, 0xa8, 0x5a],
|
|
199
|
+
pkgDevFg: [0x3d, 0x7e, 0xc8],
|
|
200
|
+
pkgFg: [0x9a, 0xa2, 0xac],
|
|
201
|
+
pkgChainFg: [0x3a, 0x42, 0x4c],
|
|
178
202
|
shaDarkFg: [0x0b, 0x3d, 0x70],
|
|
179
203
|
stagedDelLineFg: [0x57, 0x60, 0x6a],
|
|
180
204
|
stagedDelLineBg: [0xf3, 0xf4, 0xf6],
|
|
@@ -188,10 +212,26 @@ const LIGHT_UI = {
|
|
|
188
212
|
dashTileBg: [0xe4, 0xe7, 0xeb],
|
|
189
213
|
dashRowBg: [0xd9, 0xdc, 0xe1],
|
|
190
214
|
dashHeadBg: [0xe8, 0xeb, 0xef],
|
|
215
|
+
dashDimHeadBg: [0xb0, 0xb6, 0xbe],
|
|
216
|
+
dashDimTileBg: [0xc4, 0xc8, 0xce],
|
|
217
|
+
dashDimRowBg: [0xb0, 0xb4, 0xba],
|
|
191
218
|
dashHeadMuted: [0x3e, 0x46, 0x52],
|
|
192
219
|
dashDelFg: [0xd0, 0x3a, 0x44],
|
|
193
|
-
|
|
220
|
+
heatTrack: [0xc5, 0xca, 0xd3],
|
|
221
|
+
heat1: [0xc8, 0xe6, 0xd4],
|
|
222
|
+
heat2: [0x7d, 0xc8, 0x96],
|
|
223
|
+
heat3: [0xe6, 0xb4, 0x3c],
|
|
224
|
+
heat4: [0xe0, 0x78, 0x68],
|
|
225
|
+
heatFg: [0x1f, 0x23, 0x28],
|
|
226
|
+
heatBlink: [0xff, 0xf3, 0xb0],
|
|
227
|
+
heatBlinkFg: [0x1a, 0x16, 0x08],
|
|
194
228
|
dashKeyFg: [0x00, 0x00, 0x00],
|
|
229
|
+
taskHeadFg: [0xff, 0xff, 0xff],
|
|
230
|
+
taskHeadBg: [0x6e, 0x77, 0x81],
|
|
231
|
+
searchFg: [0x1a, 0x16, 0x08],
|
|
232
|
+
searchBg: [0xf0, 0xc8, 0x6a],
|
|
233
|
+
searchOnFg: [0x1a, 0x16, 0x08],
|
|
234
|
+
searchOnBg: [0xff, 0xe1, 0x4a],
|
|
195
235
|
};
|
|
196
236
|
|
|
197
237
|
const LIGHT_CODE = {
|
package/lib/api.js
CHANGED
package/lib/cli.js
CHANGED
|
@@ -21,6 +21,8 @@ const ansi = require('./ansi.js');
|
|
|
21
21
|
const { setTheme } = ansi;
|
|
22
22
|
const reportRun = require('./report-run.js');
|
|
23
23
|
const { runCapture } = reportRun;
|
|
24
|
+
const workspace = require('./workspace.js');
|
|
25
|
+
const { workspaceFromArgs } = workspace;
|
|
24
26
|
|
|
25
27
|
const HEX_REV = /^[0-9a-f]{4,40}$/i;
|
|
26
28
|
const HEAD_REV = /^HEAD([~^].*)?$/i;
|
|
@@ -85,6 +87,20 @@ const resolveScope = async (cwd, paths, repo) => {
|
|
|
85
87
|
|
|
86
88
|
const localOptions = async (deps, args, selected, cwd) => {
|
|
87
89
|
const rawRepo = deps.repo ?? createGitRepo();
|
|
90
|
+
const found = workspaceFromArgs(cwd, selected.paths);
|
|
91
|
+
if (found) {
|
|
92
|
+
const capabilities = capabilitiesFor('local', { readOnly: args.readOnly });
|
|
93
|
+
return {
|
|
94
|
+
cwd: found.root,
|
|
95
|
+
repo: attachCapabilities(rawRepo, capabilities),
|
|
96
|
+
paths: [],
|
|
97
|
+
rev: null,
|
|
98
|
+
capabilities,
|
|
99
|
+
startPane: 'repos',
|
|
100
|
+
workspace: found,
|
|
101
|
+
repoName: path.basename(found.root),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
88
104
|
const scope = await resolveScope(cwd, selected.paths, rawRepo);
|
|
89
105
|
const kind = scope.rev ? 'commit' : 'local';
|
|
90
106
|
const capabilities = capabilitiesFor(kind, { readOnly: args.readOnly });
|
|
@@ -143,7 +159,7 @@ const loadSession = async (proc, deps, args) => {
|
|
|
143
159
|
audit: true,
|
|
144
160
|
...extra,
|
|
145
161
|
});
|
|
146
|
-
session.ensureRepo();
|
|
162
|
+
if (session.pane !== 'repos') session.ensureRepo();
|
|
147
163
|
return session;
|
|
148
164
|
};
|
|
149
165
|
|
|
@@ -176,6 +192,10 @@ const run = async (proc, deps = {}) => {
|
|
|
176
192
|
}
|
|
177
193
|
const tty = proc.stdin.isTTY === true && stdout.isTTY === true;
|
|
178
194
|
if (tty) return runUi(session, proc);
|
|
195
|
+
if (session.pane === 'repos') {
|
|
196
|
+
stderr.write('reslop: interactive terminal required\n');
|
|
197
|
+
return 1;
|
|
198
|
+
}
|
|
179
199
|
try {
|
|
180
200
|
await session.loadReady();
|
|
181
201
|
} catch (error) {
|
package/lib/dashboard/model.js
CHANGED
|
@@ -5,6 +5,7 @@ const { folderOf, extOf } = tree;
|
|
|
5
5
|
const files = require('../files.js');
|
|
6
6
|
const { TASK_KINDS, taskKind } = files;
|
|
7
7
|
|
|
8
|
+
const MINUTE_MS = 60_000;
|
|
8
9
|
const RUNS_SHOWN = 6;
|
|
9
10
|
const CHAIN_GAP_MS = 2000;
|
|
10
11
|
const NPM_NAMED = /^npm (?:run|exec)(?:\s+-s)?\s+(\S+)/;
|
|
@@ -237,12 +238,25 @@ const versionFields = (pkg, outdatedMap) => {
|
|
|
237
238
|
return { current, wanted: hit.wanted, latest: hit.latest };
|
|
238
239
|
};
|
|
239
240
|
|
|
241
|
+
const auditFields = (pkg, auditMap) => {
|
|
242
|
+
const finding = auditMap ? auditMap.get(pkg.name) : null;
|
|
243
|
+
return {
|
|
244
|
+
audit: Boolean(finding),
|
|
245
|
+
fix: finding && finding.fix ? finding.fix : '',
|
|
246
|
+
range: finding && finding.range ? finding.range : '',
|
|
247
|
+
severity: finding && finding.severity ? finding.severity : '',
|
|
248
|
+
title: finding && finding.title ? finding.title : '',
|
|
249
|
+
};
|
|
250
|
+
};
|
|
251
|
+
|
|
240
252
|
const modulesModel = (modules, auditMap, outdatedMap) => {
|
|
241
253
|
const packages = modules.packages.map((pkg) => ({
|
|
242
254
|
name: pkg.name,
|
|
243
255
|
bytes: pkg.bytes,
|
|
244
256
|
dev: pkg.dev,
|
|
245
|
-
|
|
257
|
+
transitive: Boolean(pkg.transitive),
|
|
258
|
+
chain: pkg.chain || '',
|
|
259
|
+
...auditFields(pkg, auditMap),
|
|
246
260
|
...versionFields(pkg, outdatedMap),
|
|
247
261
|
}));
|
|
248
262
|
return { ...modules, packages };
|
|
@@ -266,6 +280,8 @@ const npmModel = (input) => {
|
|
|
266
280
|
};
|
|
267
281
|
};
|
|
268
282
|
|
|
283
|
+
const minuteOf = (at) => Math.floor((at || 0) / MINUTE_MS);
|
|
284
|
+
|
|
269
285
|
const filesModel = (input) => {
|
|
270
286
|
const index = input.index;
|
|
271
287
|
const ready = Boolean(index && index.ready);
|
|
@@ -288,7 +304,7 @@ const diffsModel = (input) => {
|
|
|
288
304
|
dirs: groups.dirs,
|
|
289
305
|
exts: groups.exts,
|
|
290
306
|
files: input.entries.length,
|
|
291
|
-
|
|
307
|
+
activity: input.activity ?? { minutes: [], live: false },
|
|
292
308
|
delta: input.diffDelta,
|
|
293
309
|
};
|
|
294
310
|
};
|
|
@@ -329,4 +345,13 @@ const buildModel = (input) => ({
|
|
|
329
345
|
tasks: tasksModel(input),
|
|
330
346
|
});
|
|
331
347
|
|
|
332
|
-
module.exports = {
|
|
348
|
+
module.exports = {
|
|
349
|
+
MINUTE_MS,
|
|
350
|
+
minuteOf,
|
|
351
|
+
groupChanges,
|
|
352
|
+
mergeRuns,
|
|
353
|
+
scriptName,
|
|
354
|
+
runName,
|
|
355
|
+
npmModel,
|
|
356
|
+
buildModel,
|
|
357
|
+
};
|
package/lib/dashboard/npm.js
CHANGED
|
@@ -60,7 +60,28 @@ const rankedPackages = (totals) => {
|
|
|
60
60
|
);
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
const
|
|
63
|
+
const DIRECT_SECTIONS = [
|
|
64
|
+
'dependencies',
|
|
65
|
+
'devDependencies',
|
|
66
|
+
'optionalDependencies',
|
|
67
|
+
'peerDependencies',
|
|
68
|
+
];
|
|
69
|
+
const CHILD_SECTIONS = [
|
|
70
|
+
'dependencies',
|
|
71
|
+
'optionalDependencies',
|
|
72
|
+
'peerDependencies',
|
|
73
|
+
];
|
|
74
|
+
const CHAIN_MARK = ' 🢒 ';
|
|
75
|
+
|
|
76
|
+
const directNames = (pkg) => {
|
|
77
|
+
const names = new Set();
|
|
78
|
+
for (const section of DIRECT_SECTIONS) {
|
|
79
|
+
for (const name of declaredNames(pkg, section)) names.add(name);
|
|
80
|
+
}
|
|
81
|
+
return names;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const devNames = (pkg) => {
|
|
64
85
|
const keep = new Set([
|
|
65
86
|
...declaredNames(pkg, 'dependencies'),
|
|
66
87
|
...declaredNames(pkg, 'optionalDependencies'),
|
|
@@ -69,16 +90,84 @@ const devOnlyNames = (pkg, lock) => {
|
|
|
69
90
|
for (const name of declaredNames(pkg, 'devDependencies')) {
|
|
70
91
|
if (!keep.has(name)) names.add(name);
|
|
71
92
|
}
|
|
93
|
+
return names;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const entryName = (key) => {
|
|
97
|
+
const at = key.lastIndexOf(MODULE_PREFIX);
|
|
98
|
+
if (at < 0) return '';
|
|
99
|
+
const rest = key.slice(at + MODULE_PREFIX.length);
|
|
100
|
+
if (rest.startsWith('@')) {
|
|
101
|
+
const parts = rest.split('/');
|
|
102
|
+
if (!parts[1]) return '';
|
|
103
|
+
return `${parts[0]}/${parts[1]}`;
|
|
104
|
+
}
|
|
105
|
+
return rest.split('/')[0] || '';
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const childNames = (entry) => {
|
|
109
|
+
const names = [];
|
|
110
|
+
if (!entry || typeof entry !== 'object') return names;
|
|
111
|
+
for (const section of CHILD_SECTIONS) {
|
|
112
|
+
const map = entry[section];
|
|
113
|
+
if (!map || typeof map !== 'object') continue;
|
|
114
|
+
for (const name of Object.keys(map)) {
|
|
115
|
+
if (!names.includes(name)) names.push(name);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return names;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
const dependencyChildren = (lock) => {
|
|
122
|
+
const children = new Map();
|
|
72
123
|
const packages = lockPackages(lock);
|
|
73
|
-
if (!packages) return
|
|
124
|
+
if (!packages) return children;
|
|
74
125
|
for (const key of Object.keys(packages)) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
126
|
+
if (!key) continue;
|
|
127
|
+
const name = entryName(key);
|
|
128
|
+
if (!name) continue;
|
|
129
|
+
const list = children.get(name) ?? [];
|
|
130
|
+
for (const child of childNames(packages[key])) {
|
|
131
|
+
if (!list.includes(child)) list.push(child);
|
|
132
|
+
}
|
|
133
|
+
if (list.length) children.set(name, list);
|
|
80
134
|
}
|
|
81
|
-
return
|
|
135
|
+
return children;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
const parentChain = (name, declared, children) => {
|
|
139
|
+
if (declared.has(name)) return '';
|
|
140
|
+
const prev = new Map();
|
|
141
|
+
const queue = [];
|
|
142
|
+
const roots = [...declared].sort();
|
|
143
|
+
for (const root of roots) {
|
|
144
|
+
if (prev.has(root)) continue;
|
|
145
|
+
prev.set(root, '');
|
|
146
|
+
queue.push(root);
|
|
147
|
+
}
|
|
148
|
+
let head = 0;
|
|
149
|
+
while (head < queue.length) {
|
|
150
|
+
const at = queue[head];
|
|
151
|
+
head += 1;
|
|
152
|
+
const kids = children.get(at);
|
|
153
|
+
if (!kids) continue;
|
|
154
|
+
for (const kid of kids) {
|
|
155
|
+
if (prev.has(kid)) continue;
|
|
156
|
+
prev.set(kid, at);
|
|
157
|
+
if (kid !== name) {
|
|
158
|
+
queue.push(kid);
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
const path = [];
|
|
162
|
+
let cursor = at;
|
|
163
|
+
while (cursor) {
|
|
164
|
+
path.push(cursor);
|
|
165
|
+
cursor = prev.get(cursor) || '';
|
|
166
|
+
}
|
|
167
|
+
return path.join(CHAIN_MARK);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return '';
|
|
82
171
|
};
|
|
83
172
|
|
|
84
173
|
const lockVersion = (lock, name) => {
|
|
@@ -89,16 +178,24 @@ const lockVersion = (lock, name) => {
|
|
|
89
178
|
};
|
|
90
179
|
|
|
91
180
|
const topPackages = (ranked, pkg, lock) => {
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
181
|
+
const declared = directNames(pkg);
|
|
182
|
+
const devs = devNames(pkg);
|
|
183
|
+
const children = dependencyChildren(lock);
|
|
184
|
+
const rows = ranked.map((item) => {
|
|
185
|
+
const direct = declared.has(item.name);
|
|
186
|
+
return {
|
|
187
|
+
name: item.name,
|
|
188
|
+
bytes: item.bytes,
|
|
189
|
+
dev: direct && devs.has(item.name),
|
|
190
|
+
transitive: !direct,
|
|
191
|
+
chain: parentChain(item.name, declared, children),
|
|
192
|
+
version: lockVersion(lock, item.name),
|
|
193
|
+
};
|
|
194
|
+
});
|
|
195
|
+
const runtime = rows.filter((row) => !row.dev && !row.transitive);
|
|
196
|
+
const development = rows.filter((row) => row.dev);
|
|
197
|
+
const nested = rows.filter((row) => row.transitive);
|
|
198
|
+
return [...runtime, ...development, ...nested];
|
|
102
199
|
};
|
|
103
200
|
|
|
104
201
|
const measureModules = async (root) => {
|
|
@@ -190,4 +287,4 @@ const readNpmSummary = async (top, previous) => {
|
|
|
190
287
|
};
|
|
191
288
|
};
|
|
192
289
|
|
|
193
|
-
module.exports = { readNpmSummary };
|
|
290
|
+
module.exports = { TOP_PACKAGES, readNpmSummary };
|
package/lib/files.js
CHANGED
|
@@ -6,19 +6,24 @@ 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(' ');
|
|
11
13
|
const TASK_KINDS = [
|
|
12
|
-
{ id: '
|
|
13
|
-
{ id: '
|
|
14
|
-
{ id: '
|
|
15
|
-
{ id: '
|
|
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' },
|
|
16
19
|
];
|
|
17
20
|
const TASK_KIND_IDS = new Set(TASK_KINDS.map((kind) => kind.id));
|
|
21
|
+
const TASK_KIND_ALIAS = { backlog: 'debt', issues: 'debt' };
|
|
18
22
|
|
|
19
23
|
const taskKind = (todo) => {
|
|
20
24
|
const kind = todo && todo.kind;
|
|
21
|
-
|
|
25
|
+
if (TASK_KIND_IDS.has(kind)) return kind;
|
|
26
|
+
return TASK_KIND_ALIAS[kind] ?? 'debt';
|
|
22
27
|
};
|
|
23
28
|
|
|
24
29
|
const taskTitle = (id) => {
|
|
@@ -236,6 +241,7 @@ module.exports = {
|
|
|
236
241
|
isTotalEntry,
|
|
237
242
|
makeTaskItem,
|
|
238
243
|
fileStatus,
|
|
244
|
+
lineDelta,
|
|
239
245
|
fileEntries,
|
|
240
246
|
fileTotals,
|
|
241
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 };
|
package/lib/git-deps.js
CHANGED
package/lib/git-worktree.js
CHANGED
|
@@ -8,7 +8,7 @@ const { runProc, runProcSync, isAbort, oneLine } = utilities;
|
|
|
8
8
|
const diff = require('./diff/diff.js');
|
|
9
9
|
const { parseDiff, synthesizeNewFile, itemsFromFiles } = diff;
|
|
10
10
|
const files = require('./files.js');
|
|
11
|
-
const { itemPath, isReadOnlyOrigin, REVIEW_DIR } = files;
|
|
11
|
+
const { itemPath, isReadOnlyOrigin, lineDelta, REVIEW_DIR } = files;
|
|
12
12
|
const edit = require('./diff/edit.js');
|
|
13
13
|
const { replaceBlockAdds } = edit;
|
|
14
14
|
|
|
@@ -119,6 +119,8 @@ const untrackedArgs = (paths) => [
|
|
|
119
119
|
|
|
120
120
|
const isReviewFile = (rel) => rel.split(/[/\\]/)[0] === REVIEW_DIR;
|
|
121
121
|
|
|
122
|
+
const isDirEntry = (rel) => rel.endsWith('/');
|
|
123
|
+
|
|
122
124
|
const dropReviewItems = (items) =>
|
|
123
125
|
items.filter((item) => !isReviewFile(itemPath(item)));
|
|
124
126
|
|
|
@@ -132,7 +134,9 @@ const listScopeFiles = (top, paths = [], options = {}) => {
|
|
|
132
134
|
? ['ls-tree', '-r', '--name-only', '-z', rev, ...pathArgs(paths)]
|
|
133
135
|
: ['ls-files', '-co', '--exclude-standard', '-z', ...pathArgs(paths)];
|
|
134
136
|
const result = requireOk(runGit(args, top), 'ls-files failed');
|
|
135
|
-
const names = namesFromLs(result.stdout).filter(
|
|
137
|
+
const names = namesFromLs(result.stdout).filter(
|
|
138
|
+
(rel) => !isReviewFile(rel) && !isDirEntry(rel),
|
|
139
|
+
);
|
|
136
140
|
names.sort((left, right) => left.localeCompare(right, 'en'));
|
|
137
141
|
return names;
|
|
138
142
|
};
|
|
@@ -150,12 +154,12 @@ const binaryNewFile = (rel) => ({
|
|
|
150
154
|
const untrackedItemsFrom = (top, names) => {
|
|
151
155
|
const files = [];
|
|
152
156
|
for (const rel of names) {
|
|
153
|
-
if (isReviewFile(rel)) continue;
|
|
157
|
+
if (isReviewFile(rel) || isDirEntry(rel)) continue;
|
|
154
158
|
let buf;
|
|
155
159
|
try {
|
|
156
160
|
buf = fs.readFileSync(path.join(top, rel));
|
|
157
161
|
} catch (error) {
|
|
158
|
-
if (error.code === 'ENOENT') continue;
|
|
162
|
+
if (error.code === 'ENOENT' || error.code === 'EISDIR') continue;
|
|
159
163
|
throw error;
|
|
160
164
|
}
|
|
161
165
|
if (isBinaryBuffer(buf)) files.push(binaryNewFile(rel));
|
|
@@ -164,6 +168,19 @@ const untrackedItemsFrom = (top, names) => {
|
|
|
164
168
|
return itemsFromFiles(files, 'untracked');
|
|
165
169
|
};
|
|
166
170
|
|
|
171
|
+
const countUntrackedAdded = async (top) => {
|
|
172
|
+
try {
|
|
173
|
+
const result = await runGitAsync(untrackedArgs([]), top);
|
|
174
|
+
if (result.status !== 0) return 0;
|
|
175
|
+
const items = untrackedItemsFrom(top, namesFromLs(result.stdout));
|
|
176
|
+
let added = 0;
|
|
177
|
+
for (const item of items) added += lineDelta(item).added;
|
|
178
|
+
return added;
|
|
179
|
+
} catch {
|
|
180
|
+
return 0;
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
|
|
167
184
|
const groupByPath = (items) => {
|
|
168
185
|
const byPath = new Map();
|
|
169
186
|
for (const item of items) {
|
|
@@ -424,6 +441,7 @@ module.exports = {
|
|
|
424
441
|
resolveRev,
|
|
425
442
|
currentBranch,
|
|
426
443
|
fileText,
|
|
444
|
+
countUntrackedAdded,
|
|
427
445
|
listScopeFiles,
|
|
428
446
|
readSnapshot,
|
|
429
447
|
readSnapshotAsync,
|
package/lib/keys.js
CHANGED
|
@@ -8,6 +8,7 @@ const { DIFF_DISABLED, FILES_GIT_DISABLED, FILES_HIDDEN } = actions;
|
|
|
8
8
|
const { TASKS_DISABLED, BRANCHES_DISABLED } = actions;
|
|
9
9
|
const { COMMITS_DISABLED, UNIT_DISABLED, NPM_DISABLED } = actions;
|
|
10
10
|
const { diffGitDisabled, branchGitDisabled, commitGitDisabled } = actions;
|
|
11
|
+
const { entryHasDiff, lineHasDiff, withoutMoves } = actions;
|
|
11
12
|
const { actionLetter, buttonWord } = actions;
|
|
12
13
|
const { CONFIRM, confirmPrefix, promptFromChoices } = actions;
|
|
13
14
|
|
|
@@ -20,14 +21,35 @@ const CSI_KEYS = {
|
|
|
20
21
|
F: 'end',
|
|
21
22
|
};
|
|
22
23
|
|
|
24
|
+
const SS3_KEYS = {
|
|
25
|
+
...CSI_KEYS,
|
|
26
|
+
P: 'f1',
|
|
27
|
+
Q: 'f2',
|
|
28
|
+
R: 'f3',
|
|
29
|
+
S: 'f4',
|
|
30
|
+
};
|
|
31
|
+
|
|
23
32
|
const CSI_TILDE = {
|
|
24
33
|
1: 'home',
|
|
34
|
+
2: 'insert',
|
|
25
35
|
3: 'delete',
|
|
26
36
|
4: 'end',
|
|
27
37
|
5: 'pageUp',
|
|
28
38
|
6: 'pageDown',
|
|
29
39
|
7: 'home',
|
|
30
40
|
8: 'end',
|
|
41
|
+
11: 'f1',
|
|
42
|
+
12: 'f2',
|
|
43
|
+
13: 'f3',
|
|
44
|
+
14: 'f4',
|
|
45
|
+
15: 'f5',
|
|
46
|
+
17: 'f6',
|
|
47
|
+
18: 'f7',
|
|
48
|
+
19: 'f8',
|
|
49
|
+
20: 'f9',
|
|
50
|
+
21: 'f10',
|
|
51
|
+
23: 'f11',
|
|
52
|
+
24: 'f12',
|
|
31
53
|
};
|
|
32
54
|
|
|
33
55
|
const CSI_MAX = 16;
|
|
@@ -121,7 +143,7 @@ const parseEsc = (text, start) => {
|
|
|
121
143
|
if (rest.startsWith('\x1b[')) return parseCsi(rest, start);
|
|
122
144
|
if (rest.startsWith('\x1bO')) {
|
|
123
145
|
if (rest.length < 3) return NEED_MORE;
|
|
124
|
-
const key =
|
|
146
|
+
const key = SS3_KEYS[rest[2]];
|
|
125
147
|
return key ? keyAt(key, start + 3) : keyAt('escape', start + 1);
|
|
126
148
|
}
|
|
127
149
|
return keyAt('escape', start + 1);
|
|
@@ -184,6 +206,9 @@ module.exports = {
|
|
|
184
206
|
NPM_DISABLED,
|
|
185
207
|
UNIT_DISABLED,
|
|
186
208
|
diffGitDisabled,
|
|
209
|
+
entryHasDiff,
|
|
210
|
+
lineHasDiff,
|
|
211
|
+
withoutMoves,
|
|
187
212
|
branchGitDisabled,
|
|
188
213
|
commitGitDisabled,
|
|
189
214
|
decodeChunk,
|