reslop 0.2.1 → 0.2.3
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 +14 -1
- package/README.md +11 -1
- package/lib/ansi.js +120 -4
- package/lib/cli.js +3 -0
- package/lib/deps.js +43 -3
- package/lib/git-branches.js +47 -15
- package/lib/keys.js +10 -4
- package/lib/npm-commands.js +513 -0
- package/lib/render/branches.js +21 -28
- package/lib/render/chrome.js +137 -122
- package/lib/render/commits.js +604 -54
- package/lib/render/frame.js +38 -4
- package/lib/render/npm.js +228 -0
- package/lib/render/primitives.js +7 -36
- package/lib/report-model.js +127 -0
- package/lib/report-parse.js +94 -0
- package/lib/report-render.js +87 -0
- package/lib/report-run.js +164 -0
- package/lib/report-test.js +506 -0
- package/lib/report-tools.js +311 -0
- package/lib/session/actions.js +51 -2
- package/lib/session/commit.js +94 -19
- package/lib/session/compose.js +48 -5
- package/lib/session/dispatch.js +30 -2
- package/lib/session/files-pane.js +20 -4
- package/lib/session/lifecycle.js +4 -1
- package/lib/session/navigation.js +1 -0
- package/lib/session/npm.js +335 -0
- package/lib/session/pointer.js +8 -0
- package/lib/session/view.js +14 -3
- package/lib/session.js +10 -2
- package/package.json +6 -6
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased][unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.2.3][] - 2026-09-27
|
|
6
|
+
|
|
7
|
+
- Add brief and full commit lists, toggled with `v`
|
|
8
|
+
- Select a commit from the list to view its diff
|
|
9
|
+
- Edit a multi-line commit message
|
|
10
|
+
- Propose unused removals only for libraries that export code
|
|
11
|
+
- Improve TUI/TUX, multiple small fixes
|
|
12
|
+
- Implement npm command runner with termination and re-run
|
|
13
|
+
- Filter tests output to reduce AI costs: `npm t` and other scripts
|
|
14
|
+
- Save script logs under `.log/`
|
|
15
|
+
- Group similar problems in test and other output
|
|
16
|
+
|
|
5
17
|
## [0.2.1][] - 2026-09-23
|
|
6
18
|
|
|
7
19
|
- Type a new commit message at the top of the list
|
|
@@ -117,7 +129,8 @@
|
|
|
117
129
|
- Feedback and todos that produce a repair plan for an agent
|
|
118
130
|
- Intra-line highlighting in unified, mixed, and side-by-side layouts
|
|
119
131
|
|
|
120
|
-
[unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.2.
|
|
132
|
+
[unreleased]: https://github.com/tshemsedinov/reslop/compare/v0.2.3...HEAD
|
|
133
|
+
[0.2.3]: https://github.com/tshemsedinov/reslop/compare/v0.2.1...v0.2.3
|
|
121
134
|
[0.2.1]: https://github.com/tshemsedinov/reslop/compare/v0.2.0...v0.2.1
|
|
122
135
|
[0.2.0]: https://github.com/tshemsedinov/reslop/compare/v0.1.9...v0.2.0
|
|
123
136
|
[0.1.9]: https://github.com/tshemsedinov/reslop/compare/v0.1.8...v0.1.9
|
package/README.md
CHANGED
|
@@ -39,6 +39,7 @@ npm i -g reslop
|
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
Works on Linux, macOS, and Windows. On Windows use Windows Terminal.
|
|
42
|
+
Requires Node.js `>=18.15.0`.
|
|
42
43
|
|
|
43
44
|
## Usage
|
|
44
45
|
|
|
@@ -49,6 +50,16 @@ Works on Linux, macOS, and Windows. On Windows use Windows Terminal.
|
|
|
49
50
|
- `reslop -n` start a new review even if the latest is still editing
|
|
50
51
|
- `reslop -r` read-only mode
|
|
51
52
|
- `reslop -light` light color theme (default dark)
|
|
53
|
+
- `reslop t -- <program> [args]` run one program and print a reduced report
|
|
54
|
+
|
|
55
|
+
Prefix each program in an npm script with `reslop t --`. Keep `&&` between those wraps, so each program is captured on its own and a failing step does not start the next one:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
"lint": "reslop t -- eslint . && reslop t -- prettier -c \"**/*.js\"",
|
|
59
|
+
"test": "reslop t -- npm run -s lint && reslop t -- node --test"
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
The report is Markdown: failures, diagnostics, and a short summary. Passing results are omitted and similar problems are grouped. The same text is saved under `.log/`. Set `RESLOP_OUTPUT=raw` to pass the command through unchanged. The review screen runs scripts in raw mode and applies its own filter once.
|
|
52
63
|
|
|
53
64
|
Reviews go in `.review/YYYY-MM-DD-NN.md` with frontmatter `status`:
|
|
54
65
|
|
|
@@ -65,7 +76,6 @@ Reviews go in `.review/YYYY-MM-DD-NN.md` with frontmatter `status`:
|
|
|
65
76
|
- Send anonymized code blocks for expert review
|
|
66
77
|
- Send questions and the repair plan to experts for approval
|
|
67
78
|
- Ask experts
|
|
68
|
-
- Verify the codebase
|
|
69
79
|
- Apply refactoring skills
|
|
70
80
|
- Call agents, harnesses, and IDEs to execute prepared plans
|
|
71
81
|
- Squash, cherry-pick, and interactive rebase
|
package/lib/ansi.js
CHANGED
|
@@ -60,7 +60,6 @@ const DARK_UI = {
|
|
|
60
60
|
headerDirFg: [0x6e, 0x6e, 0x6e],
|
|
61
61
|
headerSlashFg: [0x8a, 0x8a, 0x8a],
|
|
62
62
|
headerFileFg: [0x4e, 0x4e, 0x4e],
|
|
63
|
-
headerMetaFg: [0x5a, 0x5a, 0x5a],
|
|
64
63
|
chromeFg: [0xd0, 0xd0, 0xe0],
|
|
65
64
|
chromeBg: [0x18, 0x18, 0x24],
|
|
66
65
|
noteBg: [0x24, 0x24, 0x36],
|
|
@@ -153,7 +152,6 @@ const LIGHT_UI = {
|
|
|
153
152
|
headerDirFg: [0x57, 0x60, 0x6a],
|
|
154
153
|
headerSlashFg: [0x8c, 0x95, 0x9f],
|
|
155
154
|
headerFileFg: [0x1f, 0x23, 0x28],
|
|
156
|
-
headerMetaFg: [0x42, 0x4a, 0x53],
|
|
157
155
|
chromeFg: [0x24, 0x29, 0x2f],
|
|
158
156
|
chromeBg: [0xf0, 0xf2, 0xf5],
|
|
159
157
|
noteBg: [0xe8, 0xf0, 0xfe],
|
|
@@ -280,9 +278,14 @@ const codeFg = (style) => {
|
|
|
280
278
|
return CODE_FG.plain;
|
|
281
279
|
};
|
|
282
280
|
|
|
283
|
-
const
|
|
281
|
+
const channels = (rgb) => {
|
|
282
|
+
if (typeof rgb === 'number') return `${rgb};${rgb};${rgb}`;
|
|
283
|
+
return `${rgb[0]};${rgb[1]};${rgb[2]}`;
|
|
284
|
+
};
|
|
285
|
+
|
|
286
|
+
const fg = (rgb) => `${ESC}[38;2;${channels(rgb)}m`;
|
|
284
287
|
|
|
285
|
-
const bg = (rgb) => `${ESC}[48;2;${rgb
|
|
288
|
+
const bg = (rgb) => `${ESC}[48;2;${channels(rgb)}m`;
|
|
286
289
|
|
|
287
290
|
const seq = (fgRgb, bgRgb) => `${fg(fgRgb)}${bg(bgRgb)}`;
|
|
288
291
|
|
|
@@ -352,6 +355,117 @@ const truncateVisible = (text, width) => {
|
|
|
352
355
|
return out;
|
|
353
356
|
};
|
|
354
357
|
|
|
358
|
+
const csiLength = (text, index) => {
|
|
359
|
+
if (text[index] !== ESC || text[index + 1] !== '[') return 0;
|
|
360
|
+
let at = index + 2;
|
|
361
|
+
while (at < text.length) {
|
|
362
|
+
const code = text.charCodeAt(at);
|
|
363
|
+
at += 1;
|
|
364
|
+
if (code >= 64 && code <= 126) return at - index;
|
|
365
|
+
}
|
|
366
|
+
return 0;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const firstGrapheme = (text) => {
|
|
370
|
+
const next = SEGMENTER.segment(text)[Symbol.iterator]().next();
|
|
371
|
+
if (next.done || !next.value) return '';
|
|
372
|
+
return next.value.segment;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
const clipAnsi = (text, width) => {
|
|
376
|
+
const raw = `${text ?? ''}`;
|
|
377
|
+
if (width <= 0) return '';
|
|
378
|
+
if (visibleWidth(raw) <= width) return raw;
|
|
379
|
+
let used = 0;
|
|
380
|
+
let out = '';
|
|
381
|
+
let index = 0;
|
|
382
|
+
while (index < raw.length && used < width) {
|
|
383
|
+
const csi = csiLength(raw, index);
|
|
384
|
+
if (csi) {
|
|
385
|
+
out += raw.slice(index, index + csi);
|
|
386
|
+
index += csi;
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
const ch = firstGrapheme(raw.slice(index));
|
|
390
|
+
if (!ch) break;
|
|
391
|
+
const w = graphemeWidth(ch, used);
|
|
392
|
+
if (used + w > width) break;
|
|
393
|
+
out += ch;
|
|
394
|
+
used += w;
|
|
395
|
+
index += ch.length;
|
|
396
|
+
}
|
|
397
|
+
if (stripAnsi(out) !== stripAnsi(raw)) out += RESET;
|
|
398
|
+
return out;
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
const sgrParams = (seq) => {
|
|
402
|
+
const inner = seq.slice(2, -1);
|
|
403
|
+
if (!inner) return [0];
|
|
404
|
+
return inner.split(';').map((part) => (part === '' ? 0 : Number(part)));
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const colorSpan = (params, index) => {
|
|
408
|
+
const mode = params[index + 1];
|
|
409
|
+
if (mode === 5) return 2;
|
|
410
|
+
if (mode === 2) return 4;
|
|
411
|
+
return 0;
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const keepFgParams = (params) => {
|
|
415
|
+
const kept = [];
|
|
416
|
+
for (let i = 0; i < params.length; i++) {
|
|
417
|
+
const code = params[i];
|
|
418
|
+
if (code === 38 || code === 48) {
|
|
419
|
+
const extra = colorSpan(params, i);
|
|
420
|
+
if (code === 38 && extra) {
|
|
421
|
+
kept.push(code, ...params.slice(i + 1, i + 1 + extra));
|
|
422
|
+
}
|
|
423
|
+
i += extra;
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
if (code >= 40 && code <= 49) continue;
|
|
427
|
+
if (code >= 100 && code <= 107) continue;
|
|
428
|
+
if (code === 7 || code === 27) continue;
|
|
429
|
+
kept.push(code);
|
|
430
|
+
}
|
|
431
|
+
return kept;
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const foregroundOn = (text, bgRgb) => {
|
|
435
|
+
const raw = `${text ?? ''}`;
|
|
436
|
+
const ground = bg(bgRgb);
|
|
437
|
+
let out = '';
|
|
438
|
+
let index = 0;
|
|
439
|
+
while (index < raw.length) {
|
|
440
|
+
if (raw[index] !== ESC) {
|
|
441
|
+
const next = raw.indexOf(ESC, index);
|
|
442
|
+
const end = next < 0 ? raw.length : next;
|
|
443
|
+
out += raw.slice(index, end);
|
|
444
|
+
index = end;
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
const csi = csiLength(raw, index);
|
|
448
|
+
if (!csi) {
|
|
449
|
+
out += raw[index];
|
|
450
|
+
index += 1;
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
const seq = raw.slice(index, index + csi);
|
|
454
|
+
index += csi;
|
|
455
|
+
if (!seq.endsWith('m')) {
|
|
456
|
+
out += seq;
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
const kept = keepFgParams(sgrParams(seq));
|
|
460
|
+
const reset = kept.includes(0);
|
|
461
|
+
const rest = kept.filter((code) => code !== 0);
|
|
462
|
+
if (reset) out += `${ESC}[0m`;
|
|
463
|
+
out += ground;
|
|
464
|
+
if (rest.length) out += `${ESC}[${rest.join(';')}m`;
|
|
465
|
+
}
|
|
466
|
+
return out;
|
|
467
|
+
};
|
|
468
|
+
|
|
355
469
|
module.exports = {
|
|
356
470
|
ESC,
|
|
357
471
|
RESET,
|
|
@@ -373,4 +487,6 @@ module.exports = {
|
|
|
373
487
|
graphemeWidth,
|
|
374
488
|
visibleWidth,
|
|
375
489
|
truncateVisible,
|
|
490
|
+
clipAnsi,
|
|
491
|
+
foregroundOn,
|
|
376
492
|
};
|
package/lib/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ const { selectChangeSource, createLoadedSource } = require('./source.js');
|
|
|
11
11
|
const { Session } = require('./session.js');
|
|
12
12
|
const { capabilitiesFor, attachCapabilities } = require('./capabilities.js');
|
|
13
13
|
const { setTheme } = require('./ansi.js');
|
|
14
|
+
const { runCapture } = require('./report-run.js');
|
|
14
15
|
|
|
15
16
|
const HEX_REV = /^[0-9a-f]{4,40}$/i;
|
|
16
17
|
const HEAD_REV = /^HEAD([~^].*)?$/i;
|
|
@@ -180,6 +181,8 @@ const runUi = async (session, proc) => {
|
|
|
180
181
|
};
|
|
181
182
|
|
|
182
183
|
const run = async (proc, deps = {}) => {
|
|
184
|
+
const argv = proc.argv.slice(2);
|
|
185
|
+
if (argv[0] === 't') return runCapture(proc, deps);
|
|
183
186
|
const stdout = proc.stdout;
|
|
184
187
|
const stderr = proc.stderr;
|
|
185
188
|
let args;
|
package/lib/deps.js
CHANGED
|
@@ -73,6 +73,46 @@ const pkgHasExportEntry = (pkg) => {
|
|
|
73
73
|
return Object.keys(exported).length > 0;
|
|
74
74
|
};
|
|
75
75
|
|
|
76
|
+
const isTypeKey = (key) => key === 'types' || key === 'typings';
|
|
77
|
+
|
|
78
|
+
const isRuntimeFile = (value) => {
|
|
79
|
+
if (typeof value !== 'string' || !value) return false;
|
|
80
|
+
return !value.endsWith('.d.ts');
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const hasRuntimeExport = (value) => {
|
|
84
|
+
if (isRuntimeFile(value)) return true;
|
|
85
|
+
if (Array.isArray(value)) {
|
|
86
|
+
for (const item of value) {
|
|
87
|
+
if (hasRuntimeExport(item)) return true;
|
|
88
|
+
}
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
if (!value || typeof value !== 'object') return false;
|
|
92
|
+
const keys = Object.keys(value);
|
|
93
|
+
for (const key of keys) {
|
|
94
|
+
if (isTypeKey(key)) continue;
|
|
95
|
+
if (hasRuntimeExport(value[key])) return true;
|
|
96
|
+
}
|
|
97
|
+
return false;
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
const pkgHasBin = (pkg) => {
|
|
101
|
+
if (!pkg || typeof pkg !== 'object') return false;
|
|
102
|
+
const bin = pkg.bin;
|
|
103
|
+
if (typeof bin === 'string') return !!bin;
|
|
104
|
+
if (!bin || typeof bin !== 'object' || Array.isArray(bin)) return false;
|
|
105
|
+
return Object.keys(bin).length > 0;
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const pkgExportsLibrary = (pkg) => {
|
|
109
|
+
if (!pkg || typeof pkg !== 'object') return false;
|
|
110
|
+
if (pkgHasBin(pkg)) return false;
|
|
111
|
+
if (isRuntimeFile(pkg.main)) return true;
|
|
112
|
+
if (isRuntimeFile(pkg.module)) return true;
|
|
113
|
+
return hasRuntimeExport(pkg.exports);
|
|
114
|
+
};
|
|
115
|
+
|
|
76
116
|
const SKIP_WALK = ['node_modules', '.git', '.review', 'coverage', 'dist'];
|
|
77
117
|
|
|
78
118
|
const collectUsedFromManifest = (used, rel) => {
|
|
@@ -156,8 +196,8 @@ const readDepPkg = (root, pkgDir, name) => {
|
|
|
156
196
|
|
|
157
197
|
const depHasExportEntry = (root, pkgDir, name) => {
|
|
158
198
|
const pkg = readDepPkg(root, pkgDir, name);
|
|
159
|
-
if (!pkg) return
|
|
160
|
-
return
|
|
199
|
+
if (!pkg) return false;
|
|
200
|
+
return pkgExportsLibrary(pkg);
|
|
161
201
|
};
|
|
162
202
|
|
|
163
203
|
const collectExportEntries = (root, pkgDir, names) => {
|
|
@@ -349,7 +389,7 @@ const markOutdatedChanges = (changes, outdated) =>
|
|
|
349
389
|
markChanges(changes, outdated, 'outdated');
|
|
350
390
|
|
|
351
391
|
const depExports = (exportEntries, name) => {
|
|
352
|
-
if (!exportEntries || !exportEntries.has(name)) return
|
|
392
|
+
if (!exportEntries || !exportEntries.has(name)) return false;
|
|
353
393
|
return exportEntries.get(name) === true;
|
|
354
394
|
};
|
|
355
395
|
|
package/lib/git-branches.js
CHANGED
|
@@ -33,21 +33,50 @@ const commitMessage = (top, sha) => {
|
|
|
33
33
|
|
|
34
34
|
const lastMessage = (top) => commitMessage(top, 'HEAD');
|
|
35
35
|
|
|
36
|
-
const
|
|
36
|
+
const COMMIT_FIELDS = ['%H', '%h', '%an', '%ae', '%ar', '%ci', '%D', '%s'];
|
|
37
|
+
const COMMIT_FORMAT = `${COMMIT_FIELDS.join('%x1f')}%x1e%B`;
|
|
37
38
|
const COMMIT_LIMIT = 256;
|
|
38
39
|
const UNIT = '\x1f';
|
|
40
|
+
const RECORD = '\x1e';
|
|
41
|
+
const TAG_REF = /^tag: /;
|
|
42
|
+
const REMOTE_HEAD = /\/HEAD(?: -> |$)/;
|
|
43
|
+
|
|
44
|
+
const formatCommitRefs = (raw) => {
|
|
45
|
+
const text = `${raw ?? ''}`.trim();
|
|
46
|
+
if (!text) return '';
|
|
47
|
+
const names = [];
|
|
48
|
+
for (const part of text.split(', ')) {
|
|
49
|
+
const name = part.trim();
|
|
50
|
+
if (!name || TAG_REF.test(name)) continue;
|
|
51
|
+
if (name !== 'HEAD' && REMOTE_HEAD.test(name)) continue;
|
|
52
|
+
names.push(name);
|
|
53
|
+
}
|
|
54
|
+
return names.join(', ');
|
|
55
|
+
};
|
|
39
56
|
|
|
40
|
-
const
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
57
|
+
const parseCommitRecord = (record, index) => {
|
|
58
|
+
const cut = record.indexOf(RECORD);
|
|
59
|
+
if (cut < 0) return null;
|
|
60
|
+
const parts = record.slice(0, cut).split(UNIT);
|
|
61
|
+
if (parts.length < COMMIT_FIELDS.length) return null;
|
|
43
62
|
const sha = (parts[0] ?? '').trim();
|
|
44
63
|
if (!sha) return null;
|
|
64
|
+
const body = record.slice(cut + 1).replace(/\s+$/, '');
|
|
65
|
+
const read = (spec) => {
|
|
66
|
+
const at = COMMIT_FIELDS.indexOf(spec);
|
|
67
|
+
return (parts[at] ?? '').trim();
|
|
68
|
+
};
|
|
69
|
+
const subject = read('%s') || body.split('\n')[0].trim();
|
|
45
70
|
return {
|
|
46
71
|
sha,
|
|
47
|
-
shortSha: (
|
|
48
|
-
author: (
|
|
49
|
-
|
|
50
|
-
|
|
72
|
+
shortSha: read('%h') || sha.slice(0, 7),
|
|
73
|
+
author: read('%an'),
|
|
74
|
+
email: read('%ae'),
|
|
75
|
+
date: read('%ar'),
|
|
76
|
+
when: read('%ci'),
|
|
77
|
+
refs: formatCommitRefs(read('%D')),
|
|
78
|
+
subject,
|
|
79
|
+
body,
|
|
51
80
|
head: index === 0,
|
|
52
81
|
};
|
|
53
82
|
};
|
|
@@ -55,14 +84,15 @@ const parseCommitLine = (line, index) => {
|
|
|
55
84
|
const listCommits = (top) => {
|
|
56
85
|
const args = [
|
|
57
86
|
'log',
|
|
87
|
+
'-z',
|
|
58
88
|
`--format=${COMMIT_FORMAT}`,
|
|
59
89
|
`--max-count=${COMMIT_LIMIT}`,
|
|
60
90
|
];
|
|
61
91
|
const result = requireOk(runGit(args, top), 'log failed');
|
|
62
92
|
const commits = [];
|
|
63
|
-
for (const
|
|
64
|
-
if (!
|
|
65
|
-
const entry =
|
|
93
|
+
for (const record of result.stdout.split('\0')) {
|
|
94
|
+
if (!record) continue;
|
|
95
|
+
const entry = parseCommitRecord(record, commits.length);
|
|
66
96
|
if (entry) commits.push(entry);
|
|
67
97
|
}
|
|
68
98
|
return commits;
|
|
@@ -154,13 +184,15 @@ const commitSubject = (top, sha) => {
|
|
|
154
184
|
};
|
|
155
185
|
|
|
156
186
|
const ancestorWithSubject = (top, sha, want) => {
|
|
157
|
-
const args = ['log',
|
|
187
|
+
const args = ['log', '--format=%H%x1f%s', `${sha}^`];
|
|
158
188
|
const result = runGit(args, top);
|
|
159
189
|
if (result.status !== 0) return '';
|
|
160
190
|
for (const line of result.stdout.split('\n')) {
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
191
|
+
const cut = line.indexOf(UNIT);
|
|
192
|
+
if (cut < 0) continue;
|
|
193
|
+
const subject = line.slice(cut + 1).trim();
|
|
194
|
+
if (subject !== want) continue;
|
|
195
|
+
return line.slice(0, cut).trim();
|
|
164
196
|
}
|
|
165
197
|
return '';
|
|
166
198
|
};
|
package/lib/keys.js
CHANGED
|
@@ -6,7 +6,7 @@ const { ACTIONS, ACTION_IDS, BRANCH_ACTIONS, COMMIT_ACTIONS } = actions;
|
|
|
6
6
|
const { DIFF_DISABLED, FILES_DISABLED, FILES_GIT_DISABLED } = actions;
|
|
7
7
|
const { FILES_HIDDEN, FILES_TODO_DISABLED, TODO_DISABLED, BRANCHES_DISABLED } =
|
|
8
8
|
actions;
|
|
9
|
-
const { COMMITS_DISABLED, UNIT_DISABLED } = actions;
|
|
9
|
+
const { COMMITS_DISABLED, UNIT_DISABLED, NPM_DISABLED } = actions;
|
|
10
10
|
const { disabledActions, diffGitDisabled, branchGitDisabled } = actions;
|
|
11
11
|
const { commitGitDisabled } = actions;
|
|
12
12
|
const { actionFromKey, actionHot, actionLetter, buttonWord } = actions;
|
|
@@ -65,15 +65,20 @@ const parseCsiSeq = (rest) => {
|
|
|
65
65
|
return { params: body.slice(0, i), final: body[i], eat: 2 + i + 1 };
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
+
const ctrlHeld = (modText) => {
|
|
69
|
+
const mod = parseInt(modText, 10);
|
|
70
|
+
return Number.isFinite(mod) && ((mod - 1) & 4) !== 0;
|
|
71
|
+
};
|
|
72
|
+
|
|
68
73
|
const keyFromCsi = (paramsText, final) => {
|
|
69
74
|
const params = paramsText ? paramsText.split(';') : [];
|
|
70
75
|
if (final === '~') return CSI_TILDE[params[0]] ?? null;
|
|
71
76
|
const base = CSI_KEYS[final];
|
|
72
77
|
if (!base) return null;
|
|
73
78
|
if (params.length < 2) return base;
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
if (
|
|
79
|
+
const arrow =
|
|
80
|
+
base === 'left' || base === 'right' || base === 'up' || base === 'down';
|
|
81
|
+
if (ctrlHeld(params[1]) && arrow) return `ctrl-${base}`;
|
|
77
82
|
return base;
|
|
78
83
|
};
|
|
79
84
|
|
|
@@ -193,6 +198,7 @@ module.exports = {
|
|
|
193
198
|
TODO_DISABLED,
|
|
194
199
|
BRANCHES_DISABLED,
|
|
195
200
|
COMMITS_DISABLED,
|
|
201
|
+
NPM_DISABLED,
|
|
196
202
|
UNIT_DISABLED,
|
|
197
203
|
disabledActions,
|
|
198
204
|
diffGitDisabled,
|