opencode-git-plugin 0.2.1 → 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/README.md +3 -1
- package/dist/tui.js +447 -152
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -82,6 +82,7 @@ Do not add the plugin to `opencode.json(c)`; that file is for server plugins and
|
|
|
82
82
|
| Option | Type | Default | Description |
|
|
83
83
|
| --- | --- | --- | --- |
|
|
84
84
|
| `intervalMs` | `number` | `4000` | Refresh interval in milliseconds; values below `1000` are clamped. |
|
|
85
|
+
| `folderTree` | `string` | `"none"` | File list style: `"none"` (full paths), `"comfortable"` (indented tree, 2 cols/level), `"compact"` (tree, 1 col/level), or `"minimal"` (plain indentation). |
|
|
85
86
|
|
|
86
87
|
## Usage
|
|
87
88
|
|
|
@@ -92,6 +93,7 @@ Do not add the plugin to `opencode.json(c)`; that file is for server plugins and
|
|
|
92
93
|
- Click a row to open its diff; click the branch name to enter stage mode (every clicked file toggles staged/unstaged; branch click or `ESC` exits)
|
|
93
94
|
- Clicking the prompt input exits list mode so typing is never hijacked
|
|
94
95
|
- Staging also via palette (`Git Panel: Stage/Unstage File`)
|
|
96
|
+
- With `folderTree` enabled, clicking a folder collapses/expands it; in stage mode, clicking a folder stages/unstages everything inside it (unstages only when all of its files are already staged)
|
|
95
97
|
|
|
96
98
|
### Diff panel
|
|
97
99
|
|
|
@@ -110,7 +112,7 @@ Do not add the plugin to `opencode.json(c)`; that file is for server plugins and
|
|
|
110
112
|
|
|
111
113
|
- `Loading…` while fetching
|
|
112
114
|
- `Working tree clean` when there is nothing to show (header stats hidden)
|
|
113
|
-
- `Empty repo — no commits yet` before the first commit
|
|
115
|
+
- `Empty repo — no commits yet` before the first commit when nothing is staged or untracked (files still list normally once they exist)
|
|
114
116
|
- `Not a git repository` outside a repo, plus raw fetch errors
|
|
115
117
|
|
|
116
118
|
## Development
|
package/dist/tui.js
CHANGED
|
@@ -24,6 +24,11 @@ function intervalMs(ctx) {
|
|
|
24
24
|
return DEFAULT_INTERVAL_MS;
|
|
25
25
|
return Math.max(MIN_INTERVAL_MS, Math.floor(raw));
|
|
26
26
|
}
|
|
27
|
+
var TREE_STYLES = ["none", "comfortable", "compact", "minimal"];
|
|
28
|
+
function folderTree(ctx) {
|
|
29
|
+
const raw = ctx.options.folderTree;
|
|
30
|
+
return TREE_STYLES.includes(raw) ? raw : "none";
|
|
31
|
+
}
|
|
27
32
|
// src/git/diff-parse.ts
|
|
28
33
|
var DIFF_PREAMBLE_RE = /^(diff --git |index |new file mode|deleted file mode|similarity index|rename from|rename to|old mode|new mode)/;
|
|
29
34
|
var HUNK_HEADER_RE = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@ ?(.*)$/;
|
|
@@ -59,17 +64,95 @@ function parseDiff(patch) {
|
|
|
59
64
|
return out;
|
|
60
65
|
}
|
|
61
66
|
// src/git/porcelain.ts
|
|
62
|
-
|
|
63
|
-
|
|
67
|
+
var NO_COUNTS = { additions: 0, deletions: 0 };
|
|
68
|
+
function parsePorcelainRecords(porcelain) {
|
|
69
|
+
const out = [];
|
|
64
70
|
for (const record of porcelain.split("\x00")) {
|
|
65
71
|
if (record.length < 4)
|
|
66
72
|
continue;
|
|
67
|
-
|
|
68
|
-
|
|
73
|
+
out.push({ x: record[0], y: record[1], file: record.slice(3) });
|
|
74
|
+
}
|
|
75
|
+
return out;
|
|
76
|
+
}
|
|
77
|
+
var isStaged = (r) => r.x !== " " && r.x !== "?";
|
|
78
|
+
function parseStagedPorcelain(porcelain) {
|
|
79
|
+
return new Set(parsePorcelainRecords(porcelain).filter(isStaged).map((r) => r.file));
|
|
80
|
+
}
|
|
81
|
+
function porcelainChangeStatus(x, y) {
|
|
82
|
+
if (x === "?" || y === "?")
|
|
83
|
+
return "added";
|
|
84
|
+
if (x === "D" || y === "D")
|
|
85
|
+
return "deleted";
|
|
86
|
+
if (x === "A")
|
|
87
|
+
return "added";
|
|
88
|
+
return "modified";
|
|
89
|
+
}
|
|
90
|
+
function parseNumstat(output) {
|
|
91
|
+
const counts = new Map;
|
|
92
|
+
for (const record of output.split("\x00")) {
|
|
93
|
+
if (!record)
|
|
69
94
|
continue;
|
|
70
|
-
|
|
95
|
+
const firstTab = record.indexOf("\t");
|
|
96
|
+
if (firstTab < 0)
|
|
97
|
+
continue;
|
|
98
|
+
const secondTab = record.indexOf("\t", firstTab + 1);
|
|
99
|
+
if (secondTab < 0)
|
|
100
|
+
continue;
|
|
101
|
+
const additions = Number(record.slice(0, firstTab));
|
|
102
|
+
const deletions = Number(record.slice(firstTab + 1, secondTab));
|
|
103
|
+
const file = record.slice(secondTab + 1);
|
|
104
|
+
if (!file)
|
|
105
|
+
continue;
|
|
106
|
+
counts.set(file, {
|
|
107
|
+
additions: Number.isFinite(additions) && additions >= 0 ? additions : 0,
|
|
108
|
+
deletions: Number.isFinite(deletions) && deletions >= 0 ? deletions : 0
|
|
109
|
+
});
|
|
71
110
|
}
|
|
72
|
-
return
|
|
111
|
+
return counts;
|
|
112
|
+
}
|
|
113
|
+
var countsFor = (counts, file) => counts.get(file) ?? NO_COUNTS;
|
|
114
|
+
function buildFallbackFiles(porcelain, cachedNumstat, worktreeNumstat) {
|
|
115
|
+
const cached = parseNumstat(cachedNumstat);
|
|
116
|
+
const worktree = parseNumstat(worktreeNumstat);
|
|
117
|
+
return parsePorcelainRecords(porcelain).map((r) => {
|
|
118
|
+
const c = isStaged(r) ? countsFor(cached, r.file) : NO_COUNTS;
|
|
119
|
+
const w = countsFor(worktree, r.file);
|
|
120
|
+
return {
|
|
121
|
+
file: r.file,
|
|
122
|
+
status: porcelainChangeStatus(r.x, r.y),
|
|
123
|
+
additions: c.additions + w.additions,
|
|
124
|
+
deletions: c.deletions + w.deletions
|
|
125
|
+
};
|
|
126
|
+
}).sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : 0);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// src/git/fallback.ts
|
|
130
|
+
function cleanBranchName(output) {
|
|
131
|
+
const name = output.split(`
|
|
132
|
+
`, 1)[0]?.trim() ?? "";
|
|
133
|
+
return name === "" || name === "HEAD" ? "" : name;
|
|
134
|
+
}
|
|
135
|
+
async function fetchWorkingCopyFallback(git) {
|
|
136
|
+
const statusRes = await git.run("status --porcelain=v1 -z -uall --no-renames");
|
|
137
|
+
if (statusRes.exit !== 0)
|
|
138
|
+
throw new Error("Not a git repository");
|
|
139
|
+
const [cachedRes, worktreeRes, headRes, symRes] = await Promise.all([
|
|
140
|
+
git.run("diff --cached --numstat -z --no-renames"),
|
|
141
|
+
git.run("diff --numstat -z --no-renames"),
|
|
142
|
+
git.run("rev-parse --verify HEAD --"),
|
|
143
|
+
git.run("symbolic-ref --short HEAD")
|
|
144
|
+
]);
|
|
145
|
+
let branch = symRes.exit === 0 ? cleanBranchName(symRes.output) : "";
|
|
146
|
+
if (!branch) {
|
|
147
|
+
const current = await git.run("branch --show-current");
|
|
148
|
+
if (current.exit === 0)
|
|
149
|
+
branch = cleanBranchName(current.output);
|
|
150
|
+
}
|
|
151
|
+
return {
|
|
152
|
+
files: buildFallbackFiles(statusRes.output, cachedRes.output, worktreeRes.output),
|
|
153
|
+
branch,
|
|
154
|
+
hasHead: headRes.exit === 0
|
|
155
|
+
};
|
|
73
156
|
}
|
|
74
157
|
// src/git/runner.ts
|
|
75
158
|
function shellQuote(s) {
|
|
@@ -261,8 +344,91 @@ function syntaxFiletype(file) {
|
|
|
261
344
|
}
|
|
262
345
|
return LANGUAGE_BY_EXTENSION[file.slice(dot).toLowerCase()];
|
|
263
346
|
}
|
|
347
|
+
// src/ui/tree.ts
|
|
348
|
+
var PREFIXES = {
|
|
349
|
+
comfortable: { branch: "\u251C ", lastBranch: "\u2514 ", guide: "\u2502 ", emptyGuide: " ", indent: "" },
|
|
350
|
+
compact: { branch: "\u251C", lastBranch: "\u2514", guide: "\u2502", emptyGuide: " ", indent: "" },
|
|
351
|
+
minimal: { branch: "", lastBranch: "", guide: "", emptyGuide: "", indent: " " }
|
|
352
|
+
};
|
|
353
|
+
var compareNames = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
354
|
+
var baseName = (path) => path.slice(path.lastIndexOf("/") + 1);
|
|
355
|
+
var newNode = () => ({ dirs: new Map, files: [] });
|
|
356
|
+
function buildTree(files) {
|
|
357
|
+
const root = newNode();
|
|
358
|
+
for (const f of files) {
|
|
359
|
+
let node = root;
|
|
360
|
+
for (const part of f.file.split("/").slice(0, -1)) {
|
|
361
|
+
let next = node.dirs.get(part);
|
|
362
|
+
if (!next) {
|
|
363
|
+
next = newNode();
|
|
364
|
+
node.dirs.set(part, next);
|
|
365
|
+
}
|
|
366
|
+
node = next;
|
|
367
|
+
}
|
|
368
|
+
node.files.push(f);
|
|
369
|
+
}
|
|
370
|
+
return root;
|
|
371
|
+
}
|
|
372
|
+
function buildFileTree(files, style = "comfortable", collapsed = new Set) {
|
|
373
|
+
const rows = [];
|
|
374
|
+
const p = PREFIXES[style];
|
|
375
|
+
const prefixOf = (depth, guides, last) => {
|
|
376
|
+
if (depth === 0)
|
|
377
|
+
return "";
|
|
378
|
+
if (style === "minimal")
|
|
379
|
+
return p.indent.repeat(depth);
|
|
380
|
+
return guides.map((g) => g ? p.guide : p.emptyGuide).join("") + (last ? p.lastBranch : p.branch);
|
|
381
|
+
};
|
|
382
|
+
const render = (node, depth, guides, basePath) => {
|
|
383
|
+
const dirNames = [...node.dirs.keys()].sort(compareNames);
|
|
384
|
+
const sortedFiles = [...node.files].sort((a, b) => compareNames(baseName(a.file), baseName(b.file)));
|
|
385
|
+
const total = dirNames.length + sortedFiles.length;
|
|
386
|
+
dirNames.forEach((name, i) => {
|
|
387
|
+
const last = i === total - 1;
|
|
388
|
+
let label = `${name}/`;
|
|
389
|
+
let fullPath = `${basePath}${name}/`;
|
|
390
|
+
let child = node.dirs.get(name);
|
|
391
|
+
while (child && child.dirs.size === 1 && child.files.length === 0) {
|
|
392
|
+
const [[nextName, next]] = child.dirs;
|
|
393
|
+
label += `${nextName}/`;
|
|
394
|
+
fullPath += `${nextName}/`;
|
|
395
|
+
child = next;
|
|
396
|
+
}
|
|
397
|
+
rows.push({ kind: "dir", prefix: prefixOf(depth, guides, last), name: label, path: fullPath });
|
|
398
|
+
if (child && !collapsed.has(fullPath))
|
|
399
|
+
render(child, depth + 1, [...guides, !last], fullPath);
|
|
400
|
+
});
|
|
401
|
+
sortedFiles.forEach((f, j) => {
|
|
402
|
+
const last = dirNames.length + j === total - 1;
|
|
403
|
+
rows.push({ kind: "file", prefix: prefixOf(depth, guides, last), name: baseName(f.file), file: f });
|
|
404
|
+
});
|
|
405
|
+
};
|
|
406
|
+
render(buildTree(files), 0, [], "");
|
|
407
|
+
return rows;
|
|
408
|
+
}
|
|
264
409
|
// src/diff/open-diff.ts
|
|
265
|
-
function
|
|
410
|
+
async function gitFallbackPatch(git, file) {
|
|
411
|
+
const q = shellQuote(file);
|
|
412
|
+
return git.enqueue(async () => {
|
|
413
|
+
const cached = await git.run(`diff --cached --no-renames -U3 -- ${q}`);
|
|
414
|
+
const worktree = await git.run(`diff --no-renames -U3 -- ${q}`);
|
|
415
|
+
const combined = [cached.output, worktree.output].filter((s) => s.trim() !== "").join(`
|
|
416
|
+
`);
|
|
417
|
+
if (combined.trim() !== "")
|
|
418
|
+
return combined;
|
|
419
|
+
const untracked = await git.run(`diff --no-index --no-renames -U3 -- /dev/null ${q}`);
|
|
420
|
+
return untracked.output.trim() !== "" ? untracked.output : undefined;
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
async function vcsPatch(ctx, location, file) {
|
|
424
|
+
try {
|
|
425
|
+
const res = await ctx.client.vcs.diff({ location, mode: "working", context: 3 });
|
|
426
|
+
return res.data.find((d) => d.file === file)?.patch;
|
|
427
|
+
} catch {
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
function createDiffOpener(ctx, location, git) {
|
|
266
432
|
const [diffView, setDiffState] = useDiffStore(ctx);
|
|
267
433
|
let activeRequestId = 0;
|
|
268
434
|
return async (f) => {
|
|
@@ -283,15 +449,21 @@ function createDiffOpener(ctx, location) {
|
|
|
283
449
|
});
|
|
284
450
|
ctx.ui.panel.open(DIFF_PANEL_NAME, { presentation: "panel" });
|
|
285
451
|
try {
|
|
286
|
-
|
|
452
|
+
let patch = await vcsPatch(ctx, location(), f.file);
|
|
287
453
|
if (isStale())
|
|
288
454
|
return;
|
|
289
|
-
|
|
290
|
-
|
|
455
|
+
if (patch == null) {
|
|
456
|
+
patch = await gitFallbackPatch(git, f.file).catch(() => {
|
|
457
|
+
return;
|
|
458
|
+
});
|
|
459
|
+
if (isStale())
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
const parsed = patch ? parseDiff(patch) : [];
|
|
291
463
|
setDiffState((draft) => {
|
|
292
464
|
draft.loading = false;
|
|
293
|
-
draft.patch =
|
|
294
|
-
if (!
|
|
465
|
+
draft.patch = patch ?? "";
|
|
466
|
+
if (!patch) {
|
|
295
467
|
draft.message = "No diff available";
|
|
296
468
|
} else if (parsed.length === 0) {
|
|
297
469
|
draft.message = "No visible changes (empty or binary file)";
|
|
@@ -929,73 +1101,119 @@ import { insert as _$insert7 } from "@opentui/solid";
|
|
|
929
1101
|
import { use as _$use2 } from "@opentui/solid";
|
|
930
1102
|
import { setProp as _$setProp7 } from "@opentui/solid";
|
|
931
1103
|
import { createElement as _$createElement7 } from "@opentui/solid";
|
|
932
|
-
import { memo as _$
|
|
1104
|
+
import { memo as _$memo6 } from "@opentui/solid";
|
|
933
1105
|
import { createComponent as _$createComponent7 } from "@opentui/solid";
|
|
934
|
-
import { createEffect as createEffect3, createSignal as createSignal3, onCleanup as onCleanup4, Show as
|
|
1106
|
+
import { createEffect as createEffect3, createSignal as createSignal3, onCleanup as onCleanup4, Show as Show7 } from "solid-js";
|
|
935
1107
|
|
|
936
1108
|
// src/sidebar/file-list.tsx
|
|
1109
|
+
import { memo as _$memo3 } from "@opentui/solid";
|
|
1110
|
+
import { createComponent as _$createComponent4 } from "@opentui/solid";
|
|
937
1111
|
import { effect as _$effect3 } from "@opentui/solid";
|
|
938
1112
|
import { createTextNode as _$createTextNode3 } from "@opentui/solid";
|
|
939
1113
|
import { insertNode as _$insertNode3 } from "@opentui/solid";
|
|
940
1114
|
import { insert as _$insert4 } from "@opentui/solid";
|
|
941
|
-
import { createComponent as _$createComponent4 } from "@opentui/solid";
|
|
942
1115
|
import { setProp as _$setProp4 } from "@opentui/solid";
|
|
943
1116
|
import { createElement as _$createElement4 } from "@opentui/solid";
|
|
944
|
-
import { For as For2 } from "solid-js";
|
|
1117
|
+
import { createMemo as createMemo3, For as For2, Show as Show4 } from "solid-js";
|
|
945
1118
|
function FileList(props) {
|
|
946
1119
|
const ctx = props.ctx;
|
|
1120
|
+
const rows = createMemo3(() => props.treeStyle === "none" ? [] : buildFileTree(props.files, props.treeStyle, props.collapsed));
|
|
1121
|
+
const indexByFile = createMemo3(() => new Map(props.files.map((f, i) => [f.file, i])));
|
|
1122
|
+
const pathLabel = (f, name, prefix) => {
|
|
1123
|
+
const max = Math.max(8, filePathMaxWidth(props.width, {
|
|
1124
|
+
additions: f.additions,
|
|
1125
|
+
deletions: f.deletions
|
|
1126
|
+
}) - displayWidth(prefix));
|
|
1127
|
+
return prefix + truncatePath(name, max);
|
|
1128
|
+
};
|
|
1129
|
+
const fileRow = (f, index, name, prefix) => {
|
|
1130
|
+
const b = statusBadge(f.status, ctx.theme);
|
|
1131
|
+
const label = () => props.staged.has(f.file) ? `[${b.label}]` : ` ${b.label} `;
|
|
1132
|
+
return (() => {
|
|
1133
|
+
var _el$ = _$createElement4("box"), _el$2 = _$createElement4("text"), _el$3 = _$createElement4("b"), _el$4 = _$createElement4("text"), _el$5 = _$createElement4("box"), _el$6 = _$createElement4("text"), _el$7 = _$createTextNode3(`+`), _el$8 = _$createElement4("text"), _el$9 = _$createTextNode3(`-`);
|
|
1134
|
+
_$insertNode3(_el$, _el$2);
|
|
1135
|
+
_$insertNode3(_el$, _el$4);
|
|
1136
|
+
_$insertNode3(_el$, _el$5);
|
|
1137
|
+
_$insertNode3(_el$, _el$6);
|
|
1138
|
+
_$insertNode3(_el$, _el$8);
|
|
1139
|
+
_$setProp4(_el$, "flexDirection", "row");
|
|
1140
|
+
_$setProp4(_el$, "gap", 1);
|
|
1141
|
+
_$setProp4(_el$, "onMouseUp", () => props.onSelect(f, index));
|
|
1142
|
+
_$insertNode3(_el$2, _el$3);
|
|
1143
|
+
_$setProp4(_el$2, "width", FILE_ROW_BADGE_WIDTH);
|
|
1144
|
+
_$insert4(_el$3, label);
|
|
1145
|
+
_$setProp4(_el$4, "wrapMode", "none");
|
|
1146
|
+
_$insert4(_el$4, () => pathLabel(f, name, prefix));
|
|
1147
|
+
_$setProp4(_el$5, "flexGrow", 1);
|
|
1148
|
+
_$insertNode3(_el$6, _el$7);
|
|
1149
|
+
_$insert4(_el$6, () => f.additions, null);
|
|
1150
|
+
_$insertNode3(_el$8, _el$9);
|
|
1151
|
+
_$insert4(_el$8, () => f.deletions, null);
|
|
1152
|
+
_$effect3((_p$) => {
|
|
1153
|
+
var _v$ = b.color, _v$2 = props.navMode && index === props.selected ? ctx.theme.text.base : ctx.theme.text.muted, _v$3 = ctx.theme.diff.text.added, _v$4 = ctx.theme.diff.text.removed;
|
|
1154
|
+
_v$ !== _p$.e && (_p$.e = _$setProp4(_el$2, "fg", _v$, _p$.e));
|
|
1155
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp4(_el$4, "fg", _v$2, _p$.t));
|
|
1156
|
+
_v$3 !== _p$.a && (_p$.a = _$setProp4(_el$6, "fg", _v$3, _p$.a));
|
|
1157
|
+
_v$4 !== _p$.o && (_p$.o = _$setProp4(_el$8, "fg", _v$4, _p$.o));
|
|
1158
|
+
return _p$;
|
|
1159
|
+
}, {
|
|
1160
|
+
e: undefined,
|
|
1161
|
+
t: undefined,
|
|
1162
|
+
a: undefined,
|
|
1163
|
+
o: undefined
|
|
1164
|
+
});
|
|
1165
|
+
return _el$;
|
|
1166
|
+
})();
|
|
1167
|
+
};
|
|
947
1168
|
return (() => {
|
|
948
|
-
var _el$ = _$createElement4("box");
|
|
949
|
-
_$setProp4(_el
|
|
950
|
-
_$setProp4(_el
|
|
951
|
-
_$insert4(_el
|
|
952
|
-
get
|
|
953
|
-
return props.
|
|
1169
|
+
var _el$0 = _$createElement4("box");
|
|
1170
|
+
_$setProp4(_el$0, "flexDirection", "column");
|
|
1171
|
+
_$setProp4(_el$0, "gap", 0);
|
|
1172
|
+
_$insert4(_el$0, _$createComponent4(Show4, {
|
|
1173
|
+
get when() {
|
|
1174
|
+
return props.treeStyle !== "none";
|
|
954
1175
|
},
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
});
|
|
994
|
-
return _el$2;
|
|
995
|
-
})();
|
|
1176
|
+
get fallback() {
|
|
1177
|
+
return _$createComponent4(For2, {
|
|
1178
|
+
get each() {
|
|
1179
|
+
return props.files;
|
|
1180
|
+
},
|
|
1181
|
+
children: (f, i) => fileRow(f, i(), f.file, "")
|
|
1182
|
+
});
|
|
1183
|
+
},
|
|
1184
|
+
get children() {
|
|
1185
|
+
return _$createComponent4(For2, {
|
|
1186
|
+
get each() {
|
|
1187
|
+
return rows();
|
|
1188
|
+
},
|
|
1189
|
+
children: (row) => row.kind === "dir" ? (() => {
|
|
1190
|
+
var _el$1 = _$createElement4("box"), _el$10 = _$createElement4("text"), _el$12 = _$createElement4("text");
|
|
1191
|
+
_$insertNode3(_el$1, _el$10);
|
|
1192
|
+
_$insertNode3(_el$1, _el$12);
|
|
1193
|
+
_$setProp4(_el$1, "flexDirection", "row");
|
|
1194
|
+
_$setProp4(_el$1, "gap", 1);
|
|
1195
|
+
_$setProp4(_el$1, "onMouseUp", () => props.onSelectDir(row.path));
|
|
1196
|
+
_$insertNode3(_el$10, _$createTextNode3(` `));
|
|
1197
|
+
_$setProp4(_el$10, "width", FILE_ROW_BADGE_WIDTH);
|
|
1198
|
+
_$setProp4(_el$12, "wrapMode", "none");
|
|
1199
|
+
_$insert4(_el$12, () => row.prefix, null);
|
|
1200
|
+
_$insert4(_el$12, () => props.collapsed.has(row.path) ? "\u25B8 " : "\u25BE ", null);
|
|
1201
|
+
_$insert4(_el$12, () => row.name, null);
|
|
1202
|
+
_$effect3((_p$) => {
|
|
1203
|
+
var _v$5 = ctx.theme.text.muted, _v$6 = ctx.theme.text.muted;
|
|
1204
|
+
_v$5 !== _p$.e && (_p$.e = _$setProp4(_el$10, "fg", _v$5, _p$.e));
|
|
1205
|
+
_v$6 !== _p$.t && (_p$.t = _$setProp4(_el$12, "fg", _v$6, _p$.t));
|
|
1206
|
+
return _p$;
|
|
1207
|
+
}, {
|
|
1208
|
+
e: undefined,
|
|
1209
|
+
t: undefined
|
|
1210
|
+
});
|
|
1211
|
+
return _el$1;
|
|
1212
|
+
})() : fileRow(row.file, indexByFile().get(row.file.file) ?? -1, row.name, row.prefix)
|
|
1213
|
+
});
|
|
996
1214
|
}
|
|
997
1215
|
}));
|
|
998
|
-
return _el
|
|
1216
|
+
return _el$0;
|
|
999
1217
|
})();
|
|
1000
1218
|
}
|
|
1001
1219
|
|
|
@@ -1039,12 +1257,12 @@ function bindSidebarFocus(ctx, opts) {
|
|
|
1039
1257
|
import { createComponent as _$createComponent5 } from "@opentui/solid";
|
|
1040
1258
|
import { effect as _$effect4 } from "@opentui/solid";
|
|
1041
1259
|
import { insert as _$insert5 } from "@opentui/solid";
|
|
1042
|
-
import { memo as _$
|
|
1260
|
+
import { memo as _$memo4 } from "@opentui/solid";
|
|
1043
1261
|
import { createTextNode as _$createTextNode4 } from "@opentui/solid";
|
|
1044
1262
|
import { insertNode as _$insertNode4 } from "@opentui/solid";
|
|
1045
1263
|
import { setProp as _$setProp5 } from "@opentui/solid";
|
|
1046
1264
|
import { createElement as _$createElement5 } from "@opentui/solid";
|
|
1047
|
-
import { Show as
|
|
1265
|
+
import { Show as Show5 } from "solid-js";
|
|
1048
1266
|
function SidebarHeader(props) {
|
|
1049
1267
|
const ctx = props.ctx;
|
|
1050
1268
|
return (() => {
|
|
@@ -1059,9 +1277,9 @@ function SidebarHeader(props) {
|
|
|
1059
1277
|
_$insertNode4(_el$3, _el$4);
|
|
1060
1278
|
_$insertNode4(_el$4, _$createTextNode4(`Git Panel`));
|
|
1061
1279
|
_$setProp5(_el$6, "flexGrow", 1);
|
|
1062
|
-
_$insert5(_el$2, _$createComponent5(
|
|
1280
|
+
_$insert5(_el$2, _$createComponent5(Show5, {
|
|
1063
1281
|
get when() {
|
|
1064
|
-
return _$
|
|
1282
|
+
return _$memo4(() => !!props.branch)() && props.fileCount > 0;
|
|
1065
1283
|
},
|
|
1066
1284
|
get children() {
|
|
1067
1285
|
return [(() => {
|
|
@@ -1082,9 +1300,9 @@ function SidebarHeader(props) {
|
|
|
1082
1300
|
})()];
|
|
1083
1301
|
}
|
|
1084
1302
|
}), null);
|
|
1085
|
-
_$insert5(_el$2, _$createComponent5(
|
|
1303
|
+
_$insert5(_el$2, _$createComponent5(Show5, {
|
|
1086
1304
|
get when() {
|
|
1087
|
-
return _$
|
|
1305
|
+
return _$memo4(() => !!props.ready)() && props.fileCount > 0;
|
|
1088
1306
|
},
|
|
1089
1307
|
get children() {
|
|
1090
1308
|
return [(() => {
|
|
@@ -1102,9 +1320,9 @@ function SidebarHeader(props) {
|
|
|
1102
1320
|
})()];
|
|
1103
1321
|
}
|
|
1104
1322
|
}), null);
|
|
1105
|
-
_$insert5(_el$, _$createComponent5(
|
|
1323
|
+
_$insert5(_el$, _$createComponent5(Show5, {
|
|
1106
1324
|
get when() {
|
|
1107
|
-
return _$
|
|
1325
|
+
return _$memo4(() => !!(props.ready && props.fileCount > 0))() && props.navMode;
|
|
1108
1326
|
},
|
|
1109
1327
|
get children() {
|
|
1110
1328
|
var _el$15 = _$createElement5("box"), _el$16 = _$createElement5("box");
|
|
@@ -1112,7 +1330,7 @@ function SidebarHeader(props) {
|
|
|
1112
1330
|
_$setProp5(_el$15, "flexDirection", "row");
|
|
1113
1331
|
_$setProp5(_el$15, "gap", 1);
|
|
1114
1332
|
_$setProp5(_el$16, "flexGrow", 1);
|
|
1115
|
-
_$insert5(_el$15, _$createComponent5(
|
|
1333
|
+
_$insert5(_el$15, _$createComponent5(Show5, {
|
|
1116
1334
|
get when() {
|
|
1117
1335
|
return props.stageMode;
|
|
1118
1336
|
},
|
|
@@ -1131,7 +1349,7 @@ function SidebarHeader(props) {
|
|
|
1131
1349
|
return _el$17;
|
|
1132
1350
|
}
|
|
1133
1351
|
}), null);
|
|
1134
|
-
_$insert5(_el$15, _$createComponent5(
|
|
1352
|
+
_$insert5(_el$15, _$createComponent5(Show5, {
|
|
1135
1353
|
get when() {
|
|
1136
1354
|
return props.hasStaged;
|
|
1137
1355
|
},
|
|
@@ -1351,7 +1569,15 @@ function createListNavigation(ctx, files) {
|
|
|
1351
1569
|
}
|
|
1352
1570
|
|
|
1353
1571
|
// src/sidebar/staging.tsx
|
|
1354
|
-
import { createMemo as
|
|
1572
|
+
import { createMemo as createMemo4 } from "solid-js";
|
|
1573
|
+
function shouldStageAll(files, staged) {
|
|
1574
|
+
return !files.every((f) => staged.has(f));
|
|
1575
|
+
}
|
|
1576
|
+
var HEAD_ERROR_RE = /could not resolve HEAD|ambiguous argument ['"]?HEAD|invalid object name ['"]?HEAD/i;
|
|
1577
|
+
function buildStageArgs(stage, files) {
|
|
1578
|
+
const paths = files.map(shellQuote).join(" ");
|
|
1579
|
+
return stage ? `add -- ${paths}` : `restore --staged -- ${paths}`;
|
|
1580
|
+
}
|
|
1355
1581
|
function createStaging(ctx, git, location, refreshWorkingCopy) {
|
|
1356
1582
|
const [stagedMem, setStagedMem] = ctx.storage.memory(STAGED_STORE_KEY, {
|
|
1357
1583
|
initial: {
|
|
@@ -1359,7 +1585,7 @@ function createStaging(ctx, git, location, refreshWorkingCopy) {
|
|
|
1359
1585
|
files: []
|
|
1360
1586
|
}
|
|
1361
1587
|
});
|
|
1362
|
-
const staged =
|
|
1588
|
+
const staged = createMemo4(() => new Set(stagedMem.files));
|
|
1363
1589
|
if (stagedMem.directory !== location().directory) {
|
|
1364
1590
|
setStagedMem((draft) => {
|
|
1365
1591
|
draft.directory = location().directory;
|
|
@@ -1370,12 +1596,12 @@ function createStaging(ctx, git, location, refreshWorkingCopy) {
|
|
|
1370
1596
|
const fetchStatuses = async () => {
|
|
1371
1597
|
try {
|
|
1372
1598
|
const res = await git.run("status --porcelain=v1 -z -uall --no-renames");
|
|
1373
|
-
if (res.exit
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
}
|
|
1599
|
+
if (res.exit !== 0)
|
|
1600
|
+
return;
|
|
1601
|
+
const files = [...parseStagedPorcelain(res.output)];
|
|
1602
|
+
setStagedMem((draft) => {
|
|
1603
|
+
draft.files = files;
|
|
1604
|
+
});
|
|
1379
1605
|
} catch {}
|
|
1380
1606
|
};
|
|
1381
1607
|
const scheduleRefresh = async () => {
|
|
@@ -1386,43 +1612,54 @@ function createStaging(ctx, git, location, refreshWorkingCopy) {
|
|
|
1386
1612
|
});
|
|
1387
1613
|
return refreshTask;
|
|
1388
1614
|
};
|
|
1389
|
-
const
|
|
1615
|
+
const toastError = (message) => ctx.ui.toast.show({
|
|
1616
|
+
message,
|
|
1617
|
+
variant: "error"
|
|
1618
|
+
});
|
|
1619
|
+
const tail = (output) => output.trim().slice(0, 200);
|
|
1620
|
+
const stageFiles = async (stage, files) => {
|
|
1621
|
+
const res = await git.run(buildStageArgs(stage, files));
|
|
1622
|
+
if (res.exit === 0)
|
|
1623
|
+
return;
|
|
1624
|
+
if (!stage && HEAD_ERROR_RE.test(res.output)) {
|
|
1625
|
+
for (const file of files) {
|
|
1626
|
+
const q = shellQuote(file);
|
|
1627
|
+
let r = await git.run(`restore --staged -- ${q}`);
|
|
1628
|
+
if (r.exit !== 0 && HEAD_ERROR_RE.test(r.output))
|
|
1629
|
+
r = await git.run(`rm --cached -- ${q}`);
|
|
1630
|
+
if (r.exit !== 0)
|
|
1631
|
+
toastError(`git restore failed: ${tail(r.output)}`);
|
|
1632
|
+
}
|
|
1633
|
+
return;
|
|
1634
|
+
}
|
|
1635
|
+
toastError(`git ${stage ? "add" : "restore"} failed: ${tail(res.output)}`);
|
|
1636
|
+
};
|
|
1637
|
+
const applyFiles = (files, stage) => {
|
|
1638
|
+
if (files.length === 0)
|
|
1639
|
+
return;
|
|
1390
1640
|
setStagedMem((draft) => {
|
|
1391
1641
|
const next = new Set(draft.files);
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1642
|
+
for (const file of files) {
|
|
1643
|
+
if (stage)
|
|
1644
|
+
next.add(file);
|
|
1645
|
+
else
|
|
1646
|
+
next.delete(file);
|
|
1647
|
+
}
|
|
1396
1648
|
draft.files = [...next];
|
|
1397
1649
|
});
|
|
1398
1650
|
git.enqueue(async () => {
|
|
1399
1651
|
try {
|
|
1400
|
-
|
|
1401
|
-
if (!stage && res.exit !== 0 && /could not resolve HEAD|ambiguous argument ['"]?HEAD|invalid object name ['"]?HEAD/i.test(res.output)) {
|
|
1402
|
-
res = await git.run(`rm --cached -- ${shellQuote(file)}`);
|
|
1403
|
-
}
|
|
1404
|
-
if (res.exit !== 0) {
|
|
1405
|
-
ctx.ui.toast.show({
|
|
1406
|
-
message: `git ${stage ? "add" : "restore"} failed: ${res.output.trim().slice(0, 200)}`,
|
|
1407
|
-
variant: "error"
|
|
1408
|
-
});
|
|
1409
|
-
await fetchStatuses();
|
|
1410
|
-
return;
|
|
1411
|
-
}
|
|
1412
|
-
await fetchStatuses();
|
|
1652
|
+
await stageFiles(stage, files);
|
|
1413
1653
|
} catch (e) {
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
variant: "error"
|
|
1417
|
-
});
|
|
1654
|
+
toastError(`git failed: ${friendlyError(e)}`);
|
|
1655
|
+
} finally {
|
|
1418
1656
|
await fetchStatuses();
|
|
1419
|
-
return;
|
|
1420
1657
|
}
|
|
1421
1658
|
});
|
|
1422
1659
|
};
|
|
1423
|
-
const
|
|
1424
|
-
|
|
1425
|
-
|
|
1660
|
+
const applyChange = (file, stage) => applyFiles([file], stage);
|
|
1661
|
+
const toggle = (file) => applyChange(file, !staged().has(file));
|
|
1662
|
+
const toggleAll = (files) => applyFiles(files, shouldStageAll(files, staged()));
|
|
1426
1663
|
const commit = async () => {
|
|
1427
1664
|
await scheduleRefresh();
|
|
1428
1665
|
if (staged().size === 0) {
|
|
@@ -1442,10 +1679,7 @@ function createStaging(ctx, git, location, refreshWorkingCopy) {
|
|
|
1442
1679
|
try {
|
|
1443
1680
|
const res = await git.run(`commit -m ${shellQuote(message)}`);
|
|
1444
1681
|
if (res.exit !== 0) {
|
|
1445
|
-
|
|
1446
|
-
message: `git commit failed: ${res.output.trim().slice(0, 200)}`,
|
|
1447
|
-
variant: "error"
|
|
1448
|
-
});
|
|
1682
|
+
toastError(`git commit failed: ${tail(res.output)}`);
|
|
1449
1683
|
} else {
|
|
1450
1684
|
ctx.ui.toast.show({
|
|
1451
1685
|
message: "Committed",
|
|
@@ -1453,10 +1687,7 @@ function createStaging(ctx, git, location, refreshWorkingCopy) {
|
|
|
1453
1687
|
});
|
|
1454
1688
|
}
|
|
1455
1689
|
} catch (e) {
|
|
1456
|
-
|
|
1457
|
-
message: `git commit failed: ${friendlyError(e)}`,
|
|
1458
|
-
variant: "error"
|
|
1459
|
-
});
|
|
1690
|
+
toastError(`git commit failed: ${friendlyError(e)}`);
|
|
1460
1691
|
}
|
|
1461
1692
|
await refreshWorkingCopy();
|
|
1462
1693
|
});
|
|
@@ -1466,23 +1697,24 @@ function createStaging(ctx, git, location, refreshWorkingCopy) {
|
|
|
1466
1697
|
scheduleRefresh,
|
|
1467
1698
|
applyChange,
|
|
1468
1699
|
toggle,
|
|
1700
|
+
toggleAll,
|
|
1469
1701
|
commit
|
|
1470
1702
|
};
|
|
1471
1703
|
}
|
|
1472
1704
|
|
|
1473
1705
|
// src/sidebar/status.tsx
|
|
1474
1706
|
import { insert as _$insert6 } from "@opentui/solid";
|
|
1475
|
-
import { memo as _$
|
|
1707
|
+
import { memo as _$memo5 } from "@opentui/solid";
|
|
1476
1708
|
import { createComponent as _$createComponent6 } from "@opentui/solid";
|
|
1477
1709
|
import { setProp as _$setProp6 } from "@opentui/solid";
|
|
1478
1710
|
import { effect as _$effect5 } from "@opentui/solid";
|
|
1479
1711
|
import { createTextNode as _$createTextNode5 } from "@opentui/solid";
|
|
1480
1712
|
import { insertNode as _$insertNode5 } from "@opentui/solid";
|
|
1481
1713
|
import { createElement as _$createElement6 } from "@opentui/solid";
|
|
1482
|
-
import { Show as
|
|
1714
|
+
import { Show as Show6 } from "solid-js";
|
|
1483
1715
|
function SidebarStatus(props) {
|
|
1484
1716
|
const ctx = props.ctx;
|
|
1485
|
-
return [_$createComponent6(
|
|
1717
|
+
return [_$createComponent6(Show6, {
|
|
1486
1718
|
get when() {
|
|
1487
1719
|
return props.loading;
|
|
1488
1720
|
},
|
|
@@ -1492,9 +1724,9 @@ function SidebarStatus(props) {
|
|
|
1492
1724
|
_$effect5((_$p) => _$setProp6(_el$, "fg", ctx.theme.text.muted, _$p));
|
|
1493
1725
|
return _el$;
|
|
1494
1726
|
}
|
|
1495
|
-
}), _$createComponent6(
|
|
1727
|
+
}), _$createComponent6(Show6, {
|
|
1496
1728
|
get when() {
|
|
1497
|
-
return _$
|
|
1729
|
+
return _$memo5(() => !!!props.loading)() && props.error;
|
|
1498
1730
|
},
|
|
1499
1731
|
get children() {
|
|
1500
1732
|
var _el$3 = _$createElement6("text");
|
|
@@ -1502,9 +1734,9 @@ function SidebarStatus(props) {
|
|
|
1502
1734
|
_$effect5((_$p) => _$setProp6(_el$3, "fg", ctx.theme.text.feedback.error.base, _$p));
|
|
1503
1735
|
return _el$3;
|
|
1504
1736
|
}
|
|
1505
|
-
}), _$createComponent6(
|
|
1737
|
+
}), _$createComponent6(Show6, {
|
|
1506
1738
|
get when() {
|
|
1507
|
-
return _$
|
|
1739
|
+
return _$memo5(() => !!(!props.loading && !props.error && !props.hasFiles))() && !props.hasHead;
|
|
1508
1740
|
},
|
|
1509
1741
|
get children() {
|
|
1510
1742
|
var _el$4 = _$createElement6("text");
|
|
@@ -1512,9 +1744,9 @@ function SidebarStatus(props) {
|
|
|
1512
1744
|
_$effect5((_$p) => _$setProp6(_el$4, "fg", ctx.theme.text.muted, _$p));
|
|
1513
1745
|
return _el$4;
|
|
1514
1746
|
}
|
|
1515
|
-
}), _$createComponent6(
|
|
1747
|
+
}), _$createComponent6(Show6, {
|
|
1516
1748
|
get when() {
|
|
1517
|
-
return _$
|
|
1749
|
+
return _$memo5(() => !!(!props.loading && !props.error && !props.hasFiles))() && props.hasHead;
|
|
1518
1750
|
},
|
|
1519
1751
|
get children() {
|
|
1520
1752
|
var _el$6 = _$createElement6("text");
|
|
@@ -1526,44 +1758,69 @@ function SidebarStatus(props) {
|
|
|
1526
1758
|
}
|
|
1527
1759
|
|
|
1528
1760
|
// src/sidebar/working-copy.tsx
|
|
1529
|
-
import { createEffect as createEffect2, createMemo as
|
|
1530
|
-
|
|
1761
|
+
import { createEffect as createEffect2, createMemo as createMemo5, createSignal as createSignal2, onCleanup as onCleanup3 } from "solid-js";
|
|
1762
|
+
var EMPTY_VCS = {
|
|
1763
|
+
files: [],
|
|
1764
|
+
branch: "",
|
|
1765
|
+
hasHead: true
|
|
1766
|
+
};
|
|
1767
|
+
function createWorkingCopy(ctx, location, git, onRefreshed) {
|
|
1531
1768
|
const [files, setFiles] = createSignal2([]);
|
|
1532
1769
|
const [branch, setBranch] = createSignal2("");
|
|
1770
|
+
const [hasHead, setHasHead] = createSignal2(true);
|
|
1533
1771
|
const [loading, setLoading] = createSignal2(true);
|
|
1534
1772
|
const [error, setError] = createSignal2(undefined);
|
|
1535
1773
|
let isRefreshing = false;
|
|
1536
1774
|
let lastEventRefreshAt = 0;
|
|
1537
|
-
const
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1775
|
+
const applyFiles = (next) => {
|
|
1776
|
+
const prevByFile = new Map(files().map((f) => [f.file, f]));
|
|
1777
|
+
setFiles(next.map((d) => {
|
|
1778
|
+
const p = prevByFile.get(d.file);
|
|
1779
|
+
return p && p.additions === d.additions && p.deletions === d.deletions && p.status === d.status ? p : {
|
|
1780
|
+
...d
|
|
1781
|
+
};
|
|
1782
|
+
}));
|
|
1783
|
+
};
|
|
1784
|
+
const loadFromVcs = async (loc) => {
|
|
1541
1785
|
try {
|
|
1542
|
-
const loc = location();
|
|
1543
1786
|
const [statusRes, infoRes] = await Promise.all([ctx.client.vcs.status({
|
|
1544
1787
|
location: loc
|
|
1545
1788
|
}), ctx.client.vcs.get({
|
|
1546
1789
|
location: loc
|
|
1547
1790
|
})]);
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1791
|
+
return {
|
|
1792
|
+
files: statusRes.data,
|
|
1793
|
+
branch: infoRes.data.branch.current ?? "",
|
|
1794
|
+
hasHead: true
|
|
1795
|
+
};
|
|
1796
|
+
} catch {
|
|
1797
|
+
return EMPTY_VCS;
|
|
1798
|
+
}
|
|
1799
|
+
};
|
|
1800
|
+
const isEmpty = (s) => s.files.length === 0 && s.branch === "";
|
|
1801
|
+
const refresh = async () => {
|
|
1802
|
+
if (isRefreshing)
|
|
1803
|
+
return;
|
|
1804
|
+
isRefreshing = true;
|
|
1805
|
+
try {
|
|
1806
|
+
const vcs = await loadFromVcs(location());
|
|
1807
|
+
const next = isEmpty(vcs) ? await git.enqueue(() => fetchWorkingCopyFallback(git)) : vcs;
|
|
1808
|
+
applyFiles(next.files);
|
|
1809
|
+
setBranch(next.branch);
|
|
1810
|
+
setHasHead(next.hasHead);
|
|
1811
|
+
onRefreshed(next.files.length);
|
|
1556
1812
|
setError(undefined);
|
|
1557
|
-
onRefreshed(statusRes.data.length);
|
|
1558
1813
|
} catch (e) {
|
|
1814
|
+
applyFiles([]);
|
|
1815
|
+
setBranch("");
|
|
1559
1816
|
setError(friendlyError(e));
|
|
1560
1817
|
} finally {
|
|
1561
1818
|
setLoading(false);
|
|
1562
1819
|
isRefreshing = false;
|
|
1563
1820
|
}
|
|
1564
1821
|
};
|
|
1565
|
-
const additions =
|
|
1566
|
-
const deletions =
|
|
1822
|
+
const additions = createMemo5(() => files().reduce((n, f) => n + f.additions, 0));
|
|
1823
|
+
const deletions = createMemo5(() => files().reduce((n, f) => n + f.deletions, 0));
|
|
1567
1824
|
const [refreshSignal, bumpRefresh] = ctx.storage.memory(REFRESH_STORE_KEY, {
|
|
1568
1825
|
initial: {
|
|
1569
1826
|
nonce: 0
|
|
@@ -1598,6 +1855,7 @@ function createWorkingCopy(ctx, location, onRefreshed) {
|
|
|
1598
1855
|
return {
|
|
1599
1856
|
files,
|
|
1600
1857
|
branch,
|
|
1858
|
+
hasHead,
|
|
1601
1859
|
loading,
|
|
1602
1860
|
error,
|
|
1603
1861
|
additions,
|
|
@@ -1613,6 +1871,7 @@ function GitChangesView(props) {
|
|
|
1613
1871
|
ctx
|
|
1614
1872
|
} = props;
|
|
1615
1873
|
const [sidebarWidth, setSidebarWidth] = createSignal3(46);
|
|
1874
|
+
const [collapsedDirs, setCollapsedDirs] = createSignal3(new Set);
|
|
1616
1875
|
const [diffView] = useDiffStore(ctx);
|
|
1617
1876
|
let sidebarNode;
|
|
1618
1877
|
const currentLocation = () => {
|
|
@@ -1622,8 +1881,8 @@ function GitChangesView(props) {
|
|
|
1622
1881
|
};
|
|
1623
1882
|
};
|
|
1624
1883
|
const git = createGitRunner(ctx, () => currentLocation().directory);
|
|
1625
|
-
const openDiff = createDiffOpener(ctx, currentLocation);
|
|
1626
|
-
const workingCopy = createWorkingCopy(ctx, currentLocation, (count) => {
|
|
1884
|
+
const openDiff = createDiffOpener(ctx, currentLocation, git);
|
|
1885
|
+
const workingCopy = createWorkingCopy(ctx, currentLocation, git, (count) => {
|
|
1627
1886
|
nav.clampSelection(count);
|
|
1628
1887
|
staging.scheduleRefresh();
|
|
1629
1888
|
});
|
|
@@ -1646,6 +1905,30 @@ function GitChangesView(props) {
|
|
|
1646
1905
|
else
|
|
1647
1906
|
openDiff(f).catch(() => {});
|
|
1648
1907
|
};
|
|
1908
|
+
const toggleCollapsed = (dirPath) => setCollapsedDirs((prev) => {
|
|
1909
|
+
const next = new Set(prev);
|
|
1910
|
+
if (!next.delete(dirPath))
|
|
1911
|
+
next.add(dirPath);
|
|
1912
|
+
return next;
|
|
1913
|
+
});
|
|
1914
|
+
const selectDir = (dirPath) => {
|
|
1915
|
+
if (!nav.stageMode()) {
|
|
1916
|
+
toggleCollapsed(dirPath);
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
const contained = workingCopy.files().map((f, i) => ({
|
|
1920
|
+
f,
|
|
1921
|
+
i
|
|
1922
|
+
})).filter(({
|
|
1923
|
+
f
|
|
1924
|
+
}) => f.file.startsWith(dirPath));
|
|
1925
|
+
if (contained.length === 0)
|
|
1926
|
+
return;
|
|
1927
|
+
staging.toggleAll(contained.map(({
|
|
1928
|
+
f
|
|
1929
|
+
}) => f.file));
|
|
1930
|
+
nav.setSelected(contained[0].i);
|
|
1931
|
+
};
|
|
1649
1932
|
workingCopy.refresh().catch(() => {});
|
|
1650
1933
|
staging.scheduleRefresh().catch(() => {});
|
|
1651
1934
|
registerSidebarKeymaps(ctx, {
|
|
@@ -1734,12 +2017,12 @@ function GitChangesView(props) {
|
|
|
1734
2017
|
get hasFiles() {
|
|
1735
2018
|
return hasFiles();
|
|
1736
2019
|
},
|
|
1737
|
-
get
|
|
1738
|
-
return workingCopy.
|
|
2020
|
+
get hasHead() {
|
|
2021
|
+
return workingCopy.hasHead();
|
|
1739
2022
|
}
|
|
1740
|
-
}), _$createComponent7(
|
|
2023
|
+
}), _$createComponent7(Show7, {
|
|
1741
2024
|
get when() {
|
|
1742
|
-
return _$
|
|
2025
|
+
return _$memo6(() => !!ready())() && hasFiles();
|
|
1743
2026
|
},
|
|
1744
2027
|
get children() {
|
|
1745
2028
|
return _$createComponent7(FileList, {
|
|
@@ -1759,11 +2042,18 @@ function GitChangesView(props) {
|
|
|
1759
2042
|
get width() {
|
|
1760
2043
|
return sidebarWidth();
|
|
1761
2044
|
},
|
|
1762
|
-
|
|
2045
|
+
get treeStyle() {
|
|
2046
|
+
return folderTree(ctx);
|
|
2047
|
+
},
|
|
2048
|
+
get collapsed() {
|
|
2049
|
+
return collapsedDirs();
|
|
2050
|
+
},
|
|
2051
|
+
onSelect: selectFile,
|
|
2052
|
+
onSelectDir: selectDir
|
|
1763
2053
|
});
|
|
1764
2054
|
}
|
|
1765
2055
|
})];
|
|
1766
|
-
return _$createComponent7(
|
|
2056
|
+
return _$createComponent7(Show7, {
|
|
1767
2057
|
get when() {
|
|
1768
2058
|
return nav.navMode();
|
|
1769
2059
|
},
|
|
@@ -1822,7 +2112,12 @@ var tui_default = Plugin.define({
|
|
|
1822
2112
|
}
|
|
1823
2113
|
});
|
|
1824
2114
|
export {
|
|
2115
|
+
porcelainChangeStatus,
|
|
1825
2116
|
parseStagedPorcelain,
|
|
2117
|
+
parsePorcelainRecords,
|
|
2118
|
+
parseNumstat,
|
|
1826
2119
|
parseDiff,
|
|
1827
|
-
tui_default as default
|
|
2120
|
+
tui_default as default,
|
|
2121
|
+
buildFileTree,
|
|
2122
|
+
buildFallbackFiles
|
|
1828
2123
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "opencode-git-plugin",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.3.0",
|
|
5
5
|
"description": "OpenCode v2 TUI sidebar plugin listing git working-copy changes with diff preview",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"exports": {
|