opencode-git-plugin 0.2.0 → 0.2.1
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 +5 -2
- package/dist/tui.js +1828 -0
- package/package.json +5 -4
- package/src/config/constants.ts +0 -16
- package/src/config/index.ts +0 -2
- package/src/config/options.ts +0 -8
- package/src/diff/body.tsx +0 -90
- package/src/diff/header.tsx +0 -77
- package/src/diff/index.ts +0 -2
- package/src/diff/keymap.ts +0 -113
- package/src/diff/open-diff.ts +0 -57
- package/src/diff/panel.tsx +0 -156
- package/src/diff/search.tsx +0 -38
- package/src/git/diff-parse.ts +0 -35
- package/src/git/index.ts +0 -3
- package/src/git/porcelain.ts +0 -10
- package/src/git/runner.ts +0 -45
- package/src/sidebar/file-list.tsx +0 -47
- package/src/sidebar/focus.tsx +0 -37
- package/src/sidebar/header.tsx +0 -55
- package/src/sidebar/index.ts +0 -1
- package/src/sidebar/keymap.ts +0 -143
- package/src/sidebar/list-navigation.tsx +0 -96
- package/src/sidebar/staging.tsx +0 -108
- package/src/sidebar/status.tsx +0 -29
- package/src/sidebar/view.tsx +0 -159
- package/src/sidebar/working-copy.tsx +0 -81
- package/src/state/diff-store.ts +0 -34
- package/src/state/index.ts +0 -1
- package/src/tui.tsx +0 -25
- package/src/types.ts +0 -63
- package/src/ui/format.ts +0 -67
- package/src/ui/index.ts +0 -2
- package/src/ui/syntax.ts +0 -61
- package/tui.ts +0 -1
package/dist/tui.js
ADDED
|
@@ -0,0 +1,1828 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/tui.tsx
|
|
3
|
+
import { createComponent as _$createComponent8 } from "@opentui/solid";
|
|
4
|
+
import { Plugin } from "@opencode/plugin/tui";
|
|
5
|
+
|
|
6
|
+
// src/config/constants.ts
|
|
7
|
+
var DEFAULT_INTERVAL_MS = 4000;
|
|
8
|
+
var MIN_INTERVAL_MS = 1000;
|
|
9
|
+
var MAX_DIFF_LINES = 4000;
|
|
10
|
+
var GIT_TIMEOUT_MS = 120000;
|
|
11
|
+
var GIT_POLL_MS = 50;
|
|
12
|
+
var DIFF_STORE_KEY = "git-panel.diff-view";
|
|
13
|
+
var DIFF_PANEL_NAME = "git-panel.diff";
|
|
14
|
+
var REFRESH_STORE_KEY = "git-panel.refresh";
|
|
15
|
+
var SETTINGS_STORE_KEY = "git-panel.settings";
|
|
16
|
+
var STAGED_STORE_KEY = "git-panel.staged";
|
|
17
|
+
var LIST_NAV_MODE = "git-panel-list";
|
|
18
|
+
var FILE_ROW_BADGE_WIDTH = 3;
|
|
19
|
+
var EVENT_REFRESH_DEBOUNCE_MS = 2000;
|
|
20
|
+
// src/config/options.ts
|
|
21
|
+
function intervalMs(ctx) {
|
|
22
|
+
const raw = ctx.options.intervalMs;
|
|
23
|
+
if (typeof raw !== "number" || !Number.isFinite(raw))
|
|
24
|
+
return DEFAULT_INTERVAL_MS;
|
|
25
|
+
return Math.max(MIN_INTERVAL_MS, Math.floor(raw));
|
|
26
|
+
}
|
|
27
|
+
// src/git/diff-parse.ts
|
|
28
|
+
var DIFF_PREAMBLE_RE = /^(diff --git |index |new file mode|deleted file mode|similarity index|rename from|rename to|old mode|new mode)/;
|
|
29
|
+
var HUNK_HEADER_RE = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,\d+)? @@ ?(.*)$/;
|
|
30
|
+
function formatHunkHeader(raw) {
|
|
31
|
+
const m = raw.match(HUNK_HEADER_RE);
|
|
32
|
+
if (!m)
|
|
33
|
+
return raw;
|
|
34
|
+
const [, newStart, trailingContext] = m;
|
|
35
|
+
return trailingContext ? `\u22EF ${trailingContext}` : `\u22EF line ${newStart}`;
|
|
36
|
+
}
|
|
37
|
+
function parseDiff(patch) {
|
|
38
|
+
const out = [];
|
|
39
|
+
let newLine = 0;
|
|
40
|
+
for (const line of patch.split(`
|
|
41
|
+
`)) {
|
|
42
|
+
if (line === "")
|
|
43
|
+
continue;
|
|
44
|
+
if (line.startsWith("@@")) {
|
|
45
|
+
const m = line.match(HUNK_HEADER_RE);
|
|
46
|
+
if (m)
|
|
47
|
+
newLine = parseInt(m[1], 10);
|
|
48
|
+
out.push({ kind: "hunk", text: formatHunkHeader(line) });
|
|
49
|
+
} else if (line.startsWith("+++") || line.startsWith("---")) {} else if (DIFF_PREAMBLE_RE.test(line)) {} else if (line.startsWith("Binary files")) {
|
|
50
|
+
out.push({ kind: "meta", text: line });
|
|
51
|
+
} else if (line.startsWith("+")) {
|
|
52
|
+
out.push({ kind: "add", text: line.slice(1), line: newLine++ });
|
|
53
|
+
} else if (line.startsWith("-")) {
|
|
54
|
+
out.push({ kind: "del", text: line.slice(1) });
|
|
55
|
+
} else {
|
|
56
|
+
out.push({ kind: "context", text: line.startsWith(" ") ? line.slice(1) : line, line: newLine++ });
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
// src/git/porcelain.ts
|
|
62
|
+
function parseStagedPorcelain(porcelain) {
|
|
63
|
+
const staged = new Set;
|
|
64
|
+
for (const record of porcelain.split("\x00")) {
|
|
65
|
+
if (record.length < 4)
|
|
66
|
+
continue;
|
|
67
|
+
const indexStatus = record[0];
|
|
68
|
+
if (indexStatus === " " || indexStatus === "?")
|
|
69
|
+
continue;
|
|
70
|
+
staged.add(record.slice(3));
|
|
71
|
+
}
|
|
72
|
+
return staged;
|
|
73
|
+
}
|
|
74
|
+
// src/git/runner.ts
|
|
75
|
+
function shellQuote(s) {
|
|
76
|
+
return `'${s.replace(/'/g, `'\\''`)}'`;
|
|
77
|
+
}
|
|
78
|
+
function createGitRunner(ctx, directory) {
|
|
79
|
+
let queue = Promise.resolve();
|
|
80
|
+
const enqueue = (op) => {
|
|
81
|
+
const run2 = queue.then(op);
|
|
82
|
+
queue = run2.then(() => {
|
|
83
|
+
return;
|
|
84
|
+
}, () => {
|
|
85
|
+
return;
|
|
86
|
+
});
|
|
87
|
+
return run2;
|
|
88
|
+
};
|
|
89
|
+
const run = async (args) => {
|
|
90
|
+
const location = { directory: directory() };
|
|
91
|
+
const created = await ctx.client.shell.create({
|
|
92
|
+
location,
|
|
93
|
+
command: `git ${args}`,
|
|
94
|
+
cwd: location.directory
|
|
95
|
+
});
|
|
96
|
+
const id = created.data.id;
|
|
97
|
+
try {
|
|
98
|
+
let final = await ctx.client.shell.get({ id, location });
|
|
99
|
+
for (let elapsed = 0;final.data.status === "running" && elapsed < GIT_TIMEOUT_MS; elapsed += GIT_POLL_MS) {
|
|
100
|
+
await new Promise((resolve) => setTimeout(resolve, GIT_POLL_MS));
|
|
101
|
+
final = await ctx.client.shell.get({ id, location });
|
|
102
|
+
}
|
|
103
|
+
if (final.data.status === "running")
|
|
104
|
+
throw new Error(`Git command timed out after ${GIT_TIMEOUT_MS / 1000}s`);
|
|
105
|
+
const out = await ctx.client.shell.output({ id, location });
|
|
106
|
+
return { exit: final.data.exit, output: out.data.output };
|
|
107
|
+
} finally {
|
|
108
|
+
await ctx.client.shell.remove({ id, location }).catch(() => {});
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
return { enqueue, run };
|
|
112
|
+
}
|
|
113
|
+
// src/state/diff-store.ts
|
|
114
|
+
var initialDiffState = {
|
|
115
|
+
file: "",
|
|
116
|
+
additions: 0,
|
|
117
|
+
deletions: 0,
|
|
118
|
+
lines: [],
|
|
119
|
+
patch: "",
|
|
120
|
+
message: "",
|
|
121
|
+
truncated: false,
|
|
122
|
+
loading: false,
|
|
123
|
+
query: "",
|
|
124
|
+
matchCursor: 0,
|
|
125
|
+
panelOpen: false
|
|
126
|
+
};
|
|
127
|
+
function useDiffStore(ctx) {
|
|
128
|
+
return ctx.storage.memory(DIFF_STORE_KEY, { initial: initialDiffState });
|
|
129
|
+
}
|
|
130
|
+
var diffClosedHandler;
|
|
131
|
+
function setDiffClosedHandler(handler) {
|
|
132
|
+
diffClosedHandler = handler;
|
|
133
|
+
}
|
|
134
|
+
function notifyDiffClosed() {
|
|
135
|
+
try {
|
|
136
|
+
diffClosedHandler?.();
|
|
137
|
+
} catch {}
|
|
138
|
+
}
|
|
139
|
+
// src/ui/format.ts
|
|
140
|
+
var ELLIPSIS = "...";
|
|
141
|
+
var runtime = globalThis;
|
|
142
|
+
var graphemes = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
143
|
+
function displayWidth(text) {
|
|
144
|
+
return runtime.Bun?.stringWidth?.(text) ?? [...text].length;
|
|
145
|
+
}
|
|
146
|
+
function truncatePath(path, max) {
|
|
147
|
+
if (displayWidth(path) <= max)
|
|
148
|
+
return path;
|
|
149
|
+
const budget = max - ELLIPSIS.length;
|
|
150
|
+
if (budget <= 0)
|
|
151
|
+
return ELLIPSIS.slice(0, Math.max(0, max));
|
|
152
|
+
const parts = Array.from(graphemes.segment(path), (s) => s.segment);
|
|
153
|
+
let width = 0;
|
|
154
|
+
let start = parts.length;
|
|
155
|
+
for (let i = parts.length - 1;i >= 0; i--) {
|
|
156
|
+
const w = displayWidth(parts[i]);
|
|
157
|
+
if (width + w > budget)
|
|
158
|
+
break;
|
|
159
|
+
width += w;
|
|
160
|
+
start = i;
|
|
161
|
+
}
|
|
162
|
+
return ELLIPSIS + parts.slice(start).join("");
|
|
163
|
+
}
|
|
164
|
+
function maxTextWidth(available, reserved) {
|
|
165
|
+
return Math.max(8, available - reserved);
|
|
166
|
+
}
|
|
167
|
+
function filePathMaxWidth(rowWidth, row) {
|
|
168
|
+
const gaps = 4;
|
|
169
|
+
const fixed = FILE_ROW_BADGE_WIDTH + displayWidth(`+${row.additions}`) + displayWidth(`-${row.deletions}`);
|
|
170
|
+
return maxTextWidth(rowWidth, fixed + gaps);
|
|
171
|
+
}
|
|
172
|
+
function friendlyError(e) {
|
|
173
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
174
|
+
if (/not a git repository|not a repository|no git|vcs|worktree/i.test(msg))
|
|
175
|
+
return "Not a git repository";
|
|
176
|
+
return msg.length > 120 ? `${msg.slice(0, 117)}\u2026` : msg;
|
|
177
|
+
}
|
|
178
|
+
function statusBadge(status, theme) {
|
|
179
|
+
if (status === "added")
|
|
180
|
+
return { label: "A", color: theme.text.feedback.success.base };
|
|
181
|
+
if (status === "deleted")
|
|
182
|
+
return { label: "D", color: theme.text.feedback.error.base };
|
|
183
|
+
return { label: "M", color: theme.text.feedback.warning.base };
|
|
184
|
+
}
|
|
185
|
+
function diffLineStyle(theme, kind) {
|
|
186
|
+
if (kind === "hunk")
|
|
187
|
+
return { fg: theme.diff.text.hunkHeader, bg: undefined };
|
|
188
|
+
if (kind === "add")
|
|
189
|
+
return { fg: theme.diff.text.added, bg: theme.diff.background.added };
|
|
190
|
+
if (kind === "del")
|
|
191
|
+
return { fg: theme.diff.text.removed, bg: theme.diff.background.removed };
|
|
192
|
+
if (kind === "meta")
|
|
193
|
+
return { fg: theme.text.muted, bg: undefined };
|
|
194
|
+
return { fg: theme.diff.text.context, bg: theme.diff.background.context };
|
|
195
|
+
}
|
|
196
|
+
function diffLinePrefix(kind) {
|
|
197
|
+
if (kind === "add")
|
|
198
|
+
return "+";
|
|
199
|
+
if (kind === "del")
|
|
200
|
+
return "-";
|
|
201
|
+
if (kind === "context")
|
|
202
|
+
return " ";
|
|
203
|
+
return "";
|
|
204
|
+
}
|
|
205
|
+
// src/ui/syntax.ts
|
|
206
|
+
var SYNTAX_BY_LANGUAGE = [
|
|
207
|
+
["typescript", ["ts", "mts", "cts", "tsx", "mtsx", "js", "mjs", "cjs", "jsx"]],
|
|
208
|
+
["python", ["py"]],
|
|
209
|
+
["rust", ["rs"]],
|
|
210
|
+
["go", ["go"]],
|
|
211
|
+
["java", ["java"]],
|
|
212
|
+
["kotlin", ["kt", "kts"]],
|
|
213
|
+
["ruby", ["rb"]],
|
|
214
|
+
["php", ["php"]],
|
|
215
|
+
["swift", ["swift"]],
|
|
216
|
+
["c", ["c", "h"]],
|
|
217
|
+
["cpp", ["cpp", "cc", "hpp"]],
|
|
218
|
+
["csharp", ["cs"]],
|
|
219
|
+
["css", ["css"]],
|
|
220
|
+
["scss", ["scss"]],
|
|
221
|
+
["less", ["less"]],
|
|
222
|
+
["html", ["html", "htm"]],
|
|
223
|
+
["xml", ["xml"]],
|
|
224
|
+
["vue", ["vue"]],
|
|
225
|
+
["svelte", ["svelte"]],
|
|
226
|
+
["astro", ["astro"]],
|
|
227
|
+
["json", ["json"]],
|
|
228
|
+
["yaml", ["yaml", "yml"]],
|
|
229
|
+
["toml", ["toml"]],
|
|
230
|
+
["ini", ["ini"]],
|
|
231
|
+
["markdown", ["md", "markdown"]],
|
|
232
|
+
["bash", ["sh", "bash", "zsh"]],
|
|
233
|
+
["sql", ["sql"]],
|
|
234
|
+
["lua", ["lua"]],
|
|
235
|
+
["r", ["r"]],
|
|
236
|
+
["julia", ["jl"]],
|
|
237
|
+
["haskell", ["hs"]],
|
|
238
|
+
["ocaml", ["ml", "mli"]],
|
|
239
|
+
["elixir", ["ex", "exs"]],
|
|
240
|
+
["erlang", ["erl"]],
|
|
241
|
+
["clojure", ["clj", "cljs"]],
|
|
242
|
+
["scala", ["scala"]],
|
|
243
|
+
["groovy", ["groovy"]],
|
|
244
|
+
["dart", ["dart"]],
|
|
245
|
+
["zig", ["zig"]],
|
|
246
|
+
["nix", ["nix"]],
|
|
247
|
+
["terraform", ["tf"]],
|
|
248
|
+
["hcl", ["hcl"]],
|
|
249
|
+
["dockerfile", ["dockerfile"]],
|
|
250
|
+
["latex", ["tex"]],
|
|
251
|
+
["diff", ["diff", "patch"]]
|
|
252
|
+
];
|
|
253
|
+
var LANGUAGE_BY_EXTENSION = Object.fromEntries(SYNTAX_BY_LANGUAGE.flatMap(([lang, exts]) => exts.map((e) => [`.${e}`, lang])));
|
|
254
|
+
function syntaxFiletype(file) {
|
|
255
|
+
const dot = file.lastIndexOf(".");
|
|
256
|
+
if (dot < 0) {
|
|
257
|
+
const base = file.split("/").pop()?.toLowerCase() ?? "";
|
|
258
|
+
if (base === "dockerfile" || base === "makefile")
|
|
259
|
+
return base;
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
return LANGUAGE_BY_EXTENSION[file.slice(dot).toLowerCase()];
|
|
263
|
+
}
|
|
264
|
+
// src/diff/open-diff.ts
|
|
265
|
+
function createDiffOpener(ctx, location) {
|
|
266
|
+
const [diffView, setDiffState] = useDiffStore(ctx);
|
|
267
|
+
let activeRequestId = 0;
|
|
268
|
+
return async (f) => {
|
|
269
|
+
const requestId = ++activeRequestId;
|
|
270
|
+
const isStale = () => requestId !== activeRequestId || diffView.file !== f.file || !diffView.panelOpen;
|
|
271
|
+
setDiffState((draft) => {
|
|
272
|
+
draft.file = f.file;
|
|
273
|
+
draft.additions = f.additions;
|
|
274
|
+
draft.deletions = f.deletions;
|
|
275
|
+
draft.lines = [];
|
|
276
|
+
draft.patch = "";
|
|
277
|
+
draft.message = "";
|
|
278
|
+
draft.truncated = false;
|
|
279
|
+
draft.loading = true;
|
|
280
|
+
draft.query = "";
|
|
281
|
+
draft.matchCursor = 0;
|
|
282
|
+
draft.panelOpen = true;
|
|
283
|
+
});
|
|
284
|
+
ctx.ui.panel.open(DIFF_PANEL_NAME, { presentation: "panel" });
|
|
285
|
+
try {
|
|
286
|
+
const res = await ctx.client.vcs.diff({ location: location(), mode: "working", context: 3 });
|
|
287
|
+
if (isStale())
|
|
288
|
+
return;
|
|
289
|
+
const entry = res.data.find((d) => d.file === f.file);
|
|
290
|
+
const parsed = entry ? parseDiff(entry.patch) : [];
|
|
291
|
+
setDiffState((draft) => {
|
|
292
|
+
draft.loading = false;
|
|
293
|
+
draft.patch = entry?.patch ?? "";
|
|
294
|
+
if (!entry) {
|
|
295
|
+
draft.message = "No diff available";
|
|
296
|
+
} else if (parsed.length === 0) {
|
|
297
|
+
draft.message = "No visible changes (empty or binary file)";
|
|
298
|
+
} else if (parsed.length > MAX_DIFF_LINES) {
|
|
299
|
+
draft.lines = parsed.slice(0, MAX_DIFF_LINES);
|
|
300
|
+
draft.truncated = true;
|
|
301
|
+
} else {
|
|
302
|
+
draft.lines = parsed;
|
|
303
|
+
}
|
|
304
|
+
});
|
|
305
|
+
} catch (e) {
|
|
306
|
+
if (isStale())
|
|
307
|
+
return;
|
|
308
|
+
setDiffState((draft) => {
|
|
309
|
+
draft.loading = false;
|
|
310
|
+
draft.message = friendlyError(e);
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
// src/diff/panel.tsx
|
|
316
|
+
import { insert as _$insert3 } from "@opentui/solid";
|
|
317
|
+
import { createComponent as _$createComponent3 } from "@opentui/solid";
|
|
318
|
+
import { setProp as _$setProp3 } from "@opentui/solid";
|
|
319
|
+
import { createElement as _$createElement3 } from "@opentui/solid";
|
|
320
|
+
import { createEffect, createMemo as createMemo2, Show as Show3 } from "solid-js";
|
|
321
|
+
import { generateSyntax } from "@opencode/theme/tui";
|
|
322
|
+
|
|
323
|
+
// src/diff/body.tsx
|
|
324
|
+
import { use as _$use } from "@opentui/solid";
|
|
325
|
+
import { createComponent as _$createComponent } from "@opentui/solid";
|
|
326
|
+
import { createTextNode as _$createTextNode } from "@opentui/solid";
|
|
327
|
+
import { effect as _$effect } from "@opentui/solid";
|
|
328
|
+
import { insertNode as _$insertNode } from "@opentui/solid";
|
|
329
|
+
import { insert as _$insert } from "@opentui/solid";
|
|
330
|
+
import { memo as _$memo } from "@opentui/solid";
|
|
331
|
+
import { setProp as _$setProp } from "@opentui/solid";
|
|
332
|
+
import { createElement as _$createElement } from "@opentui/solid";
|
|
333
|
+
import { For, Show } from "solid-js";
|
|
334
|
+
function DiffLineRow(props) {
|
|
335
|
+
const theme = props.theme;
|
|
336
|
+
const row = props.row;
|
|
337
|
+
const style = diffLineStyle(theme, row.kind);
|
|
338
|
+
const isCurrent = row.state === "current";
|
|
339
|
+
const gutter = row.line !== undefined ? String(row.line).padStart(4, " ") : " ";
|
|
340
|
+
const fg = isCurrent ? theme.diff.background.context : row.state === "hit" ? theme.text.feedback.warning.base : style.fg;
|
|
341
|
+
const bg = isCurrent ? theme.text.feedback.warning.base : style.bg;
|
|
342
|
+
return (() => {
|
|
343
|
+
var _el$ = _$createElement("box"), _el$2 = _$createElement("text"), _el$3 = _$createElement("text");
|
|
344
|
+
_$insertNode(_el$, _el$2);
|
|
345
|
+
_$insertNode(_el$, _el$3);
|
|
346
|
+
_$setProp(_el$, "flexDirection", "row");
|
|
347
|
+
_$setProp(_el$, "width", "100%");
|
|
348
|
+
_$setProp(_el$2, "width", 5);
|
|
349
|
+
_$insert(_el$2, () => isCurrent ? `\u276F${gutter.slice(1)}` : `${gutter} `);
|
|
350
|
+
_$setProp(_el$3, "fg", fg);
|
|
351
|
+
_$setProp(_el$3, "bg", bg);
|
|
352
|
+
_$setProp(_el$3, "wrapMode", "word");
|
|
353
|
+
_$setProp(_el$3, "flexGrow", 1);
|
|
354
|
+
_$insert(_el$3, () => diffLinePrefix(row.kind), null);
|
|
355
|
+
_$insert(_el$3, () => row.text, null);
|
|
356
|
+
_$effect((_p$) => {
|
|
357
|
+
var _v$ = `git-panel-diff-line-${props.index}`, _v$2 = isCurrent ? theme.text.feedback.warning.base : theme.text.muted;
|
|
358
|
+
_v$ !== _p$.e && (_p$.e = _$setProp(_el$, "id", _v$, _p$.e));
|
|
359
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp(_el$2, "fg", _v$2, _p$.t));
|
|
360
|
+
return _p$;
|
|
361
|
+
}, {
|
|
362
|
+
e: undefined,
|
|
363
|
+
t: undefined
|
|
364
|
+
});
|
|
365
|
+
return _el$;
|
|
366
|
+
})();
|
|
367
|
+
}
|
|
368
|
+
function DiffBody(props) {
|
|
369
|
+
const theme = props.theme;
|
|
370
|
+
return [_$createComponent(Show, {
|
|
371
|
+
get when() {
|
|
372
|
+
return props.state.loading;
|
|
373
|
+
},
|
|
374
|
+
get children() {
|
|
375
|
+
var _el$4 = _$createElement("text");
|
|
376
|
+
_$insertNode(_el$4, _$createTextNode(`Loading diff\u2026`));
|
|
377
|
+
_$effect((_$p) => _$setProp(_el$4, "fg", theme.text.muted, _$p));
|
|
378
|
+
return _el$4;
|
|
379
|
+
}
|
|
380
|
+
}), _$createComponent(Show, {
|
|
381
|
+
get when() {
|
|
382
|
+
return _$memo(() => !!!props.state.loading)() && props.state.message;
|
|
383
|
+
},
|
|
384
|
+
get children() {
|
|
385
|
+
var _el$6 = _$createElement("text");
|
|
386
|
+
_$insert(_el$6, () => props.state.message);
|
|
387
|
+
_$effect((_$p) => _$setProp(_el$6, "fg", theme.text.muted, _$p));
|
|
388
|
+
return _el$6;
|
|
389
|
+
}
|
|
390
|
+
}), _$createComponent(Show, {
|
|
391
|
+
get when() {
|
|
392
|
+
return _$memo(() => !!!props.state.loading)() && !props.state.message;
|
|
393
|
+
},
|
|
394
|
+
get children() {
|
|
395
|
+
var _el$7 = _$createElement("scrollbox");
|
|
396
|
+
_$use((el) => props.onScrollRef(el), _el$7);
|
|
397
|
+
_$setProp(_el$7, "width", "100%");
|
|
398
|
+
_$setProp(_el$7, "flexGrow", 1);
|
|
399
|
+
_$setProp(_el$7, "flexShrink", 1);
|
|
400
|
+
_$setProp(_el$7, "minHeight", 0);
|
|
401
|
+
_$setProp(_el$7, "scrollY", true);
|
|
402
|
+
_$setProp(_el$7, "scrollX", false);
|
|
403
|
+
_$setProp(_el$7, "contentOptions", {
|
|
404
|
+
flexDirection: "column",
|
|
405
|
+
gap: 0
|
|
406
|
+
});
|
|
407
|
+
_$setProp(_el$7, "wrapperOptions", {
|
|
408
|
+
paddingRight: 1
|
|
409
|
+
});
|
|
410
|
+
_$insert(_el$7, _$createComponent(Show, {
|
|
411
|
+
get when() {
|
|
412
|
+
return props.syntaxActive;
|
|
413
|
+
},
|
|
414
|
+
get children() {
|
|
415
|
+
var _el$8 = _$createElement("diff");
|
|
416
|
+
_$setProp(_el$8, "view", "unified");
|
|
417
|
+
_$setProp(_el$8, "showLineNumbers", true);
|
|
418
|
+
_$setProp(_el$8, "wrapMode", "word");
|
|
419
|
+
_$effect((_p$) => {
|
|
420
|
+
var _v$3 = props.state.patch, _v$4 = syntaxFiletype(props.state.file), _v$5 = props.syntaxStyle, _v$6 = theme.text.base, _v$7 = theme.diff.background.added, _v$8 = theme.diff.background.removed, _v$9 = theme.diff.background.context, _v$0 = theme.diff.text.added, _v$1 = theme.diff.text.removed, _v$10 = theme.text.muted;
|
|
421
|
+
_v$3 !== _p$.e && (_p$.e = _$setProp(_el$8, "diff", _v$3, _p$.e));
|
|
422
|
+
_v$4 !== _p$.t && (_p$.t = _$setProp(_el$8, "filetype", _v$4, _p$.t));
|
|
423
|
+
_v$5 !== _p$.a && (_p$.a = _$setProp(_el$8, "syntaxStyle", _v$5, _p$.a));
|
|
424
|
+
_v$6 !== _p$.o && (_p$.o = _$setProp(_el$8, "fg", _v$6, _p$.o));
|
|
425
|
+
_v$7 !== _p$.i && (_p$.i = _$setProp(_el$8, "addedBg", _v$7, _p$.i));
|
|
426
|
+
_v$8 !== _p$.n && (_p$.n = _$setProp(_el$8, "removedBg", _v$8, _p$.n));
|
|
427
|
+
_v$9 !== _p$.s && (_p$.s = _$setProp(_el$8, "contextBg", _v$9, _p$.s));
|
|
428
|
+
_v$0 !== _p$.h && (_p$.h = _$setProp(_el$8, "addedSignColor", _v$0, _p$.h));
|
|
429
|
+
_v$1 !== _p$.r && (_p$.r = _$setProp(_el$8, "removedSignColor", _v$1, _p$.r));
|
|
430
|
+
_v$10 !== _p$.d && (_p$.d = _$setProp(_el$8, "lineNumberFg", _v$10, _p$.d));
|
|
431
|
+
return _p$;
|
|
432
|
+
}, {
|
|
433
|
+
e: undefined,
|
|
434
|
+
t: undefined,
|
|
435
|
+
a: undefined,
|
|
436
|
+
o: undefined,
|
|
437
|
+
i: undefined,
|
|
438
|
+
n: undefined,
|
|
439
|
+
s: undefined,
|
|
440
|
+
h: undefined,
|
|
441
|
+
r: undefined,
|
|
442
|
+
d: undefined
|
|
443
|
+
});
|
|
444
|
+
return _el$8;
|
|
445
|
+
}
|
|
446
|
+
}), null);
|
|
447
|
+
_$insert(_el$7, _$createComponent(Show, {
|
|
448
|
+
get when() {
|
|
449
|
+
return !props.syntaxActive;
|
|
450
|
+
},
|
|
451
|
+
get children() {
|
|
452
|
+
return _$createComponent(For, {
|
|
453
|
+
get each() {
|
|
454
|
+
return props.rows;
|
|
455
|
+
},
|
|
456
|
+
children: (row, i) => _$createComponent(DiffLineRow, {
|
|
457
|
+
theme,
|
|
458
|
+
row,
|
|
459
|
+
get index() {
|
|
460
|
+
return i();
|
|
461
|
+
}
|
|
462
|
+
})
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}), null);
|
|
466
|
+
_$insert(_el$7, _$createComponent(Show, {
|
|
467
|
+
get when() {
|
|
468
|
+
return props.state.truncated;
|
|
469
|
+
},
|
|
470
|
+
get children() {
|
|
471
|
+
var _el$9 = _$createElement("text");
|
|
472
|
+
_$insertNode(_el$9, _$createTextNode(`\u2026 truncated`));
|
|
473
|
+
_$effect((_$p) => _$setProp(_el$9, "fg", theme.text.muted, _$p));
|
|
474
|
+
return _el$9;
|
|
475
|
+
}
|
|
476
|
+
}), null);
|
|
477
|
+
_$effect((_$p) => _$setProp(_el$7, "focused", props.focused, _$p));
|
|
478
|
+
return _el$7;
|
|
479
|
+
}
|
|
480
|
+
})];
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// src/diff/header.tsx
|
|
484
|
+
import { createComponent as _$createComponent2 } from "@opentui/solid";
|
|
485
|
+
import { effect as _$effect2 } from "@opentui/solid";
|
|
486
|
+
import { memo as _$memo2 } from "@opentui/solid";
|
|
487
|
+
import { createTextNode as _$createTextNode2 } from "@opentui/solid";
|
|
488
|
+
import { insertNode as _$insertNode2 } from "@opentui/solid";
|
|
489
|
+
import { insert as _$insert2 } from "@opentui/solid";
|
|
490
|
+
import { setProp as _$setProp2 } from "@opentui/solid";
|
|
491
|
+
import { createElement as _$createElement2 } from "@opentui/solid";
|
|
492
|
+
import { Show as Show2 } from "solid-js";
|
|
493
|
+
function DiffHeader(props) {
|
|
494
|
+
const theme = props.theme;
|
|
495
|
+
return (() => {
|
|
496
|
+
var _el$ = _$createElement2("box"), _el$2 = _$createElement2("box"), _el$3 = _$createElement2("text"), _el$4 = _$createElement2("b"), _el$5 = _$createElement2("box"), _el$6 = _$createElement2("box"), _el$7 = _$createElement2("text"), _el$8 = _$createTextNode2(`+`), _el$9 = _$createElement2("text"), _el$0 = _$createTextNode2(`-`), _el$11 = _$createElement2("box"), _el$12 = _$createElement2("box"), _el$13 = _$createElement2("text"), _el$17 = _$createElement2("text"), _el$18 = _$createElement2("text"), _el$20 = _$createElement2("text");
|
|
497
|
+
_$insertNode2(_el$, _el$2);
|
|
498
|
+
_$insertNode2(_el$, _el$11);
|
|
499
|
+
_$insertNode2(_el$, _el$12);
|
|
500
|
+
_$insertNode2(_el$, _el$20);
|
|
501
|
+
_$setProp2(_el$, "flexDirection", "column");
|
|
502
|
+
_$setProp2(_el$, "gap", 0);
|
|
503
|
+
_$setProp2(_el$, "flexShrink", 0);
|
|
504
|
+
_$insertNode2(_el$2, _el$3);
|
|
505
|
+
_$insertNode2(_el$2, _el$5);
|
|
506
|
+
_$insertNode2(_el$2, _el$6);
|
|
507
|
+
_$setProp2(_el$2, "flexDirection", "row");
|
|
508
|
+
_$setProp2(_el$2, "flexShrink", 0);
|
|
509
|
+
_$insertNode2(_el$3, _el$4);
|
|
510
|
+
_$setProp2(_el$3, "wrapMode", "none");
|
|
511
|
+
_$setProp2(_el$3, "flexShrink", 0);
|
|
512
|
+
_$insert2(_el$4, () => truncatePath(props.file, maxTextWidth(props.width, displayWidth(`+${props.additions}`) + 1 + displayWidth(`-${props.deletions}`) + 1)));
|
|
513
|
+
_$setProp2(_el$5, "flexGrow", 1);
|
|
514
|
+
_$insertNode2(_el$6, _el$7);
|
|
515
|
+
_$insertNode2(_el$6, _el$9);
|
|
516
|
+
_$setProp2(_el$6, "flexDirection", "row");
|
|
517
|
+
_$setProp2(_el$6, "gap", 1);
|
|
518
|
+
_$setProp2(_el$6, "flexShrink", 0);
|
|
519
|
+
_$insertNode2(_el$7, _el$8);
|
|
520
|
+
_$setProp2(_el$7, "wrapMode", "none");
|
|
521
|
+
_$setProp2(_el$7, "flexShrink", 0);
|
|
522
|
+
_$insert2(_el$7, () => props.additions, null);
|
|
523
|
+
_$insertNode2(_el$9, _el$0);
|
|
524
|
+
_$setProp2(_el$9, "wrapMode", "none");
|
|
525
|
+
_$setProp2(_el$9, "flexShrink", 0);
|
|
526
|
+
_$insert2(_el$9, () => props.deletions, null);
|
|
527
|
+
_$insert2(_el$, _$createComponent2(Show2, {
|
|
528
|
+
get when() {
|
|
529
|
+
return props.query;
|
|
530
|
+
},
|
|
531
|
+
get children() {
|
|
532
|
+
var _el$1 = _$createElement2("text"), _el$10 = _$createTextNode2(` \xB7 `);
|
|
533
|
+
_$insertNode2(_el$1, _el$10);
|
|
534
|
+
_$setProp2(_el$1, "wrapMode", "none");
|
|
535
|
+
_$insert2(_el$1, (() => {
|
|
536
|
+
var _c$ = _$memo2(() => props.matchCount > 0);
|
|
537
|
+
return () => _c$() ? `${props.matchCursor + 1}/${props.matchCount}` : "0/0";
|
|
538
|
+
})(), _el$10);
|
|
539
|
+
_$insert2(_el$1, () => props.query, null);
|
|
540
|
+
_$effect2((_$p) => _$setProp2(_el$1, "fg", theme.text.feedback.warning.base, _$p));
|
|
541
|
+
return _el$1;
|
|
542
|
+
}
|
|
543
|
+
}), _el$11);
|
|
544
|
+
_$setProp2(_el$11, "height", 1);
|
|
545
|
+
_$setProp2(_el$11, "flexShrink", 0);
|
|
546
|
+
_$insertNode2(_el$12, _el$13);
|
|
547
|
+
_$insertNode2(_el$12, _el$17);
|
|
548
|
+
_$insertNode2(_el$12, _el$18);
|
|
549
|
+
_$setProp2(_el$12, "flexDirection", "row");
|
|
550
|
+
_$setProp2(_el$12, "flexWrap", "wrap");
|
|
551
|
+
_$setProp2(_el$12, "gap", 1);
|
|
552
|
+
_$setProp2(_el$12, "flexShrink", 0);
|
|
553
|
+
_$insertNode2(_el$13, _$createTextNode2(`[? : Search]`));
|
|
554
|
+
_$setProp2(_el$13, "wrapMode", "none");
|
|
555
|
+
_$setProp2(_el$13, "flexShrink", 0);
|
|
556
|
+
_$setProp2(_el$13, "onMouseUp", () => props.onSearch());
|
|
557
|
+
_$insert2(_el$12, _$createComponent2(Show2, {
|
|
558
|
+
get when() {
|
|
559
|
+
return props.query;
|
|
560
|
+
},
|
|
561
|
+
get children() {
|
|
562
|
+
var _el$15 = _$createElement2("text");
|
|
563
|
+
_$insertNode2(_el$15, _$createTextNode2(`[\u2191\u2193 : Jump]`));
|
|
564
|
+
_$setProp2(_el$15, "wrapMode", "none");
|
|
565
|
+
_$setProp2(_el$15, "flexShrink", 0);
|
|
566
|
+
_$effect2((_$p) => _$setProp2(_el$15, "fg", theme.text.muted, _$p));
|
|
567
|
+
return _el$15;
|
|
568
|
+
}
|
|
569
|
+
}), _el$17);
|
|
570
|
+
_$setProp2(_el$17, "wrapMode", "none");
|
|
571
|
+
_$setProp2(_el$17, "flexShrink", 0);
|
|
572
|
+
_$setProp2(_el$17, "onMouseUp", () => props.onToggleSyntax());
|
|
573
|
+
_$insert2(_el$17, () => props.syntax ? "[syntax:on]" : "[syntax:off]");
|
|
574
|
+
_$insertNode2(_el$18, _$createTextNode2(`[ Close \xB7 ESC/\u232B ]`));
|
|
575
|
+
_$setProp2(_el$18, "wrapMode", "none");
|
|
576
|
+
_$setProp2(_el$18, "flexShrink", 0);
|
|
577
|
+
_$setProp2(_el$18, "onMouseUp", () => props.onClose());
|
|
578
|
+
_$setProp2(_el$20, "wrapMode", "none");
|
|
579
|
+
_$insert2(_el$20, () => "\u2500".repeat(Math.max(1, props.width)));
|
|
580
|
+
_$effect2((_p$) => {
|
|
581
|
+
var _v$ = Math.max(1, props.width), _v$2 = theme.text.base, _v$3 = theme.diff.text.added, _v$4 = theme.diff.text.removed, _v$5 = Math.max(1, props.width), _v$6 = theme.text.muted, _v$7 = props.syntax ? theme.text.feedback.warning.base : theme.text.muted, _v$8 = theme.text.base, _v$9 = theme.text.muted;
|
|
582
|
+
_v$ !== _p$.e && (_p$.e = _$setProp2(_el$2, "width", _v$, _p$.e));
|
|
583
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp2(_el$3, "fg", _v$2, _p$.t));
|
|
584
|
+
_v$3 !== _p$.a && (_p$.a = _$setProp2(_el$7, "fg", _v$3, _p$.a));
|
|
585
|
+
_v$4 !== _p$.o && (_p$.o = _$setProp2(_el$9, "fg", _v$4, _p$.o));
|
|
586
|
+
_v$5 !== _p$.i && (_p$.i = _$setProp2(_el$12, "width", _v$5, _p$.i));
|
|
587
|
+
_v$6 !== _p$.n && (_p$.n = _$setProp2(_el$13, "fg", _v$6, _p$.n));
|
|
588
|
+
_v$7 !== _p$.s && (_p$.s = _$setProp2(_el$17, "fg", _v$7, _p$.s));
|
|
589
|
+
_v$8 !== _p$.h && (_p$.h = _$setProp2(_el$18, "fg", _v$8, _p$.h));
|
|
590
|
+
_v$9 !== _p$.r && (_p$.r = _$setProp2(_el$20, "fg", _v$9, _p$.r));
|
|
591
|
+
return _p$;
|
|
592
|
+
}, {
|
|
593
|
+
e: undefined,
|
|
594
|
+
t: undefined,
|
|
595
|
+
a: undefined,
|
|
596
|
+
o: undefined,
|
|
597
|
+
i: undefined,
|
|
598
|
+
n: undefined,
|
|
599
|
+
s: undefined,
|
|
600
|
+
h: undefined,
|
|
601
|
+
r: undefined
|
|
602
|
+
});
|
|
603
|
+
return _el$;
|
|
604
|
+
})();
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
// src/diff/keymap.ts
|
|
608
|
+
function registerDiffKeymap(ctx, deps) {
|
|
609
|
+
ctx.keymap.layer(() => {
|
|
610
|
+
const searching = deps.searching();
|
|
611
|
+
const hasMatches = deps.hasMatches();
|
|
612
|
+
const inSyntax = deps.syntaxActive();
|
|
613
|
+
return {
|
|
614
|
+
mode: "global",
|
|
615
|
+
priority: 20,
|
|
616
|
+
enabled: () => deps.input().name === DIFF_PANEL_NAME,
|
|
617
|
+
bindings: [
|
|
618
|
+
"git-panel.diff-search",
|
|
619
|
+
"git-panel.diff-next-match",
|
|
620
|
+
"git-panel.diff-prev-match",
|
|
621
|
+
"git-panel.diff-clear-search",
|
|
622
|
+
"git-panel.close-diff",
|
|
623
|
+
"git-panel.close-diff-backspace",
|
|
624
|
+
"git-panel.toggle-syntax",
|
|
625
|
+
"git-panel.syntax-down",
|
|
626
|
+
"git-panel.syntax-up"
|
|
627
|
+
],
|
|
628
|
+
commands: [
|
|
629
|
+
{
|
|
630
|
+
id: "git-panel.diff-search",
|
|
631
|
+
title: "Git Panel: Search in Diff",
|
|
632
|
+
group: "Git Panel",
|
|
633
|
+
bind: "?",
|
|
634
|
+
run: () => deps.search()
|
|
635
|
+
},
|
|
636
|
+
{
|
|
637
|
+
id: "git-panel.diff-next-match",
|
|
638
|
+
title: "Git Panel: Next Diff Match",
|
|
639
|
+
group: "Git Panel",
|
|
640
|
+
bind: "down",
|
|
641
|
+
enabled: searching && hasMatches,
|
|
642
|
+
run: () => deps.stepMatch(1)
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
id: "git-panel.diff-prev-match",
|
|
646
|
+
title: "Git Panel: Previous Diff Match",
|
|
647
|
+
group: "Git Panel",
|
|
648
|
+
bind: "up",
|
|
649
|
+
enabled: searching && hasMatches,
|
|
650
|
+
run: () => deps.stepMatch(-1)
|
|
651
|
+
},
|
|
652
|
+
{
|
|
653
|
+
id: "git-panel.diff-clear-search",
|
|
654
|
+
title: "Git Panel: Clear Diff Search",
|
|
655
|
+
group: "Git Panel",
|
|
656
|
+
bind: "escape",
|
|
657
|
+
enabled: searching,
|
|
658
|
+
run: () => deps.clearSearch()
|
|
659
|
+
},
|
|
660
|
+
{
|
|
661
|
+
id: "git-panel.close-diff",
|
|
662
|
+
title: "Git Panel: Close Diff",
|
|
663
|
+
group: "Git Panel",
|
|
664
|
+
bind: "escape",
|
|
665
|
+
enabled: !searching,
|
|
666
|
+
run: () => deps.close()
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
id: "git-panel.close-diff-backspace",
|
|
670
|
+
title: "Git Panel: Close Diff (Backspace)",
|
|
671
|
+
group: "Git Panel",
|
|
672
|
+
bind: "backspace",
|
|
673
|
+
enabled: !searching,
|
|
674
|
+
run: () => deps.close()
|
|
675
|
+
},
|
|
676
|
+
{
|
|
677
|
+
id: "git-panel.toggle-syntax",
|
|
678
|
+
title: "Git Panel: Toggle Diff Syntax Highlighting",
|
|
679
|
+
group: "Git Panel",
|
|
680
|
+
bind: "s",
|
|
681
|
+
palette: true,
|
|
682
|
+
run: () => deps.toggleSyntax()
|
|
683
|
+
},
|
|
684
|
+
{
|
|
685
|
+
id: "git-panel.syntax-down",
|
|
686
|
+
title: "Git Panel: Scroll Syntax Diff Down",
|
|
687
|
+
group: "Git Panel",
|
|
688
|
+
bind: "j",
|
|
689
|
+
enabled: inSyntax,
|
|
690
|
+
run: () => deps.scrollBy(3)
|
|
691
|
+
},
|
|
692
|
+
{
|
|
693
|
+
id: "git-panel.syntax-up",
|
|
694
|
+
title: "Git Panel: Scroll Syntax Diff Up",
|
|
695
|
+
group: "Git Panel",
|
|
696
|
+
bind: "k",
|
|
697
|
+
enabled: inSyntax,
|
|
698
|
+
run: () => deps.scrollBy(-3)
|
|
699
|
+
}
|
|
700
|
+
]
|
|
701
|
+
};
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/diff/search.tsx
|
|
706
|
+
import { createMemo } from "solid-js";
|
|
707
|
+
function createDiffSearch(state) {
|
|
708
|
+
const matchIndices = createMemo(() => {
|
|
709
|
+
const q = state.query.toLowerCase();
|
|
710
|
+
if (!q)
|
|
711
|
+
return [];
|
|
712
|
+
const out = [];
|
|
713
|
+
state.lines.forEach((line, i) => {
|
|
714
|
+
if (line.text.toLowerCase().includes(q))
|
|
715
|
+
out.push(i);
|
|
716
|
+
});
|
|
717
|
+
return out;
|
|
718
|
+
});
|
|
719
|
+
const currentMatchIndex = createMemo(() => {
|
|
720
|
+
const m = matchIndices();
|
|
721
|
+
if (m.length === 0)
|
|
722
|
+
return -1;
|
|
723
|
+
return m[(state.matchCursor % m.length + m.length) % m.length];
|
|
724
|
+
});
|
|
725
|
+
let previousRows = [];
|
|
726
|
+
const visibleRows = createMemo(() => {
|
|
727
|
+
const q = state.query.toLowerCase();
|
|
728
|
+
const cur = currentMatchIndex();
|
|
729
|
+
const prev = previousRows;
|
|
730
|
+
const next = state.lines.map((line, idx) => {
|
|
731
|
+
const hit = q !== "" && line.text.toLowerCase().includes(q);
|
|
732
|
+
const rowState = idx === cur ? "current" : hit ? "hit" : "plain";
|
|
733
|
+
const p = prev[idx];
|
|
734
|
+
if (p && p.kind === line.kind && p.text === line.text && p.line === line.line && p.state === rowState)
|
|
735
|
+
return p;
|
|
736
|
+
return {
|
|
737
|
+
kind: line.kind,
|
|
738
|
+
text: line.text,
|
|
739
|
+
line: line.line,
|
|
740
|
+
state: rowState
|
|
741
|
+
};
|
|
742
|
+
});
|
|
743
|
+
previousRows = next;
|
|
744
|
+
return next;
|
|
745
|
+
});
|
|
746
|
+
return {
|
|
747
|
+
matchIndices,
|
|
748
|
+
currentMatchIndex,
|
|
749
|
+
visibleRows
|
|
750
|
+
};
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
// src/diff/panel.tsx
|
|
754
|
+
function DiffPanel(props) {
|
|
755
|
+
const ctx = props.ctx;
|
|
756
|
+
const input = () => props.input;
|
|
757
|
+
const theme = ctx.theme;
|
|
758
|
+
const [diffState, setDiffState] = useDiffStore(ctx);
|
|
759
|
+
const [settings, setSettings] = ctx.storage.store(SETTINGS_STORE_KEY, {
|
|
760
|
+
initial: {
|
|
761
|
+
syntax: false
|
|
762
|
+
}
|
|
763
|
+
});
|
|
764
|
+
const toggleSyntax = () => {
|
|
765
|
+
setSettings((draft) => {
|
|
766
|
+
draft.syntax = !draft.syntax;
|
|
767
|
+
}).catch(() => {});
|
|
768
|
+
};
|
|
769
|
+
const isSyntaxViewActive = () => settings.syntax && !diffState.truncated && diffState.query === "" && diffState.patch !== "";
|
|
770
|
+
const syntaxHighlightStyle = createMemo2(() => settings.syntax ? generateSyntax(theme) : undefined);
|
|
771
|
+
const search = createDiffSearch(diffState);
|
|
772
|
+
createEffect(() => {
|
|
773
|
+
if (input().name !== DIFF_PANEL_NAME && diffState.panelOpen) {
|
|
774
|
+
setDiffState((draft) => {
|
|
775
|
+
draft.panelOpen = false;
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
});
|
|
779
|
+
createEffect(() => {
|
|
780
|
+
if (input().name === DIFF_PANEL_NAME && !input().focused) {
|
|
781
|
+
try {
|
|
782
|
+
input().focus();
|
|
783
|
+
} catch {}
|
|
784
|
+
}
|
|
785
|
+
});
|
|
786
|
+
let scrollEl;
|
|
787
|
+
const safeScrollTo = (y) => {
|
|
788
|
+
const el = scrollEl;
|
|
789
|
+
if (!el)
|
|
790
|
+
return;
|
|
791
|
+
try {
|
|
792
|
+
el.scrollTo({
|
|
793
|
+
x: 0,
|
|
794
|
+
y: Math.max(0, y)
|
|
795
|
+
});
|
|
796
|
+
} catch {}
|
|
797
|
+
};
|
|
798
|
+
createEffect(() => {
|
|
799
|
+
const line = search.currentMatchIndex();
|
|
800
|
+
if (line < 0 || input().name !== DIFF_PANEL_NAME || !scrollEl)
|
|
801
|
+
return;
|
|
802
|
+
const target = Math.max(0, line - 10);
|
|
803
|
+
setTimeout(() => safeScrollTo(target), 50);
|
|
804
|
+
});
|
|
805
|
+
const scrollSyntaxBy = (delta) => {
|
|
806
|
+
if (!scrollEl)
|
|
807
|
+
return;
|
|
808
|
+
safeScrollTo(scrollEl.scrollTop + delta);
|
|
809
|
+
};
|
|
810
|
+
const stepSearchMatch = (dir) => {
|
|
811
|
+
const m = search.matchIndices();
|
|
812
|
+
if (m.length === 0)
|
|
813
|
+
return;
|
|
814
|
+
setDiffState((draft) => {
|
|
815
|
+
draft.matchCursor = (draft.matchCursor + dir + m.length) % m.length;
|
|
816
|
+
});
|
|
817
|
+
};
|
|
818
|
+
const clearSearch = () => {
|
|
819
|
+
setDiffState((draft) => {
|
|
820
|
+
draft.query = "";
|
|
821
|
+
draft.matchCursor = 0;
|
|
822
|
+
});
|
|
823
|
+
};
|
|
824
|
+
const closeDiff = () => {
|
|
825
|
+
setDiffState((draft) => {
|
|
826
|
+
draft.panelOpen = false;
|
|
827
|
+
});
|
|
828
|
+
ctx.ui.panel.close();
|
|
829
|
+
notifyDiffClosed();
|
|
830
|
+
};
|
|
831
|
+
const promptSearchQuery = async () => {
|
|
832
|
+
try {
|
|
833
|
+
const q = await ctx.ui.dialog.prompt({
|
|
834
|
+
title: "Search in diff",
|
|
835
|
+
placeholder: "Type to highlight matches",
|
|
836
|
+
value: diffState.query
|
|
837
|
+
});
|
|
838
|
+
if (q === undefined)
|
|
839
|
+
return;
|
|
840
|
+
setDiffState((draft) => {
|
|
841
|
+
draft.query = q;
|
|
842
|
+
draft.matchCursor = 0;
|
|
843
|
+
});
|
|
844
|
+
} catch (e) {
|
|
845
|
+
ctx.ui.toast.show({
|
|
846
|
+
message: `Search failed: ${friendlyError(e)}`,
|
|
847
|
+
variant: "error"
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
registerDiffKeymap(ctx, {
|
|
852
|
+
input,
|
|
853
|
+
searching: () => diffState.query.length > 0,
|
|
854
|
+
hasMatches: () => search.matchIndices().length > 0,
|
|
855
|
+
syntaxActive: isSyntaxViewActive,
|
|
856
|
+
search: () => void promptSearchQuery(),
|
|
857
|
+
stepMatch: stepSearchMatch,
|
|
858
|
+
clearSearch,
|
|
859
|
+
close: closeDiff,
|
|
860
|
+
toggleSyntax,
|
|
861
|
+
scrollBy: scrollSyntaxBy
|
|
862
|
+
});
|
|
863
|
+
return _$createComponent3(Show3, {
|
|
864
|
+
get when() {
|
|
865
|
+
return input().name === DIFF_PANEL_NAME;
|
|
866
|
+
},
|
|
867
|
+
get children() {
|
|
868
|
+
var _el$ = _$createElement3("box");
|
|
869
|
+
_$setProp3(_el$, "flexDirection", "column");
|
|
870
|
+
_$setProp3(_el$, "gap", 1);
|
|
871
|
+
_$setProp3(_el$, "width", "100%");
|
|
872
|
+
_$setProp3(_el$, "height", "100%");
|
|
873
|
+
_$insert3(_el$, _$createComponent3(DiffHeader, {
|
|
874
|
+
theme,
|
|
875
|
+
get width() {
|
|
876
|
+
return input().width;
|
|
877
|
+
},
|
|
878
|
+
get file() {
|
|
879
|
+
return diffState.file;
|
|
880
|
+
},
|
|
881
|
+
get additions() {
|
|
882
|
+
return diffState.additions;
|
|
883
|
+
},
|
|
884
|
+
get deletions() {
|
|
885
|
+
return diffState.deletions;
|
|
886
|
+
},
|
|
887
|
+
get query() {
|
|
888
|
+
return diffState.query;
|
|
889
|
+
},
|
|
890
|
+
get matchCursor() {
|
|
891
|
+
return diffState.matchCursor;
|
|
892
|
+
},
|
|
893
|
+
get matchCount() {
|
|
894
|
+
return search.matchIndices().length;
|
|
895
|
+
},
|
|
896
|
+
get syntax() {
|
|
897
|
+
return settings.syntax;
|
|
898
|
+
},
|
|
899
|
+
onSearch: () => void promptSearchQuery(),
|
|
900
|
+
onToggleSyntax: toggleSyntax,
|
|
901
|
+
onClose: closeDiff
|
|
902
|
+
}), null);
|
|
903
|
+
_$insert3(_el$, _$createComponent3(DiffBody, {
|
|
904
|
+
theme,
|
|
905
|
+
state: diffState,
|
|
906
|
+
get rows() {
|
|
907
|
+
return search.visibleRows();
|
|
908
|
+
},
|
|
909
|
+
get focused() {
|
|
910
|
+
return input().focused;
|
|
911
|
+
},
|
|
912
|
+
get syntaxActive() {
|
|
913
|
+
return isSyntaxViewActive();
|
|
914
|
+
},
|
|
915
|
+
get syntaxStyle() {
|
|
916
|
+
return syntaxHighlightStyle();
|
|
917
|
+
},
|
|
918
|
+
onScrollRef: (el) => {
|
|
919
|
+
scrollEl = el;
|
|
920
|
+
}
|
|
921
|
+
}), null);
|
|
922
|
+
return _el$;
|
|
923
|
+
}
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
// src/sidebar/view.tsx
|
|
927
|
+
import { effect as _$effect6 } from "@opentui/solid";
|
|
928
|
+
import { insert as _$insert7 } from "@opentui/solid";
|
|
929
|
+
import { use as _$use2 } from "@opentui/solid";
|
|
930
|
+
import { setProp as _$setProp7 } from "@opentui/solid";
|
|
931
|
+
import { createElement as _$createElement7 } from "@opentui/solid";
|
|
932
|
+
import { memo as _$memo5 } from "@opentui/solid";
|
|
933
|
+
import { createComponent as _$createComponent7 } from "@opentui/solid";
|
|
934
|
+
import { createEffect as createEffect3, createSignal as createSignal3, onCleanup as onCleanup4, Show as Show6 } from "solid-js";
|
|
935
|
+
|
|
936
|
+
// src/sidebar/file-list.tsx
|
|
937
|
+
import { effect as _$effect3 } from "@opentui/solid";
|
|
938
|
+
import { createTextNode as _$createTextNode3 } from "@opentui/solid";
|
|
939
|
+
import { insertNode as _$insertNode3 } from "@opentui/solid";
|
|
940
|
+
import { insert as _$insert4 } from "@opentui/solid";
|
|
941
|
+
import { createComponent as _$createComponent4 } from "@opentui/solid";
|
|
942
|
+
import { setProp as _$setProp4 } from "@opentui/solid";
|
|
943
|
+
import { createElement as _$createElement4 } from "@opentui/solid";
|
|
944
|
+
import { For as For2 } from "solid-js";
|
|
945
|
+
function FileList(props) {
|
|
946
|
+
const ctx = props.ctx;
|
|
947
|
+
return (() => {
|
|
948
|
+
var _el$ = _$createElement4("box");
|
|
949
|
+
_$setProp4(_el$, "flexDirection", "column");
|
|
950
|
+
_$setProp4(_el$, "gap", 0);
|
|
951
|
+
_$insert4(_el$, _$createComponent4(For2, {
|
|
952
|
+
get each() {
|
|
953
|
+
return props.files;
|
|
954
|
+
},
|
|
955
|
+
children: (f, i) => {
|
|
956
|
+
const b = statusBadge(f.status, ctx.theme);
|
|
957
|
+
const label = () => props.staged.has(f.file) ? `[${b.label}]` : ` ${b.label} `;
|
|
958
|
+
return (() => {
|
|
959
|
+
var _el$2 = _$createElement4("box"), _el$3 = _$createElement4("text"), _el$4 = _$createElement4("b"), _el$5 = _$createElement4("text"), _el$6 = _$createElement4("box"), _el$7 = _$createElement4("text"), _el$8 = _$createTextNode3(`+`), _el$9 = _$createElement4("text"), _el$0 = _$createTextNode3(`-`);
|
|
960
|
+
_$insertNode3(_el$2, _el$3);
|
|
961
|
+
_$insertNode3(_el$2, _el$5);
|
|
962
|
+
_$insertNode3(_el$2, _el$6);
|
|
963
|
+
_$insertNode3(_el$2, _el$7);
|
|
964
|
+
_$insertNode3(_el$2, _el$9);
|
|
965
|
+
_$setProp4(_el$2, "flexDirection", "row");
|
|
966
|
+
_$setProp4(_el$2, "gap", 1);
|
|
967
|
+
_$setProp4(_el$2, "onMouseUp", () => props.onSelect(f, i()));
|
|
968
|
+
_$insertNode3(_el$3, _el$4);
|
|
969
|
+
_$setProp4(_el$3, "width", FILE_ROW_BADGE_WIDTH);
|
|
970
|
+
_$insert4(_el$4, label);
|
|
971
|
+
_$setProp4(_el$5, "wrapMode", "none");
|
|
972
|
+
_$insert4(_el$5, () => truncatePath(f.file, filePathMaxWidth(props.width, {
|
|
973
|
+
additions: f.additions,
|
|
974
|
+
deletions: f.deletions
|
|
975
|
+
})));
|
|
976
|
+
_$setProp4(_el$6, "flexGrow", 1);
|
|
977
|
+
_$insertNode3(_el$7, _el$8);
|
|
978
|
+
_$insert4(_el$7, () => f.additions, null);
|
|
979
|
+
_$insertNode3(_el$9, _el$0);
|
|
980
|
+
_$insert4(_el$9, () => f.deletions, null);
|
|
981
|
+
_$effect3((_p$) => {
|
|
982
|
+
var _v$ = b.color, _v$2 = props.navMode && i() === props.selected ? ctx.theme.text.base : ctx.theme.text.muted, _v$3 = ctx.theme.diff.text.added, _v$4 = ctx.theme.diff.text.removed;
|
|
983
|
+
_v$ !== _p$.e && (_p$.e = _$setProp4(_el$3, "fg", _v$, _p$.e));
|
|
984
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp4(_el$5, "fg", _v$2, _p$.t));
|
|
985
|
+
_v$3 !== _p$.a && (_p$.a = _$setProp4(_el$7, "fg", _v$3, _p$.a));
|
|
986
|
+
_v$4 !== _p$.o && (_p$.o = _$setProp4(_el$9, "fg", _v$4, _p$.o));
|
|
987
|
+
return _p$;
|
|
988
|
+
}, {
|
|
989
|
+
e: undefined,
|
|
990
|
+
t: undefined,
|
|
991
|
+
a: undefined,
|
|
992
|
+
o: undefined
|
|
993
|
+
});
|
|
994
|
+
return _el$2;
|
|
995
|
+
})();
|
|
996
|
+
}
|
|
997
|
+
}));
|
|
998
|
+
return _el$;
|
|
999
|
+
})();
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
// src/sidebar/focus.tsx
|
|
1003
|
+
import { onCleanup } from "solid-js";
|
|
1004
|
+
function bindSidebarFocus(ctx, opts) {
|
|
1005
|
+
const isInsideSidebar = (node) => {
|
|
1006
|
+
const container = opts.container();
|
|
1007
|
+
let current = node;
|
|
1008
|
+
while (current) {
|
|
1009
|
+
if (current === container)
|
|
1010
|
+
return true;
|
|
1011
|
+
current = current.parent;
|
|
1012
|
+
}
|
|
1013
|
+
return false;
|
|
1014
|
+
};
|
|
1015
|
+
const rendererEvents = ctx.renderer;
|
|
1016
|
+
const handleFocusedRenderable = (current) => {
|
|
1017
|
+
if (!opts.active())
|
|
1018
|
+
return;
|
|
1019
|
+
if (ctx.ui.panel.current()?.name === DIFF_PANEL_NAME)
|
|
1020
|
+
return;
|
|
1021
|
+
if (!isInsideSidebar(current))
|
|
1022
|
+
opts.exit();
|
|
1023
|
+
};
|
|
1024
|
+
const handleFocusedEditor = (editor) => {
|
|
1025
|
+
if (editor == null)
|
|
1026
|
+
return;
|
|
1027
|
+
if (opts.active())
|
|
1028
|
+
opts.exit();
|
|
1029
|
+
};
|
|
1030
|
+
rendererEvents.on("focused_renderable", handleFocusedRenderable);
|
|
1031
|
+
rendererEvents.on("focused_editor", handleFocusedEditor);
|
|
1032
|
+
onCleanup(() => {
|
|
1033
|
+
rendererEvents.off("focused_renderable", handleFocusedRenderable);
|
|
1034
|
+
rendererEvents.off("focused_editor", handleFocusedEditor);
|
|
1035
|
+
});
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// src/sidebar/header.tsx
|
|
1039
|
+
import { createComponent as _$createComponent5 } from "@opentui/solid";
|
|
1040
|
+
import { effect as _$effect4 } from "@opentui/solid";
|
|
1041
|
+
import { insert as _$insert5 } from "@opentui/solid";
|
|
1042
|
+
import { memo as _$memo3 } from "@opentui/solid";
|
|
1043
|
+
import { createTextNode as _$createTextNode4 } from "@opentui/solid";
|
|
1044
|
+
import { insertNode as _$insertNode4 } from "@opentui/solid";
|
|
1045
|
+
import { setProp as _$setProp5 } from "@opentui/solid";
|
|
1046
|
+
import { createElement as _$createElement5 } from "@opentui/solid";
|
|
1047
|
+
import { Show as Show4 } from "solid-js";
|
|
1048
|
+
function SidebarHeader(props) {
|
|
1049
|
+
const ctx = props.ctx;
|
|
1050
|
+
return (() => {
|
|
1051
|
+
var _el$ = _$createElement5("box"), _el$2 = _$createElement5("box"), _el$3 = _$createElement5("text"), _el$4 = _$createElement5("b"), _el$6 = _$createElement5("box");
|
|
1052
|
+
_$insertNode4(_el$, _el$2);
|
|
1053
|
+
_$setProp5(_el$, "flexDirection", "column");
|
|
1054
|
+
_$setProp5(_el$, "gap", 0);
|
|
1055
|
+
_$insertNode4(_el$2, _el$3);
|
|
1056
|
+
_$insertNode4(_el$2, _el$6);
|
|
1057
|
+
_$setProp5(_el$2, "flexDirection", "row");
|
|
1058
|
+
_$setProp5(_el$2, "gap", 1);
|
|
1059
|
+
_$insertNode4(_el$3, _el$4);
|
|
1060
|
+
_$insertNode4(_el$4, _$createTextNode4(`Git Panel`));
|
|
1061
|
+
_$setProp5(_el$6, "flexGrow", 1);
|
|
1062
|
+
_$insert5(_el$2, _$createComponent5(Show4, {
|
|
1063
|
+
get when() {
|
|
1064
|
+
return _$memo3(() => !!props.branch)() && props.fileCount > 0;
|
|
1065
|
+
},
|
|
1066
|
+
get children() {
|
|
1067
|
+
return [(() => {
|
|
1068
|
+
var _el$7 = _$createElement5("text"), _el$8 = _$createTextNode4(`\u25B6 `), _el$9 = _$createTextNode4(` `);
|
|
1069
|
+
_$insertNode4(_el$7, _el$8);
|
|
1070
|
+
_$insertNode4(_el$7, _el$9);
|
|
1071
|
+
_$setProp5(_el$7, "onMouseUp", () => props.onBranchClick());
|
|
1072
|
+
_$insert5(_el$7, () => props.branch, _el$9);
|
|
1073
|
+
_$effect4((_$p) => _$setProp5(_el$7, "fg", ctx.theme.text.muted, _$p));
|
|
1074
|
+
return _el$7;
|
|
1075
|
+
})(), (() => {
|
|
1076
|
+
var _el$0 = _$createElement5("text"), _el$1 = _$createTextNode4(`(`), _el$10 = _$createTextNode4(`)`);
|
|
1077
|
+
_$insertNode4(_el$0, _el$1);
|
|
1078
|
+
_$insertNode4(_el$0, _el$10);
|
|
1079
|
+
_$insert5(_el$0, () => props.fileCount, _el$10);
|
|
1080
|
+
_$effect4((_$p) => _$setProp5(_el$0, "fg", ctx.theme.text.base, _$p));
|
|
1081
|
+
return _el$0;
|
|
1082
|
+
})()];
|
|
1083
|
+
}
|
|
1084
|
+
}), null);
|
|
1085
|
+
_$insert5(_el$2, _$createComponent5(Show4, {
|
|
1086
|
+
get when() {
|
|
1087
|
+
return _$memo3(() => !!props.ready)() && props.fileCount > 0;
|
|
1088
|
+
},
|
|
1089
|
+
get children() {
|
|
1090
|
+
return [(() => {
|
|
1091
|
+
var _el$11 = _$createElement5("text"), _el$12 = _$createTextNode4(`+`);
|
|
1092
|
+
_$insertNode4(_el$11, _el$12);
|
|
1093
|
+
_$insert5(_el$11, () => props.additions, null);
|
|
1094
|
+
_$effect4((_$p) => _$setProp5(_el$11, "fg", ctx.theme.diff.text.added, _$p));
|
|
1095
|
+
return _el$11;
|
|
1096
|
+
})(), (() => {
|
|
1097
|
+
var _el$13 = _$createElement5("text"), _el$14 = _$createTextNode4(`-`);
|
|
1098
|
+
_$insertNode4(_el$13, _el$14);
|
|
1099
|
+
_$insert5(_el$13, () => props.deletions, null);
|
|
1100
|
+
_$effect4((_$p) => _$setProp5(_el$13, "fg", ctx.theme.diff.text.removed, _$p));
|
|
1101
|
+
return _el$13;
|
|
1102
|
+
})()];
|
|
1103
|
+
}
|
|
1104
|
+
}), null);
|
|
1105
|
+
_$insert5(_el$, _$createComponent5(Show4, {
|
|
1106
|
+
get when() {
|
|
1107
|
+
return _$memo3(() => !!(props.ready && props.fileCount > 0))() && props.navMode;
|
|
1108
|
+
},
|
|
1109
|
+
get children() {
|
|
1110
|
+
var _el$15 = _$createElement5("box"), _el$16 = _$createElement5("box");
|
|
1111
|
+
_$insertNode4(_el$15, _el$16);
|
|
1112
|
+
_$setProp5(_el$15, "flexDirection", "row");
|
|
1113
|
+
_$setProp5(_el$15, "gap", 1);
|
|
1114
|
+
_$setProp5(_el$16, "flexGrow", 1);
|
|
1115
|
+
_$insert5(_el$15, _$createComponent5(Show4, {
|
|
1116
|
+
get when() {
|
|
1117
|
+
return props.stageMode;
|
|
1118
|
+
},
|
|
1119
|
+
get fallback() {
|
|
1120
|
+
return (() => {
|
|
1121
|
+
var _el$21 = _$createElement5("text");
|
|
1122
|
+
_$insertNode4(_el$21, _$createTextNode4(`[\u2190 \u2192 : stage/unstage]`));
|
|
1123
|
+
_$effect4((_$p) => _$setProp5(_el$21, "fg", ctx.theme.text.muted, _$p));
|
|
1124
|
+
return _el$21;
|
|
1125
|
+
})();
|
|
1126
|
+
},
|
|
1127
|
+
get children() {
|
|
1128
|
+
var _el$17 = _$createElement5("text");
|
|
1129
|
+
_$insertNode4(_el$17, _$createTextNode4(`[click : stage/unstage]`));
|
|
1130
|
+
_$effect4((_$p) => _$setProp5(_el$17, "fg", ctx.theme.text.feedback.warning.base, _$p));
|
|
1131
|
+
return _el$17;
|
|
1132
|
+
}
|
|
1133
|
+
}), null);
|
|
1134
|
+
_$insert5(_el$15, _$createComponent5(Show4, {
|
|
1135
|
+
get when() {
|
|
1136
|
+
return props.hasStaged;
|
|
1137
|
+
},
|
|
1138
|
+
get children() {
|
|
1139
|
+
var _el$19 = _$createElement5("text");
|
|
1140
|
+
_$insertNode4(_el$19, _$createTextNode4(`[c : Commit]`));
|
|
1141
|
+
_$setProp5(_el$19, "onMouseUp", () => props.onCommit());
|
|
1142
|
+
_$effect4((_$p) => _$setProp5(_el$19, "fg", ctx.theme.text.feedback.success.base, _$p));
|
|
1143
|
+
return _el$19;
|
|
1144
|
+
}
|
|
1145
|
+
}), null);
|
|
1146
|
+
return _el$15;
|
|
1147
|
+
}
|
|
1148
|
+
}), null);
|
|
1149
|
+
_$effect4((_$p) => _$setProp5(_el$3, "fg", ctx.theme.text.base, _$p));
|
|
1150
|
+
return _el$;
|
|
1151
|
+
})();
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// src/sidebar/keymap.ts
|
|
1155
|
+
function buildFileCommands(actions, binds, palette, hasFiles) {
|
|
1156
|
+
return [
|
|
1157
|
+
{
|
|
1158
|
+
id: "git-panel.next",
|
|
1159
|
+
title: "Git Panel: Next File",
|
|
1160
|
+
group: "Git Panel",
|
|
1161
|
+
bind: binds.next,
|
|
1162
|
+
enabled: hasFiles,
|
|
1163
|
+
run: () => actions.next()
|
|
1164
|
+
},
|
|
1165
|
+
{
|
|
1166
|
+
id: "git-panel.prev",
|
|
1167
|
+
title: "Git Panel: Previous File",
|
|
1168
|
+
group: "Git Panel",
|
|
1169
|
+
bind: binds.prev,
|
|
1170
|
+
enabled: hasFiles,
|
|
1171
|
+
run: () => actions.prev()
|
|
1172
|
+
},
|
|
1173
|
+
{
|
|
1174
|
+
id: "git-panel.open",
|
|
1175
|
+
title: "Git Panel: Open Diff",
|
|
1176
|
+
group: "Git Panel",
|
|
1177
|
+
bind: binds.open,
|
|
1178
|
+
palette: palette ? true : undefined,
|
|
1179
|
+
enabled: hasFiles,
|
|
1180
|
+
run: () => actions.open()
|
|
1181
|
+
},
|
|
1182
|
+
{
|
|
1183
|
+
id: "git-panel.stage",
|
|
1184
|
+
title: "Git Panel: Stage File",
|
|
1185
|
+
group: "Git Panel",
|
|
1186
|
+
bind: binds.stage,
|
|
1187
|
+
palette: palette ? true : undefined,
|
|
1188
|
+
enabled: hasFiles,
|
|
1189
|
+
run: () => actions.setStaged(true)
|
|
1190
|
+
},
|
|
1191
|
+
{
|
|
1192
|
+
id: "git-panel.unstage",
|
|
1193
|
+
title: "Git Panel: Unstage File",
|
|
1194
|
+
group: "Git Panel",
|
|
1195
|
+
bind: binds.unstage,
|
|
1196
|
+
palette: palette ? true : undefined,
|
|
1197
|
+
enabled: hasFiles,
|
|
1198
|
+
run: () => actions.setStaged(false)
|
|
1199
|
+
},
|
|
1200
|
+
{
|
|
1201
|
+
id: "git-panel.commit",
|
|
1202
|
+
title: "Git Panel: Commit Staged",
|
|
1203
|
+
group: "Git Panel",
|
|
1204
|
+
bind: binds.commit,
|
|
1205
|
+
palette: palette ? true : undefined,
|
|
1206
|
+
run: () => actions.commit()
|
|
1207
|
+
}
|
|
1208
|
+
];
|
|
1209
|
+
}
|
|
1210
|
+
function registerSidebarKeymaps(ctx, deps) {
|
|
1211
|
+
const { files, nav, actions } = deps;
|
|
1212
|
+
ctx.keymap.layer(() => {
|
|
1213
|
+
const hasFiles = files().length > 0;
|
|
1214
|
+
return {
|
|
1215
|
+
mode: "global",
|
|
1216
|
+
bindings: ["git-panel.focus-list", "git-panel.next", "git-panel.prev", "git-panel.open", "git-panel.stage", "git-panel.unstage", "git-panel.commit"],
|
|
1217
|
+
commands: [
|
|
1218
|
+
{
|
|
1219
|
+
id: "git-panel.refresh",
|
|
1220
|
+
title: "Git Panel: Refresh",
|
|
1221
|
+
group: "Git Panel",
|
|
1222
|
+
palette: true,
|
|
1223
|
+
run: () => deps.requestRefresh()
|
|
1224
|
+
},
|
|
1225
|
+
{
|
|
1226
|
+
id: "git-panel.focus-list",
|
|
1227
|
+
title: "Git Panel: Focus List",
|
|
1228
|
+
group: "Git Panel",
|
|
1229
|
+
bind: "<leader>G",
|
|
1230
|
+
palette: true,
|
|
1231
|
+
enabled: hasFiles,
|
|
1232
|
+
run: () => nav.enter()
|
|
1233
|
+
},
|
|
1234
|
+
...buildFileCommands(actions, {}, true, hasFiles)
|
|
1235
|
+
]
|
|
1236
|
+
};
|
|
1237
|
+
});
|
|
1238
|
+
ctx.keymap.layer(() => {
|
|
1239
|
+
const hasFiles = files().length > 0;
|
|
1240
|
+
const canExitNav = nav.navMode() && !deps.diffOpen() && !nav.stageMode();
|
|
1241
|
+
return {
|
|
1242
|
+
mode: LIST_NAV_MODE,
|
|
1243
|
+
priority: 10,
|
|
1244
|
+
bindings: ["git-panel.next", "git-panel.prev", "git-panel.open", "git-panel.stage", "git-panel.unstage", "git-panel.commit", "git-panel.exit-stage-mode", "git-panel.unfocus-list"],
|
|
1245
|
+
commands: [
|
|
1246
|
+
...buildFileCommands(actions, { next: "down", prev: "up", open: "return", stage: "right", unstage: "left", commit: "c" }, false, hasFiles),
|
|
1247
|
+
{
|
|
1248
|
+
id: "git-panel.exit-stage-mode",
|
|
1249
|
+
title: "Git Panel: Exit Stage Mode",
|
|
1250
|
+
group: "Git Panel",
|
|
1251
|
+
bind: "escape",
|
|
1252
|
+
enabled: nav.stageMode(),
|
|
1253
|
+
run: () => nav.setStageMode(false)
|
|
1254
|
+
},
|
|
1255
|
+
{
|
|
1256
|
+
id: "git-panel.unfocus-list",
|
|
1257
|
+
title: "Git Panel: Exit List Navigation",
|
|
1258
|
+
group: "Git Panel",
|
|
1259
|
+
bind: "escape",
|
|
1260
|
+
enabled: canExitNav,
|
|
1261
|
+
run: () => nav.exit()
|
|
1262
|
+
}
|
|
1263
|
+
]
|
|
1264
|
+
};
|
|
1265
|
+
});
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// src/sidebar/list-navigation.tsx
|
|
1269
|
+
import { createSignal, onCleanup as onCleanup2 } from "solid-js";
|
|
1270
|
+
function createListNavigation(ctx, files) {
|
|
1271
|
+
const [navMode, setNavMode] = createSignal(false);
|
|
1272
|
+
const [stageMode, setStageMode] = createSignal(false);
|
|
1273
|
+
const [selected, setSelected] = createSignal(0);
|
|
1274
|
+
let popListNavMode = null;
|
|
1275
|
+
const pushListNavMode = () => {
|
|
1276
|
+
popListNavMode = ctx.keymap.mode.push(LIST_NAV_MODE);
|
|
1277
|
+
};
|
|
1278
|
+
const enter = () => {
|
|
1279
|
+
if (popListNavMode) {
|
|
1280
|
+
try {
|
|
1281
|
+
if (ctx.keymap.mode.current() === LIST_NAV_MODE)
|
|
1282
|
+
return;
|
|
1283
|
+
} catch {
|
|
1284
|
+
return;
|
|
1285
|
+
}
|
|
1286
|
+
popListNavMode = null;
|
|
1287
|
+
}
|
|
1288
|
+
pushListNavMode();
|
|
1289
|
+
setNavMode(true);
|
|
1290
|
+
};
|
|
1291
|
+
const exit = () => {
|
|
1292
|
+
try {
|
|
1293
|
+
if (ctx.keymap.mode.current() === LIST_NAV_MODE)
|
|
1294
|
+
popListNavMode?.();
|
|
1295
|
+
} catch {}
|
|
1296
|
+
popListNavMode = null;
|
|
1297
|
+
setNavMode(false);
|
|
1298
|
+
setStageMode(false);
|
|
1299
|
+
};
|
|
1300
|
+
const ensure = () => {
|
|
1301
|
+
if (!navMode())
|
|
1302
|
+
return;
|
|
1303
|
+
try {
|
|
1304
|
+
if (ctx.keymap.mode.current() !== LIST_NAV_MODE) {
|
|
1305
|
+
popListNavMode = null;
|
|
1306
|
+
pushListNavMode();
|
|
1307
|
+
}
|
|
1308
|
+
} catch {}
|
|
1309
|
+
};
|
|
1310
|
+
const selectNext = () => {
|
|
1311
|
+
const count = files().length;
|
|
1312
|
+
if (count > 0)
|
|
1313
|
+
setSelected((prev) => (prev + 1) % count);
|
|
1314
|
+
};
|
|
1315
|
+
const selectPrevious = () => {
|
|
1316
|
+
const count = files().length;
|
|
1317
|
+
if (count > 0)
|
|
1318
|
+
setSelected((prev) => (prev - 1 + count) % count);
|
|
1319
|
+
};
|
|
1320
|
+
const clampSelection = (count) => {
|
|
1321
|
+
setSelected((prev) => Math.min(prev, Math.max(0, count - 1)));
|
|
1322
|
+
};
|
|
1323
|
+
const selectedFile = () => {
|
|
1324
|
+
const list = files();
|
|
1325
|
+
return list[Math.min(selected(), list.length - 1)];
|
|
1326
|
+
};
|
|
1327
|
+
const toggleStageMode = () => {
|
|
1328
|
+
if (files().length === 0)
|
|
1329
|
+
return;
|
|
1330
|
+
const next = !stageMode();
|
|
1331
|
+
setStageMode(next);
|
|
1332
|
+
if (next)
|
|
1333
|
+
enter();
|
|
1334
|
+
};
|
|
1335
|
+
onCleanup2(exit);
|
|
1336
|
+
return {
|
|
1337
|
+
navMode,
|
|
1338
|
+
stageMode,
|
|
1339
|
+
setStageMode,
|
|
1340
|
+
selected,
|
|
1341
|
+
setSelected,
|
|
1342
|
+
enter,
|
|
1343
|
+
exit,
|
|
1344
|
+
ensure,
|
|
1345
|
+
selectNext,
|
|
1346
|
+
selectPrevious,
|
|
1347
|
+
clampSelection,
|
|
1348
|
+
selectedFile,
|
|
1349
|
+
toggleStageMode
|
|
1350
|
+
};
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
// src/sidebar/staging.tsx
|
|
1354
|
+
import { createMemo as createMemo3 } from "solid-js";
|
|
1355
|
+
function createStaging(ctx, git, location, refreshWorkingCopy) {
|
|
1356
|
+
const [stagedMem, setStagedMem] = ctx.storage.memory(STAGED_STORE_KEY, {
|
|
1357
|
+
initial: {
|
|
1358
|
+
directory: "",
|
|
1359
|
+
files: []
|
|
1360
|
+
}
|
|
1361
|
+
});
|
|
1362
|
+
const staged = createMemo3(() => new Set(stagedMem.files));
|
|
1363
|
+
if (stagedMem.directory !== location().directory) {
|
|
1364
|
+
setStagedMem((draft) => {
|
|
1365
|
+
draft.directory = location().directory;
|
|
1366
|
+
draft.files = [];
|
|
1367
|
+
});
|
|
1368
|
+
}
|
|
1369
|
+
let refreshTask;
|
|
1370
|
+
const fetchStatuses = async () => {
|
|
1371
|
+
try {
|
|
1372
|
+
const res = await git.run("status --porcelain=v1 -z -uall --no-renames");
|
|
1373
|
+
if (res.exit === 0) {
|
|
1374
|
+
const files = [...parseStagedPorcelain(res.output)];
|
|
1375
|
+
setStagedMem((draft) => {
|
|
1376
|
+
draft.files = files;
|
|
1377
|
+
});
|
|
1378
|
+
}
|
|
1379
|
+
} catch {}
|
|
1380
|
+
};
|
|
1381
|
+
const scheduleRefresh = async () => {
|
|
1382
|
+
if (refreshTask)
|
|
1383
|
+
return refreshTask;
|
|
1384
|
+
refreshTask = git.enqueue(fetchStatuses).finally(() => {
|
|
1385
|
+
refreshTask = undefined;
|
|
1386
|
+
});
|
|
1387
|
+
return refreshTask;
|
|
1388
|
+
};
|
|
1389
|
+
const applyChange = (file, stage) => {
|
|
1390
|
+
setStagedMem((draft) => {
|
|
1391
|
+
const next = new Set(draft.files);
|
|
1392
|
+
if (stage)
|
|
1393
|
+
next.add(file);
|
|
1394
|
+
else
|
|
1395
|
+
next.delete(file);
|
|
1396
|
+
draft.files = [...next];
|
|
1397
|
+
});
|
|
1398
|
+
git.enqueue(async () => {
|
|
1399
|
+
try {
|
|
1400
|
+
let res = await git.run(stage ? `add -- ${shellQuote(file)}` : `restore --staged -- ${shellQuote(file)}`);
|
|
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();
|
|
1413
|
+
} catch (e) {
|
|
1414
|
+
ctx.ui.toast.show({
|
|
1415
|
+
message: `git failed: ${friendlyError(e)}`,
|
|
1416
|
+
variant: "error"
|
|
1417
|
+
});
|
|
1418
|
+
await fetchStatuses();
|
|
1419
|
+
return;
|
|
1420
|
+
}
|
|
1421
|
+
});
|
|
1422
|
+
};
|
|
1423
|
+
const toggle = (file) => {
|
|
1424
|
+
applyChange(file, !staged().has(file));
|
|
1425
|
+
};
|
|
1426
|
+
const commit = async () => {
|
|
1427
|
+
await scheduleRefresh();
|
|
1428
|
+
if (staged().size === 0) {
|
|
1429
|
+
ctx.ui.toast.show({
|
|
1430
|
+
message: "Nothing staged \u2014 stage files with \u2192 first",
|
|
1431
|
+
variant: "warning"
|
|
1432
|
+
});
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
const message = await ctx.ui.dialog.prompt({
|
|
1436
|
+
title: "Commit message",
|
|
1437
|
+
placeholder: "Describe the change"
|
|
1438
|
+
});
|
|
1439
|
+
if (!message)
|
|
1440
|
+
return;
|
|
1441
|
+
await git.enqueue(async () => {
|
|
1442
|
+
try {
|
|
1443
|
+
const res = await git.run(`commit -m ${shellQuote(message)}`);
|
|
1444
|
+
if (res.exit !== 0) {
|
|
1445
|
+
ctx.ui.toast.show({
|
|
1446
|
+
message: `git commit failed: ${res.output.trim().slice(0, 200)}`,
|
|
1447
|
+
variant: "error"
|
|
1448
|
+
});
|
|
1449
|
+
} else {
|
|
1450
|
+
ctx.ui.toast.show({
|
|
1451
|
+
message: "Committed",
|
|
1452
|
+
variant: "success"
|
|
1453
|
+
});
|
|
1454
|
+
}
|
|
1455
|
+
} catch (e) {
|
|
1456
|
+
ctx.ui.toast.show({
|
|
1457
|
+
message: `git commit failed: ${friendlyError(e)}`,
|
|
1458
|
+
variant: "error"
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
await refreshWorkingCopy();
|
|
1462
|
+
});
|
|
1463
|
+
};
|
|
1464
|
+
return {
|
|
1465
|
+
staged,
|
|
1466
|
+
scheduleRefresh,
|
|
1467
|
+
applyChange,
|
|
1468
|
+
toggle,
|
|
1469
|
+
commit
|
|
1470
|
+
};
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
// src/sidebar/status.tsx
|
|
1474
|
+
import { insert as _$insert6 } from "@opentui/solid";
|
|
1475
|
+
import { memo as _$memo4 } from "@opentui/solid";
|
|
1476
|
+
import { createComponent as _$createComponent6 } from "@opentui/solid";
|
|
1477
|
+
import { setProp as _$setProp6 } from "@opentui/solid";
|
|
1478
|
+
import { effect as _$effect5 } from "@opentui/solid";
|
|
1479
|
+
import { createTextNode as _$createTextNode5 } from "@opentui/solid";
|
|
1480
|
+
import { insertNode as _$insertNode5 } from "@opentui/solid";
|
|
1481
|
+
import { createElement as _$createElement6 } from "@opentui/solid";
|
|
1482
|
+
import { Show as Show5 } from "solid-js";
|
|
1483
|
+
function SidebarStatus(props) {
|
|
1484
|
+
const ctx = props.ctx;
|
|
1485
|
+
return [_$createComponent6(Show5, {
|
|
1486
|
+
get when() {
|
|
1487
|
+
return props.loading;
|
|
1488
|
+
},
|
|
1489
|
+
get children() {
|
|
1490
|
+
var _el$ = _$createElement6("text");
|
|
1491
|
+
_$insertNode5(_el$, _$createTextNode5(`Loading\u2026`));
|
|
1492
|
+
_$effect5((_$p) => _$setProp6(_el$, "fg", ctx.theme.text.muted, _$p));
|
|
1493
|
+
return _el$;
|
|
1494
|
+
}
|
|
1495
|
+
}), _$createComponent6(Show5, {
|
|
1496
|
+
get when() {
|
|
1497
|
+
return _$memo4(() => !!!props.loading)() && props.error;
|
|
1498
|
+
},
|
|
1499
|
+
get children() {
|
|
1500
|
+
var _el$3 = _$createElement6("text");
|
|
1501
|
+
_$insert6(_el$3, () => props.error);
|
|
1502
|
+
_$effect5((_$p) => _$setProp6(_el$3, "fg", ctx.theme.text.feedback.error.base, _$p));
|
|
1503
|
+
return _el$3;
|
|
1504
|
+
}
|
|
1505
|
+
}), _$createComponent6(Show5, {
|
|
1506
|
+
get when() {
|
|
1507
|
+
return _$memo4(() => !!(!props.loading && !props.error && !props.hasFiles))() && !props.branch;
|
|
1508
|
+
},
|
|
1509
|
+
get children() {
|
|
1510
|
+
var _el$4 = _$createElement6("text");
|
|
1511
|
+
_$insertNode5(_el$4, _$createTextNode5(`Empty repo \u2014 no commits yet`));
|
|
1512
|
+
_$effect5((_$p) => _$setProp6(_el$4, "fg", ctx.theme.text.muted, _$p));
|
|
1513
|
+
return _el$4;
|
|
1514
|
+
}
|
|
1515
|
+
}), _$createComponent6(Show5, {
|
|
1516
|
+
get when() {
|
|
1517
|
+
return _$memo4(() => !!(!props.loading && !props.error && !props.hasFiles))() && !!props.branch;
|
|
1518
|
+
},
|
|
1519
|
+
get children() {
|
|
1520
|
+
var _el$6 = _$createElement6("text");
|
|
1521
|
+
_$insertNode5(_el$6, _$createTextNode5(`Working tree clean`));
|
|
1522
|
+
_$effect5((_$p) => _$setProp6(_el$6, "fg", ctx.theme.text.muted, _$p));
|
|
1523
|
+
return _el$6;
|
|
1524
|
+
}
|
|
1525
|
+
})];
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
// src/sidebar/working-copy.tsx
|
|
1529
|
+
import { createEffect as createEffect2, createMemo as createMemo4, createSignal as createSignal2, onCleanup as onCleanup3 } from "solid-js";
|
|
1530
|
+
function createWorkingCopy(ctx, location, onRefreshed) {
|
|
1531
|
+
const [files, setFiles] = createSignal2([]);
|
|
1532
|
+
const [branch, setBranch] = createSignal2("");
|
|
1533
|
+
const [loading, setLoading] = createSignal2(true);
|
|
1534
|
+
const [error, setError] = createSignal2(undefined);
|
|
1535
|
+
let isRefreshing = false;
|
|
1536
|
+
let lastEventRefreshAt = 0;
|
|
1537
|
+
const refresh = async () => {
|
|
1538
|
+
if (isRefreshing)
|
|
1539
|
+
return;
|
|
1540
|
+
isRefreshing = true;
|
|
1541
|
+
try {
|
|
1542
|
+
const loc = location();
|
|
1543
|
+
const [statusRes, infoRes] = await Promise.all([ctx.client.vcs.status({
|
|
1544
|
+
location: loc
|
|
1545
|
+
}), ctx.client.vcs.get({
|
|
1546
|
+
location: loc
|
|
1547
|
+
})]);
|
|
1548
|
+
const prevByFile = new Map(files().map((f) => [f.file, f]));
|
|
1549
|
+
setFiles(statusRes.data.map((d) => {
|
|
1550
|
+
const p = prevByFile.get(d.file);
|
|
1551
|
+
return p && p.additions === d.additions && p.deletions === d.deletions && p.status === d.status ? p : {
|
|
1552
|
+
...d
|
|
1553
|
+
};
|
|
1554
|
+
}));
|
|
1555
|
+
setBranch(infoRes.data.branch.current ?? "");
|
|
1556
|
+
setError(undefined);
|
|
1557
|
+
onRefreshed(statusRes.data.length);
|
|
1558
|
+
} catch (e) {
|
|
1559
|
+
setError(friendlyError(e));
|
|
1560
|
+
} finally {
|
|
1561
|
+
setLoading(false);
|
|
1562
|
+
isRefreshing = false;
|
|
1563
|
+
}
|
|
1564
|
+
};
|
|
1565
|
+
const additions = createMemo4(() => files().reduce((n, f) => n + f.additions, 0));
|
|
1566
|
+
const deletions = createMemo4(() => files().reduce((n, f) => n + f.deletions, 0));
|
|
1567
|
+
const [refreshSignal, bumpRefresh] = ctx.storage.memory(REFRESH_STORE_KEY, {
|
|
1568
|
+
initial: {
|
|
1569
|
+
nonce: 0
|
|
1570
|
+
}
|
|
1571
|
+
});
|
|
1572
|
+
const requestRefresh = () => bumpRefresh((draft) => {
|
|
1573
|
+
draft.nonce += 1;
|
|
1574
|
+
});
|
|
1575
|
+
createEffect2((prev) => {
|
|
1576
|
+
const nonce = refreshSignal.nonce;
|
|
1577
|
+
if (prev !== undefined)
|
|
1578
|
+
refresh().catch(() => {});
|
|
1579
|
+
return nonce;
|
|
1580
|
+
});
|
|
1581
|
+
const timer = setInterval(() => void refresh().catch(() => {}), intervalMs(ctx));
|
|
1582
|
+
const stopListening = ctx.data.listen(({
|
|
1583
|
+
details
|
|
1584
|
+
}) => {
|
|
1585
|
+
const t = details.type;
|
|
1586
|
+
if (!(t.includes("session") || t.includes("vcs") || t.includes("server.connected") || t.includes("location")))
|
|
1587
|
+
return;
|
|
1588
|
+
const now = Date.now();
|
|
1589
|
+
if (now - lastEventRefreshAt < EVENT_REFRESH_DEBOUNCE_MS)
|
|
1590
|
+
return;
|
|
1591
|
+
lastEventRefreshAt = now;
|
|
1592
|
+
refresh().catch(() => {});
|
|
1593
|
+
});
|
|
1594
|
+
onCleanup3(() => {
|
|
1595
|
+
clearInterval(timer);
|
|
1596
|
+
stopListening();
|
|
1597
|
+
});
|
|
1598
|
+
return {
|
|
1599
|
+
files,
|
|
1600
|
+
branch,
|
|
1601
|
+
loading,
|
|
1602
|
+
error,
|
|
1603
|
+
additions,
|
|
1604
|
+
deletions,
|
|
1605
|
+
refresh,
|
|
1606
|
+
requestRefresh
|
|
1607
|
+
};
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// src/sidebar/view.tsx
|
|
1611
|
+
function GitChangesView(props) {
|
|
1612
|
+
const {
|
|
1613
|
+
ctx
|
|
1614
|
+
} = props;
|
|
1615
|
+
const [sidebarWidth, setSidebarWidth] = createSignal3(46);
|
|
1616
|
+
const [diffView] = useDiffStore(ctx);
|
|
1617
|
+
let sidebarNode;
|
|
1618
|
+
const currentLocation = () => {
|
|
1619
|
+
const loc = ctx.location ?? ctx.data.location.default();
|
|
1620
|
+
return {
|
|
1621
|
+
directory: loc.directory
|
|
1622
|
+
};
|
|
1623
|
+
};
|
|
1624
|
+
const git = createGitRunner(ctx, () => currentLocation().directory);
|
|
1625
|
+
const openDiff = createDiffOpener(ctx, currentLocation);
|
|
1626
|
+
const workingCopy = createWorkingCopy(ctx, currentLocation, (count) => {
|
|
1627
|
+
nav.clampSelection(count);
|
|
1628
|
+
staging.scheduleRefresh();
|
|
1629
|
+
});
|
|
1630
|
+
const staging = createStaging(ctx, git, currentLocation, workingCopy.refresh);
|
|
1631
|
+
const nav = createListNavigation(ctx, workingCopy.files);
|
|
1632
|
+
const openSelectedDiff = () => {
|
|
1633
|
+
const f = nav.selectedFile();
|
|
1634
|
+
if (f)
|
|
1635
|
+
openDiff(f).catch(() => {});
|
|
1636
|
+
};
|
|
1637
|
+
const setSelectedFileStaged = (stage) => {
|
|
1638
|
+
const f = nav.selectedFile();
|
|
1639
|
+
if (f)
|
|
1640
|
+
staging.applyChange(f.file, stage);
|
|
1641
|
+
};
|
|
1642
|
+
const selectFile = (f, index) => {
|
|
1643
|
+
nav.setSelected(index);
|
|
1644
|
+
if (nav.stageMode())
|
|
1645
|
+
staging.toggle(f.file);
|
|
1646
|
+
else
|
|
1647
|
+
openDiff(f).catch(() => {});
|
|
1648
|
+
};
|
|
1649
|
+
workingCopy.refresh().catch(() => {});
|
|
1650
|
+
staging.scheduleRefresh().catch(() => {});
|
|
1651
|
+
registerSidebarKeymaps(ctx, {
|
|
1652
|
+
files: workingCopy.files,
|
|
1653
|
+
nav,
|
|
1654
|
+
diffOpen: () => diffView.panelOpen,
|
|
1655
|
+
requestRefresh: workingCopy.requestRefresh,
|
|
1656
|
+
actions: {
|
|
1657
|
+
next: nav.selectNext,
|
|
1658
|
+
prev: nav.selectPrevious,
|
|
1659
|
+
open: openSelectedDiff,
|
|
1660
|
+
setStaged: setSelectedFileStaged,
|
|
1661
|
+
commit: () => void staging.commit()
|
|
1662
|
+
}
|
|
1663
|
+
});
|
|
1664
|
+
const restoreListFocus = () => {
|
|
1665
|
+
if (!nav.navMode() || diffView.panelOpen)
|
|
1666
|
+
return;
|
|
1667
|
+
nav.ensure();
|
|
1668
|
+
};
|
|
1669
|
+
setDiffClosedHandler(() => {
|
|
1670
|
+
if (!nav.navMode() && workingCopy.files().length > 0)
|
|
1671
|
+
nav.enter();
|
|
1672
|
+
restoreListFocus();
|
|
1673
|
+
});
|
|
1674
|
+
createEffect3((wasOpen) => {
|
|
1675
|
+
const open = diffView.panelOpen;
|
|
1676
|
+
if (wasOpen === true && open === false && nav.navMode()) {
|
|
1677
|
+
setTimeout(() => restoreListFocus(), 0);
|
|
1678
|
+
}
|
|
1679
|
+
return open;
|
|
1680
|
+
});
|
|
1681
|
+
bindSidebarFocus(ctx, {
|
|
1682
|
+
container: () => sidebarNode,
|
|
1683
|
+
active: () => nav.navMode() && !diffView.panelOpen,
|
|
1684
|
+
exit: nav.exit
|
|
1685
|
+
});
|
|
1686
|
+
onCleanup4(() => setDiffClosedHandler(undefined));
|
|
1687
|
+
const setSidebarRef = (el) => {
|
|
1688
|
+
sidebarNode = el;
|
|
1689
|
+
setSidebarWidth(el.width);
|
|
1690
|
+
const onNodeResize = () => setSidebarWidth(el.width);
|
|
1691
|
+
el.on("resize", onNodeResize);
|
|
1692
|
+
onCleanup4(() => el.off("resize", onNodeResize));
|
|
1693
|
+
};
|
|
1694
|
+
const ready = () => !workingCopy.loading() && !workingCopy.error();
|
|
1695
|
+
const hasFiles = () => workingCopy.files().length > 0;
|
|
1696
|
+
const sidebarBody = [_$createComponent7(SidebarHeader, {
|
|
1697
|
+
ctx,
|
|
1698
|
+
get branch() {
|
|
1699
|
+
return workingCopy.branch();
|
|
1700
|
+
},
|
|
1701
|
+
get fileCount() {
|
|
1702
|
+
return workingCopy.files().length;
|
|
1703
|
+
},
|
|
1704
|
+
get ready() {
|
|
1705
|
+
return ready();
|
|
1706
|
+
},
|
|
1707
|
+
get additions() {
|
|
1708
|
+
return workingCopy.additions();
|
|
1709
|
+
},
|
|
1710
|
+
get deletions() {
|
|
1711
|
+
return workingCopy.deletions();
|
|
1712
|
+
},
|
|
1713
|
+
get navMode() {
|
|
1714
|
+
return nav.navMode();
|
|
1715
|
+
},
|
|
1716
|
+
get stageMode() {
|
|
1717
|
+
return nav.stageMode();
|
|
1718
|
+
},
|
|
1719
|
+
get hasStaged() {
|
|
1720
|
+
return staging.staged().size > 0;
|
|
1721
|
+
},
|
|
1722
|
+
get onBranchClick() {
|
|
1723
|
+
return nav.toggleStageMode;
|
|
1724
|
+
},
|
|
1725
|
+
onCommit: () => void staging.commit()
|
|
1726
|
+
}), _$createComponent7(SidebarStatus, {
|
|
1727
|
+
ctx,
|
|
1728
|
+
get loading() {
|
|
1729
|
+
return workingCopy.loading();
|
|
1730
|
+
},
|
|
1731
|
+
get error() {
|
|
1732
|
+
return workingCopy.error();
|
|
1733
|
+
},
|
|
1734
|
+
get hasFiles() {
|
|
1735
|
+
return hasFiles();
|
|
1736
|
+
},
|
|
1737
|
+
get branch() {
|
|
1738
|
+
return workingCopy.branch();
|
|
1739
|
+
}
|
|
1740
|
+
}), _$createComponent7(Show6, {
|
|
1741
|
+
get when() {
|
|
1742
|
+
return _$memo5(() => !!ready())() && hasFiles();
|
|
1743
|
+
},
|
|
1744
|
+
get children() {
|
|
1745
|
+
return _$createComponent7(FileList, {
|
|
1746
|
+
ctx,
|
|
1747
|
+
get files() {
|
|
1748
|
+
return workingCopy.files();
|
|
1749
|
+
},
|
|
1750
|
+
get staged() {
|
|
1751
|
+
return staging.staged();
|
|
1752
|
+
},
|
|
1753
|
+
get selected() {
|
|
1754
|
+
return nav.selected();
|
|
1755
|
+
},
|
|
1756
|
+
get navMode() {
|
|
1757
|
+
return nav.navMode();
|
|
1758
|
+
},
|
|
1759
|
+
get width() {
|
|
1760
|
+
return sidebarWidth();
|
|
1761
|
+
},
|
|
1762
|
+
onSelect: selectFile
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
})];
|
|
1766
|
+
return _$createComponent7(Show6, {
|
|
1767
|
+
get when() {
|
|
1768
|
+
return nav.navMode();
|
|
1769
|
+
},
|
|
1770
|
+
get fallback() {
|
|
1771
|
+
return (() => {
|
|
1772
|
+
var _el$2 = _$createElement7("box");
|
|
1773
|
+
_$use2(setSidebarRef, _el$2);
|
|
1774
|
+
_$setProp7(_el$2, "flexDirection", "column");
|
|
1775
|
+
_$setProp7(_el$2, "gap", 1);
|
|
1776
|
+
_$insert7(_el$2, sidebarBody);
|
|
1777
|
+
return _el$2;
|
|
1778
|
+
})();
|
|
1779
|
+
},
|
|
1780
|
+
get children() {
|
|
1781
|
+
var _el$ = _$createElement7("box");
|
|
1782
|
+
_$use2(setSidebarRef, _el$);
|
|
1783
|
+
_$setProp7(_el$, "flexDirection", "column");
|
|
1784
|
+
_$setProp7(_el$, "gap", 1);
|
|
1785
|
+
_$setProp7(_el$, "border", ["top", "bottom"]);
|
|
1786
|
+
_$setProp7(_el$, "borderStyle", "rounded");
|
|
1787
|
+
_$insert7(_el$, sidebarBody);
|
|
1788
|
+
_$effect6((_p$) => {
|
|
1789
|
+
var _v$ = ctx.theme.text.feedback.warning.base, _v$2 = ctx.theme.text.feedback.warning.base;
|
|
1790
|
+
_v$ !== _p$.e && (_p$.e = _$setProp7(_el$, "borderColor", _v$, _p$.e));
|
|
1791
|
+
_v$2 !== _p$.t && (_p$.t = _$setProp7(_el$, "focusedBorderColor", _v$2, _p$.t));
|
|
1792
|
+
return _p$;
|
|
1793
|
+
}, {
|
|
1794
|
+
e: undefined,
|
|
1795
|
+
t: undefined
|
|
1796
|
+
});
|
|
1797
|
+
return _el$;
|
|
1798
|
+
}
|
|
1799
|
+
});
|
|
1800
|
+
}
|
|
1801
|
+
// src/tui.tsx
|
|
1802
|
+
var tui_default = Plugin.define({
|
|
1803
|
+
id: "git-panel.cli",
|
|
1804
|
+
setup(ctx) {
|
|
1805
|
+
const unregisterSidebar = ctx.ui.slot({
|
|
1806
|
+
append: "sidebar.content",
|
|
1807
|
+
render: () => _$createComponent8(GitChangesView, {
|
|
1808
|
+
ctx
|
|
1809
|
+
})
|
|
1810
|
+
});
|
|
1811
|
+
const unregisterPanel = ctx.ui.slot({
|
|
1812
|
+
append: "session.panel",
|
|
1813
|
+
render: (input) => _$createComponent8(DiffPanel, {
|
|
1814
|
+
ctx,
|
|
1815
|
+
input
|
|
1816
|
+
})
|
|
1817
|
+
});
|
|
1818
|
+
return () => {
|
|
1819
|
+
unregisterSidebar();
|
|
1820
|
+
unregisterPanel();
|
|
1821
|
+
};
|
|
1822
|
+
}
|
|
1823
|
+
});
|
|
1824
|
+
export {
|
|
1825
|
+
parseStagedPorcelain,
|
|
1826
|
+
parseDiff,
|
|
1827
|
+
tui_default as default
|
|
1828
|
+
};
|