reslop 0.2.7 → 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 +17 -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 +43 -68
- package/lib/{files.js → common/files.js} +48 -55
- package/lib/{find.js → common/find.js} +1 -1
- 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 +90 -49
- package/lib/dashboard/npm.js +17 -13
- package/lib/dashboard/tree.js +27 -30
- package/lib/{session → dashboard}/watch.js +36 -31
- package/lib/{workspace.js → dashboard/workspace.js} +23 -38
- 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 -96
- 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 +111 -48
- package/lib/{editor.js → input/editor.js} +7 -8
- package/lib/{keys.js → input/keys.js} +15 -43
- 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/{dash-blocks.js → dashboard/blocks.js} +131 -137
- 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 +9 -14
- package/lib/render/footer.js +383 -0
- package/lib/render/frame.js +123 -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 +16 -11
- package/lib/render/npm.js +322 -196
- package/lib/render/packages.js +9 -13
- package/lib/render/primitives.js +101 -15
- package/lib/render/render.js +7 -11
- package/lib/render/repos.js +13 -25
- package/lib/render/status.js +370 -0
- package/lib/render/status.json +85 -0
- package/lib/render/{dash-table.js → table.js} +13 -60
- package/lib/render/tiles.js +109 -48
- 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 +33 -39
- package/lib/session/change-actions.js +34 -100
- package/lib/session/commit.js +91 -52
- package/lib/session/compose.js +181 -145
- package/lib/session/dashboard.js +23 -22
- package/lib/session/dispatch.js +85 -21
- package/lib/session/files-pane.js +47 -26
- package/lib/session/find.js +16 -29
- package/lib/session/import.js +82 -0
- package/lib/session/items.js +8 -17
- package/lib/session/lifecycle.js +75 -72
- package/lib/session/load.js +2 -4
- package/lib/session/navigation.js +6 -3
- package/lib/session/npm/history.js +249 -0
- package/lib/session/npm/npm.js +815 -0
- package/lib/session/{packages.js → npm/packages.js} +21 -14
- package/lib/session/ops.js +125 -34
- package/lib/session/pointer.js +202 -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} +156 -63
- package/lib/session/tasks.js +320 -65
- 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 +65 -65
- package/lib/session/workspace.js +39 -78
- 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 -545
- package/lib/detect.js +0 -34
- package/lib/diff/edit.js +0 -33
- package/lib/git-worktree.js +0 -455
- package/lib/git.js +0 -79
- package/lib/npm-commands.js +0 -522
- package/lib/render/chrome.js +0 -806
- package/lib/render/present.js +0 -19
- package/lib/render/report.js +0 -43
- package/lib/render/search.js +0 -25
- package/lib/review.js +0 -745
- 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,475 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
|
|
6
|
+
const { runProc, isAbort } = require('../common/process.js');
|
|
7
|
+
const { oneLine } = require('../common/utilities.js');
|
|
8
|
+
const diff = require('../diff/diff.js');
|
|
9
|
+
const { parseDiff, synthesizeNewFile, itemsFromFiles } = diff;
|
|
10
|
+
const files = require('../common/files.js');
|
|
11
|
+
const { groupByPath } = files;
|
|
12
|
+
const { itemPath, isReadOnlyOrigin, lineDelta, REVIEW_DIR } = files;
|
|
13
|
+
const { replaceBlockAdds } = require('../diff/patch.js');
|
|
14
|
+
|
|
15
|
+
const GIT_CONFIG = [
|
|
16
|
+
'-c',
|
|
17
|
+
'core.quotepath=false',
|
|
18
|
+
'-c',
|
|
19
|
+
'i18n.logOutputEncoding=utf-8',
|
|
20
|
+
];
|
|
21
|
+
const DIFF_OPTS = ['--no-color', '--no-ext-diff', '--no-renames', '-U3'];
|
|
22
|
+
const BINARY_SIZE = 1000000;
|
|
23
|
+
|
|
24
|
+
const gitOptions = (cwd, extra) => ({
|
|
25
|
+
cwd,
|
|
26
|
+
env: { ...process.env, GIT_OPTIONAL_LOCKS: '0', ...extra.env },
|
|
27
|
+
input: extra.input,
|
|
28
|
+
signal: extra.signal,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const gitResult = (result) => {
|
|
32
|
+
if (!result.error) return result;
|
|
33
|
+
if (isAbort(result.error)) throw result.error;
|
|
34
|
+
if (result.error.code === 'ENOENT') throw new Error('git not found');
|
|
35
|
+
throw result.error;
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const requireOk = (result, fallback = 'git failed') => {
|
|
39
|
+
if (result.status === 0) return result;
|
|
40
|
+
const msg = result.stderr || result.stdout || fallback;
|
|
41
|
+
throw new Error(oneLine(msg, fallback));
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const runGit = (args, cwd, extra = {}) =>
|
|
45
|
+
runProc('git', args, gitOptions(cwd, extra)).then(gitResult);
|
|
46
|
+
|
|
47
|
+
const command = (args, extra = {}) => ({
|
|
48
|
+
args,
|
|
49
|
+
input: extra.input,
|
|
50
|
+
env: extra.env,
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const runSteps = async (steps, top, signal) => {
|
|
54
|
+
try {
|
|
55
|
+
let step = steps.next();
|
|
56
|
+
while (!step.done) {
|
|
57
|
+
const extra = { ...step.value, signal };
|
|
58
|
+
step = steps.next(await runGit(step.value.args, top, extra));
|
|
59
|
+
}
|
|
60
|
+
return step.value;
|
|
61
|
+
} finally {
|
|
62
|
+
steps.return();
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const gitOp =
|
|
67
|
+
(op) =>
|
|
68
|
+
(top, ...args) =>
|
|
69
|
+
runSteps(op(...args), top);
|
|
70
|
+
|
|
71
|
+
const pathArgs = (paths) => (paths.length ? ['--', ...paths] : []);
|
|
72
|
+
|
|
73
|
+
const diffArgs = (revs, paths) => [
|
|
74
|
+
...GIT_CONFIG,
|
|
75
|
+
'diff',
|
|
76
|
+
...DIFF_OPTS,
|
|
77
|
+
...revs,
|
|
78
|
+
...pathArgs(paths),
|
|
79
|
+
];
|
|
80
|
+
|
|
81
|
+
const commitDiffArgs = (commit, parent, paths) => {
|
|
82
|
+
if (parent.status === 0) {
|
|
83
|
+
return diffArgs([parent.stdout.trim(), commit], paths);
|
|
84
|
+
}
|
|
85
|
+
return [
|
|
86
|
+
...GIT_CONFIG,
|
|
87
|
+
'diff-tree',
|
|
88
|
+
'--no-commit-id',
|
|
89
|
+
'--root',
|
|
90
|
+
'-p',
|
|
91
|
+
...DIFF_OPTS,
|
|
92
|
+
commit,
|
|
93
|
+
...pathArgs(paths),
|
|
94
|
+
];
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const untrackedArgs = (paths) => [
|
|
98
|
+
'ls-files',
|
|
99
|
+
'-o',
|
|
100
|
+
'--exclude-standard',
|
|
101
|
+
'-z',
|
|
102
|
+
...pathArgs(paths),
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
const isReviewFile = (rel) => rel.split(/[/\\]/)[0] === REVIEW_DIR;
|
|
106
|
+
|
|
107
|
+
const isDirEntry = (rel) => rel.endsWith('/');
|
|
108
|
+
|
|
109
|
+
const dropReviewItems = (items) =>
|
|
110
|
+
items.filter((item) => !isReviewFile(itemPath(item)));
|
|
111
|
+
|
|
112
|
+
const isBinaryBuffer = (buf) => buf.length > BINARY_SIZE || buf.includes(0);
|
|
113
|
+
|
|
114
|
+
const namesFromLs = (stdout) => stdout.split('\0').filter(Boolean);
|
|
115
|
+
|
|
116
|
+
const listScopeFiles = async (top, paths = [], options = {}) => {
|
|
117
|
+
const rev = options.commit;
|
|
118
|
+
const scope = pathArgs(paths);
|
|
119
|
+
let args = ['ls-files', '-co', '--exclude-standard', '-z', ...scope];
|
|
120
|
+
if (rev) args = ['ls-tree', '-r', '--name-only', '-z', rev, ...scope];
|
|
121
|
+
const result = requireOk(await runGit(args, top), 'ls-files failed');
|
|
122
|
+
const names = namesFromLs(result.stdout).filter(
|
|
123
|
+
(rel) => !isReviewFile(rel) && !isDirEntry(rel),
|
|
124
|
+
);
|
|
125
|
+
names.sort((left, right) => left.localeCompare(right, 'en'));
|
|
126
|
+
return names;
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const binaryNewFile = (rel) => ({
|
|
130
|
+
oldPath: rel,
|
|
131
|
+
newPath: rel,
|
|
132
|
+
isNew: true,
|
|
133
|
+
isDeleted: false,
|
|
134
|
+
isBinary: true,
|
|
135
|
+
preamble: [`diff --git a/${rel} b/${rel}`, 'new file mode 100644'],
|
|
136
|
+
hunks: [],
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const UNTRACKED_READS = 16;
|
|
140
|
+
|
|
141
|
+
const untrackedNames = (names) => {
|
|
142
|
+
const keep = (rel) => !isReviewFile(rel) && !isDirEntry(rel);
|
|
143
|
+
return names.filter(keep);
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const untrackedFile = (rel, buf) => {
|
|
147
|
+
if (isBinaryBuffer(buf)) return binaryNewFile(rel);
|
|
148
|
+
const text = buf.toString('utf8');
|
|
149
|
+
return synthesizeNewFile(rel, text);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const isGone = (error) => error.code === 'ENOENT' || error.code === 'EISDIR';
|
|
153
|
+
|
|
154
|
+
const untrackedItemsFrom = (top, names) => {
|
|
155
|
+
const files = [];
|
|
156
|
+
for (const rel of untrackedNames(names)) {
|
|
157
|
+
try {
|
|
158
|
+
files.push(untrackedFile(rel, fs.readFileSync(path.join(top, rel))));
|
|
159
|
+
} catch (error) {
|
|
160
|
+
if (!isGone(error)) throw error;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return itemsFromFiles(files, 'untracked');
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const readUntracked = async (top, rel) => {
|
|
167
|
+
try {
|
|
168
|
+
return untrackedFile(rel, await fs.promises.readFile(path.join(top, rel)));
|
|
169
|
+
} catch (error) {
|
|
170
|
+
if (isGone(error)) return null;
|
|
171
|
+
throw error;
|
|
172
|
+
}
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
const untrackedItems = async (top, names) => {
|
|
176
|
+
const rels = untrackedNames(names);
|
|
177
|
+
const files = [];
|
|
178
|
+
for (let at = 0; at < rels.length; at += UNTRACKED_READS) {
|
|
179
|
+
const batch = rels.slice(at, at + UNTRACKED_READS);
|
|
180
|
+
const read = await Promise.all(batch.map((rel) => readUntracked(top, rel)));
|
|
181
|
+
for (const file of read) if (file) files.push(file);
|
|
182
|
+
}
|
|
183
|
+
return itemsFromFiles(files, 'untracked');
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
const countUntrackedAdded = async (top) => {
|
|
187
|
+
try {
|
|
188
|
+
const result = await runGit(untrackedArgs([]), top);
|
|
189
|
+
if (result.status !== 0) return 0;
|
|
190
|
+
const items = untrackedItemsFrom(top, namesFromLs(result.stdout));
|
|
191
|
+
let added = 0;
|
|
192
|
+
for (const item of items) added += lineDelta(item).added;
|
|
193
|
+
return added;
|
|
194
|
+
} catch {
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
const gitText = async (top, spec) => {
|
|
200
|
+
const result = await runGit(['show', spec], top);
|
|
201
|
+
return result.status === 0 ? result.stdout : '';
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const worktreeText = async (top, rel) => {
|
|
205
|
+
try {
|
|
206
|
+
return await fs.promises.readFile(path.join(top, rel), 'utf8');
|
|
207
|
+
} catch {
|
|
208
|
+
return '';
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
const indexedText = async (top, rel) => {
|
|
213
|
+
const staged = await gitText(top, `:${rel}`);
|
|
214
|
+
return staged || gitText(top, `HEAD:${rel}`);
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const fileText = async (top, rel, rev) => {
|
|
218
|
+
if (!rel) return '';
|
|
219
|
+
if (rev) return gitText(top, `${rev}:${rel}`);
|
|
220
|
+
return worktreeText(top, rel);
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
const DEP_SIDES = {
|
|
224
|
+
untracked: (top, rel) => ['', worktreeText(top, rel)],
|
|
225
|
+
staged: (top, rel) => [gitText(top, `HEAD:${rel}`), gitText(top, `:${rel}`)],
|
|
226
|
+
unstaged: (top, rel) => [indexedText(top, rel), worktreeText(top, rel)],
|
|
227
|
+
commit: (top, rel, rev) => [
|
|
228
|
+
gitText(top, `${rev}^:${rel}`),
|
|
229
|
+
gitText(top, `${rev}:${rel}`),
|
|
230
|
+
],
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const readDepSides = async (top, rev, origin, rel) => {
|
|
234
|
+
const known = Object.hasOwn(DEP_SIDES, origin);
|
|
235
|
+
if (!known || (origin === 'commit' && !rev)) {
|
|
236
|
+
return { oldText: '', newText: '' };
|
|
237
|
+
}
|
|
238
|
+
const reads = DEP_SIDES[origin](top, rel, rev);
|
|
239
|
+
const [oldText, newText] = await Promise.all(reads);
|
|
240
|
+
return { oldText, newText };
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const toplevel = async (cwd, signal) => {
|
|
244
|
+
const args = ['rev-parse', '--show-toplevel'];
|
|
245
|
+
const result = await runGit(args, cwd, { signal });
|
|
246
|
+
if (result.status !== 0) throw new Error('not a git repository');
|
|
247
|
+
return result.stdout.trim();
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
const resolveRev = async (cwd, spec) => {
|
|
251
|
+
if (!spec) return null;
|
|
252
|
+
const args = ['rev-parse', '--verify', `${spec}^{commit}`];
|
|
253
|
+
const result = await runGit(args, await toplevel(cwd));
|
|
254
|
+
return result.status === 0 ? result.stdout.trim() : null;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const shortRev = async (top, commit) => {
|
|
258
|
+
const result = await runGit(['rev-parse', '--short', commit], top);
|
|
259
|
+
return result.status === 0 ? result.stdout.trim() : commit.slice(0, 7);
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const branchName = (result) => {
|
|
263
|
+
if (result.status !== 0) return '';
|
|
264
|
+
const name = result.stdout.trim();
|
|
265
|
+
return name === 'HEAD' ? '' : name;
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const BRANCH_ARGS = ['rev-parse', '--abbrev-ref', 'HEAD'];
|
|
269
|
+
|
|
270
|
+
const currentBranch = async (top) => branchName(await runGit(BRANCH_ARGS, top));
|
|
271
|
+
|
|
272
|
+
const worktreeSnapshot = (top, outputs, untracked, branch) => {
|
|
273
|
+
const staged = itemsFromFiles(parseDiff(outputs[0]), 'staged');
|
|
274
|
+
const unstaged = itemsFromFiles(parseDiff(outputs[1]), 'unstaged');
|
|
275
|
+
const grouped = groupByPath([...staged, ...unstaged, ...untracked]);
|
|
276
|
+
const parsed = dropReviewItems([...grouped.values()].flat());
|
|
277
|
+
return { top, parsed, branch };
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const snapshotCommit = async (top, commit, files) => {
|
|
281
|
+
const parsed = dropReviewItems(itemsFromFiles(files, 'commit'));
|
|
282
|
+
const names = [shortRev(top, commit), currentBranch(top)];
|
|
283
|
+
const [revShort, branch] = await Promise.all(names);
|
|
284
|
+
return { top, parsed, rev: commit, revShort, branch };
|
|
285
|
+
};
|
|
286
|
+
|
|
287
|
+
function* commitDiff(commit, paths) {
|
|
288
|
+
const parent = yield command(['rev-parse', '--verify', `${commit}^`]);
|
|
289
|
+
const result = yield command(commitDiffArgs(commit, parent, paths));
|
|
290
|
+
return parseDiff(requireOk(result).stdout);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
const worktreeCommands = (paths) => [
|
|
294
|
+
diffArgs(['--cached'], paths),
|
|
295
|
+
diffArgs([], paths),
|
|
296
|
+
untrackedArgs(paths),
|
|
297
|
+
];
|
|
298
|
+
|
|
299
|
+
const readSnapshot = async (cwd, paths, options) => {
|
|
300
|
+
const { commit: rev, signal } = options;
|
|
301
|
+
const top = await toplevel(cwd, signal);
|
|
302
|
+
if (rev) {
|
|
303
|
+
const files = await runSteps(commitDiff(rev, paths), top, signal);
|
|
304
|
+
return snapshotCommit(top, rev, files);
|
|
305
|
+
}
|
|
306
|
+
const commands = [...worktreeCommands(paths), BRANCH_ARGS];
|
|
307
|
+
const running = commands.map((args) => runGit(args, top, { signal }));
|
|
308
|
+
const results = await Promise.all(running);
|
|
309
|
+
const branch = branchName(results.pop());
|
|
310
|
+
const outputs = results.map((result) => requireOk(result).stdout);
|
|
311
|
+
const names = namesFromLs(outputs[2]);
|
|
312
|
+
const untracked = await untrackedItems(top, names);
|
|
313
|
+
return worktreeSnapshot(top, outputs, untracked, branch);
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
const applyPatch = async (cwd, patch, flags) => {
|
|
317
|
+
const args = ['apply', ...flags, '--whitespace=nowarn', '-'];
|
|
318
|
+
const result = await runGit(args, cwd, { input: patch });
|
|
319
|
+
requireOk(result, 'apply failed');
|
|
320
|
+
};
|
|
321
|
+
|
|
322
|
+
const gitOk = async (args, top) => requireOk(await runGit(args, top));
|
|
323
|
+
|
|
324
|
+
const addItem = async (top, item) => {
|
|
325
|
+
if (item.origin === 'staged' || item.origin === 'commit') return;
|
|
326
|
+
if (item.origin === 'untracked' || item.file.isBinary) {
|
|
327
|
+
await gitOk(['add', '--', item.file.newPath], top);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
await applyPatch(top, item.patchAdd, ['--cached']);
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const unstageItem = async (top, item) => {
|
|
334
|
+
if (item.origin !== 'staged') return;
|
|
335
|
+
if (item.file.isBinary) {
|
|
336
|
+
await gitOk(['restore', '--staged', '--', itemPath(item)], top);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
await applyPatch(top, item.patchAdd, ['--reverse', '--cached']);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const revertOnePath = async (top, rel, items) => {
|
|
343
|
+
const origins = new Set(items.map((item) => item.origin));
|
|
344
|
+
const isNew = items.some((item) => item.file?.isNew);
|
|
345
|
+
if (isNew && origins.has('staged')) {
|
|
346
|
+
await gitOk(['rm', '-f', '--', rel], top);
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
const onlyUntracked = origins.size === 1 && origins.has('untracked');
|
|
350
|
+
if (isNew || onlyUntracked) return void fs.unlinkSync(path.join(top, rel));
|
|
351
|
+
const args = ['restore', '-s', 'HEAD', '--worktree', '--staged', '--', rel];
|
|
352
|
+
await gitOk(args, top);
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const revertItem = async (top, item) => {
|
|
356
|
+
const rel = itemPath(item);
|
|
357
|
+
if (item.origin === 'untracked') {
|
|
358
|
+
return void fs.unlinkSync(path.join(top, rel));
|
|
359
|
+
}
|
|
360
|
+
if (item.file.isBinary) {
|
|
361
|
+
const args = ['restore', '-s', 'HEAD', '--worktree'];
|
|
362
|
+
if (item.origin === 'staged') args.push('--staged');
|
|
363
|
+
await gitOk([...args, '--', rel], top);
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
if (item.origin === 'unstaged') {
|
|
367
|
+
await applyPatch(top, item.patchRevert, ['--reverse']);
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
await applyPatch(top, item.patchRevert, ['--reverse', '--cached']);
|
|
371
|
+
try {
|
|
372
|
+
await applyPatch(top, item.patchRevert, ['--reverse']);
|
|
373
|
+
} catch (error) {
|
|
374
|
+
if (!error.message.includes('does not apply')) throw error;
|
|
375
|
+
}
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
const isExecutable = async (abs) => {
|
|
379
|
+
try {
|
|
380
|
+
const stat = await fs.promises.stat(abs);
|
|
381
|
+
return (stat.mode & 0o111) !== 0;
|
|
382
|
+
} catch (error) {
|
|
383
|
+
if (error.code !== 'ENOENT') throw error;
|
|
384
|
+
return false;
|
|
385
|
+
}
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
const indexMode = async (top, rel) => {
|
|
389
|
+
const result = await gitOk(['ls-files', '--stage', '--', rel], top);
|
|
390
|
+
const line = result.stdout.trim();
|
|
391
|
+
if (line) return line.split(' ')[0];
|
|
392
|
+
const executable = await isExecutable(path.join(top, rel));
|
|
393
|
+
return executable ? '100755' : '100644';
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
const writeIndex = async (top, rel, text) => {
|
|
397
|
+
const hashArgs = ['hash-object', '-w', `--path=${rel}`, '--stdin'];
|
|
398
|
+
const hashed = await runGit(hashArgs, top, { input: text });
|
|
399
|
+
const sha = requireOk(hashed, 'hash-object failed').stdout.trim();
|
|
400
|
+
const mode = await indexMode(top, rel);
|
|
401
|
+
const args = ['update-index', '--add', '--cacheinfo', mode, sha, rel];
|
|
402
|
+
requireOk(await runGit(args, top), 'update-index failed');
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
const readIfPresent = async (abs) => {
|
|
406
|
+
try {
|
|
407
|
+
return await fs.promises.readFile(abs, 'utf8');
|
|
408
|
+
} catch (error) {
|
|
409
|
+
if (error.code !== 'ENOENT') throw error;
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
const editStaged = async (top, rel, abs, item, text) => {
|
|
415
|
+
const shown = await runGit(['show', `:${rel}`], top);
|
|
416
|
+
const source = requireOk(shown, 'show failed').stdout;
|
|
417
|
+
const next = replaceBlockAdds(source, item.hunk, item.blockId, text);
|
|
418
|
+
await writeIndex(top, rel, next);
|
|
419
|
+
const work = await readIfPresent(abs);
|
|
420
|
+
if (work === null || work === source) await fs.promises.writeFile(abs, next);
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
const editWorktree = async (abs, item, text) => {
|
|
424
|
+
const source = await fs.promises.readFile(abs, 'utf8');
|
|
425
|
+
const next = replaceBlockAdds(source, item.hunk, item.blockId, text);
|
|
426
|
+
if (next !== source) await fs.promises.writeFile(abs, next);
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
const editItem = async (top, item, text) => {
|
|
430
|
+
if (isReadOnlyOrigin(item.origin)) return;
|
|
431
|
+
const rel = itemPath(item);
|
|
432
|
+
if (!rel || !item.hunk) return;
|
|
433
|
+
const abs = path.join(top, rel);
|
|
434
|
+
if (item.origin === 'staged') await editStaged(top, rel, abs, item, text);
|
|
435
|
+
else await editWorktree(abs, item, text);
|
|
436
|
+
};
|
|
437
|
+
|
|
438
|
+
const writeFile = async (top, rel, text) => {
|
|
439
|
+
if (!rel) return;
|
|
440
|
+
const abs = path.join(top, rel);
|
|
441
|
+
await fs.promises.mkdir(path.dirname(abs), { recursive: true });
|
|
442
|
+
await fs.promises.writeFile(abs, text);
|
|
443
|
+
};
|
|
444
|
+
|
|
445
|
+
const stagePath = async (top, rel) => {
|
|
446
|
+
if (rel) await gitOk(['add', '--', rel], top);
|
|
447
|
+
};
|
|
448
|
+
|
|
449
|
+
module.exports = {
|
|
450
|
+
requireOk,
|
|
451
|
+
gitOk,
|
|
452
|
+
runGit,
|
|
453
|
+
command,
|
|
454
|
+
gitOp,
|
|
455
|
+
groupByPath,
|
|
456
|
+
gitText,
|
|
457
|
+
indexedText,
|
|
458
|
+
worktreeText,
|
|
459
|
+
readDepSides,
|
|
460
|
+
revertOnePath,
|
|
461
|
+
toplevel,
|
|
462
|
+
resolveRev,
|
|
463
|
+
currentBranch,
|
|
464
|
+
fileText,
|
|
465
|
+
countUntrackedAdded,
|
|
466
|
+
listScopeFiles,
|
|
467
|
+
readSnapshot,
|
|
468
|
+
editItem,
|
|
469
|
+
writeFile,
|
|
470
|
+
writeIndex,
|
|
471
|
+
stagePath,
|
|
472
|
+
addItem,
|
|
473
|
+
unstageItem,
|
|
474
|
+
revertItem,
|
|
475
|
+
};
|
package/lib/highlight/bash.js
CHANGED
package/lib/highlight/csv.js
CHANGED
package/lib/highlight/dot.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const {
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { fileExt } = require('metautil');
|
|
5
|
+
const { tokensText, words } = require('./core.js');
|
|
5
6
|
|
|
6
7
|
const PLUGIN_NAMES = words('bash css csv dart dot html js json log txt');
|
|
7
8
|
|
|
@@ -9,7 +10,7 @@ const HIGHLIGHTERS = Object.create(null);
|
|
|
9
10
|
const LANG = Object.create(null);
|
|
10
11
|
|
|
11
12
|
for (const name of PLUGIN_NAMES) {
|
|
12
|
-
const plugin = require(
|
|
13
|
+
const plugin = require(`./${name}.js`);
|
|
13
14
|
for (const id of plugin.langs) {
|
|
14
15
|
HIGHLIGHTERS[id] = plugin.highlight;
|
|
15
16
|
LANG[id] = id;
|
|
@@ -42,12 +43,11 @@ const overlayTokens = (tokens, spans) => {
|
|
|
42
43
|
const spanText = span.text ?? '';
|
|
43
44
|
const n = Math.min(tokenText.length - tokenOff, spanText.length - spanOff);
|
|
44
45
|
if (n > 0) {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
});
|
|
46
|
+
const text = tokenText.slice(tokenOff, tokenOff + n);
|
|
47
|
+
const named = typeof token.style === 'string' && token.style;
|
|
48
|
+
const style = named || 'plain';
|
|
49
|
+
const changed = span.changed === true;
|
|
50
|
+
pieces.push({ text, style, changed });
|
|
51
51
|
}
|
|
52
52
|
tokenOff += n;
|
|
53
53
|
spanOff += n;
|
|
@@ -64,11 +64,42 @@ const overlayTokens = (tokens, spans) => {
|
|
|
64
64
|
const span = diff[spanIndex];
|
|
65
65
|
const text = (span.text ?? '').slice(spanOff);
|
|
66
66
|
if (text) {
|
|
67
|
-
|
|
67
|
+
const style = 'plain';
|
|
68
|
+
const changed = span.changed === true;
|
|
69
|
+
pieces.push({ text, style, changed });
|
|
68
70
|
}
|
|
69
71
|
spanOff = 0;
|
|
70
72
|
}
|
|
71
73
|
return pieces;
|
|
72
74
|
};
|
|
73
75
|
|
|
74
|
-
|
|
76
|
+
const PROSE_BASENAMES = [
|
|
77
|
+
'license',
|
|
78
|
+
'licence',
|
|
79
|
+
'copying',
|
|
80
|
+
'authors',
|
|
81
|
+
'notice',
|
|
82
|
+
'copyright',
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const isProse = (base) => {
|
|
86
|
+
const stem = base.replace(/\.(txt|text|md|markdown)$/, '');
|
|
87
|
+
return PROSE_BASENAMES.includes(stem);
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
const detectLang = (filePath) => {
|
|
91
|
+
const base = path.basename(filePath || '').toLowerCase();
|
|
92
|
+
if (base.endsWith('.d.ts')) return 'dts';
|
|
93
|
+
if (isProse(base)) return 'md';
|
|
94
|
+
if (base.endsWith('.log')) return 'log';
|
|
95
|
+
if (base.startsWith('.') || !base.includes('.')) return 'dot';
|
|
96
|
+
const ext = fileExt(base);
|
|
97
|
+
return ext ? mapLang(ext) : 'dot';
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
module.exports = {
|
|
101
|
+
tokensText,
|
|
102
|
+
tokenize,
|
|
103
|
+
overlayTokens,
|
|
104
|
+
detectLang,
|
|
105
|
+
};
|
package/lib/highlight/html.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const { QUOTES, emit, scanWhile, quotedEnd } = core;
|
|
3
|
+
const { QUOTES, emit, scanWhile, quotedEnd } = require('./core.js');
|
|
5
4
|
|
|
6
5
|
const colorHtmlTag = (tag) => {
|
|
7
6
|
const out = [];
|
|
@@ -40,26 +39,29 @@ const tagEnd = (source, i) => {
|
|
|
40
39
|
return j < n ? j + 1 : j;
|
|
41
40
|
};
|
|
42
41
|
|
|
42
|
+
const paintSpan = (source, i, n, out) => {
|
|
43
|
+
if (source.startsWith('<!--', i)) {
|
|
44
|
+
const close = source.indexOf('-->', i + 4);
|
|
45
|
+
const end = close < 0 ? n : close + 3;
|
|
46
|
+
emit(out, 'comment', source.slice(i, end));
|
|
47
|
+
return end;
|
|
48
|
+
}
|
|
49
|
+
if (source[i] === '<') {
|
|
50
|
+
const end = tagEnd(source, i);
|
|
51
|
+
for (const token of colorHtmlTag(source.slice(i, end))) out.push(token);
|
|
52
|
+
return end;
|
|
53
|
+
}
|
|
54
|
+
const next = source.indexOf('<', i + 1);
|
|
55
|
+
const end = next < 0 ? n : next;
|
|
56
|
+
emit(out, 'plain', source.slice(i, end));
|
|
57
|
+
return end;
|
|
58
|
+
};
|
|
59
|
+
|
|
43
60
|
const highlight = (source) => {
|
|
44
61
|
const out = [];
|
|
45
62
|
const n = source.length;
|
|
46
63
|
let i = 0;
|
|
47
|
-
while (i < n)
|
|
48
|
-
let end;
|
|
49
|
-
if (source.startsWith('<!--', i)) {
|
|
50
|
-
const close = source.indexOf('-->', i + 4);
|
|
51
|
-
end = close < 0 ? n : close + 3;
|
|
52
|
-
emit(out, 'comment', source.slice(i, end));
|
|
53
|
-
} else if (source[i] === '<') {
|
|
54
|
-
end = tagEnd(source, i);
|
|
55
|
-
for (const token of colorHtmlTag(source.slice(i, end))) out.push(token);
|
|
56
|
-
} else {
|
|
57
|
-
const next = source.indexOf('<', i + 1);
|
|
58
|
-
end = next < 0 ? n : next;
|
|
59
|
-
emit(out, 'plain', source.slice(i, end));
|
|
60
|
-
}
|
|
61
|
-
i = end;
|
|
62
|
-
}
|
|
64
|
+
while (i < n) i = paintSpan(source, i, n, out);
|
|
63
65
|
return out;
|
|
64
66
|
};
|
|
65
67
|
|
package/lib/highlight/json.js
CHANGED
package/lib/highlight/log.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const
|
|
4
|
-
const { emit, highlightLines, wordStyles } = core;
|
|
3
|
+
const { emit, highlightLines, wordStyles } = require('./core.js');
|
|
5
4
|
|
|
6
5
|
const LOG_DATETIME_RE = new RegExp(
|
|
7
6
|
'^(\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}' +
|