@spader/spall-tui 1.0.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/dist/App.js +3636 -0
- package/dist/App.js.map +39 -0
- package/dist/bash-nbhfht5r.scm +56 -0
- package/dist/c-4zy7d3fw.scm +81 -0
- package/dist/c_sharp-tb7n62t2.scm +212 -0
- package/dist/cpp-stxr9ffp.scm +77 -0
- package/dist/css-b1p1h6ys.scm +76 -0
- package/dist/dart-t89b32ej.scm +13 -0
- package/dist/elisp-ax9wx1b9.scm +72 -0
- package/dist/elixir-77300bss.scm +223 -0
- package/dist/elm-k2q5knqb.scm +76 -0
- package/dist/embedded_template-k7ypjkba.scm +12 -0
- package/dist/go-9wx8m64k.scm +123 -0
- package/dist/html-g95aq3vw.scm +13 -0
- package/dist/index.js +3635 -0
- package/dist/index.js.map +39 -0
- package/dist/java-wasz6t7a.scm +149 -0
- package/dist/javascript-cswtymww.scm +204 -0
- package/dist/json-pf4aw8w1.scm +16 -0
- package/dist/kotlin-m6cj2y7m.scm +380 -0
- package/dist/lib/git.js +117 -0
- package/dist/lib/git.js.map +10 -0
- package/dist/lua-7h6jk3pd.scm +204 -0
- package/dist/objc-6yjdwy1z.scm +216 -0
- package/dist/ocaml-jfj520qk.scm +148 -0
- package/dist/parsers/index.d.ts +8 -0
- package/dist/parsers/index.d.ts.map +1 -0
- package/dist/php-bds7v5h1.scm +203 -0
- package/dist/python-k2cbtg72.scm +137 -0
- package/dist/ql-qs2vxbeq.scm +154 -0
- package/dist/ruby-3ybh5bp2.scm +154 -0
- package/dist/rust-edtnatpk.scm +161 -0
- package/dist/scala-91f2sb1z.scm +260 -0
- package/dist/src/App.d.ts +6 -0
- package/dist/src/App.d.ts.map +1 -0
- package/dist/src/Review.d.ts +6 -0
- package/dist/src/Review.d.ts.map +1 -0
- package/dist/src/components/CommandPalette.d.ts +2 -0
- package/dist/src/components/CommandPalette.d.ts.map +1 -0
- package/dist/src/components/DiffPanel.d.ts +16 -0
- package/dist/src/components/DiffPanel.d.ts.map +1 -0
- package/dist/src/components/EditorPanel.d.ts +9 -0
- package/dist/src/components/EditorPanel.d.ts.map +1 -0
- package/dist/src/components/ErrorFallback.d.ts +6 -0
- package/dist/src/components/ErrorFallback.d.ts.map +1 -0
- package/dist/src/components/HalfLineShadow.d.ts +18 -0
- package/dist/src/components/HalfLineShadow.d.ts.map +1 -0
- package/dist/src/components/index.d.ts +7 -0
- package/dist/src/components/index.d.ts.map +1 -0
- package/dist/src/components/sidebar/CommentList.d.ts +10 -0
- package/dist/src/components/sidebar/CommentList.d.ts.map +1 -0
- package/dist/src/components/sidebar/FileList.d.ts +13 -0
- package/dist/src/components/sidebar/FileList.d.ts.map +1 -0
- package/dist/src/components/sidebar/PatchList.d.ts +14 -0
- package/dist/src/components/sidebar/PatchList.d.ts.map +1 -0
- package/dist/src/components/sidebar/ProjectStatus.d.ts +7 -0
- package/dist/src/components/sidebar/ProjectStatus.d.ts.map +1 -0
- package/dist/src/components/sidebar/Section.d.ts +12 -0
- package/dist/src/components/sidebar/Section.d.ts.map +1 -0
- package/dist/src/components/sidebar/ServerStatus.d.ts +2 -0
- package/dist/src/components/sidebar/ServerStatus.d.ts.map +1 -0
- package/dist/src/components/sidebar/index.d.ts +7 -0
- package/dist/src/components/sidebar/index.d.ts.map +1 -0
- package/dist/src/context/command.d.ts +10 -0
- package/dist/src/context/command.d.ts.map +1 -0
- package/dist/src/context/dialog.d.ts +11 -0
- package/dist/src/context/dialog.d.ts.map +1 -0
- package/dist/src/context/exit.d.ts +10 -0
- package/dist/src/context/exit.d.ts.map +1 -0
- package/dist/src/context/review.d.ts +47 -0
- package/dist/src/context/review.d.ts.map +1 -0
- package/dist/src/context/server.d.ts +10 -0
- package/dist/src/context/server.d.ts.map +1 -0
- package/dist/src/context/sidebar.d.ts +13 -0
- package/dist/src/context/sidebar.d.ts.map +1 -0
- package/dist/src/context/theme.d.ts +47 -0
- package/dist/src/context/theme.d.ts.map +1 -0
- package/dist/src/index.d.ts +3 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/lib/diff.d.ts +24 -0
- package/dist/src/lib/diff.d.ts.map +1 -0
- package/dist/src/lib/git.d.ts +15 -0
- package/dist/src/lib/git.d.ts.map +1 -0
- package/dist/src/lib/keybind.d.ts +23 -0
- package/dist/src/lib/keybind.d.ts.map +1 -0
- package/dist/src/lib/tree.d.ts +39 -0
- package/dist/src/lib/tree.d.ts.map +1 -0
- package/dist/src/store/comment.d.ts +20 -0
- package/dist/src/store/comment.d.ts.map +1 -0
- package/dist/src/store/db.d.ts +6 -0
- package/dist/src/store/db.d.ts.map +1 -0
- package/dist/src/store/index.d.ts +7 -0
- package/dist/src/store/index.d.ts.map +1 -0
- package/dist/src/store/patch.d.ts +22 -0
- package/dist/src/store/patch.d.ts.map +1 -0
- package/dist/src/store/repo.d.ts +8 -0
- package/dist/src/store/repo.d.ts.map +1 -0
- package/dist/src/store/review.d.ts +18 -0
- package/dist/src/store/review.d.ts.map +1 -0
- package/dist/store/index.js +303 -0
- package/dist/store/index.js.map +14 -0
- package/dist/swift-a4tt704b.scm +336 -0
- package/dist/toml-hcqfz0bg.scm +53 -0
- package/dist/tsx-c3k3mmc9.scm +239 -0
- package/dist/typescript-hz4pg6ze.scm +239 -0
- package/dist/vue-xbeea1aj.scm +9 -0
- package/dist/yaml-mbfb25g0.scm +79 -0
- package/dist/zig-ds3q8m26.scm +283 -0
- package/package.json +74 -0
package/dist/App.js
ADDED
|
@@ -0,0 +1,3636 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __returnValue = (v) => v;
|
|
5
|
+
function __exportSetter(name, newValue) {
|
|
6
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
7
|
+
}
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, {
|
|
11
|
+
get: all[name],
|
|
12
|
+
enumerable: true,
|
|
13
|
+
configurable: true,
|
|
14
|
+
set: __exportSetter.bind(all, name)
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/lib/git.ts
|
|
19
|
+
var {$ } = globalThis.Bun;
|
|
20
|
+
var Git;
|
|
21
|
+
((Git) => {
|
|
22
|
+
async function root(startPath) {
|
|
23
|
+
try {
|
|
24
|
+
const result = await $`git -C ${startPath} rev-parse --show-toplevel`.quiet();
|
|
25
|
+
return result.stdout.toString().trim() || null;
|
|
26
|
+
} catch {
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
Git.root = root;
|
|
31
|
+
async function head(repoPath) {
|
|
32
|
+
try {
|
|
33
|
+
const result = await $`git -C ${repoPath} rev-parse HEAD`.quiet();
|
|
34
|
+
return result.stdout.toString().trim() || null;
|
|
35
|
+
} catch {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
Git.head = head;
|
|
40
|
+
async function diff(repoPath) {
|
|
41
|
+
const e = await entries(repoPath);
|
|
42
|
+
return e.map((x) => x.content).join(`
|
|
43
|
+
`);
|
|
44
|
+
}
|
|
45
|
+
Git.diff = diff;
|
|
46
|
+
async function hash(repoPath) {
|
|
47
|
+
const trackedResult = await $`git -C ${repoPath} diff HEAD --name-only --relative`.quiet();
|
|
48
|
+
const untrackedResult = await $`git -C ${repoPath} ls-files --others --exclude-standard`.quiet();
|
|
49
|
+
const combined = trackedResult.stdout.toString() + "\x00" + untrackedResult.stdout.toString();
|
|
50
|
+
return Number(Bun.hash(combined));
|
|
51
|
+
}
|
|
52
|
+
Git.hash = hash;
|
|
53
|
+
async function entries(repoPath) {
|
|
54
|
+
const context = 0;
|
|
55
|
+
const result = [];
|
|
56
|
+
const filesResult = await $`git -C ${repoPath} diff -U${context} HEAD --name-only --relative`.quiet();
|
|
57
|
+
const filesOutput = filesResult.stdout.toString().trim();
|
|
58
|
+
const trackedFiles = filesOutput ? filesOutput.split(`
|
|
59
|
+
`).filter(Boolean) : [];
|
|
60
|
+
const deletedResult = await $`git -C ${repoPath} diff -U${context} HEAD --name-only --diff-filter=D --relative`.quiet();
|
|
61
|
+
const deletedOutput = deletedResult.stdout.toString().trim();
|
|
62
|
+
const deletedFiles = new Set(deletedOutput ? deletedOutput.split(`
|
|
63
|
+
`).filter(Boolean) : []);
|
|
64
|
+
for (const file of trackedFiles) {
|
|
65
|
+
const diffResult = await $`git -C ${repoPath} diff -U${context} HEAD -- ${file}`.quiet();
|
|
66
|
+
const content = diffResult.stdout.toString();
|
|
67
|
+
const isDeleted = deletedFiles.has(file);
|
|
68
|
+
result.push({ file, content, isNew: false, isDeleted });
|
|
69
|
+
}
|
|
70
|
+
const untrackedResult = await $`git -C ${repoPath} ls-files --others --exclude-standard`.quiet();
|
|
71
|
+
const untrackedOutput = untrackedResult.stdout.toString().trim();
|
|
72
|
+
const untrackedFiles = untrackedOutput ? untrackedOutput.split(`
|
|
73
|
+
`).filter(Boolean) : [];
|
|
74
|
+
for (const file of untrackedFiles) {
|
|
75
|
+
const fileContent = await Bun.file(`${repoPath}/${file}`).text();
|
|
76
|
+
const lines2 = fileContent.split(`
|
|
77
|
+
`);
|
|
78
|
+
const diffLines = lines2.map((line) => `+${line}`);
|
|
79
|
+
const content = `diff --git a/${file} b/${file}
|
|
80
|
+
new file mode 100644
|
|
81
|
+
--- /dev/null
|
|
82
|
+
+++ b/${file}
|
|
83
|
+
@@ -0,0 +1,${lines2.length} @@
|
|
84
|
+
${diffLines.join(`
|
|
85
|
+
`)}`;
|
|
86
|
+
result.push({ file, content, isNew: true, isDeleted: false });
|
|
87
|
+
}
|
|
88
|
+
return result;
|
|
89
|
+
}
|
|
90
|
+
Git.entries = entries;
|
|
91
|
+
function lines(diffContent) {
|
|
92
|
+
if (!diffContent)
|
|
93
|
+
return 0;
|
|
94
|
+
const allLines = diffContent.split(`
|
|
95
|
+
`);
|
|
96
|
+
let count = 0;
|
|
97
|
+
let inHunk = false;
|
|
98
|
+
for (const line of allLines) {
|
|
99
|
+
if (line.startsWith("@@")) {
|
|
100
|
+
inHunk = true;
|
|
101
|
+
continue;
|
|
102
|
+
}
|
|
103
|
+
if (!inHunk)
|
|
104
|
+
continue;
|
|
105
|
+
const firstChar = line[0];
|
|
106
|
+
if (firstChar === " " || firstChar === "+" || firstChar === "-") {
|
|
107
|
+
count++;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return count;
|
|
111
|
+
}
|
|
112
|
+
Git.lines = lines;
|
|
113
|
+
})(Git ||= {});
|
|
114
|
+
|
|
115
|
+
// src/store/db.ts
|
|
116
|
+
var exports_db = {};
|
|
117
|
+
__export(exports_db, {
|
|
118
|
+
path: () => path,
|
|
119
|
+
init: () => init,
|
|
120
|
+
get: () => get,
|
|
121
|
+
close: () => close
|
|
122
|
+
});
|
|
123
|
+
import { Database } from "bun:sqlite";
|
|
124
|
+
import { mkdirSync, existsSync } from "fs";
|
|
125
|
+
import { dirname, join } from "path";
|
|
126
|
+
import { Config } from "@spader/spall-core/config";
|
|
127
|
+
var DB_PATH = join(Config.get().dirs.data, "tui.db");
|
|
128
|
+
var CREATE_REPOS_TABLE = `
|
|
129
|
+
CREATE TABLE IF NOT EXISTS repos (
|
|
130
|
+
id INTEGER PRIMARY KEY,
|
|
131
|
+
path TEXT NOT NULL UNIQUE
|
|
132
|
+
)
|
|
133
|
+
`;
|
|
134
|
+
var CREATE_REVIEWS_TABLE = `
|
|
135
|
+
CREATE TABLE IF NOT EXISTS reviews (
|
|
136
|
+
id INTEGER PRIMARY KEY,
|
|
137
|
+
repo INTEGER NOT NULL,
|
|
138
|
+
commit_sha TEXT NOT NULL,
|
|
139
|
+
name TEXT,
|
|
140
|
+
created_at INTEGER NOT NULL,
|
|
141
|
+
FOREIGN KEY (repo) REFERENCES repos(id)
|
|
142
|
+
)
|
|
143
|
+
`;
|
|
144
|
+
var CREATE_PATCHES_TABLE = `
|
|
145
|
+
CREATE TABLE IF NOT EXISTS patches (
|
|
146
|
+
id INTEGER PRIMARY KEY,
|
|
147
|
+
review INTEGER NOT NULL,
|
|
148
|
+
seq INTEGER NOT NULL,
|
|
149
|
+
hash TEXT NOT NULL,
|
|
150
|
+
content TEXT NOT NULL,
|
|
151
|
+
created_at INTEGER NOT NULL,
|
|
152
|
+
FOREIGN KEY (review) REFERENCES reviews(id),
|
|
153
|
+
UNIQUE (review, seq)
|
|
154
|
+
)
|
|
155
|
+
`;
|
|
156
|
+
var CREATE_REVIEW_COMMENTS_TABLE = `
|
|
157
|
+
CREATE TABLE IF NOT EXISTS review_comments (
|
|
158
|
+
id INTEGER PRIMARY KEY,
|
|
159
|
+
review INTEGER NOT NULL,
|
|
160
|
+
note_id INTEGER NOT NULL,
|
|
161
|
+
file TEXT NOT NULL,
|
|
162
|
+
patch_id INTEGER NOT NULL,
|
|
163
|
+
start_row INTEGER NOT NULL,
|
|
164
|
+
end_row INTEGER NOT NULL,
|
|
165
|
+
created_at INTEGER NOT NULL,
|
|
166
|
+
FOREIGN KEY (review) REFERENCES reviews(id)
|
|
167
|
+
)
|
|
168
|
+
`;
|
|
169
|
+
var db = null;
|
|
170
|
+
function path() {
|
|
171
|
+
return DB_PATH;
|
|
172
|
+
}
|
|
173
|
+
function init() {
|
|
174
|
+
if (db)
|
|
175
|
+
return db;
|
|
176
|
+
const dir = dirname(DB_PATH);
|
|
177
|
+
if (!existsSync(dir)) {
|
|
178
|
+
mkdirSync(dir, { recursive: true });
|
|
179
|
+
}
|
|
180
|
+
db = new Database(DB_PATH);
|
|
181
|
+
db.exec(CREATE_REPOS_TABLE);
|
|
182
|
+
db.exec(CREATE_REVIEWS_TABLE);
|
|
183
|
+
db.exec(CREATE_PATCHES_TABLE);
|
|
184
|
+
db.exec(CREATE_REVIEW_COMMENTS_TABLE);
|
|
185
|
+
return db;
|
|
186
|
+
}
|
|
187
|
+
function get() {
|
|
188
|
+
if (!db) {
|
|
189
|
+
throw new Error("Store not initialized. Call init() first.");
|
|
190
|
+
}
|
|
191
|
+
return db;
|
|
192
|
+
}
|
|
193
|
+
function close() {
|
|
194
|
+
if (db) {
|
|
195
|
+
db.close();
|
|
196
|
+
db = null;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/store/repo.ts
|
|
201
|
+
var exports_repo = {};
|
|
202
|
+
__export(exports_repo, {
|
|
203
|
+
getOrCreate: () => getOrCreate,
|
|
204
|
+
getByPath: () => getByPath,
|
|
205
|
+
create: () => create
|
|
206
|
+
});
|
|
207
|
+
function create(path2) {
|
|
208
|
+
const row = get().prepare(`INSERT INTO repos (path) VALUES (?) RETURNING id`).get(path2);
|
|
209
|
+
return { id: row.id, path: path2 };
|
|
210
|
+
}
|
|
211
|
+
function getByPath(path2) {
|
|
212
|
+
const row = get().prepare(`SELECT * FROM repos WHERE path = ?`).get(path2);
|
|
213
|
+
if (!row)
|
|
214
|
+
return null;
|
|
215
|
+
return { id: row.id, path: row.path };
|
|
216
|
+
}
|
|
217
|
+
function getOrCreate(path2) {
|
|
218
|
+
const existing = getByPath(path2);
|
|
219
|
+
if (existing)
|
|
220
|
+
return existing;
|
|
221
|
+
return create(path2);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/store/review.ts
|
|
225
|
+
var exports_review = {};
|
|
226
|
+
__export(exports_review, {
|
|
227
|
+
list: () => list,
|
|
228
|
+
latest: () => latest,
|
|
229
|
+
getOrCreate: () => getOrCreate2,
|
|
230
|
+
getByRepoAndCommit: () => getByRepoAndCommit,
|
|
231
|
+
get: () => get2,
|
|
232
|
+
create: () => create2
|
|
233
|
+
});
|
|
234
|
+
function rowToInfo(row) {
|
|
235
|
+
return {
|
|
236
|
+
id: row.id,
|
|
237
|
+
repo: row.repo,
|
|
238
|
+
commitSha: row.commit_sha,
|
|
239
|
+
name: row.name,
|
|
240
|
+
createdAt: row.created_at
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
function create2(input) {
|
|
244
|
+
const createdAt = Date.now();
|
|
245
|
+
const row = get().prepare(`INSERT INTO reviews (repo, commit_sha, name, created_at)
|
|
246
|
+
VALUES (?, ?, ?, ?) RETURNING *`).get(input.repo, input.commitSha, input.name ?? null, createdAt);
|
|
247
|
+
return rowToInfo(row);
|
|
248
|
+
}
|
|
249
|
+
function list(repo) {
|
|
250
|
+
const rows = get().prepare(`SELECT * FROM reviews WHERE repo = ? ORDER BY created_at DESC`).all(repo);
|
|
251
|
+
return rows.map(rowToInfo);
|
|
252
|
+
}
|
|
253
|
+
function get2(id) {
|
|
254
|
+
const row = get().prepare(`SELECT * FROM reviews WHERE id = ?`).get(id);
|
|
255
|
+
if (!row)
|
|
256
|
+
return null;
|
|
257
|
+
return rowToInfo(row);
|
|
258
|
+
}
|
|
259
|
+
function latest(repo) {
|
|
260
|
+
const row = get().prepare(`SELECT * FROM reviews WHERE repo = ? ORDER BY created_at DESC LIMIT 1`).get(repo);
|
|
261
|
+
if (!row)
|
|
262
|
+
return null;
|
|
263
|
+
return rowToInfo(row);
|
|
264
|
+
}
|
|
265
|
+
function getByRepoAndCommit(repo, commitSha) {
|
|
266
|
+
const row = get().prepare(`SELECT * FROM reviews WHERE repo = ? AND commit_sha = ?`).get(repo, commitSha);
|
|
267
|
+
if (!row)
|
|
268
|
+
return null;
|
|
269
|
+
return rowToInfo(row);
|
|
270
|
+
}
|
|
271
|
+
function getOrCreate2(repo, commitSha) {
|
|
272
|
+
const existing = getByRepoAndCommit(repo, commitSha);
|
|
273
|
+
if (existing)
|
|
274
|
+
return existing;
|
|
275
|
+
return create2({ repo, commitSha });
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// src/store/patch.ts
|
|
279
|
+
var exports_patch = {};
|
|
280
|
+
__export(exports_patch, {
|
|
281
|
+
pruneUnreferenced: () => pruneUnreferenced,
|
|
282
|
+
nextSeq: () => nextSeq,
|
|
283
|
+
list: () => list2,
|
|
284
|
+
latest: () => latest2,
|
|
285
|
+
getOrCreate: () => getOrCreate3,
|
|
286
|
+
getByHash: () => getByHash,
|
|
287
|
+
get: () => get3,
|
|
288
|
+
create: () => create3
|
|
289
|
+
});
|
|
290
|
+
function rowToInfo2(row) {
|
|
291
|
+
return {
|
|
292
|
+
id: row.id,
|
|
293
|
+
review: row.review,
|
|
294
|
+
seq: row.seq,
|
|
295
|
+
hash: row.hash,
|
|
296
|
+
content: row.content,
|
|
297
|
+
createdAt: row.created_at
|
|
298
|
+
};
|
|
299
|
+
}
|
|
300
|
+
function create3(input) {
|
|
301
|
+
const row = get().prepare(`INSERT INTO patches (review, seq, hash, content, created_at)
|
|
302
|
+
VALUES (?, ?, ?, ?, ?) RETURNING *`).get(input.review, input.seq, input.hash, input.content, Date.now());
|
|
303
|
+
return rowToInfo2(row);
|
|
304
|
+
}
|
|
305
|
+
function get3(id) {
|
|
306
|
+
const row = get().prepare(`SELECT * FROM patches WHERE id = ?`).get(id);
|
|
307
|
+
if (!row)
|
|
308
|
+
return null;
|
|
309
|
+
return rowToInfo2(row);
|
|
310
|
+
}
|
|
311
|
+
function getByHash(review, hash) {
|
|
312
|
+
const row = get().prepare(`SELECT * FROM patches WHERE review = ? AND hash = ?`).get(review, hash);
|
|
313
|
+
if (!row)
|
|
314
|
+
return null;
|
|
315
|
+
return rowToInfo2(row);
|
|
316
|
+
}
|
|
317
|
+
function latest2(review) {
|
|
318
|
+
const row = get().prepare(`SELECT * FROM patches WHERE review = ? ORDER BY seq DESC LIMIT 1`).get(review);
|
|
319
|
+
if (!row)
|
|
320
|
+
return null;
|
|
321
|
+
return rowToInfo2(row);
|
|
322
|
+
}
|
|
323
|
+
function list2(review) {
|
|
324
|
+
const rows = get().prepare(`SELECT * FROM patches WHERE review = ? ORDER BY seq ASC`).all(review);
|
|
325
|
+
return rows.map(rowToInfo2);
|
|
326
|
+
}
|
|
327
|
+
function pruneUnreferenced(review, keepPatchIds = []) {
|
|
328
|
+
const keep = keepPatchIds.filter((x) => Number.isFinite(x));
|
|
329
|
+
const keepSql = keep.length > 0 ? ` AND id NOT IN (${keep.map(() => "?").join(",")})` : "";
|
|
330
|
+
const stmt = get().prepare(`DELETE FROM patches
|
|
331
|
+
WHERE review = ?
|
|
332
|
+
AND id NOT IN (
|
|
333
|
+
SELECT DISTINCT patch_id FROM review_comments WHERE review = ?
|
|
334
|
+
)${keepSql}`);
|
|
335
|
+
const result = stmt.run(review, review, ...keep);
|
|
336
|
+
return Number(result.changes ?? 0);
|
|
337
|
+
}
|
|
338
|
+
function nextSeq(review) {
|
|
339
|
+
const last = latest2(review);
|
|
340
|
+
return last ? last.seq + 1 : 0;
|
|
341
|
+
}
|
|
342
|
+
function getOrCreate3(review, content) {
|
|
343
|
+
const hash = String(Bun.hash(content));
|
|
344
|
+
const existing = getByHash(review, hash);
|
|
345
|
+
if (existing)
|
|
346
|
+
return existing;
|
|
347
|
+
const seq = nextSeq(review);
|
|
348
|
+
return create3({ review, seq, hash, content });
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// src/store/comment.ts
|
|
352
|
+
var exports_comment = {};
|
|
353
|
+
__export(exports_comment, {
|
|
354
|
+
list: () => list3,
|
|
355
|
+
create: () => create4
|
|
356
|
+
});
|
|
357
|
+
function create4(input) {
|
|
358
|
+
const now = Date.now();
|
|
359
|
+
const row = get().prepare(`INSERT INTO review_comments (
|
|
360
|
+
review,
|
|
361
|
+
note_id,
|
|
362
|
+
file,
|
|
363
|
+
patch_id,
|
|
364
|
+
start_row,
|
|
365
|
+
end_row,
|
|
366
|
+
created_at
|
|
367
|
+
)
|
|
368
|
+
VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING id`).get(input.review, input.noteId, input.file, input.patchId, input.startRow, input.endRow, now);
|
|
369
|
+
return {
|
|
370
|
+
id: row.id,
|
|
371
|
+
review: input.review,
|
|
372
|
+
noteId: input.noteId,
|
|
373
|
+
file: input.file,
|
|
374
|
+
patchId: input.patchId,
|
|
375
|
+
startRow: input.startRow,
|
|
376
|
+
endRow: input.endRow,
|
|
377
|
+
createdAt: now
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
function list3(review) {
|
|
381
|
+
const rows = get().prepare(`SELECT * FROM review_comments WHERE review = ? ORDER BY created_at ASC`).all(review);
|
|
382
|
+
return rows.map((row) => ({
|
|
383
|
+
id: row.id,
|
|
384
|
+
review: row.review,
|
|
385
|
+
noteId: row.note_id,
|
|
386
|
+
file: row.file,
|
|
387
|
+
patchId: row.patch_id,
|
|
388
|
+
startRow: row.start_row,
|
|
389
|
+
endRow: row.end_row,
|
|
390
|
+
createdAt: row.created_at
|
|
391
|
+
}));
|
|
392
|
+
}
|
|
393
|
+
// src/App.tsx
|
|
394
|
+
import { render } from "@opentui/solid";
|
|
395
|
+
import { SliderRenderable, addDefaultParsers } from "@opentui/core";
|
|
396
|
+
import { ErrorBoundary } from "solid-js";
|
|
397
|
+
|
|
398
|
+
// src/Review.tsx
|
|
399
|
+
import { useKeyboard as useKeyboard3, useTerminalDimensions as useTerminalDimensions2 } from "@opentui/solid";
|
|
400
|
+
import { createSignal as createSignal8, createEffect as createEffect6, createMemo as createMemo4, Show as Show7 } from "solid-js";
|
|
401
|
+
|
|
402
|
+
// src/components/DiffPanel.tsx
|
|
403
|
+
import { Show, For, createMemo as createMemo2, createEffect, Index } from "solid-js";
|
|
404
|
+
|
|
405
|
+
// src/lib/diff.ts
|
|
406
|
+
import { parsePatch } from "diff";
|
|
407
|
+
function parseFileDiff(content, file) {
|
|
408
|
+
if (!content)
|
|
409
|
+
return { hunks: [], totalRows: 0 };
|
|
410
|
+
try {
|
|
411
|
+
const patches = parsePatch(content);
|
|
412
|
+
if (patches.length === 0 && !content.endsWith(`
|
|
413
|
+
`)) {
|
|
414
|
+
patches.push(...parsePatch(`${content}
|
|
415
|
+
`));
|
|
416
|
+
}
|
|
417
|
+
if (patches.length === 0)
|
|
418
|
+
return { hunks: [], totalRows: 0 };
|
|
419
|
+
const rawHunks = patches[0]?.hunks ?? [];
|
|
420
|
+
const hunks = [];
|
|
421
|
+
const header = `diff --git a/${file} b/${file}
|
|
422
|
+
--- a/${file}
|
|
423
|
+
+++ b/${file}`;
|
|
424
|
+
let currentRow = 1;
|
|
425
|
+
for (let i = 0;i < rawHunks.length; i++) {
|
|
426
|
+
const hunk = rawHunks[i];
|
|
427
|
+
let skipBefore = 0;
|
|
428
|
+
if (i > 0) {
|
|
429
|
+
const prev = rawHunks[i - 1];
|
|
430
|
+
const prevEnd = prev.oldStart + prev.oldLines;
|
|
431
|
+
skipBefore = hunk.oldStart - prevEnd;
|
|
432
|
+
}
|
|
433
|
+
const hunkHeader = `@@ -${hunk.oldStart},${hunk.oldLines} +${hunk.newStart},${hunk.newLines} @@`;
|
|
434
|
+
const diffString = `${header}
|
|
435
|
+
${hunkHeader}
|
|
436
|
+
${hunk.lines.join(`
|
|
437
|
+
`)}`;
|
|
438
|
+
let lineCount = 0;
|
|
439
|
+
for (const line of hunk.lines) {
|
|
440
|
+
const c = line[0];
|
|
441
|
+
if (c === " " || c === "+" || c === "-")
|
|
442
|
+
lineCount++;
|
|
443
|
+
}
|
|
444
|
+
const startRow = currentRow;
|
|
445
|
+
const endRow = currentRow + lineCount - 1;
|
|
446
|
+
currentRow = endRow + 1;
|
|
447
|
+
hunks.push({
|
|
448
|
+
diffString,
|
|
449
|
+
oldStart: hunk.oldStart,
|
|
450
|
+
oldLines: hunk.oldLines,
|
|
451
|
+
newStart: hunk.newStart,
|
|
452
|
+
newLines: hunk.newLines,
|
|
453
|
+
skipBefore,
|
|
454
|
+
lineCount,
|
|
455
|
+
startRow,
|
|
456
|
+
endRow
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
return { hunks, totalRows: Math.max(0, currentRow - 1) };
|
|
460
|
+
} catch {
|
|
461
|
+
return { hunks: [], totalRows: 0 };
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
function getHunkIndexForRow(content, file, row) {
|
|
465
|
+
const { hunks } = parseFileDiff(content, file);
|
|
466
|
+
for (let i = 0;i < hunks.length; i++) {
|
|
467
|
+
const hunk = hunks[i];
|
|
468
|
+
if (row >= hunk.startRow && row <= hunk.endRow)
|
|
469
|
+
return i;
|
|
470
|
+
}
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
473
|
+
function stripPrefix(path2) {
|
|
474
|
+
if (!path2)
|
|
475
|
+
return "";
|
|
476
|
+
return path2.replace(/^a\//, "").replace(/^b\//, "");
|
|
477
|
+
}
|
|
478
|
+
function parsePatchEntries(content) {
|
|
479
|
+
if (!content.trim())
|
|
480
|
+
return [];
|
|
481
|
+
const chunks = content.trimEnd().split(/\n(?=diff --git )/);
|
|
482
|
+
const entries = [];
|
|
483
|
+
for (const chunk of chunks) {
|
|
484
|
+
if (!chunk.trim())
|
|
485
|
+
continue;
|
|
486
|
+
const parsed = parsePatch(chunk);
|
|
487
|
+
const patch = parsed[0];
|
|
488
|
+
let file = "";
|
|
489
|
+
if (patch) {
|
|
490
|
+
const newName = stripPrefix(patch.newFileName);
|
|
491
|
+
const oldName = stripPrefix(patch.oldFileName);
|
|
492
|
+
file = newName !== "/dev/null" ? newName : oldName;
|
|
493
|
+
}
|
|
494
|
+
if (!file) {
|
|
495
|
+
const first = chunk.split(`
|
|
496
|
+
`)[0] ?? "";
|
|
497
|
+
const match = first.match(/^diff --git a\/(.+?) b\/(.+)$/);
|
|
498
|
+
if (match)
|
|
499
|
+
file = match[2] ?? match[1] ?? "";
|
|
500
|
+
}
|
|
501
|
+
if (!file)
|
|
502
|
+
continue;
|
|
503
|
+
const isNew = patch?.oldFileName === "/dev/null" || chunk.includes(`
|
|
504
|
+
new file mode`);
|
|
505
|
+
const isDeleted = patch?.newFileName === "/dev/null" || chunk.includes(`
|
|
506
|
+
deleted file mode`);
|
|
507
|
+
entries.push({ file, content: chunk, isNew, isDeleted });
|
|
508
|
+
}
|
|
509
|
+
return entries;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// src/context/theme.tsx
|
|
513
|
+
import {
|
|
514
|
+
createContext,
|
|
515
|
+
useContext,
|
|
516
|
+
createSignal,
|
|
517
|
+
createMemo
|
|
518
|
+
} from "solid-js";
|
|
519
|
+
import { SyntaxStyle } from "@opentui/core";
|
|
520
|
+
import { jsxDEV } from "@opentui/solid/jsx-dev-runtime";
|
|
521
|
+
var ThemeContext = createContext();
|
|
522
|
+
var defaultTheme = {
|
|
523
|
+
background: "#0a0a0a",
|
|
524
|
+
backgroundPanel: "#141414",
|
|
525
|
+
backgroundElement: "#1e1e1e",
|
|
526
|
+
text: "#eeeeee",
|
|
527
|
+
textMuted: "#808080",
|
|
528
|
+
primary: "#63a088",
|
|
529
|
+
primaryDark: "#1d2f28",
|
|
530
|
+
secondary: "#608999",
|
|
531
|
+
secondaryDark: "#182226",
|
|
532
|
+
added: "#284036",
|
|
533
|
+
removed: "#e06c75",
|
|
534
|
+
modified: "#e5c07b",
|
|
535
|
+
indicatorDefault: "#4a4a4a",
|
|
536
|
+
connected: "#73d936",
|
|
537
|
+
disconnected: "#ff5555",
|
|
538
|
+
diffAddedBg: "#20303b",
|
|
539
|
+
diffRemovedBg: "#37222c",
|
|
540
|
+
diffContextBg: "111111",
|
|
541
|
+
diffSignAdded: "#b8db87",
|
|
542
|
+
diffSignRemoved: "#e26a75",
|
|
543
|
+
diffAddedLineNumberBg: "#1a2a30",
|
|
544
|
+
diffRemovedLineNumberBg: "#2d1a22",
|
|
545
|
+
diffLineNumberFg: "#888888",
|
|
546
|
+
syntaxComment: "#5c6773",
|
|
547
|
+
syntaxKeyword: "#ff7733",
|
|
548
|
+
syntaxFunction: "#ffb454",
|
|
549
|
+
syntaxVariable: "#cbccc6",
|
|
550
|
+
syntaxString: "#bae67e",
|
|
551
|
+
syntaxNumber: "#ffcc66",
|
|
552
|
+
syntaxType: "#73d0ff",
|
|
553
|
+
syntaxOperator: "#f29e74",
|
|
554
|
+
syntaxPunctuation: "#cbccc6"
|
|
555
|
+
};
|
|
556
|
+
var systemTheme = {
|
|
557
|
+
background: "transparent",
|
|
558
|
+
backgroundPanel: "brightBlack",
|
|
559
|
+
backgroundElement: "gray",
|
|
560
|
+
text: "white",
|
|
561
|
+
textMuted: "brightBlack",
|
|
562
|
+
primary: "cyan",
|
|
563
|
+
primaryDark: "blue",
|
|
564
|
+
secondary: "red",
|
|
565
|
+
secondaryDark: "magenta",
|
|
566
|
+
added: "green",
|
|
567
|
+
removed: "red",
|
|
568
|
+
modified: "yellow",
|
|
569
|
+
indicatorDefault: "gray",
|
|
570
|
+
connected: "brightGreen",
|
|
571
|
+
disconnected: "brightRed",
|
|
572
|
+
diffAddedBg: "green",
|
|
573
|
+
diffRemovedBg: "red",
|
|
574
|
+
diffContextBg: "transparent",
|
|
575
|
+
diffSignAdded: "brightGreen",
|
|
576
|
+
diffSignRemoved: "brightRed",
|
|
577
|
+
diffAddedLineNumberBg: "green",
|
|
578
|
+
diffRemovedLineNumberBg: "red",
|
|
579
|
+
diffLineNumberFg: "brightBlack",
|
|
580
|
+
syntaxComment: "brightBlack",
|
|
581
|
+
syntaxKeyword: "magenta",
|
|
582
|
+
syntaxFunction: "blue",
|
|
583
|
+
syntaxVariable: "white",
|
|
584
|
+
syntaxString: "green",
|
|
585
|
+
syntaxNumber: "yellow",
|
|
586
|
+
syntaxType: "cyan",
|
|
587
|
+
syntaxOperator: "white",
|
|
588
|
+
syntaxPunctuation: "white"
|
|
589
|
+
};
|
|
590
|
+
var themes = {
|
|
591
|
+
default: defaultTheme,
|
|
592
|
+
system: systemTheme
|
|
593
|
+
};
|
|
594
|
+
function getSyntaxRules(theme) {
|
|
595
|
+
return [
|
|
596
|
+
{
|
|
597
|
+
scope: ["comment", "comment.documentation"],
|
|
598
|
+
style: { foreground: theme.syntaxComment, italic: true }
|
|
599
|
+
},
|
|
600
|
+
{
|
|
601
|
+
scope: ["string", "string.special", "character"],
|
|
602
|
+
style: { foreground: theme.syntaxString }
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
scope: ["number", "boolean", "constant"],
|
|
606
|
+
style: { foreground: theme.syntaxNumber }
|
|
607
|
+
},
|
|
608
|
+
{
|
|
609
|
+
scope: [
|
|
610
|
+
"keyword",
|
|
611
|
+
"keyword.return",
|
|
612
|
+
"keyword.conditional",
|
|
613
|
+
"keyword.repeat"
|
|
614
|
+
],
|
|
615
|
+
style: { foreground: theme.syntaxKeyword, italic: true }
|
|
616
|
+
},
|
|
617
|
+
{
|
|
618
|
+
scope: ["keyword.function", "keyword.import"],
|
|
619
|
+
style: { foreground: theme.syntaxKeyword }
|
|
620
|
+
},
|
|
621
|
+
{
|
|
622
|
+
scope: ["function", "function.call", "function.method", "constructor"],
|
|
623
|
+
style: { foreground: theme.syntaxFunction }
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
scope: ["variable", "variable.parameter", "variable.member"],
|
|
627
|
+
style: { foreground: theme.syntaxVariable }
|
|
628
|
+
},
|
|
629
|
+
{
|
|
630
|
+
scope: ["type", "type.builtin", "module"],
|
|
631
|
+
style: { foreground: theme.syntaxType }
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
scope: ["operator", "keyword.operator"],
|
|
635
|
+
style: { foreground: theme.syntaxOperator }
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
scope: ["punctuation", "punctuation.delimiter", "punctuation.bracket"],
|
|
639
|
+
style: { foreground: theme.syntaxPunctuation }
|
|
640
|
+
},
|
|
641
|
+
{
|
|
642
|
+
scope: ["property"],
|
|
643
|
+
style: { foreground: theme.syntaxVariable }
|
|
644
|
+
},
|
|
645
|
+
{
|
|
646
|
+
scope: ["tag", "tag.attribute"],
|
|
647
|
+
style: { foreground: theme.syntaxKeyword }
|
|
648
|
+
}
|
|
649
|
+
];
|
|
650
|
+
}
|
|
651
|
+
function ThemeProvider(props) {
|
|
652
|
+
const [themeName, setThemeName] = createSignal("default");
|
|
653
|
+
const syntax = createMemo(() => {
|
|
654
|
+
const theme = themes[themeName()];
|
|
655
|
+
return SyntaxStyle.fromTheme(getSyntaxRules(theme));
|
|
656
|
+
});
|
|
657
|
+
const value = {
|
|
658
|
+
get theme() {
|
|
659
|
+
return themes[themeName()];
|
|
660
|
+
},
|
|
661
|
+
themeName,
|
|
662
|
+
setTheme: setThemeName,
|
|
663
|
+
syntax
|
|
664
|
+
};
|
|
665
|
+
return /* @__PURE__ */ jsxDEV(ThemeContext.Provider, {
|
|
666
|
+
value,
|
|
667
|
+
children: props.children
|
|
668
|
+
}, undefined, false, undefined, this);
|
|
669
|
+
}
|
|
670
|
+
function useTheme() {
|
|
671
|
+
const ctx = useContext(ThemeContext);
|
|
672
|
+
if (!ctx)
|
|
673
|
+
throw new Error("useTheme must be used within ThemeProvider");
|
|
674
|
+
return ctx;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
// src/components/HalfLineShadow.tsx
|
|
678
|
+
import { jsxDEV as jsxDEV2 } from "@opentui/solid/jsx-dev-runtime";
|
|
679
|
+
var EmptyBorder = {
|
|
680
|
+
topLeft: "",
|
|
681
|
+
bottomLeft: "",
|
|
682
|
+
vertical: "",
|
|
683
|
+
topRight: "",
|
|
684
|
+
bottomRight: "",
|
|
685
|
+
horizontal: " ",
|
|
686
|
+
bottomT: "",
|
|
687
|
+
topT: "",
|
|
688
|
+
cross: "",
|
|
689
|
+
leftT: "",
|
|
690
|
+
rightT: ""
|
|
691
|
+
};
|
|
692
|
+
|
|
693
|
+
// src/components/DiffPanel.tsx
|
|
694
|
+
import { jsxDEV as jsxDEV3, Fragment } from "@opentui/solid/jsx-dev-runtime";
|
|
695
|
+
var LINE_SCROLL_BUFFER = 8;
|
|
696
|
+
function getFiletype(filename) {
|
|
697
|
+
const ext = filename.split(".").pop()?.toLowerCase() ?? "";
|
|
698
|
+
const extMap = {
|
|
699
|
+
ts: "typescript",
|
|
700
|
+
tsx: "tsx",
|
|
701
|
+
js: "javascript",
|
|
702
|
+
jsx: "javascript",
|
|
703
|
+
mjs: "javascript",
|
|
704
|
+
cjs: "javascript",
|
|
705
|
+
html: "html",
|
|
706
|
+
htm: "html",
|
|
707
|
+
css: "css",
|
|
708
|
+
scss: "css",
|
|
709
|
+
vue: "vue",
|
|
710
|
+
json: "json",
|
|
711
|
+
yaml: "yaml",
|
|
712
|
+
yml: "yaml",
|
|
713
|
+
toml: "toml",
|
|
714
|
+
c: "c",
|
|
715
|
+
h: "c",
|
|
716
|
+
cpp: "cpp",
|
|
717
|
+
cc: "cpp",
|
|
718
|
+
cxx: "cpp",
|
|
719
|
+
hpp: "cpp",
|
|
720
|
+
hxx: "cpp",
|
|
721
|
+
rs: "rust",
|
|
722
|
+
go: "go",
|
|
723
|
+
zig: "zig",
|
|
724
|
+
py: "python",
|
|
725
|
+
rb: "ruby",
|
|
726
|
+
lua: "lua",
|
|
727
|
+
sh: "bash",
|
|
728
|
+
bash: "bash",
|
|
729
|
+
zsh: "bash",
|
|
730
|
+
php: "php",
|
|
731
|
+
java: "java",
|
|
732
|
+
kt: "kotlin",
|
|
733
|
+
kts: "kotlin",
|
|
734
|
+
scala: "scala",
|
|
735
|
+
cs: "c_sharp",
|
|
736
|
+
swift: "swift",
|
|
737
|
+
m: "objc",
|
|
738
|
+
mm: "objc",
|
|
739
|
+
dart: "dart",
|
|
740
|
+
ex: "elixir",
|
|
741
|
+
exs: "elixir",
|
|
742
|
+
elm: "elm",
|
|
743
|
+
ml: "ocaml",
|
|
744
|
+
mli: "ocaml",
|
|
745
|
+
el: "elisp",
|
|
746
|
+
sql: "ql",
|
|
747
|
+
erb: "embedded_template",
|
|
748
|
+
ejs: "embedded_template",
|
|
749
|
+
md: "markdown",
|
|
750
|
+
mdx: "markdown"
|
|
751
|
+
};
|
|
752
|
+
return extMap[ext] ?? "text";
|
|
753
|
+
}
|
|
754
|
+
function DiffPanel(props) {
|
|
755
|
+
const { theme, syntax } = useTheme();
|
|
756
|
+
const title = () => {
|
|
757
|
+
const entry = props.entry();
|
|
758
|
+
if (!entry)
|
|
759
|
+
return "Diff";
|
|
760
|
+
const hunkCount = props.hunkCount();
|
|
761
|
+
if (hunkCount === 0)
|
|
762
|
+
return entry.file;
|
|
763
|
+
if (props.selectionMode() === "line") {
|
|
764
|
+
const range = props.selectedRange();
|
|
765
|
+
if (range && range.startRow !== range.endRow) {
|
|
766
|
+
return `[lines ${range.startRow}-${range.endRow}]`;
|
|
767
|
+
}
|
|
768
|
+
const row = range?.startRow ?? props.cursorRow();
|
|
769
|
+
return `[line ${row}]`;
|
|
770
|
+
}
|
|
771
|
+
if (props.focused()) {
|
|
772
|
+
return `[hunk ${props.selectedHunkIndex() + 1}/${hunkCount}]`;
|
|
773
|
+
}
|
|
774
|
+
return `[${hunkCount} hunk${hunkCount === 1 ? "" : "s"}]`;
|
|
775
|
+
};
|
|
776
|
+
return /* @__PURE__ */ jsxDEV3("box", {
|
|
777
|
+
flexGrow: 1,
|
|
778
|
+
flexDirection: "column",
|
|
779
|
+
backgroundColor: theme.backgroundPanel,
|
|
780
|
+
paddingTop: 1,
|
|
781
|
+
paddingBottom: 1,
|
|
782
|
+
paddingLeft: 2,
|
|
783
|
+
paddingRight: 2,
|
|
784
|
+
children: [
|
|
785
|
+
/* @__PURE__ */ jsxDEV3("box", {
|
|
786
|
+
height: 1,
|
|
787
|
+
flexDirection: "row",
|
|
788
|
+
justifyContent: "flex-start",
|
|
789
|
+
gap: 1,
|
|
790
|
+
backgroundColor: props.focused() ? theme.primaryDark : theme.backgroundPanel,
|
|
791
|
+
border: ["left"],
|
|
792
|
+
borderColor: props.focused() ? theme.primary : theme.indicatorDefault,
|
|
793
|
+
customBorderChars: {
|
|
794
|
+
...EmptyBorder,
|
|
795
|
+
vertical: "\u258C"
|
|
796
|
+
},
|
|
797
|
+
children: /* @__PURE__ */ jsxDEV3("box", {
|
|
798
|
+
paddingLeft: 1,
|
|
799
|
+
flexDirection: "row",
|
|
800
|
+
gap: 1,
|
|
801
|
+
children: [
|
|
802
|
+
/* @__PURE__ */ jsxDEV3("text", {
|
|
803
|
+
children: /* @__PURE__ */ jsxDEV3("span", {
|
|
804
|
+
style: { italic: true },
|
|
805
|
+
children: props.entry() ? props.entry().file : "none"
|
|
806
|
+
}, undefined, false, undefined, this)
|
|
807
|
+
}, undefined, false, undefined, this),
|
|
808
|
+
/* @__PURE__ */ jsxDEV3("text", {
|
|
809
|
+
children: /* @__PURE__ */ jsxDEV3("span", {
|
|
810
|
+
style: { bold: true },
|
|
811
|
+
children: title()
|
|
812
|
+
}, undefined, false, undefined, this)
|
|
813
|
+
}, undefined, false, undefined, this)
|
|
814
|
+
]
|
|
815
|
+
}, undefined, true, undefined, this)
|
|
816
|
+
}, undefined, false, undefined, this),
|
|
817
|
+
/* @__PURE__ */ jsxDEV3(Show, {
|
|
818
|
+
when: props.entry(),
|
|
819
|
+
children: (() => {
|
|
820
|
+
const entry = props.entry();
|
|
821
|
+
const model = createMemo2(() => parseFileDiff(entry.content, entry.file));
|
|
822
|
+
const hunks = createMemo2(() => model().hunks);
|
|
823
|
+
const filetype = getFiletype(entry.file);
|
|
824
|
+
let scrollbox = null;
|
|
825
|
+
createEffect(() => {
|
|
826
|
+
const hunkIdx = props.selectedHunkIndex();
|
|
827
|
+
const hunkList = hunks();
|
|
828
|
+
if (!scrollbox || hunkList.length === 0)
|
|
829
|
+
return;
|
|
830
|
+
let scrollLine = 0;
|
|
831
|
+
if (props.selectionMode() === "line") {
|
|
832
|
+
const row = props.cursorRow();
|
|
833
|
+
const rowHunkIndex = getHunkIndexForRow(entry.content, entry.file, row);
|
|
834
|
+
const separatorOffset2 = rowHunkIndex ? rowHunkIndex : 0;
|
|
835
|
+
scrollLine = Math.max(0, row - 1 + separatorOffset2);
|
|
836
|
+
const scrollTop2 = scrollbox.scrollTop;
|
|
837
|
+
const viewportHeight2 = scrollbox.viewport.height;
|
|
838
|
+
const topBound = scrollTop2 + LINE_SCROLL_BUFFER;
|
|
839
|
+
const bottomBound = scrollTop2 + viewportHeight2 - 1 - LINE_SCROLL_BUFFER;
|
|
840
|
+
if (scrollLine < topBound) {
|
|
841
|
+
scrollbox.scrollTo(Math.max(0, scrollLine - LINE_SCROLL_BUFFER));
|
|
842
|
+
} else if (scrollLine > bottomBound) {
|
|
843
|
+
scrollbox.scrollTo(Math.max(0, scrollLine + LINE_SCROLL_BUFFER - viewportHeight2 + 1));
|
|
844
|
+
}
|
|
845
|
+
return;
|
|
846
|
+
}
|
|
847
|
+
if (hunkIdx > 0) {
|
|
848
|
+
const prev = hunkList[Math.min(hunkIdx - 1, hunkList.length - 1)];
|
|
849
|
+
if (prev)
|
|
850
|
+
scrollLine = prev.endRow + hunkIdx;
|
|
851
|
+
}
|
|
852
|
+
const activeHunk = hunkList[hunkIdx];
|
|
853
|
+
if (!activeHunk)
|
|
854
|
+
return;
|
|
855
|
+
const scrollTop = scrollbox.scrollTop;
|
|
856
|
+
const viewportHeight = scrollbox.viewport.height;
|
|
857
|
+
const separatorOffset = hunkIdx;
|
|
858
|
+
const hunkTop = Math.max(0, activeHunk.startRow - 1 + separatorOffset);
|
|
859
|
+
const hunkBottom = Math.max(0, activeHunk.endRow - 1 + separatorOffset);
|
|
860
|
+
const viewportBottom = scrollTop + viewportHeight - 1;
|
|
861
|
+
if (hunkTop < scrollTop) {
|
|
862
|
+
scrollbox.scrollTo(hunkTop);
|
|
863
|
+
} else if (hunkBottom > viewportBottom) {
|
|
864
|
+
scrollbox.scrollTo(scrollLine);
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
return /* @__PURE__ */ jsxDEV3("scrollbox", {
|
|
868
|
+
ref: (r) => {
|
|
869
|
+
scrollbox = r;
|
|
870
|
+
},
|
|
871
|
+
focused: false,
|
|
872
|
+
flexGrow: 1,
|
|
873
|
+
children: /* @__PURE__ */ jsxDEV3("box", {
|
|
874
|
+
flexDirection: "column",
|
|
875
|
+
backgroundColor: theme.background,
|
|
876
|
+
children: /* @__PURE__ */ jsxDEV3(Index, {
|
|
877
|
+
each: hunks(),
|
|
878
|
+
children: (hunk, i) => {
|
|
879
|
+
const isFocused = () => props.focused() && props.selectedHunkIndex() === i;
|
|
880
|
+
const indicatorColor = (row) => {
|
|
881
|
+
if (props.selectionMode() === "line") {
|
|
882
|
+
const range = props.selectedRange();
|
|
883
|
+
if (range && row >= range.startRow && row <= range.endRow) {
|
|
884
|
+
return theme.primary;
|
|
885
|
+
}
|
|
886
|
+
return theme.indicatorDefault;
|
|
887
|
+
}
|
|
888
|
+
if (isFocused())
|
|
889
|
+
return theme.primary;
|
|
890
|
+
return theme.indicatorDefault;
|
|
891
|
+
};
|
|
892
|
+
return /* @__PURE__ */ jsxDEV3(Fragment, {
|
|
893
|
+
children: [
|
|
894
|
+
/* @__PURE__ */ jsxDEV3(Show, {
|
|
895
|
+
when: i > 0,
|
|
896
|
+
children: /* @__PURE__ */ jsxDEV3("box", {
|
|
897
|
+
height: 1,
|
|
898
|
+
backgroundColor: theme.backgroundPanel
|
|
899
|
+
}, undefined, false, undefined, this)
|
|
900
|
+
}, undefined, false, undefined, this),
|
|
901
|
+
/* @__PURE__ */ jsxDEV3("box", {
|
|
902
|
+
flexDirection: "row",
|
|
903
|
+
children: [
|
|
904
|
+
/* @__PURE__ */ jsxDEV3("box", {
|
|
905
|
+
flexDirection: "column",
|
|
906
|
+
width: 1,
|
|
907
|
+
flexShrink: 0,
|
|
908
|
+
children: /* @__PURE__ */ jsxDEV3(For, {
|
|
909
|
+
each: Array.from({ length: hunk().lineCount }, (_, j) => j),
|
|
910
|
+
children: (j) => {
|
|
911
|
+
const row = hunk().startRow + j;
|
|
912
|
+
return /* @__PURE__ */ jsxDEV3("text", {
|
|
913
|
+
height: 1,
|
|
914
|
+
fg: indicatorColor(row),
|
|
915
|
+
children: "\u258C"
|
|
916
|
+
}, undefined, false, undefined, this);
|
|
917
|
+
}
|
|
918
|
+
}, undefined, false, undefined, this)
|
|
919
|
+
}, undefined, false, undefined, this),
|
|
920
|
+
/* @__PURE__ */ jsxDEV3("diff", {
|
|
921
|
+
diff: hunk().diffString,
|
|
922
|
+
view: "unified",
|
|
923
|
+
filetype,
|
|
924
|
+
syntaxStyle: syntax(),
|
|
925
|
+
showLineNumbers: true,
|
|
926
|
+
flexGrow: 0,
|
|
927
|
+
fg: theme.text,
|
|
928
|
+
addedBg: theme.diffAddedBg,
|
|
929
|
+
removedBg: theme.diffRemovedBg,
|
|
930
|
+
contextBg: theme.diffContextBg,
|
|
931
|
+
addedSignColor: theme.diffSignAdded,
|
|
932
|
+
removedSignColor: theme.diffSignRemoved,
|
|
933
|
+
lineNumberFg: theme.diffLineNumberFg,
|
|
934
|
+
lineNumberBg: theme.diffContextBg,
|
|
935
|
+
addedLineNumberBg: theme.diffAddedLineNumberBg,
|
|
936
|
+
removedLineNumberBg: theme.diffRemovedLineNumberBg
|
|
937
|
+
}, undefined, false, undefined, this)
|
|
938
|
+
]
|
|
939
|
+
}, undefined, true, undefined, this)
|
|
940
|
+
]
|
|
941
|
+
}, undefined, true, undefined, this);
|
|
942
|
+
}
|
|
943
|
+
}, undefined, false, undefined, this)
|
|
944
|
+
}, undefined, false, undefined, this)
|
|
945
|
+
}, undefined, false, undefined, this);
|
|
946
|
+
})()
|
|
947
|
+
}, undefined, false, undefined, this),
|
|
948
|
+
/* @__PURE__ */ jsxDEV3(Show, {
|
|
949
|
+
when: !props.entry(),
|
|
950
|
+
children: /* @__PURE__ */ jsxDEV3("box", {
|
|
951
|
+
padding: 1,
|
|
952
|
+
flexGrow: 1,
|
|
953
|
+
children: /* @__PURE__ */ jsxDEV3("text", {
|
|
954
|
+
children: "Select a file to view diff"
|
|
955
|
+
}, undefined, false, undefined, this)
|
|
956
|
+
}, undefined, false, undefined, this)
|
|
957
|
+
}, undefined, false, undefined, this)
|
|
958
|
+
]
|
|
959
|
+
}, undefined, true, undefined, this);
|
|
960
|
+
}
|
|
961
|
+
// src/components/EditorPanel.tsx
|
|
962
|
+
import { onMount as onMount3 } from "solid-js";
|
|
963
|
+
import { spawn } from "child_process";
|
|
964
|
+
import { writeFileSync, readFileSync } from "fs";
|
|
965
|
+
import { useRenderer } from "@opentui/solid";
|
|
966
|
+
|
|
967
|
+
// src/context/review.tsx
|
|
968
|
+
import {
|
|
969
|
+
createContext as createContext3,
|
|
970
|
+
useContext as useContext3,
|
|
971
|
+
createSignal as createSignal3,
|
|
972
|
+
createEffect as createEffect2,
|
|
973
|
+
onMount as onMount2
|
|
974
|
+
} from "solid-js";
|
|
975
|
+
|
|
976
|
+
// src/context/server.tsx
|
|
977
|
+
import {
|
|
978
|
+
createContext as createContext2,
|
|
979
|
+
useContext as useContext2,
|
|
980
|
+
createSignal as createSignal2,
|
|
981
|
+
onMount,
|
|
982
|
+
onCleanup
|
|
983
|
+
} from "solid-js";
|
|
984
|
+
import { Client } from "@spader/spall-sdk/client";
|
|
985
|
+
import { jsxDEV as jsxDEV4 } from "@opentui/solid/jsx-dev-runtime";
|
|
986
|
+
var ServerContext = createContext2();
|
|
987
|
+
function ServerProvider(props) {
|
|
988
|
+
const [url, setUrl] = createSignal2(null);
|
|
989
|
+
const [connected, setConnected] = createSignal2(false);
|
|
990
|
+
const [client, setClient] = createSignal2(null);
|
|
991
|
+
let shutdown = false;
|
|
992
|
+
const controller = new AbortController;
|
|
993
|
+
onMount(() => {
|
|
994
|
+
const connect = async () => {
|
|
995
|
+
while (!shutdown) {
|
|
996
|
+
try {
|
|
997
|
+
const c = await Client.connect(controller.signal);
|
|
998
|
+
const health = await c.health();
|
|
999
|
+
if (!health?.response?.ok) {
|
|
1000
|
+
throw new Error("Health check failed");
|
|
1001
|
+
}
|
|
1002
|
+
setUrl(health.response.url.replace("/health", ""));
|
|
1003
|
+
setConnected(true);
|
|
1004
|
+
setClient(c);
|
|
1005
|
+
const { stream } = await c.events({
|
|
1006
|
+
onSseError: () => {
|
|
1007
|
+
setConnected(false);
|
|
1008
|
+
setClient(null);
|
|
1009
|
+
},
|
|
1010
|
+
sseMaxRetryAttempts: 0
|
|
1011
|
+
});
|
|
1012
|
+
for await (const event of stream) {}
|
|
1013
|
+
} catch {
|
|
1014
|
+
setConnected(false);
|
|
1015
|
+
setClient(null);
|
|
1016
|
+
setUrl(null);
|
|
1017
|
+
await Bun.sleep(100);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
};
|
|
1021
|
+
connect();
|
|
1022
|
+
});
|
|
1023
|
+
onCleanup(() => {
|
|
1024
|
+
shutdown = true;
|
|
1025
|
+
controller.abort();
|
|
1026
|
+
});
|
|
1027
|
+
const value = {
|
|
1028
|
+
url,
|
|
1029
|
+
connected,
|
|
1030
|
+
client
|
|
1031
|
+
};
|
|
1032
|
+
return /* @__PURE__ */ jsxDEV4(ServerContext.Provider, {
|
|
1033
|
+
value,
|
|
1034
|
+
children: props.children
|
|
1035
|
+
}, undefined, false, undefined, this);
|
|
1036
|
+
}
|
|
1037
|
+
function useServer() {
|
|
1038
|
+
const ctx = useContext2(ServerContext);
|
|
1039
|
+
if (!ctx)
|
|
1040
|
+
throw new Error("useServer must be used within ServerProvider");
|
|
1041
|
+
return ctx;
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
// src/context/review.tsx
|
|
1045
|
+
import { jsxDEV as jsxDEV5 } from "@opentui/solid/jsx-dev-runtime";
|
|
1046
|
+
function pickCommentForRange(list4, preferredPatchId, file, startRow, endRow) {
|
|
1047
|
+
if (preferredPatchId !== null) {
|
|
1048
|
+
const exact = list4.find((c) => c.patchId === preferredPatchId && c.file === file && c.startRow === startRow && c.endRow === endRow) ?? null;
|
|
1049
|
+
if (exact)
|
|
1050
|
+
return exact;
|
|
1051
|
+
}
|
|
1052
|
+
const matches = list4.filter((c) => c.file === file && c.startRow === startRow && c.endRow === endRow);
|
|
1053
|
+
if (matches.length === 0)
|
|
1054
|
+
return null;
|
|
1055
|
+
return matches.reduce((best, c) => c.createdAt > best.createdAt ? c : best);
|
|
1056
|
+
}
|
|
1057
|
+
var ReviewContext = createContext3();
|
|
1058
|
+
function repoName(path2) {
|
|
1059
|
+
const segments = path2.split("/").filter(Boolean);
|
|
1060
|
+
return segments[segments.length - 1] || "unknown";
|
|
1061
|
+
}
|
|
1062
|
+
var REVIEW_CORPUS_NAME = "spall-review";
|
|
1063
|
+
function repoKey(root) {
|
|
1064
|
+
const base = repoName(root).replace(/[^a-zA-Z0-9._-]+/g, "-");
|
|
1065
|
+
const h = Bun.hash(root);
|
|
1066
|
+
const n = typeof h === "bigint" ? Number(h % 0xffffffffn) : h;
|
|
1067
|
+
const suffix = Math.abs(n).toString(36).slice(0, 6);
|
|
1068
|
+
return `${base}-${suffix}`;
|
|
1069
|
+
}
|
|
1070
|
+
function reviewNotePath(input) {
|
|
1071
|
+
const filePath = input.file.replace(/\\/g, "/");
|
|
1072
|
+
return `review/${repoKey(input.root)}/${input.commitSha}/p${input.patchSeq}/${filePath}__${input.startRow}-${input.endRow}.md`;
|
|
1073
|
+
}
|
|
1074
|
+
function ReviewProvider(props) {
|
|
1075
|
+
const server = useServer();
|
|
1076
|
+
const [corpusId, setCorpusId] = createSignal3(null);
|
|
1077
|
+
const corpusName = () => REVIEW_CORPUS_NAME;
|
|
1078
|
+
const [repoRoot, setRepoRoot] = createSignal3(null);
|
|
1079
|
+
const [reviewId, setReviewId] = createSignal3(null);
|
|
1080
|
+
const [commitSha, setCommitSha] = createSignal3(null);
|
|
1081
|
+
const [activePatchId, setActivePatchIdState] = createSignal3(null);
|
|
1082
|
+
const [workingTreePatchId, setWorkingTreePatchId] = createSignal3(null);
|
|
1083
|
+
const [workspaceEntries, setWorkspaceEntries] = createSignal3([]);
|
|
1084
|
+
const [workspaceDiff, setWorkspaceDiff] = createSignal3("");
|
|
1085
|
+
const [entries, setEntries] = createSignal3([]);
|
|
1086
|
+
const [loading, setLoading] = createSignal3(true);
|
|
1087
|
+
const ensureReviewId = (root, head) => {
|
|
1088
|
+
const current = reviewId();
|
|
1089
|
+
if (current !== null)
|
|
1090
|
+
return current;
|
|
1091
|
+
const repo = exports_repo.getOrCreate(root);
|
|
1092
|
+
const review = exports_review.getOrCreate(repo.id, head);
|
|
1093
|
+
setReviewId(review.id);
|
|
1094
|
+
return review.id;
|
|
1095
|
+
};
|
|
1096
|
+
const [patches, setPatches] = createSignal3([]);
|
|
1097
|
+
const [patchesLoading, setPatchesLoading] = createSignal3(false);
|
|
1098
|
+
const [comments, setComments] = createSignal3([]);
|
|
1099
|
+
const [commentsLoading, setCommentsLoading] = createSignal3(false);
|
|
1100
|
+
const [selectedFilePath, setSelectedFilePath] = createSignal3(null);
|
|
1101
|
+
const preserveSelectedFilePath = (list4, preferred) => {
|
|
1102
|
+
if (!preferred)
|
|
1103
|
+
return null;
|
|
1104
|
+
return list4.some((e) => e.file === preferred) ? preferred : null;
|
|
1105
|
+
};
|
|
1106
|
+
const setActivePatch = (patchId, preserveFilePath) => {
|
|
1107
|
+
if (patchId === null) {
|
|
1108
|
+
const wt = workingTreePatchId();
|
|
1109
|
+
if (wt !== null) {
|
|
1110
|
+
setActivePatch(wt, preserveFilePath);
|
|
1111
|
+
return;
|
|
1112
|
+
}
|
|
1113
|
+
setActivePatchIdState(null);
|
|
1114
|
+
const ws = parsePatchEntries(workspaceDiff());
|
|
1115
|
+
setEntries(ws);
|
|
1116
|
+
setSelectedFilePath(preserveSelectedFilePath(ws, preserveFilePath ?? selectedFilePath()));
|
|
1117
|
+
return;
|
|
1118
|
+
}
|
|
1119
|
+
const patch = exports_patch.get(patchId);
|
|
1120
|
+
if (!patch)
|
|
1121
|
+
return;
|
|
1122
|
+
setActivePatchIdState(patchId);
|
|
1123
|
+
const newEntries = parsePatchEntries(patch.content);
|
|
1124
|
+
setEntries(newEntries);
|
|
1125
|
+
setSelectedFilePath(preserveSelectedFilePath(newEntries, preserveFilePath ?? selectedFilePath()));
|
|
1126
|
+
};
|
|
1127
|
+
const refreshWorkingTree = async (preserveFilePath) => {
|
|
1128
|
+
const newEntries = await Git.entries(props.repoPath);
|
|
1129
|
+
setWorkspaceEntries(newEntries);
|
|
1130
|
+
const fullDiff = newEntries.map((x) => x.content).join(`
|
|
1131
|
+
`);
|
|
1132
|
+
setWorkspaceDiff(fullDiff);
|
|
1133
|
+
const root = repoRoot();
|
|
1134
|
+
const head = commitSha();
|
|
1135
|
+
if (!root || !head) {
|
|
1136
|
+
if (activePatchId() === null) {
|
|
1137
|
+
const ws = parsePatchEntries(fullDiff);
|
|
1138
|
+
setEntries(ws);
|
|
1139
|
+
setSelectedFilePath(preserveSelectedFilePath(ws, preserveFilePath ?? selectedFilePath()));
|
|
1140
|
+
}
|
|
1141
|
+
setWorkingTreePatchId(null);
|
|
1142
|
+
return;
|
|
1143
|
+
}
|
|
1144
|
+
const revId = ensureReviewId(root, head);
|
|
1145
|
+
const patch = exports_patch.getOrCreate(revId, fullDiff);
|
|
1146
|
+
const prevWt = workingTreePatchId();
|
|
1147
|
+
setWorkingTreePatchId(patch.id);
|
|
1148
|
+
setPatches((prev) => {
|
|
1149
|
+
if (prev.some((p) => p.id === patch.id))
|
|
1150
|
+
return prev;
|
|
1151
|
+
return [...prev, patch].sort((a, b) => a.seq - b.seq);
|
|
1152
|
+
});
|
|
1153
|
+
const active = activePatchId();
|
|
1154
|
+
if (active === null || prevWt !== null && active === prevWt) {
|
|
1155
|
+
setActivePatch(patch.id, preserveFilePath);
|
|
1156
|
+
}
|
|
1157
|
+
};
|
|
1158
|
+
const [initialized, setInitialized] = createSignal3(false);
|
|
1159
|
+
let corpusInitInFlight = false;
|
|
1160
|
+
let commentsHydrated = false;
|
|
1161
|
+
createEffect2(() => {
|
|
1162
|
+
const client = server.client();
|
|
1163
|
+
if (!client || !initialized())
|
|
1164
|
+
return;
|
|
1165
|
+
if (corpusId() === null && !corpusInitInFlight) {
|
|
1166
|
+
corpusInitInFlight = true;
|
|
1167
|
+
(async () => {
|
|
1168
|
+
try {
|
|
1169
|
+
const result = await client.corpus.create({
|
|
1170
|
+
name: REVIEW_CORPUS_NAME
|
|
1171
|
+
});
|
|
1172
|
+
if (result.data) {
|
|
1173
|
+
setCorpusId(result.data.id);
|
|
1174
|
+
}
|
|
1175
|
+
} catch {} finally {
|
|
1176
|
+
corpusInitInFlight = false;
|
|
1177
|
+
}
|
|
1178
|
+
})();
|
|
1179
|
+
}
|
|
1180
|
+
if (commentsHydrated)
|
|
1181
|
+
return;
|
|
1182
|
+
const pending = comments().filter((c) => c.notePath === null || c.noteContent === null);
|
|
1183
|
+
if (pending.length === 0) {
|
|
1184
|
+
commentsHydrated = true;
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
commentsHydrated = true;
|
|
1188
|
+
setCommentsLoading(true);
|
|
1189
|
+
(async () => {
|
|
1190
|
+
try {
|
|
1191
|
+
const results = await Promise.all(pending.map(async (comment) => {
|
|
1192
|
+
try {
|
|
1193
|
+
const result = await client.note.getById({
|
|
1194
|
+
id: comment.noteId.toString()
|
|
1195
|
+
});
|
|
1196
|
+
if (!result.data)
|
|
1197
|
+
return null;
|
|
1198
|
+
return {
|
|
1199
|
+
id: comment.id,
|
|
1200
|
+
notePath: result.data.path,
|
|
1201
|
+
noteContent: result.data.content
|
|
1202
|
+
};
|
|
1203
|
+
} catch {
|
|
1204
|
+
return null;
|
|
1205
|
+
}
|
|
1206
|
+
}));
|
|
1207
|
+
const byId = new Map(results.filter(Boolean).map((r) => [r.id, r]));
|
|
1208
|
+
setComments((prev) => prev.map((c) => {
|
|
1209
|
+
const upd = byId.get(c.id);
|
|
1210
|
+
return upd ? { ...c, notePath: upd.notePath, noteContent: upd.noteContent } : c;
|
|
1211
|
+
}));
|
|
1212
|
+
} finally {
|
|
1213
|
+
setCommentsLoading(false);
|
|
1214
|
+
}
|
|
1215
|
+
})();
|
|
1216
|
+
});
|
|
1217
|
+
onMount2(async () => {
|
|
1218
|
+
const root = await Git.root(props.repoPath);
|
|
1219
|
+
setRepoRoot(root);
|
|
1220
|
+
const head = await Git.head(props.repoPath);
|
|
1221
|
+
setCommitSha(head);
|
|
1222
|
+
if (root && head) {
|
|
1223
|
+
ensureReviewId(root, head);
|
|
1224
|
+
}
|
|
1225
|
+
const revId = reviewId();
|
|
1226
|
+
if (revId) {
|
|
1227
|
+
const local = exports_comment.list(revId);
|
|
1228
|
+
setComments(local.map((c) => ({
|
|
1229
|
+
id: c.id,
|
|
1230
|
+
reviewId: c.review,
|
|
1231
|
+
noteId: c.noteId,
|
|
1232
|
+
file: c.file,
|
|
1233
|
+
patchId: c.patchId,
|
|
1234
|
+
startRow: c.startRow,
|
|
1235
|
+
endRow: c.endRow,
|
|
1236
|
+
createdAt: c.createdAt,
|
|
1237
|
+
notePath: null,
|
|
1238
|
+
noteContent: null
|
|
1239
|
+
})));
|
|
1240
|
+
setPatchesLoading(true);
|
|
1241
|
+
setPatches(exports_patch.list(revId));
|
|
1242
|
+
setPatchesLoading(false);
|
|
1243
|
+
}
|
|
1244
|
+
await refreshWorkingTree(selectedFilePath() ?? undefined);
|
|
1245
|
+
if (revId) {
|
|
1246
|
+
const wt = workingTreePatchId();
|
|
1247
|
+
exports_patch.pruneUnreferenced(revId, wt !== null ? [wt] : []);
|
|
1248
|
+
setPatches(exports_patch.list(revId));
|
|
1249
|
+
}
|
|
1250
|
+
setLoading(false);
|
|
1251
|
+
setInitialized(true);
|
|
1252
|
+
});
|
|
1253
|
+
const getCommentById = (commentId) => {
|
|
1254
|
+
return comments().find((c) => c.id === commentId) ?? null;
|
|
1255
|
+
};
|
|
1256
|
+
const getCommentForRange = (file, startRow, endRow) => {
|
|
1257
|
+
const patchId = activePatchId();
|
|
1258
|
+
return pickCommentForRange(comments(), patchId, file, startRow, endRow);
|
|
1259
|
+
};
|
|
1260
|
+
const createComment = async (file, startRow, endRow, content) => {
|
|
1261
|
+
const root = repoRoot();
|
|
1262
|
+
const head = commitSha();
|
|
1263
|
+
const c = server.client();
|
|
1264
|
+
let cid = corpusId();
|
|
1265
|
+
if (!root || !head)
|
|
1266
|
+
return null;
|
|
1267
|
+
if (!content.trim())
|
|
1268
|
+
return null;
|
|
1269
|
+
const repo = exports_repo.getOrCreate(root);
|
|
1270
|
+
let revId = reviewId();
|
|
1271
|
+
if (!revId) {
|
|
1272
|
+
const review = exports_review.getOrCreate(repo.id, head);
|
|
1273
|
+
revId = review.id;
|
|
1274
|
+
setReviewId(revId);
|
|
1275
|
+
}
|
|
1276
|
+
let patch = activePatchId() ? exports_patch.get(activePatchId()) : null;
|
|
1277
|
+
if (!patch) {
|
|
1278
|
+
const wt = workingTreePatchId();
|
|
1279
|
+
patch = wt !== null ? exports_patch.get(wt) : null;
|
|
1280
|
+
if (!patch) {
|
|
1281
|
+
const fullDiff = workspaceDiff();
|
|
1282
|
+
patch = exports_patch.getOrCreate(revId, fullDiff);
|
|
1283
|
+
setWorkingTreePatchId(patch.id);
|
|
1284
|
+
setPatches((prev) => {
|
|
1285
|
+
if (prev.some((p) => p.id === patch.id))
|
|
1286
|
+
return prev;
|
|
1287
|
+
return [...prev, patch].sort((a, b) => a.seq - b.seq);
|
|
1288
|
+
});
|
|
1289
|
+
if (activePatchId() === null)
|
|
1290
|
+
setActivePatch(patch.id);
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
if (!patch)
|
|
1294
|
+
return null;
|
|
1295
|
+
if (c) {
|
|
1296
|
+
if (cid === null) {
|
|
1297
|
+
try {
|
|
1298
|
+
const ensured = await c.corpus.create({ name: REVIEW_CORPUS_NAME });
|
|
1299
|
+
if (ensured.data) {
|
|
1300
|
+
cid = ensured.data.id;
|
|
1301
|
+
setCorpusId(cid);
|
|
1302
|
+
}
|
|
1303
|
+
} catch {}
|
|
1304
|
+
}
|
|
1305
|
+
if (cid === null)
|
|
1306
|
+
return null;
|
|
1307
|
+
const path2 = reviewNotePath({
|
|
1308
|
+
root,
|
|
1309
|
+
commitSha: head,
|
|
1310
|
+
patchSeq: patch.seq,
|
|
1311
|
+
file,
|
|
1312
|
+
startRow,
|
|
1313
|
+
endRow
|
|
1314
|
+
});
|
|
1315
|
+
const result = await c.note.upsert({
|
|
1316
|
+
id: cid.toString(),
|
|
1317
|
+
path: path2,
|
|
1318
|
+
content,
|
|
1319
|
+
dupe: true
|
|
1320
|
+
});
|
|
1321
|
+
if (result.error || !result.data) {
|
|
1322
|
+
return null;
|
|
1323
|
+
}
|
|
1324
|
+
const noteId = result.data.id;
|
|
1325
|
+
const localComment = exports_comment.create({
|
|
1326
|
+
review: revId,
|
|
1327
|
+
noteId,
|
|
1328
|
+
file,
|
|
1329
|
+
patchId: patch.id,
|
|
1330
|
+
startRow,
|
|
1331
|
+
endRow
|
|
1332
|
+
});
|
|
1333
|
+
const newComment = {
|
|
1334
|
+
id: localComment.id,
|
|
1335
|
+
reviewId: revId,
|
|
1336
|
+
noteId,
|
|
1337
|
+
file,
|
|
1338
|
+
patchId: localComment.patchId,
|
|
1339
|
+
startRow: localComment.startRow,
|
|
1340
|
+
endRow: localComment.endRow,
|
|
1341
|
+
createdAt: localComment.createdAt,
|
|
1342
|
+
notePath: path2,
|
|
1343
|
+
noteContent: content
|
|
1344
|
+
};
|
|
1345
|
+
setComments((prev) => [...prev, newComment]);
|
|
1346
|
+
return newComment;
|
|
1347
|
+
}
|
|
1348
|
+
return null;
|
|
1349
|
+
};
|
|
1350
|
+
const updateComment = async (commentId, content) => {
|
|
1351
|
+
const c = server.client();
|
|
1352
|
+
if (!c)
|
|
1353
|
+
return;
|
|
1354
|
+
if (!content.trim())
|
|
1355
|
+
return;
|
|
1356
|
+
const comment = comments().find((c2) => c2.id === commentId);
|
|
1357
|
+
if (!comment)
|
|
1358
|
+
return;
|
|
1359
|
+
const result = await c.note.update({
|
|
1360
|
+
id: comment.noteId.toString(),
|
|
1361
|
+
content,
|
|
1362
|
+
dupe: true
|
|
1363
|
+
});
|
|
1364
|
+
if (result.error)
|
|
1365
|
+
return;
|
|
1366
|
+
setComments((prev) => prev.map((c2) => c2.id === commentId ? { ...c2, noteContent: content } : c2));
|
|
1367
|
+
};
|
|
1368
|
+
const value = {
|
|
1369
|
+
corpusId,
|
|
1370
|
+
corpusName,
|
|
1371
|
+
workingTreePatchId,
|
|
1372
|
+
repoRoot,
|
|
1373
|
+
repoPath: () => props.repoPath,
|
|
1374
|
+
reviewId,
|
|
1375
|
+
commitSha,
|
|
1376
|
+
activePatchId,
|
|
1377
|
+
workspaceEntries,
|
|
1378
|
+
entries,
|
|
1379
|
+
loading,
|
|
1380
|
+
patches,
|
|
1381
|
+
patchesLoading,
|
|
1382
|
+
comments,
|
|
1383
|
+
commentsLoading,
|
|
1384
|
+
selectedFilePath,
|
|
1385
|
+
setSelectedFilePath,
|
|
1386
|
+
setActivePatch,
|
|
1387
|
+
refreshWorkingTree,
|
|
1388
|
+
getCommentById,
|
|
1389
|
+
getCommentForRange,
|
|
1390
|
+
createComment,
|
|
1391
|
+
updateComment
|
|
1392
|
+
};
|
|
1393
|
+
return /* @__PURE__ */ jsxDEV5(ReviewContext.Provider, {
|
|
1394
|
+
value,
|
|
1395
|
+
children: props.children
|
|
1396
|
+
}, undefined, false, undefined, this);
|
|
1397
|
+
}
|
|
1398
|
+
function useReview() {
|
|
1399
|
+
const ctx = useContext3(ReviewContext);
|
|
1400
|
+
if (!ctx)
|
|
1401
|
+
throw new Error("useReview must be used within ReviewProvider");
|
|
1402
|
+
return ctx;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
// src/components/EditorPanel.tsx
|
|
1406
|
+
import { jsxDEV as jsxDEV6 } from "@opentui/solid/jsx-dev-runtime";
|
|
1407
|
+
function EditorPanel(props) {
|
|
1408
|
+
const { theme } = useTheme();
|
|
1409
|
+
const review = useReview();
|
|
1410
|
+
const renderer = useRenderer();
|
|
1411
|
+
let textareaRef = null;
|
|
1412
|
+
let initialized = false;
|
|
1413
|
+
const existingComment = props.commentId ? review.getCommentById(props.commentId) : review.getCommentForRange(props.file, props.startRow, props.endRow);
|
|
1414
|
+
const initialContent = existingComment?.noteContent ?? "";
|
|
1415
|
+
const shortFile = props.file.split("/").pop() ?? props.file;
|
|
1416
|
+
const rangeLabel = `rows-${props.startRow}-${props.endRow}`;
|
|
1417
|
+
const filename = `${shortFile}_${rangeLabel}.md`;
|
|
1418
|
+
onMount3(() => {
|
|
1419
|
+
setTimeout(() => {
|
|
1420
|
+
initialized = true;
|
|
1421
|
+
}, 0);
|
|
1422
|
+
});
|
|
1423
|
+
const submit = async () => {
|
|
1424
|
+
if (!textareaRef)
|
|
1425
|
+
return;
|
|
1426
|
+
const content = textareaRef.editBuffer.getText();
|
|
1427
|
+
if (!content.trim()) {
|
|
1428
|
+
props.onClose();
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
if (existingComment) {
|
|
1432
|
+
await review.updateComment(existingComment.id, content);
|
|
1433
|
+
} else {
|
|
1434
|
+
await review.createComment(props.file, props.startRow, props.endRow, content);
|
|
1435
|
+
}
|
|
1436
|
+
props.onClose();
|
|
1437
|
+
};
|
|
1438
|
+
const openInEditor = async () => {
|
|
1439
|
+
if (!textareaRef)
|
|
1440
|
+
return;
|
|
1441
|
+
const tmpFile = `/tmp/spall-${filename}`;
|
|
1442
|
+
writeFileSync(tmpFile, textareaRef.editBuffer.getText());
|
|
1443
|
+
renderer.suspend();
|
|
1444
|
+
await new Promise((resolve, reject) => {
|
|
1445
|
+
const editor = process.env.EDITOR || "nvim";
|
|
1446
|
+
const child = spawn(editor, [tmpFile], { stdio: "inherit" });
|
|
1447
|
+
child.on("close", (code) => code === 0 ? resolve() : reject(new Error(`exit ${code}`)));
|
|
1448
|
+
child.on("error", reject);
|
|
1449
|
+
});
|
|
1450
|
+
const content = readFileSync(tmpFile, "utf-8");
|
|
1451
|
+
renderer.resume();
|
|
1452
|
+
if (!content.trim()) {
|
|
1453
|
+
props.onClose();
|
|
1454
|
+
return;
|
|
1455
|
+
}
|
|
1456
|
+
if (textareaRef) {
|
|
1457
|
+
textareaRef.editBuffer.setText(content);
|
|
1458
|
+
textareaRef.cursorOffset = content.length;
|
|
1459
|
+
}
|
|
1460
|
+
};
|
|
1461
|
+
const handleKeyDown = (e) => {
|
|
1462
|
+
if (!initialized && (e.name === "c" || e.name === "return")) {
|
|
1463
|
+
e.preventDefault();
|
|
1464
|
+
return;
|
|
1465
|
+
}
|
|
1466
|
+
if (e.name === "escape") {
|
|
1467
|
+
e.preventDefault();
|
|
1468
|
+
props.onClose();
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
if (e.name === "return") {
|
|
1472
|
+
e.preventDefault();
|
|
1473
|
+
submit();
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
if (e.name === "j" && e.ctrl) {
|
|
1477
|
+
e.preventDefault();
|
|
1478
|
+
textareaRef?.editBuffer.newLine();
|
|
1479
|
+
return;
|
|
1480
|
+
}
|
|
1481
|
+
if (e.name === "e" && e.ctrl) {
|
|
1482
|
+
e.preventDefault();
|
|
1483
|
+
openInEditor();
|
|
1484
|
+
return;
|
|
1485
|
+
}
|
|
1486
|
+
};
|
|
1487
|
+
return /* @__PURE__ */ jsxDEV6("box", {
|
|
1488
|
+
flexDirection: "column",
|
|
1489
|
+
backgroundColor: theme.backgroundPanel,
|
|
1490
|
+
flexShrink: 0,
|
|
1491
|
+
children: [
|
|
1492
|
+
/* @__PURE__ */ jsxDEV6("box", {
|
|
1493
|
+
paddingLeft: 1,
|
|
1494
|
+
paddingRight: 1,
|
|
1495
|
+
paddingTop: 1,
|
|
1496
|
+
paddingBottom: 1,
|
|
1497
|
+
children: /* @__PURE__ */ jsxDEV6("textarea", {
|
|
1498
|
+
ref: (r) => {
|
|
1499
|
+
textareaRef = r;
|
|
1500
|
+
},
|
|
1501
|
+
initialValue: initialContent || undefined,
|
|
1502
|
+
focused: true,
|
|
1503
|
+
showCursor: true,
|
|
1504
|
+
wrapMode: "word",
|
|
1505
|
+
minHeight: 1,
|
|
1506
|
+
maxHeight: 6,
|
|
1507
|
+
width: "100%",
|
|
1508
|
+
onKeyDown: handleKeyDown
|
|
1509
|
+
}, undefined, false, undefined, this)
|
|
1510
|
+
}, undefined, false, undefined, this),
|
|
1511
|
+
/* @__PURE__ */ jsxDEV6("box", {
|
|
1512
|
+
height: 1,
|
|
1513
|
+
paddingLeft: 1,
|
|
1514
|
+
children: /* @__PURE__ */ jsxDEV6("text", {
|
|
1515
|
+
fg: theme.textMuted,
|
|
1516
|
+
children: filename
|
|
1517
|
+
}, undefined, false, undefined, this)
|
|
1518
|
+
}, undefined, false, undefined, this)
|
|
1519
|
+
]
|
|
1520
|
+
}, undefined, true, undefined, this);
|
|
1521
|
+
}
|
|
1522
|
+
// src/components/ErrorFallback.tsx
|
|
1523
|
+
import { useKeyboard, useRenderer as useRenderer2 } from "@opentui/solid";
|
|
1524
|
+
import { jsxDEV as jsxDEV7 } from "@opentui/solid/jsx-dev-runtime";
|
|
1525
|
+
function ErrorFallback(props) {
|
|
1526
|
+
const renderer = useRenderer2();
|
|
1527
|
+
useKeyboard((key) => {
|
|
1528
|
+
if (key.name === "r") {
|
|
1529
|
+
props.reset();
|
|
1530
|
+
}
|
|
1531
|
+
if (key.name === "q" || key.raw === "\x03") {
|
|
1532
|
+
renderer.destroy();
|
|
1533
|
+
}
|
|
1534
|
+
});
|
|
1535
|
+
return /* @__PURE__ */ jsxDEV7("box", {
|
|
1536
|
+
flexDirection: "column",
|
|
1537
|
+
padding: 2,
|
|
1538
|
+
children: [
|
|
1539
|
+
/* @__PURE__ */ jsxDEV7("text", {
|
|
1540
|
+
fg: "red",
|
|
1541
|
+
children: [
|
|
1542
|
+
"Error: ",
|
|
1543
|
+
props.error.message
|
|
1544
|
+
]
|
|
1545
|
+
}, undefined, true, undefined, this),
|
|
1546
|
+
/* @__PURE__ */ jsxDEV7("text", {}, undefined, false, undefined, this),
|
|
1547
|
+
/* @__PURE__ */ jsxDEV7("text", {
|
|
1548
|
+
fg: "gray",
|
|
1549
|
+
children: "Press 'r' to retry or 'q' to quit"
|
|
1550
|
+
}, undefined, false, undefined, this)
|
|
1551
|
+
]
|
|
1552
|
+
}, undefined, true, undefined, this);
|
|
1553
|
+
}
|
|
1554
|
+
// src/components/CommandPalette.tsx
|
|
1555
|
+
import { For as For2, createSignal as createSignal6, createMemo as createMemo3, Show as Show2 } from "solid-js";
|
|
1556
|
+
import { useKeyboard as useKeyboard2, useRenderer as useRenderer3, useTerminalDimensions } from "@opentui/solid";
|
|
1557
|
+
import { RGBA } from "@opentui/core";
|
|
1558
|
+
|
|
1559
|
+
// src/context/command.tsx
|
|
1560
|
+
import { createContext as createContext4, useContext as useContext4, createSignal as createSignal4 } from "solid-js";
|
|
1561
|
+
import { jsxDEV as jsxDEV8 } from "@opentui/solid/jsx-dev-runtime";
|
|
1562
|
+
var CommandContext = createContext4();
|
|
1563
|
+
function CommandProvider(props) {
|
|
1564
|
+
const [registrations, setRegistrations] = createSignal4([]);
|
|
1565
|
+
const value = {
|
|
1566
|
+
register: (commands) => {
|
|
1567
|
+
setRegistrations((prev) => [...prev, commands]);
|
|
1568
|
+
},
|
|
1569
|
+
entries: () => registrations().flatMap((r) => r())
|
|
1570
|
+
};
|
|
1571
|
+
return /* @__PURE__ */ jsxDEV8(CommandContext.Provider, {
|
|
1572
|
+
value,
|
|
1573
|
+
children: props.children
|
|
1574
|
+
}, undefined, false, undefined, this);
|
|
1575
|
+
}
|
|
1576
|
+
function useCommand() {
|
|
1577
|
+
const ctx = useContext4(CommandContext);
|
|
1578
|
+
if (!ctx)
|
|
1579
|
+
throw new Error("useCommand must be used within CommandProvider");
|
|
1580
|
+
return ctx;
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
// src/context/dialog.tsx
|
|
1584
|
+
import { createContext as createContext5, useContext as useContext5, createSignal as createSignal5 } from "solid-js";
|
|
1585
|
+
import { jsxDEV as jsxDEV9 } from "@opentui/solid/jsx-dev-runtime";
|
|
1586
|
+
var DialogContext = createContext5();
|
|
1587
|
+
function DialogProvider(props) {
|
|
1588
|
+
const [content, setContent] = createSignal5(null);
|
|
1589
|
+
const value = {
|
|
1590
|
+
show: (element) => setContent(() => element),
|
|
1591
|
+
clear: () => setContent(null),
|
|
1592
|
+
isOpen: () => content() !== null,
|
|
1593
|
+
content: () => {
|
|
1594
|
+
const c = content();
|
|
1595
|
+
return c ? c() : null;
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
return /* @__PURE__ */ jsxDEV9(DialogContext.Provider, {
|
|
1599
|
+
value,
|
|
1600
|
+
children: props.children
|
|
1601
|
+
}, undefined, false, undefined, this);
|
|
1602
|
+
}
|
|
1603
|
+
function useDialog() {
|
|
1604
|
+
const ctx = useContext5(DialogContext);
|
|
1605
|
+
if (!ctx)
|
|
1606
|
+
throw new Error("useDialog must be used within DialogProvider");
|
|
1607
|
+
return ctx;
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
// src/lib/keybind.ts
|
|
1611
|
+
function matchKey(key, bind) {
|
|
1612
|
+
if (key.name !== bind.name)
|
|
1613
|
+
return false;
|
|
1614
|
+
if ((bind.ctrl ?? false) !== key.ctrl)
|
|
1615
|
+
return false;
|
|
1616
|
+
if ((bind.meta ?? false) !== key.meta)
|
|
1617
|
+
return false;
|
|
1618
|
+
if ((bind.shift ?? false) !== key.shift)
|
|
1619
|
+
return false;
|
|
1620
|
+
return true;
|
|
1621
|
+
}
|
|
1622
|
+
function matchAny(key, binds) {
|
|
1623
|
+
return binds.some((bind) => matchKey(key, bind));
|
|
1624
|
+
}
|
|
1625
|
+
function formatKeybind(bind) {
|
|
1626
|
+
const parts = [];
|
|
1627
|
+
if (bind.ctrl)
|
|
1628
|
+
parts.push("C");
|
|
1629
|
+
if (bind.meta)
|
|
1630
|
+
parts.push("M");
|
|
1631
|
+
if (bind.shift)
|
|
1632
|
+
parts.push("S");
|
|
1633
|
+
parts.push(bind.name);
|
|
1634
|
+
return parts.join("-");
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// src/components/CommandPalette.tsx
|
|
1638
|
+
import { jsxDEV as jsxDEV10, Fragment as Fragment2 } from "@opentui/solid/jsx-dev-runtime";
|
|
1639
|
+
var CATEGORY_ORDER = ["movement", "selection", "actions"];
|
|
1640
|
+
function CommandPalette() {
|
|
1641
|
+
const dims = useTerminalDimensions();
|
|
1642
|
+
const renderer = useRenderer3();
|
|
1643
|
+
const command = useCommand();
|
|
1644
|
+
const dialog = useDialog();
|
|
1645
|
+
const { theme } = useTheme();
|
|
1646
|
+
const [selectedIndex, setSelectedIndex] = createSignal6(0);
|
|
1647
|
+
const entries = createMemo3(() => {
|
|
1648
|
+
const active = command.entries().filter((cmd) => cmd.isActive());
|
|
1649
|
+
const seen = new Set;
|
|
1650
|
+
return active.filter((cmd) => {
|
|
1651
|
+
if (seen.has(cmd.title))
|
|
1652
|
+
return false;
|
|
1653
|
+
seen.add(cmd.title);
|
|
1654
|
+
return true;
|
|
1655
|
+
});
|
|
1656
|
+
});
|
|
1657
|
+
const groupedEntries = createMemo3(() => {
|
|
1658
|
+
const groups = [];
|
|
1659
|
+
const byCategory = new Map;
|
|
1660
|
+
for (const cmd of entries()) {
|
|
1661
|
+
const list4 = byCategory.get(cmd.category) ?? [];
|
|
1662
|
+
list4.push(cmd);
|
|
1663
|
+
byCategory.set(cmd.category, list4);
|
|
1664
|
+
}
|
|
1665
|
+
for (const cat of CATEGORY_ORDER) {
|
|
1666
|
+
const cmds = byCategory.get(cat);
|
|
1667
|
+
if (cmds && cmds.length > 0) {
|
|
1668
|
+
groups.push({ category: cat, commands: cmds });
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
return groups;
|
|
1672
|
+
});
|
|
1673
|
+
const flatEntries = createMemo3(() => {
|
|
1674
|
+
return groupedEntries().flatMap((g) => g.commands);
|
|
1675
|
+
});
|
|
1676
|
+
useKeyboard2((key) => {
|
|
1677
|
+
if (key.ctrl && (key.name === "c" || key.name === "d")) {
|
|
1678
|
+
renderer.destroy();
|
|
1679
|
+
return;
|
|
1680
|
+
}
|
|
1681
|
+
if (key.name === "escape") {
|
|
1682
|
+
dialog.clear();
|
|
1683
|
+
return;
|
|
1684
|
+
}
|
|
1685
|
+
if (key.name === "up" || key.name === "k") {
|
|
1686
|
+
setSelectedIndex((i) => Math.max(0, i - 1));
|
|
1687
|
+
return;
|
|
1688
|
+
}
|
|
1689
|
+
if (key.name === "down" || key.name === "j") {
|
|
1690
|
+
setSelectedIndex((i) => Math.min(flatEntries().length - 1, i + 1));
|
|
1691
|
+
return;
|
|
1692
|
+
}
|
|
1693
|
+
if (key.name === "return") {
|
|
1694
|
+
const entry = flatEntries()[selectedIndex()];
|
|
1695
|
+
if (entry) {
|
|
1696
|
+
dialog.clear();
|
|
1697
|
+
entry.onExecute();
|
|
1698
|
+
}
|
|
1699
|
+
return;
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
const width = () => Math.min(40, dims().width - 4);
|
|
1703
|
+
let currentIndex = 0;
|
|
1704
|
+
return /* @__PURE__ */ jsxDEV10("box", {
|
|
1705
|
+
position: "absolute",
|
|
1706
|
+
left: 0,
|
|
1707
|
+
top: 0,
|
|
1708
|
+
width: dims().width,
|
|
1709
|
+
height: dims().height,
|
|
1710
|
+
justifyContent: "center",
|
|
1711
|
+
alignItems: "center",
|
|
1712
|
+
backgroundColor: RGBA.fromInts(0, 0, 0, 150),
|
|
1713
|
+
children: /* @__PURE__ */ jsxDEV10("box", {
|
|
1714
|
+
width: width(),
|
|
1715
|
+
flexDirection: "column",
|
|
1716
|
+
backgroundColor: theme.backgroundPanel,
|
|
1717
|
+
paddingTop: 1,
|
|
1718
|
+
paddingBottom: 1,
|
|
1719
|
+
children: [
|
|
1720
|
+
/* @__PURE__ */ jsxDEV10("box", {
|
|
1721
|
+
height: 1,
|
|
1722
|
+
justifyContent: "center",
|
|
1723
|
+
paddingLeft: 2,
|
|
1724
|
+
paddingRight: 2,
|
|
1725
|
+
children: /* @__PURE__ */ jsxDEV10("text", {
|
|
1726
|
+
children: /* @__PURE__ */ jsxDEV10("span", {
|
|
1727
|
+
style: { bold: true },
|
|
1728
|
+
children: "Commands"
|
|
1729
|
+
}, undefined, false, undefined, this)
|
|
1730
|
+
}, undefined, false, undefined, this)
|
|
1731
|
+
}, undefined, false, undefined, this),
|
|
1732
|
+
/* @__PURE__ */ jsxDEV10("box", {
|
|
1733
|
+
height: 1
|
|
1734
|
+
}, undefined, false, undefined, this),
|
|
1735
|
+
/* @__PURE__ */ jsxDEV10(For2, {
|
|
1736
|
+
each: groupedEntries(),
|
|
1737
|
+
children: (group, groupIndex) => {
|
|
1738
|
+
const startIndex = createMemo3(() => {
|
|
1739
|
+
let idx = 0;
|
|
1740
|
+
const groups = groupedEntries();
|
|
1741
|
+
for (let i = 0;i < groupIndex(); i++) {
|
|
1742
|
+
idx += groups[i].commands.length;
|
|
1743
|
+
}
|
|
1744
|
+
return idx;
|
|
1745
|
+
});
|
|
1746
|
+
return /* @__PURE__ */ jsxDEV10(Fragment2, {
|
|
1747
|
+
children: [
|
|
1748
|
+
/* @__PURE__ */ jsxDEV10(Show2, {
|
|
1749
|
+
when: groupIndex() > 0,
|
|
1750
|
+
children: /* @__PURE__ */ jsxDEV10("box", {
|
|
1751
|
+
height: 1
|
|
1752
|
+
}, undefined, false, undefined, this)
|
|
1753
|
+
}, undefined, false, undefined, this),
|
|
1754
|
+
/* @__PURE__ */ jsxDEV10("box", {
|
|
1755
|
+
paddingLeft: 2,
|
|
1756
|
+
paddingRight: 2,
|
|
1757
|
+
children: /* @__PURE__ */ jsxDEV10("text", {
|
|
1758
|
+
fg: theme.secondary,
|
|
1759
|
+
children: /* @__PURE__ */ jsxDEV10("span", {
|
|
1760
|
+
style: { bold: true },
|
|
1761
|
+
children: group.category
|
|
1762
|
+
}, undefined, false, undefined, this)
|
|
1763
|
+
}, undefined, false, undefined, this)
|
|
1764
|
+
}, undefined, false, undefined, this),
|
|
1765
|
+
/* @__PURE__ */ jsxDEV10(For2, {
|
|
1766
|
+
each: group.commands,
|
|
1767
|
+
children: (entry, cmdIndex) => {
|
|
1768
|
+
const globalIndex = () => startIndex() + cmdIndex();
|
|
1769
|
+
return /* @__PURE__ */ jsxDEV10("box", {
|
|
1770
|
+
flexDirection: "row",
|
|
1771
|
+
justifyContent: "space-between",
|
|
1772
|
+
paddingLeft: 2,
|
|
1773
|
+
paddingRight: 2,
|
|
1774
|
+
children: [
|
|
1775
|
+
/* @__PURE__ */ jsxDEV10("text", {
|
|
1776
|
+
fg: globalIndex() === selectedIndex() ? theme.primary : undefined,
|
|
1777
|
+
children: entry.title
|
|
1778
|
+
}, undefined, false, undefined, this),
|
|
1779
|
+
/* @__PURE__ */ jsxDEV10("box", {
|
|
1780
|
+
flexDirection: "row",
|
|
1781
|
+
gap: 1,
|
|
1782
|
+
children: /* @__PURE__ */ jsxDEV10(For2, {
|
|
1783
|
+
each: entry.keybinds,
|
|
1784
|
+
children: (bind) => /* @__PURE__ */ jsxDEV10("box", {
|
|
1785
|
+
children: /* @__PURE__ */ jsxDEV10("text", {
|
|
1786
|
+
fg: theme.textMuted,
|
|
1787
|
+
children: formatKeybind(bind)
|
|
1788
|
+
}, undefined, false, undefined, this)
|
|
1789
|
+
}, undefined, false, undefined, this)
|
|
1790
|
+
}, undefined, false, undefined, this)
|
|
1791
|
+
}, undefined, false, undefined, this)
|
|
1792
|
+
]
|
|
1793
|
+
}, undefined, true, undefined, this);
|
|
1794
|
+
}
|
|
1795
|
+
}, undefined, false, undefined, this)
|
|
1796
|
+
]
|
|
1797
|
+
}, undefined, true, undefined, this);
|
|
1798
|
+
}
|
|
1799
|
+
}, undefined, false, undefined, this)
|
|
1800
|
+
]
|
|
1801
|
+
}, undefined, true, undefined, this)
|
|
1802
|
+
}, undefined, false, undefined, this);
|
|
1803
|
+
}
|
|
1804
|
+
// src/components/sidebar/FileList.tsx
|
|
1805
|
+
import { For as For3, Show as Show3, createEffect as createEffect3 } from "solid-js";
|
|
1806
|
+
|
|
1807
|
+
// src/context/sidebar.tsx
|
|
1808
|
+
import {
|
|
1809
|
+
createContext as createContext6,
|
|
1810
|
+
useContext as useContext6
|
|
1811
|
+
} from "solid-js";
|
|
1812
|
+
import { jsxDEV as jsxDEV11 } from "@opentui/solid/jsx-dev-runtime";
|
|
1813
|
+
var SidebarContext = createContext6();
|
|
1814
|
+
function SidebarProvider(props) {
|
|
1815
|
+
const value = {
|
|
1816
|
+
activeSection: props.activeSection,
|
|
1817
|
+
isFocused: props.isFocused
|
|
1818
|
+
};
|
|
1819
|
+
return /* @__PURE__ */ jsxDEV11(SidebarContext.Provider, {
|
|
1820
|
+
value,
|
|
1821
|
+
children: props.children
|
|
1822
|
+
}, undefined, false, undefined, this);
|
|
1823
|
+
}
|
|
1824
|
+
function useSidebar() {
|
|
1825
|
+
const ctx = useContext6(SidebarContext);
|
|
1826
|
+
if (!ctx)
|
|
1827
|
+
throw new Error("useSidebar must be used within SidebarProvider");
|
|
1828
|
+
return ctx;
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1831
|
+
// src/components/sidebar/Section.tsx
|
|
1832
|
+
import { jsxDEV as jsxDEV12 } from "@opentui/solid/jsx-dev-runtime";
|
|
1833
|
+
function SectionTitle(props) {
|
|
1834
|
+
return /* @__PURE__ */ jsxDEV12("box", {
|
|
1835
|
+
flexShrink: 0,
|
|
1836
|
+
children: /* @__PURE__ */ jsxDEV12("text", {
|
|
1837
|
+
children: /* @__PURE__ */ jsxDEV12("span", {
|
|
1838
|
+
style: { bold: true },
|
|
1839
|
+
children: props.title
|
|
1840
|
+
}, undefined, false, undefined, this)
|
|
1841
|
+
}, undefined, false, undefined, this)
|
|
1842
|
+
}, undefined, false, undefined, this);
|
|
1843
|
+
}
|
|
1844
|
+
function Title(props) {
|
|
1845
|
+
const { theme } = useTheme();
|
|
1846
|
+
const sidebar = useSidebar();
|
|
1847
|
+
const isActive = () => sidebar.activeSection() === props.section && sidebar.isFocused();
|
|
1848
|
+
const title = () => props.section.charAt(0).toUpperCase() + props.section.slice(1);
|
|
1849
|
+
return /* @__PURE__ */ jsxDEV12("box", {
|
|
1850
|
+
flexShrink: 0,
|
|
1851
|
+
border: ["left"],
|
|
1852
|
+
borderColor: isActive() ? theme.primary : theme.indicatorDefault,
|
|
1853
|
+
backgroundColor: isActive() ? theme.backgroundElement : theme.backgroundPanel,
|
|
1854
|
+
customBorderChars: {
|
|
1855
|
+
...EmptyBorder,
|
|
1856
|
+
vertical: "\u258C"
|
|
1857
|
+
},
|
|
1858
|
+
children: /* @__PURE__ */ jsxDEV12("text", {
|
|
1859
|
+
children: /* @__PURE__ */ jsxDEV12("span", {
|
|
1860
|
+
style: { bold: true },
|
|
1861
|
+
children: title()
|
|
1862
|
+
}, undefined, false, undefined, this)
|
|
1863
|
+
}, undefined, false, undefined, this)
|
|
1864
|
+
}, undefined, false, undefined, this);
|
|
1865
|
+
}
|
|
1866
|
+
function Section(props) {
|
|
1867
|
+
const { theme } = useTheme();
|
|
1868
|
+
return /* @__PURE__ */ jsxDEV12("box", {
|
|
1869
|
+
flexDirection: "column",
|
|
1870
|
+
backgroundColor: theme.backgroundPanel,
|
|
1871
|
+
flexShrink: 0,
|
|
1872
|
+
children: [
|
|
1873
|
+
/* @__PURE__ */ jsxDEV12(SectionTitle, {
|
|
1874
|
+
title: props.title
|
|
1875
|
+
}, undefined, false, undefined, this),
|
|
1876
|
+
props.children
|
|
1877
|
+
]
|
|
1878
|
+
}, undefined, true, undefined, this);
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1881
|
+
// src/components/sidebar/FileList.tsx
|
|
1882
|
+
import { jsxDEV as jsxDEV13 } from "@opentui/solid/jsx-dev-runtime";
|
|
1883
|
+
var SCROLL_BUFFER = 2;
|
|
1884
|
+
function fileDiffStats(content) {
|
|
1885
|
+
let added = 0;
|
|
1886
|
+
let removed = 0;
|
|
1887
|
+
for (const line of content.split(`
|
|
1888
|
+
`)) {
|
|
1889
|
+
if (line.startsWith("+++") || line.startsWith("---"))
|
|
1890
|
+
continue;
|
|
1891
|
+
if (line.startsWith("+"))
|
|
1892
|
+
added++;
|
|
1893
|
+
else if (line.startsWith("-"))
|
|
1894
|
+
removed++;
|
|
1895
|
+
}
|
|
1896
|
+
return { added, removed };
|
|
1897
|
+
}
|
|
1898
|
+
function FileList(props) {
|
|
1899
|
+
const { theme } = useTheme();
|
|
1900
|
+
let scrollbox = null;
|
|
1901
|
+
const isSelected = (item) => {
|
|
1902
|
+
if (item.node.type !== "file")
|
|
1903
|
+
return false;
|
|
1904
|
+
const selectedEntryIndex = props.fileIndices()[props.selectedFileIndex()];
|
|
1905
|
+
return item.node.entryIndex === selectedEntryIndex;
|
|
1906
|
+
};
|
|
1907
|
+
const getSelectedDisplayIndex = () => {
|
|
1908
|
+
const selectedEntryIndex = props.fileIndices()[props.selectedFileIndex()];
|
|
1909
|
+
return props.displayItems().findIndex((item) => item.node.type === "file" && item.node.entryIndex === selectedEntryIndex);
|
|
1910
|
+
};
|
|
1911
|
+
createEffect3(() => {
|
|
1912
|
+
const displayIndex = getSelectedDisplayIndex();
|
|
1913
|
+
if (displayIndex < 0 || !scrollbox)
|
|
1914
|
+
return;
|
|
1915
|
+
const viewportHeight = scrollbox.viewport.height;
|
|
1916
|
+
const scrollTop = scrollbox.scrollTop;
|
|
1917
|
+
const itemTop = displayIndex;
|
|
1918
|
+
const itemBottom = displayIndex + 1;
|
|
1919
|
+
if (itemTop < scrollTop + SCROLL_BUFFER) {
|
|
1920
|
+
scrollbox.scrollTo(Math.max(0, itemTop - SCROLL_BUFFER));
|
|
1921
|
+
} else if (itemBottom > scrollTop + viewportHeight - SCROLL_BUFFER) {
|
|
1922
|
+
scrollbox.scrollTo(itemBottom - viewportHeight + SCROLL_BUFFER);
|
|
1923
|
+
}
|
|
1924
|
+
});
|
|
1925
|
+
return /* @__PURE__ */ jsxDEV13("box", {
|
|
1926
|
+
flexGrow: 1,
|
|
1927
|
+
flexDirection: "column",
|
|
1928
|
+
backgroundColor: theme.backgroundPanel,
|
|
1929
|
+
children: [
|
|
1930
|
+
/* @__PURE__ */ jsxDEV13(Title, {
|
|
1931
|
+
section: "files"
|
|
1932
|
+
}, undefined, false, undefined, this),
|
|
1933
|
+
/* @__PURE__ */ jsxDEV13(Show3, {
|
|
1934
|
+
when: props.loading(),
|
|
1935
|
+
children: /* @__PURE__ */ jsxDEV13("box", {
|
|
1936
|
+
children: /* @__PURE__ */ jsxDEV13("text", {
|
|
1937
|
+
children: "Loading..."
|
|
1938
|
+
}, undefined, false, undefined, this)
|
|
1939
|
+
}, undefined, false, undefined, this)
|
|
1940
|
+
}, undefined, false, undefined, this),
|
|
1941
|
+
/* @__PURE__ */ jsxDEV13(Show3, {
|
|
1942
|
+
when: !props.loading() && props.displayItems().length === 0,
|
|
1943
|
+
children: /* @__PURE__ */ jsxDEV13("box", {
|
|
1944
|
+
children: /* @__PURE__ */ jsxDEV13("text", {
|
|
1945
|
+
children: "No changes found"
|
|
1946
|
+
}, undefined, false, undefined, this)
|
|
1947
|
+
}, undefined, false, undefined, this)
|
|
1948
|
+
}, undefined, false, undefined, this),
|
|
1949
|
+
/* @__PURE__ */ jsxDEV13(Show3, {
|
|
1950
|
+
when: !props.loading() && props.displayItems().length > 0,
|
|
1951
|
+
children: /* @__PURE__ */ jsxDEV13("scrollbox", {
|
|
1952
|
+
ref: (r) => scrollbox = r,
|
|
1953
|
+
flexGrow: 1,
|
|
1954
|
+
children: /* @__PURE__ */ jsxDEV13(For3, {
|
|
1955
|
+
each: props.displayItems(),
|
|
1956
|
+
children: (item) => {
|
|
1957
|
+
if (item.node.type === "dir") {
|
|
1958
|
+
return /* @__PURE__ */ jsxDEV13("box", {
|
|
1959
|
+
flexDirection: "row",
|
|
1960
|
+
children: [
|
|
1961
|
+
/* @__PURE__ */ jsxDEV13(Show3, {
|
|
1962
|
+
when: item.depth > 0,
|
|
1963
|
+
children: /* @__PURE__ */ jsxDEV13("text", {
|
|
1964
|
+
children: " ".repeat(item.depth)
|
|
1965
|
+
}, undefined, false, undefined, this)
|
|
1966
|
+
}, undefined, false, undefined, this),
|
|
1967
|
+
/* @__PURE__ */ jsxDEV13("text", {
|
|
1968
|
+
children: "\u25BE "
|
|
1969
|
+
}, undefined, false, undefined, this),
|
|
1970
|
+
/* @__PURE__ */ jsxDEV13("text", {
|
|
1971
|
+
children: item.node.name
|
|
1972
|
+
}, undefined, false, undefined, this)
|
|
1973
|
+
]
|
|
1974
|
+
}, undefined, true, undefined, this);
|
|
1975
|
+
}
|
|
1976
|
+
const textColor = () => isSelected(item) ? theme.primary : undefined;
|
|
1977
|
+
const bgColor = () => isSelected(item) ? theme.backgroundElement : theme.backgroundPanel;
|
|
1978
|
+
const entryIndex = item.node.entryIndex;
|
|
1979
|
+
const entry = typeof entryIndex === "number" ? props.entries()[entryIndex] : undefined;
|
|
1980
|
+
const stats = entry ? fileDiffStats(entry.content) : null;
|
|
1981
|
+
return /* @__PURE__ */ jsxDEV13("box", {
|
|
1982
|
+
flexDirection: "row",
|
|
1983
|
+
gap: 1,
|
|
1984
|
+
backgroundColor: bgColor(),
|
|
1985
|
+
children: [
|
|
1986
|
+
/* @__PURE__ */ jsxDEV13(Show3, {
|
|
1987
|
+
when: item.depth > 0,
|
|
1988
|
+
children: /* @__PURE__ */ jsxDEV13("text", {
|
|
1989
|
+
children: " ".repeat(item.depth)
|
|
1990
|
+
}, undefined, false, undefined, this)
|
|
1991
|
+
}, undefined, false, undefined, this),
|
|
1992
|
+
/* @__PURE__ */ jsxDEV13("text", {
|
|
1993
|
+
fg: theme.textMuted,
|
|
1994
|
+
children: item.node.status
|
|
1995
|
+
}, undefined, false, undefined, this),
|
|
1996
|
+
/* @__PURE__ */ jsxDEV13("text", {
|
|
1997
|
+
fg: textColor(),
|
|
1998
|
+
children: item.node.name
|
|
1999
|
+
}, undefined, false, undefined, this),
|
|
2000
|
+
/* @__PURE__ */ jsxDEV13(Show3, {
|
|
2001
|
+
when: !!stats && stats.added > 0,
|
|
2002
|
+
children: /* @__PURE__ */ jsxDEV13("text", {
|
|
2003
|
+
fg: theme.diffSignAdded,
|
|
2004
|
+
children: `+${stats.added}`
|
|
2005
|
+
}, undefined, false, undefined, this)
|
|
2006
|
+
}, undefined, false, undefined, this),
|
|
2007
|
+
/* @__PURE__ */ jsxDEV13(Show3, {
|
|
2008
|
+
when: !!stats && stats.removed > 0,
|
|
2009
|
+
children: /* @__PURE__ */ jsxDEV13("text", {
|
|
2010
|
+
fg: theme.diffSignRemoved,
|
|
2011
|
+
children: `-${stats.removed}`
|
|
2012
|
+
}, undefined, false, undefined, this)
|
|
2013
|
+
}, undefined, false, undefined, this)
|
|
2014
|
+
]
|
|
2015
|
+
}, undefined, true, undefined, this);
|
|
2016
|
+
}
|
|
2017
|
+
}, undefined, false, undefined, this)
|
|
2018
|
+
}, undefined, false, undefined, this)
|
|
2019
|
+
}, undefined, false, undefined, this)
|
|
2020
|
+
]
|
|
2021
|
+
}, undefined, true, undefined, this);
|
|
2022
|
+
}
|
|
2023
|
+
// src/components/sidebar/CommentList.tsx
|
|
2024
|
+
import { For as For4, Show as Show4, createEffect as createEffect4 } from "solid-js";
|
|
2025
|
+
import { jsxDEV as jsxDEV14 } from "@opentui/solid/jsx-dev-runtime";
|
|
2026
|
+
var SCROLL_BUFFER2 = 2;
|
|
2027
|
+
function getShortFile(file) {
|
|
2028
|
+
const segments = file.split("/");
|
|
2029
|
+
return segments[segments.length - 1] || file;
|
|
2030
|
+
}
|
|
2031
|
+
function getPatchDisplay(patchId, patches, workingTreePatchId) {
|
|
2032
|
+
if (patchId === null)
|
|
2033
|
+
return "?";
|
|
2034
|
+
if (workingTreePatchId !== null && patchId === workingTreePatchId)
|
|
2035
|
+
return "WT";
|
|
2036
|
+
const patch = patches.find((p) => p.id === patchId);
|
|
2037
|
+
if (!patch)
|
|
2038
|
+
return "?";
|
|
2039
|
+
return `P${patch.seq}`;
|
|
2040
|
+
}
|
|
2041
|
+
function CommentList(props) {
|
|
2042
|
+
const { theme } = useTheme();
|
|
2043
|
+
const review = useReview();
|
|
2044
|
+
let scrollbox = null;
|
|
2045
|
+
createEffect4(() => {
|
|
2046
|
+
const idx = props.selectedIndex();
|
|
2047
|
+
if (idx < 0 || !scrollbox)
|
|
2048
|
+
return;
|
|
2049
|
+
const viewportHeight = scrollbox.viewport.height;
|
|
2050
|
+
const scrollTop = scrollbox.scrollTop;
|
|
2051
|
+
if (idx < scrollTop + SCROLL_BUFFER2) {
|
|
2052
|
+
scrollbox.scrollTo(Math.max(0, idx - SCROLL_BUFFER2));
|
|
2053
|
+
} else if (idx + 1 > scrollTop + viewportHeight - SCROLL_BUFFER2) {
|
|
2054
|
+
scrollbox.scrollTo(idx - viewportHeight + SCROLL_BUFFER2 + 1);
|
|
2055
|
+
}
|
|
2056
|
+
});
|
|
2057
|
+
const commentCount = () => props.comments().length;
|
|
2058
|
+
return /* @__PURE__ */ jsxDEV14("box", {
|
|
2059
|
+
flexDirection: "column",
|
|
2060
|
+
children: [
|
|
2061
|
+
/* @__PURE__ */ jsxDEV14(Title, {
|
|
2062
|
+
section: "comments"
|
|
2063
|
+
}, undefined, false, undefined, this),
|
|
2064
|
+
/* @__PURE__ */ jsxDEV14(Show4, {
|
|
2065
|
+
when: !props.loading() && commentCount() === 0,
|
|
2066
|
+
children: /* @__PURE__ */ jsxDEV14("box", {
|
|
2067
|
+
children: /* @__PURE__ */ jsxDEV14("text", {
|
|
2068
|
+
fg: theme.textMuted,
|
|
2069
|
+
children: "No comments"
|
|
2070
|
+
}, undefined, false, undefined, this)
|
|
2071
|
+
}, undefined, false, undefined, this)
|
|
2072
|
+
}, undefined, false, undefined, this),
|
|
2073
|
+
/* @__PURE__ */ jsxDEV14(Show4, {
|
|
2074
|
+
when: !props.loading() && commentCount() > 0,
|
|
2075
|
+
children: /* @__PURE__ */ jsxDEV14("scrollbox", {
|
|
2076
|
+
ref: (r) => scrollbox = r,
|
|
2077
|
+
flexGrow: 1,
|
|
2078
|
+
children: /* @__PURE__ */ jsxDEV14(For4, {
|
|
2079
|
+
each: props.comments(),
|
|
2080
|
+
children: (comment, index) => {
|
|
2081
|
+
const isSelected = () => index() === props.selectedIndex();
|
|
2082
|
+
const textColor = () => isSelected() && props.focused() ? theme.primary : undefined;
|
|
2083
|
+
const bgColor = () => isSelected() && props.focused() ? theme.backgroundElement : theme.backgroundPanel;
|
|
2084
|
+
return /* @__PURE__ */ jsxDEV14("box", {
|
|
2085
|
+
flexDirection: "row",
|
|
2086
|
+
justifyContent: "space-between",
|
|
2087
|
+
backgroundColor: bgColor(),
|
|
2088
|
+
children: [
|
|
2089
|
+
/* @__PURE__ */ jsxDEV14("box", {
|
|
2090
|
+
flexDirection: "row",
|
|
2091
|
+
gap: 1,
|
|
2092
|
+
children: [
|
|
2093
|
+
/* @__PURE__ */ jsxDEV14("text", {
|
|
2094
|
+
fg: textColor(),
|
|
2095
|
+
children: getShortFile(comment.file)
|
|
2096
|
+
}, undefined, false, undefined, this),
|
|
2097
|
+
/* @__PURE__ */ jsxDEV14("text", {
|
|
2098
|
+
fg: theme.textMuted,
|
|
2099
|
+
children: `${comment.startRow}:${comment.endRow}`
|
|
2100
|
+
}, undefined, false, undefined, this)
|
|
2101
|
+
]
|
|
2102
|
+
}, undefined, true, undefined, this),
|
|
2103
|
+
/* @__PURE__ */ jsxDEV14("text", {
|
|
2104
|
+
fg: theme.textMuted,
|
|
2105
|
+
children: getPatchDisplay(comment.patchId, review.patches(), review.workingTreePatchId())
|
|
2106
|
+
}, undefined, false, undefined, this)
|
|
2107
|
+
]
|
|
2108
|
+
}, undefined, true, undefined, this);
|
|
2109
|
+
}
|
|
2110
|
+
}, undefined, false, undefined, this)
|
|
2111
|
+
}, undefined, false, undefined, this)
|
|
2112
|
+
}, undefined, false, undefined, this)
|
|
2113
|
+
]
|
|
2114
|
+
}, undefined, true, undefined, this);
|
|
2115
|
+
}
|
|
2116
|
+
// src/components/sidebar/PatchList.tsx
|
|
2117
|
+
import {
|
|
2118
|
+
For as For5,
|
|
2119
|
+
Show as Show5,
|
|
2120
|
+
createEffect as createEffect5,
|
|
2121
|
+
createSignal as createSignal7,
|
|
2122
|
+
onCleanup as onCleanup2,
|
|
2123
|
+
onMount as onMount4
|
|
2124
|
+
} from "solid-js";
|
|
2125
|
+
import { jsxDEV as jsxDEV15, Fragment as Fragment3 } from "@opentui/solid/jsx-dev-runtime";
|
|
2126
|
+
var SCROLL_BUFFER3 = 2;
|
|
2127
|
+
function formatTime(timestamp) {
|
|
2128
|
+
const date = new Date(timestamp);
|
|
2129
|
+
return date.toLocaleTimeString([], { hour: "numeric", minute: "2-digit" });
|
|
2130
|
+
}
|
|
2131
|
+
function diffStats(content) {
|
|
2132
|
+
let added = 0;
|
|
2133
|
+
let removed = 0;
|
|
2134
|
+
for (const line of content.split(`
|
|
2135
|
+
`)) {
|
|
2136
|
+
if (line.startsWith("+++") || line.startsWith("---"))
|
|
2137
|
+
continue;
|
|
2138
|
+
if (line.startsWith("+"))
|
|
2139
|
+
added++;
|
|
2140
|
+
else if (line.startsWith("-"))
|
|
2141
|
+
removed++;
|
|
2142
|
+
}
|
|
2143
|
+
return { added, removed };
|
|
2144
|
+
}
|
|
2145
|
+
function sumEntryStats(entries) {
|
|
2146
|
+
let added = 0;
|
|
2147
|
+
let removed = 0;
|
|
2148
|
+
for (const e of entries) {
|
|
2149
|
+
const s = diffStats(e.content);
|
|
2150
|
+
added += s.added;
|
|
2151
|
+
removed += s.removed;
|
|
2152
|
+
}
|
|
2153
|
+
return { added, removed };
|
|
2154
|
+
}
|
|
2155
|
+
function PatchList(props) {
|
|
2156
|
+
const { theme } = useTheme();
|
|
2157
|
+
let scrollbox = null;
|
|
2158
|
+
const [now, setNow] = createSignal7(Date.now());
|
|
2159
|
+
onMount4(() => {
|
|
2160
|
+
let interval = null;
|
|
2161
|
+
const start = () => {
|
|
2162
|
+
const ms = Date.now();
|
|
2163
|
+
const nextMinute = Math.ceil(ms / 60000) * 60000;
|
|
2164
|
+
const delay = Math.max(0, nextMinute - ms);
|
|
2165
|
+
const timeout = setTimeout(() => {
|
|
2166
|
+
setNow(Date.now());
|
|
2167
|
+
interval = setInterval(() => setNow(Date.now()), 60000);
|
|
2168
|
+
}, delay);
|
|
2169
|
+
onCleanup2(() => clearTimeout(timeout));
|
|
2170
|
+
};
|
|
2171
|
+
start();
|
|
2172
|
+
onCleanup2(() => {
|
|
2173
|
+
if (interval)
|
|
2174
|
+
clearInterval(interval);
|
|
2175
|
+
});
|
|
2176
|
+
});
|
|
2177
|
+
createEffect5(() => {
|
|
2178
|
+
const idx = props.selectedIndex();
|
|
2179
|
+
if (idx < 0 || !scrollbox)
|
|
2180
|
+
return;
|
|
2181
|
+
const viewportHeight = scrollbox.viewport.height;
|
|
2182
|
+
const scrollTop = scrollbox.scrollTop;
|
|
2183
|
+
if (idx < scrollTop + SCROLL_BUFFER3) {
|
|
2184
|
+
scrollbox.scrollTo(Math.max(0, idx - SCROLL_BUFFER3));
|
|
2185
|
+
} else if (idx + 1 > scrollTop + viewportHeight - SCROLL_BUFFER3) {
|
|
2186
|
+
scrollbox.scrollTo(idx - viewportHeight + SCROLL_BUFFER3 + 1);
|
|
2187
|
+
}
|
|
2188
|
+
});
|
|
2189
|
+
const items = () => {
|
|
2190
|
+
const patchList = props.patches();
|
|
2191
|
+
const wt = props.workingTreePatchId();
|
|
2192
|
+
return [
|
|
2193
|
+
{ type: "workspace", id: wt, seq: -1, createdAt: now() },
|
|
2194
|
+
...patchList.slice().filter((p) => wt === null ? true : p.id !== wt).sort((a, b) => b.seq - a.seq).map((p) => ({ type: "patch", ...p }))
|
|
2195
|
+
];
|
|
2196
|
+
};
|
|
2197
|
+
const patchCount = () => {
|
|
2198
|
+
const wt = props.workingTreePatchId();
|
|
2199
|
+
const list4 = props.patches();
|
|
2200
|
+
return wt === null ? list4.length : list4.filter((p) => p.id !== wt).length;
|
|
2201
|
+
};
|
|
2202
|
+
return /* @__PURE__ */ jsxDEV15("box", {
|
|
2203
|
+
flexDirection: "column",
|
|
2204
|
+
children: [
|
|
2205
|
+
/* @__PURE__ */ jsxDEV15(Title, {
|
|
2206
|
+
section: "patches"
|
|
2207
|
+
}, undefined, false, undefined, this),
|
|
2208
|
+
/* @__PURE__ */ jsxDEV15(Show5, {
|
|
2209
|
+
when: props.loading(),
|
|
2210
|
+
children: /* @__PURE__ */ jsxDEV15("box", {
|
|
2211
|
+
children: /* @__PURE__ */ jsxDEV15("text", {
|
|
2212
|
+
fg: theme.textMuted,
|
|
2213
|
+
children: "Loading..."
|
|
2214
|
+
}, undefined, false, undefined, this)
|
|
2215
|
+
}, undefined, false, undefined, this)
|
|
2216
|
+
}, undefined, false, undefined, this),
|
|
2217
|
+
/* @__PURE__ */ jsxDEV15(Show5, {
|
|
2218
|
+
when: !props.loading() && patchCount() === 0,
|
|
2219
|
+
children: /* @__PURE__ */ jsxDEV15("box", {
|
|
2220
|
+
children: /* @__PURE__ */ jsxDEV15("box", {
|
|
2221
|
+
flexDirection: "row",
|
|
2222
|
+
justifyContent: "space-between",
|
|
2223
|
+
children: [
|
|
2224
|
+
/* @__PURE__ */ jsxDEV15("box", {
|
|
2225
|
+
flexDirection: "row",
|
|
2226
|
+
gap: 1,
|
|
2227
|
+
children: [
|
|
2228
|
+
/* @__PURE__ */ jsxDEV15(Show5, {
|
|
2229
|
+
when: props.workingTreePatchId() !== null && props.activePatchId() === props.workingTreePatchId(),
|
|
2230
|
+
children: /* @__PURE__ */ jsxDEV15("text", {
|
|
2231
|
+
fg: theme.primary,
|
|
2232
|
+
children: "\u25B8"
|
|
2233
|
+
}, undefined, false, undefined, this)
|
|
2234
|
+
}, undefined, false, undefined, this),
|
|
2235
|
+
/* @__PURE__ */ jsxDEV15(Show5, {
|
|
2236
|
+
when: props.workingTreePatchId() === null || props.activePatchId() !== props.workingTreePatchId(),
|
|
2237
|
+
children: /* @__PURE__ */ jsxDEV15("text", {
|
|
2238
|
+
fg: theme.textMuted,
|
|
2239
|
+
children: " "
|
|
2240
|
+
}, undefined, false, undefined, this)
|
|
2241
|
+
}, undefined, false, undefined, this),
|
|
2242
|
+
/* @__PURE__ */ jsxDEV15("text", {
|
|
2243
|
+
fg: props.focused() ? theme.primary : undefined,
|
|
2244
|
+
children: "Working tree"
|
|
2245
|
+
}, undefined, false, undefined, this),
|
|
2246
|
+
(() => {
|
|
2247
|
+
const s = sumEntryStats(props.workspaceEntries());
|
|
2248
|
+
if (s.added === 0 && s.removed === 0)
|
|
2249
|
+
return null;
|
|
2250
|
+
return /* @__PURE__ */ jsxDEV15(Fragment3, {
|
|
2251
|
+
children: [
|
|
2252
|
+
/* @__PURE__ */ jsxDEV15("text", {
|
|
2253
|
+
fg: theme.diffSignAdded,
|
|
2254
|
+
children: "+"
|
|
2255
|
+
}, undefined, false, undefined, this),
|
|
2256
|
+
/* @__PURE__ */ jsxDEV15("text", {
|
|
2257
|
+
fg: theme.textMuted,
|
|
2258
|
+
children: s.added
|
|
2259
|
+
}, undefined, false, undefined, this),
|
|
2260
|
+
/* @__PURE__ */ jsxDEV15("text", {
|
|
2261
|
+
fg: theme.diffSignRemoved,
|
|
2262
|
+
children: "-"
|
|
2263
|
+
}, undefined, false, undefined, this),
|
|
2264
|
+
/* @__PURE__ */ jsxDEV15("text", {
|
|
2265
|
+
fg: theme.textMuted,
|
|
2266
|
+
children: s.removed
|
|
2267
|
+
}, undefined, false, undefined, this)
|
|
2268
|
+
]
|
|
2269
|
+
}, undefined, true, undefined, this);
|
|
2270
|
+
})()
|
|
2271
|
+
]
|
|
2272
|
+
}, undefined, true, undefined, this),
|
|
2273
|
+
/* @__PURE__ */ jsxDEV15("text", {
|
|
2274
|
+
fg: theme.textMuted,
|
|
2275
|
+
children: formatTime(now())
|
|
2276
|
+
}, undefined, false, undefined, this)
|
|
2277
|
+
]
|
|
2278
|
+
}, undefined, true, undefined, this)
|
|
2279
|
+
}, undefined, false, undefined, this)
|
|
2280
|
+
}, undefined, false, undefined, this),
|
|
2281
|
+
/* @__PURE__ */ jsxDEV15(Show5, {
|
|
2282
|
+
when: !props.loading() && patchCount() > 0,
|
|
2283
|
+
children: /* @__PURE__ */ jsxDEV15("scrollbox", {
|
|
2284
|
+
ref: (r) => scrollbox = r,
|
|
2285
|
+
flexGrow: 1,
|
|
2286
|
+
children: /* @__PURE__ */ jsxDEV15(For5, {
|
|
2287
|
+
each: items(),
|
|
2288
|
+
children: (item, index) => {
|
|
2289
|
+
const isSelected = () => index() === props.selectedIndex();
|
|
2290
|
+
const isActive = () => {
|
|
2291
|
+
if (item.type === "workspace")
|
|
2292
|
+
return item.id !== null && props.activePatchId() === item.id;
|
|
2293
|
+
return props.activePatchId() === item.id;
|
|
2294
|
+
};
|
|
2295
|
+
const textColor = () => isActive() ? theme.primary : undefined;
|
|
2296
|
+
const bgColor = () => isSelected() && props.focused() ? theme.backgroundElement : theme.backgroundPanel;
|
|
2297
|
+
const label = () => item.type === "workspace" ? "Working tree" : `P${item.seq}`;
|
|
2298
|
+
const delta = () => item.type === "workspace" ? sumEntryStats(props.workspaceEntries()) : diffStats(item.content);
|
|
2299
|
+
return /* @__PURE__ */ jsxDEV15("box", {
|
|
2300
|
+
flexDirection: "row",
|
|
2301
|
+
justifyContent: "space-between",
|
|
2302
|
+
backgroundColor: bgColor(),
|
|
2303
|
+
children: [
|
|
2304
|
+
/* @__PURE__ */ jsxDEV15("box", {
|
|
2305
|
+
flexDirection: "row",
|
|
2306
|
+
gap: 1,
|
|
2307
|
+
children: [
|
|
2308
|
+
/* @__PURE__ */ jsxDEV15("text", {
|
|
2309
|
+
fg: textColor(),
|
|
2310
|
+
children: label()
|
|
2311
|
+
}, undefined, false, undefined, this),
|
|
2312
|
+
/* @__PURE__ */ jsxDEV15(Show5, {
|
|
2313
|
+
when: delta() && (delta().added > 0 || delta().removed > 0),
|
|
2314
|
+
children: [
|
|
2315
|
+
/* @__PURE__ */ jsxDEV15("text", {
|
|
2316
|
+
fg: theme.diffSignAdded,
|
|
2317
|
+
children: [
|
|
2318
|
+
"+",
|
|
2319
|
+
delta().added
|
|
2320
|
+
]
|
|
2321
|
+
}, undefined, true, undefined, this),
|
|
2322
|
+
/* @__PURE__ */ jsxDEV15("text", {
|
|
2323
|
+
fg: theme.diffSignRemoved,
|
|
2324
|
+
children: [
|
|
2325
|
+
"-",
|
|
2326
|
+
delta().removed
|
|
2327
|
+
]
|
|
2328
|
+
}, undefined, true, undefined, this)
|
|
2329
|
+
]
|
|
2330
|
+
}, undefined, true, undefined, this)
|
|
2331
|
+
]
|
|
2332
|
+
}, undefined, true, undefined, this),
|
|
2333
|
+
/* @__PURE__ */ jsxDEV15("text", {
|
|
2334
|
+
fg: theme.textMuted,
|
|
2335
|
+
children: formatTime(item.createdAt)
|
|
2336
|
+
}, undefined, false, undefined, this)
|
|
2337
|
+
]
|
|
2338
|
+
}, undefined, true, undefined, this);
|
|
2339
|
+
}
|
|
2340
|
+
}, undefined, false, undefined, this)
|
|
2341
|
+
}, undefined, false, undefined, this)
|
|
2342
|
+
}, undefined, false, undefined, this)
|
|
2343
|
+
]
|
|
2344
|
+
}, undefined, true, undefined, this);
|
|
2345
|
+
}
|
|
2346
|
+
// src/components/sidebar/ServerStatus.tsx
|
|
2347
|
+
import { jsxDEV as jsxDEV16 } from "@opentui/solid/jsx-dev-runtime";
|
|
2348
|
+
function ServerStatus() {
|
|
2349
|
+
const { theme } = useTheme();
|
|
2350
|
+
const server = useServer();
|
|
2351
|
+
return /* @__PURE__ */ jsxDEV16(Section, {
|
|
2352
|
+
title: "Server",
|
|
2353
|
+
children: /* @__PURE__ */ jsxDEV16("box", {
|
|
2354
|
+
flexDirection: "row",
|
|
2355
|
+
gap: 1,
|
|
2356
|
+
backgroundColor: theme.backgroundPanel,
|
|
2357
|
+
height: 1,
|
|
2358
|
+
children: [
|
|
2359
|
+
/* @__PURE__ */ jsxDEV16("text", {
|
|
2360
|
+
fg: server.connected() ? theme.connected : theme.disconnected,
|
|
2361
|
+
children: "\u2022"
|
|
2362
|
+
}, undefined, false, undefined, this),
|
|
2363
|
+
/* @__PURE__ */ jsxDEV16("text", {
|
|
2364
|
+
fg: server.connected() ? theme.text : theme.textMuted,
|
|
2365
|
+
children: server.url() ?? "disconnected"
|
|
2366
|
+
}, undefined, false, undefined, this)
|
|
2367
|
+
]
|
|
2368
|
+
}, undefined, true, undefined, this)
|
|
2369
|
+
}, undefined, false, undefined, this);
|
|
2370
|
+
}
|
|
2371
|
+
// src/components/sidebar/ProjectStatus.tsx
|
|
2372
|
+
import { Show as Show6 } from "solid-js";
|
|
2373
|
+
import { basename, dirname as dirname2 } from "path";
|
|
2374
|
+
import { jsxDEV as jsxDEV17 } from "@opentui/solid/jsx-dev-runtime";
|
|
2375
|
+
function ProjectStatus(props) {
|
|
2376
|
+
const { theme } = useTheme();
|
|
2377
|
+
const displayName = () => {
|
|
2378
|
+
const root = props.repoRoot();
|
|
2379
|
+
return root ? basename(root) : null;
|
|
2380
|
+
};
|
|
2381
|
+
return /* @__PURE__ */ jsxDEV17(Section, {
|
|
2382
|
+
title: "Review",
|
|
2383
|
+
children: [
|
|
2384
|
+
/* @__PURE__ */ jsxDEV17(Show6, {
|
|
2385
|
+
when: displayName(),
|
|
2386
|
+
children: /* @__PURE__ */ jsxDEV17("box", {
|
|
2387
|
+
flexDirection: "row",
|
|
2388
|
+
flexShrink: 0,
|
|
2389
|
+
children: [
|
|
2390
|
+
/* @__PURE__ */ jsxDEV17("text", {
|
|
2391
|
+
fg: theme.text,
|
|
2392
|
+
children: displayName()
|
|
2393
|
+
}, undefined, false, undefined, this),
|
|
2394
|
+
/* @__PURE__ */ jsxDEV17("text", {
|
|
2395
|
+
fg: theme.textMuted,
|
|
2396
|
+
children: [
|
|
2397
|
+
" (",
|
|
2398
|
+
props.commentCount(),
|
|
2399
|
+
" comments)"
|
|
2400
|
+
]
|
|
2401
|
+
}, undefined, true, undefined, this)
|
|
2402
|
+
]
|
|
2403
|
+
}, undefined, true, undefined, this)
|
|
2404
|
+
}, undefined, false, undefined, this),
|
|
2405
|
+
/* @__PURE__ */ jsxDEV17(Show6, {
|
|
2406
|
+
when: props.repoRoot(),
|
|
2407
|
+
children: /* @__PURE__ */ jsxDEV17("box", {
|
|
2408
|
+
flexDirection: "row",
|
|
2409
|
+
flexShrink: 0,
|
|
2410
|
+
children: [
|
|
2411
|
+
/* @__PURE__ */ jsxDEV17("text", {
|
|
2412
|
+
fg: theme.textMuted,
|
|
2413
|
+
children: [
|
|
2414
|
+
dirname2(props.repoRoot()),
|
|
2415
|
+
"/"
|
|
2416
|
+
]
|
|
2417
|
+
}, undefined, true, undefined, this),
|
|
2418
|
+
/* @__PURE__ */ jsxDEV17("text", {
|
|
2419
|
+
fg: theme.text,
|
|
2420
|
+
children: basename(props.repoRoot())
|
|
2421
|
+
}, undefined, false, undefined, this)
|
|
2422
|
+
]
|
|
2423
|
+
}, undefined, true, undefined, this)
|
|
2424
|
+
}, undefined, false, undefined, this)
|
|
2425
|
+
]
|
|
2426
|
+
}, undefined, true, undefined, this);
|
|
2427
|
+
}
|
|
2428
|
+
// src/lib/tree.ts
|
|
2429
|
+
function buildFileTree(entries) {
|
|
2430
|
+
const root = [];
|
|
2431
|
+
for (let i = 0;i < entries.length; i++) {
|
|
2432
|
+
const entry = entries[i];
|
|
2433
|
+
const parts = entry.file.split("/");
|
|
2434
|
+
insertPath(root, parts, 0, entry, i);
|
|
2435
|
+
}
|
|
2436
|
+
sortTree(root);
|
|
2437
|
+
return root;
|
|
2438
|
+
}
|
|
2439
|
+
function insertPath(nodes, parts, partIndex, entry, entryIndex) {
|
|
2440
|
+
if (partIndex >= parts.length)
|
|
2441
|
+
return;
|
|
2442
|
+
const name = parts[partIndex];
|
|
2443
|
+
const isLastPart = partIndex === parts.length - 1;
|
|
2444
|
+
const fullPath = parts.slice(0, partIndex + 1).join("/");
|
|
2445
|
+
let node = nodes.find((n) => n.name === name);
|
|
2446
|
+
if (!node) {
|
|
2447
|
+
node = {
|
|
2448
|
+
name,
|
|
2449
|
+
path: fullPath,
|
|
2450
|
+
type: isLastPart ? "file" : "dir",
|
|
2451
|
+
children: []
|
|
2452
|
+
};
|
|
2453
|
+
if (isLastPart) {
|
|
2454
|
+
node.status = entry.isDeleted ? "D" : entry.isNew ? "A" : "M";
|
|
2455
|
+
node.entryIndex = entryIndex;
|
|
2456
|
+
}
|
|
2457
|
+
nodes.push(node);
|
|
2458
|
+
}
|
|
2459
|
+
if (!isLastPart && node) {
|
|
2460
|
+
insertPath(node.children, parts, partIndex + 1, entry, entryIndex);
|
|
2461
|
+
}
|
|
2462
|
+
}
|
|
2463
|
+
function sortTree(nodes) {
|
|
2464
|
+
nodes.sort((a, b) => {
|
|
2465
|
+
if (a.type !== b.type) {
|
|
2466
|
+
return a.type === "dir" ? -1 : 1;
|
|
2467
|
+
}
|
|
2468
|
+
return a.name.localeCompare(b.name);
|
|
2469
|
+
});
|
|
2470
|
+
for (const node of nodes) {
|
|
2471
|
+
if (node.children.length > 0) {
|
|
2472
|
+
sortTree(node.children);
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
}
|
|
2476
|
+
function flattenTree(nodes) {
|
|
2477
|
+
const items = [];
|
|
2478
|
+
flattenNodes(nodes, 0, items);
|
|
2479
|
+
return items;
|
|
2480
|
+
}
|
|
2481
|
+
function flattenNodes(nodes, depth, items) {
|
|
2482
|
+
for (const node of nodes) {
|
|
2483
|
+
if (node.type === "dir") {
|
|
2484
|
+
const collapsed = collapseDir(node);
|
|
2485
|
+
items.push({ node: collapsed, depth });
|
|
2486
|
+
flattenNodes(collapsed.children, depth + 1, items);
|
|
2487
|
+
} else {
|
|
2488
|
+
items.push({ node, depth });
|
|
2489
|
+
}
|
|
2490
|
+
}
|
|
2491
|
+
}
|
|
2492
|
+
function collapseDir(node) {
|
|
2493
|
+
let current = node;
|
|
2494
|
+
const nameParts = [current.name];
|
|
2495
|
+
while (current.children.length === 1 && current.children[0].type === "dir") {
|
|
2496
|
+
current = current.children[0];
|
|
2497
|
+
nameParts.push(current.name);
|
|
2498
|
+
}
|
|
2499
|
+
if (nameParts.length === 1) {
|
|
2500
|
+
return node;
|
|
2501
|
+
}
|
|
2502
|
+
return {
|
|
2503
|
+
name: nameParts.join("/"),
|
|
2504
|
+
path: current.path,
|
|
2505
|
+
type: "dir",
|
|
2506
|
+
children: current.children
|
|
2507
|
+
};
|
|
2508
|
+
}
|
|
2509
|
+
function getFileIndices(items) {
|
|
2510
|
+
const indices = [];
|
|
2511
|
+
for (const item of items) {
|
|
2512
|
+
if (item.node.type === "file" && item.node.entryIndex !== undefined) {
|
|
2513
|
+
indices.push(item.node.entryIndex);
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
return indices;
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
// src/context/exit.tsx
|
|
2520
|
+
import { createContext as createContext7, useContext as useContext7 } from "solid-js";
|
|
2521
|
+
import { useRenderer as useRenderer4 } from "@opentui/solid";
|
|
2522
|
+
import { jsxDEV as jsxDEV18 } from "@opentui/solid/jsx-dev-runtime";
|
|
2523
|
+
var ExitContext = createContext7();
|
|
2524
|
+
function ExitProvider(props) {
|
|
2525
|
+
const renderer = useRenderer4();
|
|
2526
|
+
const cleanupFns = [];
|
|
2527
|
+
const registerCleanup = (fn) => {
|
|
2528
|
+
cleanupFns.push(fn);
|
|
2529
|
+
};
|
|
2530
|
+
const exit = async (reason) => {
|
|
2531
|
+
for (const fn of cleanupFns) {
|
|
2532
|
+
try {
|
|
2533
|
+
await fn();
|
|
2534
|
+
} catch {}
|
|
2535
|
+
}
|
|
2536
|
+
renderer.destroy();
|
|
2537
|
+
if (reason) {
|
|
2538
|
+
const message = reason instanceof Error ? reason.message : String(reason);
|
|
2539
|
+
process.stderr.write(message + `
|
|
2540
|
+
`);
|
|
2541
|
+
}
|
|
2542
|
+
process.exit(0);
|
|
2543
|
+
};
|
|
2544
|
+
return /* @__PURE__ */ jsxDEV18(ExitContext.Provider, {
|
|
2545
|
+
value: { exit, registerCleanup },
|
|
2546
|
+
children: props.children
|
|
2547
|
+
}, undefined, false, undefined, this);
|
|
2548
|
+
}
|
|
2549
|
+
function useExit() {
|
|
2550
|
+
const ctx = useContext7(ExitContext);
|
|
2551
|
+
if (!ctx)
|
|
2552
|
+
throw new Error("useExit must be used within ExitProvider");
|
|
2553
|
+
return ctx;
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
// src/Review.tsx
|
|
2557
|
+
import { jsxDEV as jsxDEV19 } from "@opentui/solid/jsx-dev-runtime";
|
|
2558
|
+
function Review(props = {}) {
|
|
2559
|
+
const repoPath = props.repoPath ?? process.argv[2] ?? ".";
|
|
2560
|
+
return /* @__PURE__ */ jsxDEV19(ThemeProvider, {
|
|
2561
|
+
children: /* @__PURE__ */ jsxDEV19(ExitProvider, {
|
|
2562
|
+
children: /* @__PURE__ */ jsxDEV19(ServerProvider, {
|
|
2563
|
+
children: /* @__PURE__ */ jsxDEV19(ReviewProvider, {
|
|
2564
|
+
repoPath,
|
|
2565
|
+
children: /* @__PURE__ */ jsxDEV19(DialogProvider, {
|
|
2566
|
+
children: /* @__PURE__ */ jsxDEV19(CommandProvider, {
|
|
2567
|
+
children: /* @__PURE__ */ jsxDEV19(App, {
|
|
2568
|
+
repoPath
|
|
2569
|
+
}, undefined, false, undefined, this)
|
|
2570
|
+
}, undefined, false, undefined, this)
|
|
2571
|
+
}, undefined, false, undefined, this)
|
|
2572
|
+
}, undefined, false, undefined, this)
|
|
2573
|
+
}, undefined, false, undefined, this)
|
|
2574
|
+
}, undefined, false, undefined, this)
|
|
2575
|
+
}, undefined, false, undefined, this);
|
|
2576
|
+
}
|
|
2577
|
+
function App(props) {
|
|
2578
|
+
const dims = useTerminalDimensions2();
|
|
2579
|
+
const dialog = useDialog();
|
|
2580
|
+
const command = useCommand();
|
|
2581
|
+
const { theme } = useTheme();
|
|
2582
|
+
const { exit } = useExit();
|
|
2583
|
+
const review = useReview();
|
|
2584
|
+
const displayItems = createMemo4(() => {
|
|
2585
|
+
const e = review.entries();
|
|
2586
|
+
if (e.length === 0)
|
|
2587
|
+
return [];
|
|
2588
|
+
const tree = buildFileTree(e);
|
|
2589
|
+
return flattenTree(tree);
|
|
2590
|
+
});
|
|
2591
|
+
const fileIndices = createMemo4(() => getFileIndices(displayItems()));
|
|
2592
|
+
createEffect6(() => {
|
|
2593
|
+
const indices = fileIndices();
|
|
2594
|
+
const entries = review.entries();
|
|
2595
|
+
const current = review.selectedFilePath();
|
|
2596
|
+
if (indices.length === 0) {
|
|
2597
|
+
review.setSelectedFilePath(null);
|
|
2598
|
+
return;
|
|
2599
|
+
}
|
|
2600
|
+
if (!current || !entries.some((e) => e.file === current)) {
|
|
2601
|
+
const firstEntryIndex = indices[0];
|
|
2602
|
+
review.setSelectedFilePath(entries[firstEntryIndex]?.file ?? null);
|
|
2603
|
+
}
|
|
2604
|
+
});
|
|
2605
|
+
const [focusPanel, setFocusPanel] = createSignal8("sidebar");
|
|
2606
|
+
const [sidebarMode, setSidebarMode] = createSignal8("files");
|
|
2607
|
+
const [selectedHunkIndex, setSelectedHunkIndex] = createSignal8(0);
|
|
2608
|
+
const [selectionMode, setSelectionMode] = createSignal8("hunk");
|
|
2609
|
+
const [selectedRange, setSelectedRange] = createSignal8(null);
|
|
2610
|
+
const [cursorRow, setCursorRow] = createSignal8(1);
|
|
2611
|
+
const [editorAnchor, setEditorAnchor] = createSignal8(null);
|
|
2612
|
+
const [selectedCommentIndex, setSelectedCommentIndex] = createSignal8(0);
|
|
2613
|
+
const [selectedPatchIndex, setSelectedPatchIndex] = createSignal8(0);
|
|
2614
|
+
const selectedFileNavIndex = createMemo4(() => {
|
|
2615
|
+
const indices = fileIndices();
|
|
2616
|
+
if (indices.length === 0)
|
|
2617
|
+
return 0;
|
|
2618
|
+
const entries = review.entries();
|
|
2619
|
+
const path2 = review.selectedFilePath();
|
|
2620
|
+
if (!path2)
|
|
2621
|
+
return 0;
|
|
2622
|
+
const entryIndex = entries.findIndex((e) => e.file === path2);
|
|
2623
|
+
if (entryIndex === -1)
|
|
2624
|
+
return 0;
|
|
2625
|
+
const navIndex = indices.indexOf(entryIndex);
|
|
2626
|
+
return navIndex === -1 ? 0 : navIndex;
|
|
2627
|
+
});
|
|
2628
|
+
const selectedEntry = () => {
|
|
2629
|
+
const entries = review.entries();
|
|
2630
|
+
if (entries.length === 0)
|
|
2631
|
+
return;
|
|
2632
|
+
const path2 = review.selectedFilePath();
|
|
2633
|
+
if (path2) {
|
|
2634
|
+
const found = entries.find((e) => e.file === path2);
|
|
2635
|
+
if (found)
|
|
2636
|
+
return found;
|
|
2637
|
+
}
|
|
2638
|
+
const firstEntryIndex = fileIndices()[0];
|
|
2639
|
+
if (firstEntryIndex !== undefined)
|
|
2640
|
+
return entries[firstEntryIndex];
|
|
2641
|
+
return entries[0];
|
|
2642
|
+
};
|
|
2643
|
+
const diffModel = createMemo4(() => {
|
|
2644
|
+
const entry = selectedEntry();
|
|
2645
|
+
if (!entry)
|
|
2646
|
+
return { hunks: [], totalRows: 0 };
|
|
2647
|
+
return parseFileDiff(entry.content, entry.file);
|
|
2648
|
+
});
|
|
2649
|
+
const hunkCount = createMemo4(() => diffModel().hunks.length);
|
|
2650
|
+
const totalRows = createMemo4(() => diffModel().totalRows);
|
|
2651
|
+
const selectedHunkRange = createMemo4(() => {
|
|
2652
|
+
const entry = selectedEntry();
|
|
2653
|
+
if (!entry)
|
|
2654
|
+
return null;
|
|
2655
|
+
const hunk = diffModel().hunks[selectedHunkIndex()];
|
|
2656
|
+
if (!hunk)
|
|
2657
|
+
return null;
|
|
2658
|
+
return { startRow: hunk.startRow, endRow: hunk.endRow };
|
|
2659
|
+
});
|
|
2660
|
+
const [previousFile, setPreviousFile] = createSignal8(null);
|
|
2661
|
+
createEffect6(() => {
|
|
2662
|
+
review.selectedFilePath();
|
|
2663
|
+
review.activePatchId();
|
|
2664
|
+
const currentFile = selectedEntry()?.file ?? null;
|
|
2665
|
+
const prevFile = previousFile();
|
|
2666
|
+
if (currentFile !== prevFile) {
|
|
2667
|
+
setSelectedHunkIndex(0);
|
|
2668
|
+
setSelectionMode("hunk");
|
|
2669
|
+
setSelectedRange(null);
|
|
2670
|
+
setCursorRow(1);
|
|
2671
|
+
} else if (currentFile !== null) {
|
|
2672
|
+
const maxHunk = Math.max(0, hunkCount() - 1);
|
|
2673
|
+
setSelectedHunkIndex((i) => Math.min(i, maxHunk));
|
|
2674
|
+
const maxRow = totalRows();
|
|
2675
|
+
if (maxRow > 0) {
|
|
2676
|
+
setSelectedRange((range) => {
|
|
2677
|
+
if (!range)
|
|
2678
|
+
return null;
|
|
2679
|
+
const clippedStart = Math.min(range.startRow, maxRow);
|
|
2680
|
+
const clippedEnd = Math.min(range.endRow, maxRow);
|
|
2681
|
+
if (clippedStart === clippedEnd && clippedStart === maxRow && maxRow > 1) {
|
|
2682
|
+
return { startRow: maxRow - 1, endRow: maxRow };
|
|
2683
|
+
}
|
|
2684
|
+
return { startRow: clippedStart, endRow: clippedEnd };
|
|
2685
|
+
});
|
|
2686
|
+
setCursorRow((r) => Math.min(Math.max(1, r), maxRow));
|
|
2687
|
+
}
|
|
2688
|
+
}
|
|
2689
|
+
setPreviousFile(currentFile);
|
|
2690
|
+
});
|
|
2691
|
+
const clampRow = (row) => {
|
|
2692
|
+
const max = Math.max(1, totalRows());
|
|
2693
|
+
return Math.min(Math.max(1, row), max);
|
|
2694
|
+
};
|
|
2695
|
+
const setSelectedNavIndex = (navIndex) => {
|
|
2696
|
+
const entryIndex = fileIndices()[navIndex];
|
|
2697
|
+
const entry = entryIndex !== undefined ? review.entries()[entryIndex] : null;
|
|
2698
|
+
review.setSelectedFilePath(entry?.file ?? null);
|
|
2699
|
+
};
|
|
2700
|
+
const navigateUp = () => {
|
|
2701
|
+
const panel = focusPanel();
|
|
2702
|
+
if (panel === "sidebar") {
|
|
2703
|
+
if (sidebarMode() === "files") {
|
|
2704
|
+
setSelectedNavIndex(Math.max(0, selectedFileNavIndex() - 1));
|
|
2705
|
+
} else if (sidebarMode() === "patches") {
|
|
2706
|
+
setSelectedPatchIndex((i) => Math.max(0, i - 1));
|
|
2707
|
+
} else {
|
|
2708
|
+
setSelectedCommentIndex((i) => Math.max(0, i - 1));
|
|
2709
|
+
}
|
|
2710
|
+
return;
|
|
2711
|
+
}
|
|
2712
|
+
if (panel === "diff" && selectionMode() === "line") {
|
|
2713
|
+
const next = clampRow(cursorRow() - 1);
|
|
2714
|
+
setCursorRow(next);
|
|
2715
|
+
setSelectedRange({ startRow: next, endRow: next });
|
|
2716
|
+
return;
|
|
2717
|
+
}
|
|
2718
|
+
if (panel === "diff") {
|
|
2719
|
+
setSelectedHunkIndex((i) => Math.max(0, i - 1));
|
|
2720
|
+
}
|
|
2721
|
+
};
|
|
2722
|
+
const navigateDown = () => {
|
|
2723
|
+
const panel = focusPanel();
|
|
2724
|
+
if (panel === "sidebar") {
|
|
2725
|
+
if (sidebarMode() === "files") {
|
|
2726
|
+
setSelectedNavIndex(Math.min(fileIndices().length - 1, selectedFileNavIndex() + 1));
|
|
2727
|
+
} else if (sidebarMode() === "patches") {
|
|
2728
|
+
const wt = review.workingTreePatchId();
|
|
2729
|
+
const patchCount = (wt === null ? review.patches().length : review.patches().filter((p) => p.id !== wt).length) + 1;
|
|
2730
|
+
setSelectedPatchIndex((i) => Math.min(patchCount - 1, i + 1));
|
|
2731
|
+
} else {
|
|
2732
|
+
const maxIndex = review.comments().length - 1;
|
|
2733
|
+
setSelectedCommentIndex((i) => Math.min(maxIndex, i + 1));
|
|
2734
|
+
}
|
|
2735
|
+
return;
|
|
2736
|
+
}
|
|
2737
|
+
if (panel === "diff" && selectionMode() === "line") {
|
|
2738
|
+
const next = clampRow(cursorRow() + 1);
|
|
2739
|
+
setCursorRow(next);
|
|
2740
|
+
setSelectedRange({ startRow: next, endRow: next });
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2743
|
+
if (panel === "diff") {
|
|
2744
|
+
setSelectedHunkIndex((i) => Math.min(hunkCount() - 1, i + 1));
|
|
2745
|
+
}
|
|
2746
|
+
};
|
|
2747
|
+
const openCommentEditor = () => {
|
|
2748
|
+
const entry = selectedEntry();
|
|
2749
|
+
const range = selectionMode() === "line" ? selectedRange() : selectedHunkRange();
|
|
2750
|
+
if (!entry || !range)
|
|
2751
|
+
return;
|
|
2752
|
+
const existing = review.getCommentForRange(entry.file, range.startRow, range.endRow);
|
|
2753
|
+
setEditorAnchor({
|
|
2754
|
+
file: entry.file,
|
|
2755
|
+
startRow: range.startRow,
|
|
2756
|
+
endRow: range.endRow,
|
|
2757
|
+
commentId: existing?.id
|
|
2758
|
+
});
|
|
2759
|
+
setFocusPanel("editor");
|
|
2760
|
+
};
|
|
2761
|
+
const toggleSidebarMode = () => {
|
|
2762
|
+
setSidebarMode((m) => {
|
|
2763
|
+
if (m === "files")
|
|
2764
|
+
return "patches";
|
|
2765
|
+
if (m === "patches")
|
|
2766
|
+
return "comments";
|
|
2767
|
+
return "files";
|
|
2768
|
+
});
|
|
2769
|
+
};
|
|
2770
|
+
const toggleHunkSelection = () => {
|
|
2771
|
+
const range = selectedHunkRange();
|
|
2772
|
+
if (!range)
|
|
2773
|
+
return;
|
|
2774
|
+
if (selectedRange()) {
|
|
2775
|
+
setSelectedRange(null);
|
|
2776
|
+
return;
|
|
2777
|
+
}
|
|
2778
|
+
setSelectionMode("hunk");
|
|
2779
|
+
setSelectedRange(range);
|
|
2780
|
+
};
|
|
2781
|
+
const toggleLineMode = () => {
|
|
2782
|
+
if (totalRows() === 0)
|
|
2783
|
+
return;
|
|
2784
|
+
if (selectionMode() === "hunk") {
|
|
2785
|
+
const range2 = selectedRange() ?? selectedHunkRange();
|
|
2786
|
+
const row = clampRow(range2?.startRow ?? 1);
|
|
2787
|
+
setSelectionMode("line");
|
|
2788
|
+
setCursorRow(row);
|
|
2789
|
+
setSelectedRange({ startRow: row, endRow: row });
|
|
2790
|
+
return;
|
|
2791
|
+
}
|
|
2792
|
+
const entry = selectedEntry();
|
|
2793
|
+
const range = selectedRange();
|
|
2794
|
+
if (entry && range) {
|
|
2795
|
+
const hunkIndex = getHunkIndexForRow(entry.content, entry.file, range.startRow) ?? 0;
|
|
2796
|
+
setSelectedHunkIndex(hunkIndex);
|
|
2797
|
+
}
|
|
2798
|
+
setSelectionMode("hunk");
|
|
2799
|
+
setSelectedRange(null);
|
|
2800
|
+
};
|
|
2801
|
+
const extendSelectionDown = () => {
|
|
2802
|
+
if (selectionMode() !== "line")
|
|
2803
|
+
return;
|
|
2804
|
+
const range = selectedRange() ?? {
|
|
2805
|
+
startRow: cursorRow(),
|
|
2806
|
+
endRow: cursorRow()
|
|
2807
|
+
};
|
|
2808
|
+
const next = clampRow(range.endRow + 1);
|
|
2809
|
+
setSelectedRange({ startRow: range.startRow, endRow: next });
|
|
2810
|
+
setCursorRow(next);
|
|
2811
|
+
};
|
|
2812
|
+
const extendSelectionUp = () => {
|
|
2813
|
+
if (selectionMode() !== "line")
|
|
2814
|
+
return;
|
|
2815
|
+
const range = selectedRange() ?? {
|
|
2816
|
+
startRow: cursorRow(),
|
|
2817
|
+
endRow: cursorRow()
|
|
2818
|
+
};
|
|
2819
|
+
const next = clampRow(range.startRow - 1);
|
|
2820
|
+
setSelectedRange({ startRow: next, endRow: range.endRow });
|
|
2821
|
+
setCursorRow(next);
|
|
2822
|
+
};
|
|
2823
|
+
const commands = () => [
|
|
2824
|
+
{
|
|
2825
|
+
id: "up",
|
|
2826
|
+
title: "up",
|
|
2827
|
+
category: "movement",
|
|
2828
|
+
keybinds: [{ name: "up" }, { name: "k" }],
|
|
2829
|
+
isActive: () => focusPanel() !== "editor",
|
|
2830
|
+
onExecute: navigateUp
|
|
2831
|
+
},
|
|
2832
|
+
{
|
|
2833
|
+
id: "down",
|
|
2834
|
+
title: "down",
|
|
2835
|
+
category: "movement",
|
|
2836
|
+
keybinds: [{ name: "down" }, { name: "j" }],
|
|
2837
|
+
isActive: () => focusPanel() !== "editor",
|
|
2838
|
+
onExecute: navigateDown
|
|
2839
|
+
},
|
|
2840
|
+
{
|
|
2841
|
+
id: "back-from-diff",
|
|
2842
|
+
title: "back",
|
|
2843
|
+
category: "movement",
|
|
2844
|
+
keybinds: [{ name: "escape" }],
|
|
2845
|
+
isActive: () => focusPanel() === "diff",
|
|
2846
|
+
onExecute: () => {
|
|
2847
|
+
setFocusPanel("sidebar");
|
|
2848
|
+
setSelectedHunkIndex(0);
|
|
2849
|
+
}
|
|
2850
|
+
},
|
|
2851
|
+
{
|
|
2852
|
+
id: "quit",
|
|
2853
|
+
title: "quit",
|
|
2854
|
+
category: "movement",
|
|
2855
|
+
keybinds: [
|
|
2856
|
+
{ name: "c", ctrl: true },
|
|
2857
|
+
{ name: "d", ctrl: true },
|
|
2858
|
+
{ name: "q", ctrl: true }
|
|
2859
|
+
],
|
|
2860
|
+
isActive: () => focusPanel() !== "editor",
|
|
2861
|
+
onExecute: () => exit()
|
|
2862
|
+
},
|
|
2863
|
+
{
|
|
2864
|
+
id: "refresh-working-tree",
|
|
2865
|
+
title: "refresh working tree",
|
|
2866
|
+
category: "movement",
|
|
2867
|
+
keybinds: [{ name: "r", ctrl: true }],
|
|
2868
|
+
isActive: () => focusPanel() !== "editor",
|
|
2869
|
+
onExecute: () => {
|
|
2870
|
+
const preserve = selectedEntry()?.file ?? review.selectedFilePath();
|
|
2871
|
+
review.refreshWorkingTree(preserve ?? undefined);
|
|
2872
|
+
}
|
|
2873
|
+
},
|
|
2874
|
+
{
|
|
2875
|
+
id: "cycle-forward",
|
|
2876
|
+
title: "cycle forward",
|
|
2877
|
+
category: "movement",
|
|
2878
|
+
keybinds: [{ name: "tab" }],
|
|
2879
|
+
isActive: () => focusPanel() !== "editor",
|
|
2880
|
+
onExecute: () => {
|
|
2881
|
+
if (focusPanel() === "sidebar") {
|
|
2882
|
+
if (sidebarMode() === "files") {
|
|
2883
|
+
setSidebarMode("patches");
|
|
2884
|
+
} else if (sidebarMode() === "patches") {
|
|
2885
|
+
setSidebarMode("comments");
|
|
2886
|
+
} else {
|
|
2887
|
+
setFocusPanel("diff");
|
|
2888
|
+
setSelectedHunkIndex(0);
|
|
2889
|
+
}
|
|
2890
|
+
} else if (focusPanel() === "diff") {
|
|
2891
|
+
setFocusPanel("sidebar");
|
|
2892
|
+
setSidebarMode("files");
|
|
2893
|
+
}
|
|
2894
|
+
}
|
|
2895
|
+
},
|
|
2896
|
+
{
|
|
2897
|
+
id: "cycle-backward",
|
|
2898
|
+
title: "cycle backward",
|
|
2899
|
+
category: "movement",
|
|
2900
|
+
keybinds: [{ name: "tab", shift: true }],
|
|
2901
|
+
isActive: () => focusPanel() !== "editor",
|
|
2902
|
+
onExecute: () => {
|
|
2903
|
+
if (focusPanel() === "sidebar") {
|
|
2904
|
+
if (sidebarMode() === "files") {
|
|
2905
|
+
setFocusPanel("diff");
|
|
2906
|
+
setSelectedHunkIndex(0);
|
|
2907
|
+
} else if (sidebarMode() === "patches") {
|
|
2908
|
+
setSidebarMode("files");
|
|
2909
|
+
} else {
|
|
2910
|
+
setSidebarMode("patches");
|
|
2911
|
+
}
|
|
2912
|
+
} else if (focusPanel() === "diff") {
|
|
2913
|
+
setFocusPanel("sidebar");
|
|
2914
|
+
setSidebarMode("comments");
|
|
2915
|
+
}
|
|
2916
|
+
}
|
|
2917
|
+
},
|
|
2918
|
+
{
|
|
2919
|
+
id: "select-hunks",
|
|
2920
|
+
title: "select hunks",
|
|
2921
|
+
category: "selection",
|
|
2922
|
+
keybinds: [{ name: "return" }],
|
|
2923
|
+
isActive: () => focusPanel() === "sidebar" && sidebarMode() === "files" && !!selectedEntry() && hunkCount() > 0,
|
|
2924
|
+
onExecute: () => {
|
|
2925
|
+
setFocusPanel("diff");
|
|
2926
|
+
setSelectedHunkIndex(0);
|
|
2927
|
+
setSelectionMode("hunk");
|
|
2928
|
+
setSelectedRange(null);
|
|
2929
|
+
}
|
|
2930
|
+
},
|
|
2931
|
+
{
|
|
2932
|
+
id: "select-lines",
|
|
2933
|
+
title: "select by lines",
|
|
2934
|
+
category: "selection",
|
|
2935
|
+
keybinds: [{ name: "a" }],
|
|
2936
|
+
isActive: () => focusPanel() === "diff" && totalRows() > 0,
|
|
2937
|
+
onExecute: toggleLineMode
|
|
2938
|
+
},
|
|
2939
|
+
{
|
|
2940
|
+
id: "toggle-hunk-selection",
|
|
2941
|
+
title: "toggle hunk selection",
|
|
2942
|
+
category: "selection",
|
|
2943
|
+
keybinds: [{ name: "space" }],
|
|
2944
|
+
isActive: () => focusPanel() === "diff" && selectionMode() === "hunk" && selectedHunkRange() !== null,
|
|
2945
|
+
onExecute: toggleHunkSelection
|
|
2946
|
+
},
|
|
2947
|
+
{
|
|
2948
|
+
id: "extend-down",
|
|
2949
|
+
title: "extend selection down",
|
|
2950
|
+
category: "selection",
|
|
2951
|
+
keybinds: [
|
|
2952
|
+
{ name: "j", shift: true },
|
|
2953
|
+
{ name: "down", shift: true }
|
|
2954
|
+
],
|
|
2955
|
+
isActive: () => focusPanel() === "diff" && selectionMode() === "line",
|
|
2956
|
+
onExecute: extendSelectionDown
|
|
2957
|
+
},
|
|
2958
|
+
{
|
|
2959
|
+
id: "extend-up",
|
|
2960
|
+
title: "extend selection up",
|
|
2961
|
+
category: "selection",
|
|
2962
|
+
keybinds: [
|
|
2963
|
+
{ name: "k", shift: true },
|
|
2964
|
+
{ name: "up", shift: true }
|
|
2965
|
+
],
|
|
2966
|
+
isActive: () => focusPanel() === "diff" && selectionMode() === "line",
|
|
2967
|
+
onExecute: extendSelectionUp
|
|
2968
|
+
},
|
|
2969
|
+
{
|
|
2970
|
+
id: "open-comment",
|
|
2971
|
+
title: "open comment",
|
|
2972
|
+
category: "selection",
|
|
2973
|
+
keybinds: [{ name: "return" }],
|
|
2974
|
+
isActive: () => focusPanel() === "sidebar" && sidebarMode() === "comments" && review.comments().length > 0,
|
|
2975
|
+
onExecute: () => {
|
|
2976
|
+
const comments = review.comments();
|
|
2977
|
+
const idx = selectedCommentIndex();
|
|
2978
|
+
const comment = comments[idx];
|
|
2979
|
+
if (!comment)
|
|
2980
|
+
return;
|
|
2981
|
+
review.setActivePatch(comment.patchId, comment.file);
|
|
2982
|
+
review.setSelectedFilePath(comment.file);
|
|
2983
|
+
const entry = review.entries().find((e) => e.file === comment.file);
|
|
2984
|
+
const hunkIndex = entry ? getHunkIndexForRow(entry.content, entry.file, comment.startRow) ?? 0 : 0;
|
|
2985
|
+
setSelectedHunkIndex(hunkIndex);
|
|
2986
|
+
setEditorAnchor({
|
|
2987
|
+
file: comment.file,
|
|
2988
|
+
startRow: comment.startRow,
|
|
2989
|
+
endRow: comment.endRow,
|
|
2990
|
+
commentId: comment.id
|
|
2991
|
+
});
|
|
2992
|
+
setFocusPanel("editor");
|
|
2993
|
+
}
|
|
2994
|
+
},
|
|
2995
|
+
{
|
|
2996
|
+
id: "select-patch",
|
|
2997
|
+
title: "select patch",
|
|
2998
|
+
category: "selection",
|
|
2999
|
+
keybinds: [{ name: "return" }],
|
|
3000
|
+
isActive: () => focusPanel() === "sidebar" && sidebarMode() === "patches" && review.patches().length > 0,
|
|
3001
|
+
onExecute: () => {
|
|
3002
|
+
const wt = review.workingTreePatchId();
|
|
3003
|
+
const patches = review.patches().slice().filter((p) => wt === null ? true : p.id !== wt).sort((a, b) => b.seq - a.seq);
|
|
3004
|
+
const idx = selectedPatchIndex();
|
|
3005
|
+
const currentEntry = selectedEntry();
|
|
3006
|
+
const currentFile = currentEntry?.file;
|
|
3007
|
+
if (idx === 0) {
|
|
3008
|
+
if (wt !== null)
|
|
3009
|
+
review.setActivePatch(wt, currentFile);
|
|
3010
|
+
} else {
|
|
3011
|
+
const patch = patches[idx - 1];
|
|
3012
|
+
if (patch) {
|
|
3013
|
+
review.setActivePatch(patch.id, currentFile);
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
}
|
|
3017
|
+
},
|
|
3018
|
+
{
|
|
3019
|
+
id: "comment",
|
|
3020
|
+
title: "comment",
|
|
3021
|
+
category: "actions",
|
|
3022
|
+
keybinds: [{ name: "c" }],
|
|
3023
|
+
isActive: () => focusPanel() === "diff" && (selectionMode() === "hunk" && selectedHunkRange() !== null || selectionMode() === "line" && selectedRange() !== null),
|
|
3024
|
+
onExecute: openCommentEditor
|
|
3025
|
+
}
|
|
3026
|
+
];
|
|
3027
|
+
command.register(commands);
|
|
3028
|
+
useKeyboard3((key) => {
|
|
3029
|
+
if (key.ctrl && key.name === "p") {
|
|
3030
|
+
dialog.show(() => /* @__PURE__ */ jsxDEV19(CommandPalette, {}, undefined, false, undefined, this));
|
|
3031
|
+
return;
|
|
3032
|
+
}
|
|
3033
|
+
if (dialog.isOpen())
|
|
3034
|
+
return;
|
|
3035
|
+
for (const cmd of commands()) {
|
|
3036
|
+
if (cmd.isActive() && matchAny(key, cmd.keybinds)) {
|
|
3037
|
+
cmd.onExecute();
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
}
|
|
3041
|
+
});
|
|
3042
|
+
const Footer = () => /* @__PURE__ */ jsxDEV19("box", {
|
|
3043
|
+
height: 1,
|
|
3044
|
+
paddingLeft: 1,
|
|
3045
|
+
flexDirection: "row",
|
|
3046
|
+
gap: 2,
|
|
3047
|
+
children: [
|
|
3048
|
+
/* @__PURE__ */ jsxDEV19("box", {
|
|
3049
|
+
flexDirection: "row",
|
|
3050
|
+
children: [
|
|
3051
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3052
|
+
children: "j/k "
|
|
3053
|
+
}, undefined, false, undefined, this),
|
|
3054
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3055
|
+
fg: "brightBlack",
|
|
3056
|
+
children: "navigate"
|
|
3057
|
+
}, undefined, false, undefined, this)
|
|
3058
|
+
]
|
|
3059
|
+
}, undefined, true, undefined, this),
|
|
3060
|
+
/* @__PURE__ */ jsxDEV19(Show7, {
|
|
3061
|
+
when: focusPanel() === "sidebar",
|
|
3062
|
+
children: /* @__PURE__ */ jsxDEV19("box", {
|
|
3063
|
+
flexDirection: "row",
|
|
3064
|
+
children: [
|
|
3065
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3066
|
+
children: "tab "
|
|
3067
|
+
}, undefined, false, undefined, this),
|
|
3068
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3069
|
+
fg: "brightBlack",
|
|
3070
|
+
children: "switch"
|
|
3071
|
+
}, undefined, false, undefined, this)
|
|
3072
|
+
]
|
|
3073
|
+
}, undefined, true, undefined, this)
|
|
3074
|
+
}, undefined, false, undefined, this),
|
|
3075
|
+
/* @__PURE__ */ jsxDEV19(Show7, {
|
|
3076
|
+
when: focusPanel() === "diff" && (selectionMode() === "hunk" && selectedHunkRange() !== null || selectionMode() === "line" && selectedRange()),
|
|
3077
|
+
children: /* @__PURE__ */ jsxDEV19("box", {
|
|
3078
|
+
flexDirection: "row",
|
|
3079
|
+
children: [
|
|
3080
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3081
|
+
children: "c "
|
|
3082
|
+
}, undefined, false, undefined, this),
|
|
3083
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3084
|
+
fg: "brightBlack",
|
|
3085
|
+
children: "comment"
|
|
3086
|
+
}, undefined, false, undefined, this)
|
|
3087
|
+
]
|
|
3088
|
+
}, undefined, true, undefined, this)
|
|
3089
|
+
}, undefined, false, undefined, this),
|
|
3090
|
+
/* @__PURE__ */ jsxDEV19(Show7, {
|
|
3091
|
+
when: focusPanel() === "editor",
|
|
3092
|
+
children: [
|
|
3093
|
+
/* @__PURE__ */ jsxDEV19("box", {
|
|
3094
|
+
flexDirection: "row",
|
|
3095
|
+
children: [
|
|
3096
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3097
|
+
children: "enter "
|
|
3098
|
+
}, undefined, false, undefined, this),
|
|
3099
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3100
|
+
fg: "brightBlack",
|
|
3101
|
+
children: "submit"
|
|
3102
|
+
}, undefined, false, undefined, this)
|
|
3103
|
+
]
|
|
3104
|
+
}, undefined, true, undefined, this),
|
|
3105
|
+
/* @__PURE__ */ jsxDEV19("box", {
|
|
3106
|
+
flexDirection: "row",
|
|
3107
|
+
children: [
|
|
3108
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3109
|
+
children: "C-j "
|
|
3110
|
+
}, undefined, false, undefined, this),
|
|
3111
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3112
|
+
fg: "brightBlack",
|
|
3113
|
+
children: "newline"
|
|
3114
|
+
}, undefined, false, undefined, this)
|
|
3115
|
+
]
|
|
3116
|
+
}, undefined, true, undefined, this),
|
|
3117
|
+
/* @__PURE__ */ jsxDEV19("box", {
|
|
3118
|
+
flexDirection: "row",
|
|
3119
|
+
children: [
|
|
3120
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3121
|
+
children: "C-e "
|
|
3122
|
+
}, undefined, false, undefined, this),
|
|
3123
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3124
|
+
fg: "brightBlack",
|
|
3125
|
+
children: "editor"
|
|
3126
|
+
}, undefined, false, undefined, this)
|
|
3127
|
+
]
|
|
3128
|
+
}, undefined, true, undefined, this),
|
|
3129
|
+
/* @__PURE__ */ jsxDEV19("box", {
|
|
3130
|
+
flexDirection: "row",
|
|
3131
|
+
children: [
|
|
3132
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3133
|
+
children: "esc "
|
|
3134
|
+
}, undefined, false, undefined, this),
|
|
3135
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3136
|
+
fg: "brightBlack",
|
|
3137
|
+
children: "cancel"
|
|
3138
|
+
}, undefined, false, undefined, this)
|
|
3139
|
+
]
|
|
3140
|
+
}, undefined, true, undefined, this)
|
|
3141
|
+
]
|
|
3142
|
+
}, undefined, true, undefined, this),
|
|
3143
|
+
/* @__PURE__ */ jsxDEV19(Show7, {
|
|
3144
|
+
when: focusPanel() !== "editor",
|
|
3145
|
+
children: /* @__PURE__ */ jsxDEV19("box", {
|
|
3146
|
+
flexDirection: "row",
|
|
3147
|
+
children: [
|
|
3148
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3149
|
+
children: "esc "
|
|
3150
|
+
}, undefined, false, undefined, this),
|
|
3151
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3152
|
+
fg: "brightBlack",
|
|
3153
|
+
children: "back"
|
|
3154
|
+
}, undefined, false, undefined, this)
|
|
3155
|
+
]
|
|
3156
|
+
}, undefined, true, undefined, this)
|
|
3157
|
+
}, undefined, false, undefined, this),
|
|
3158
|
+
/* @__PURE__ */ jsxDEV19(Show7, {
|
|
3159
|
+
when: focusPanel() !== "editor",
|
|
3160
|
+
children: /* @__PURE__ */ jsxDEV19("box", {
|
|
3161
|
+
flexDirection: "row",
|
|
3162
|
+
children: [
|
|
3163
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3164
|
+
children: "C-r "
|
|
3165
|
+
}, undefined, false, undefined, this),
|
|
3166
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3167
|
+
fg: "brightBlack",
|
|
3168
|
+
children: "refresh"
|
|
3169
|
+
}, undefined, false, undefined, this)
|
|
3170
|
+
]
|
|
3171
|
+
}, undefined, true, undefined, this)
|
|
3172
|
+
}, undefined, false, undefined, this),
|
|
3173
|
+
/* @__PURE__ */ jsxDEV19(Show7, {
|
|
3174
|
+
when: focusPanel() !== "editor",
|
|
3175
|
+
children: /* @__PURE__ */ jsxDEV19("box", {
|
|
3176
|
+
flexDirection: "row",
|
|
3177
|
+
children: [
|
|
3178
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3179
|
+
children: "C-q "
|
|
3180
|
+
}, undefined, false, undefined, this),
|
|
3181
|
+
/* @__PURE__ */ jsxDEV19("text", {
|
|
3182
|
+
fg: "brightBlack",
|
|
3183
|
+
children: "quit"
|
|
3184
|
+
}, undefined, false, undefined, this)
|
|
3185
|
+
]
|
|
3186
|
+
}, undefined, true, undefined, this)
|
|
3187
|
+
}, undefined, false, undefined, this)
|
|
3188
|
+
]
|
|
3189
|
+
}, undefined, true, undefined, this);
|
|
3190
|
+
return /* @__PURE__ */ jsxDEV19("box", {
|
|
3191
|
+
flexDirection: "column",
|
|
3192
|
+
width: dims().width,
|
|
3193
|
+
height: dims().height,
|
|
3194
|
+
backgroundColor: theme.background,
|
|
3195
|
+
children: [
|
|
3196
|
+
/* @__PURE__ */ jsxDEV19("box", {
|
|
3197
|
+
flexGrow: 1,
|
|
3198
|
+
flexDirection: "row",
|
|
3199
|
+
gap: 1,
|
|
3200
|
+
children: [
|
|
3201
|
+
/* @__PURE__ */ jsxDEV19("box", {
|
|
3202
|
+
width: 40,
|
|
3203
|
+
flexDirection: "column",
|
|
3204
|
+
gap: 1,
|
|
3205
|
+
paddingTop: 1,
|
|
3206
|
+
paddingBottom: 1,
|
|
3207
|
+
paddingLeft: 2,
|
|
3208
|
+
paddingRight: 2,
|
|
3209
|
+
backgroundColor: theme.backgroundPanel,
|
|
3210
|
+
children: [
|
|
3211
|
+
/* @__PURE__ */ jsxDEV19(ServerStatus, {}, undefined, false, undefined, this),
|
|
3212
|
+
/* @__PURE__ */ jsxDEV19(ProjectStatus, {
|
|
3213
|
+
repoRoot: review.repoRoot,
|
|
3214
|
+
commentCount: () => review.comments().length
|
|
3215
|
+
}, undefined, false, undefined, this),
|
|
3216
|
+
/* @__PURE__ */ jsxDEV19(SidebarProvider, {
|
|
3217
|
+
activeSection: sidebarMode,
|
|
3218
|
+
isFocused: () => focusPanel() === "sidebar",
|
|
3219
|
+
children: /* @__PURE__ */ jsxDEV19("box", {
|
|
3220
|
+
flexGrow: 1,
|
|
3221
|
+
flexDirection: "column",
|
|
3222
|
+
overflow: "hidden",
|
|
3223
|
+
gap: 1,
|
|
3224
|
+
children: [
|
|
3225
|
+
/* @__PURE__ */ jsxDEV19("box", {
|
|
3226
|
+
flexGrow: 1,
|
|
3227
|
+
children: /* @__PURE__ */ jsxDEV19(FileList, {
|
|
3228
|
+
displayItems,
|
|
3229
|
+
selectedFileIndex: selectedFileNavIndex,
|
|
3230
|
+
fileIndices,
|
|
3231
|
+
entries: review.entries,
|
|
3232
|
+
loading: review.loading,
|
|
3233
|
+
focused: () => focusPanel() === "sidebar" && sidebarMode() === "files"
|
|
3234
|
+
}, undefined, false, undefined, this)
|
|
3235
|
+
}, undefined, false, undefined, this),
|
|
3236
|
+
/* @__PURE__ */ jsxDEV19("box", {
|
|
3237
|
+
flexGrow: 1,
|
|
3238
|
+
flexDirection: "column",
|
|
3239
|
+
children: /* @__PURE__ */ jsxDEV19(PatchList, {
|
|
3240
|
+
patches: review.patches,
|
|
3241
|
+
activePatchId: review.activePatchId,
|
|
3242
|
+
workingTreePatchId: review.workingTreePatchId,
|
|
3243
|
+
workspaceEntries: review.workspaceEntries,
|
|
3244
|
+
loading: review.patchesLoading,
|
|
3245
|
+
selectedIndex: selectedPatchIndex,
|
|
3246
|
+
focused: () => focusPanel() === "sidebar" && sidebarMode() === "patches"
|
|
3247
|
+
}, undefined, false, undefined, this)
|
|
3248
|
+
}, undefined, false, undefined, this),
|
|
3249
|
+
/* @__PURE__ */ jsxDEV19("box", {
|
|
3250
|
+
flexGrow: 1,
|
|
3251
|
+
flexDirection: "column",
|
|
3252
|
+
children: /* @__PURE__ */ jsxDEV19(CommentList, {
|
|
3253
|
+
comments: review.comments,
|
|
3254
|
+
loading: review.commentsLoading,
|
|
3255
|
+
selectedIndex: selectedCommentIndex,
|
|
3256
|
+
focused: () => focusPanel() === "sidebar" && sidebarMode() === "comments"
|
|
3257
|
+
}, undefined, false, undefined, this)
|
|
3258
|
+
}, undefined, false, undefined, this)
|
|
3259
|
+
]
|
|
3260
|
+
}, undefined, true, undefined, this)
|
|
3261
|
+
}, undefined, false, undefined, this)
|
|
3262
|
+
]
|
|
3263
|
+
}, undefined, true, undefined, this),
|
|
3264
|
+
/* @__PURE__ */ jsxDEV19("box", {
|
|
3265
|
+
flexGrow: 1,
|
|
3266
|
+
flexDirection: "column",
|
|
3267
|
+
gap: 1,
|
|
3268
|
+
children: [
|
|
3269
|
+
/* @__PURE__ */ jsxDEV19(DiffPanel, {
|
|
3270
|
+
entry: selectedEntry,
|
|
3271
|
+
hunkCount,
|
|
3272
|
+
selectedHunkIndex,
|
|
3273
|
+
selectionMode,
|
|
3274
|
+
selectedRange,
|
|
3275
|
+
cursorRow,
|
|
3276
|
+
focused: () => focusPanel() === "diff"
|
|
3277
|
+
}, undefined, false, undefined, this),
|
|
3278
|
+
/* @__PURE__ */ jsxDEV19(Show7, {
|
|
3279
|
+
when: focusPanel() === "editor" && editorAnchor(),
|
|
3280
|
+
children: /* @__PURE__ */ jsxDEV19(EditorPanel, {
|
|
3281
|
+
file: editorAnchor().file,
|
|
3282
|
+
startRow: editorAnchor().startRow,
|
|
3283
|
+
endRow: editorAnchor().endRow,
|
|
3284
|
+
commentId: editorAnchor().commentId,
|
|
3285
|
+
onClose: () => {
|
|
3286
|
+
setFocusPanel("diff");
|
|
3287
|
+
setEditorAnchor(null);
|
|
3288
|
+
}
|
|
3289
|
+
}, undefined, false, undefined, this)
|
|
3290
|
+
}, undefined, false, undefined, this)
|
|
3291
|
+
]
|
|
3292
|
+
}, undefined, true, undefined, this)
|
|
3293
|
+
]
|
|
3294
|
+
}, undefined, true, undefined, this),
|
|
3295
|
+
/* @__PURE__ */ jsxDEV19(Footer, {}, undefined, false, undefined, this),
|
|
3296
|
+
/* @__PURE__ */ jsxDEV19(Show7, {
|
|
3297
|
+
when: dialog.isOpen(),
|
|
3298
|
+
children: dialog.content()
|
|
3299
|
+
}, undefined, false, undefined, this)
|
|
3300
|
+
]
|
|
3301
|
+
}, undefined, true, undefined, this);
|
|
3302
|
+
}
|
|
3303
|
+
|
|
3304
|
+
// src/App.tsx
|
|
3305
|
+
import { mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3306
|
+
|
|
3307
|
+
// parsers/index.ts
|
|
3308
|
+
import bash_wasm from "tree-sitter-wasms/out/tree-sitter-bash.wasm" with { type: "file" };
|
|
3309
|
+
import c_wasm from "tree-sitter-wasms/out/tree-sitter-c.wasm" with { type: "file" };
|
|
3310
|
+
import cpp_wasm from "tree-sitter-wasms/out/tree-sitter-cpp.wasm" with { type: "file" };
|
|
3311
|
+
import c_sharp_wasm from "tree-sitter-wasms/out/tree-sitter-c_sharp.wasm" with { type: "file" };
|
|
3312
|
+
import css_wasm from "tree-sitter-wasms/out/tree-sitter-css.wasm" with { type: "file" };
|
|
3313
|
+
import dart_wasm from "tree-sitter-wasms/out/tree-sitter-dart.wasm" with { type: "file" };
|
|
3314
|
+
import elisp_wasm from "tree-sitter-wasms/out/tree-sitter-elisp.wasm" with { type: "file" };
|
|
3315
|
+
import elixir_wasm from "tree-sitter-wasms/out/tree-sitter-elixir.wasm" with { type: "file" };
|
|
3316
|
+
import elm_wasm from "tree-sitter-wasms/out/tree-sitter-elm.wasm" with { type: "file" };
|
|
3317
|
+
import embedded_template_wasm from "tree-sitter-wasms/out/tree-sitter-embedded_template.wasm" with { type: "file" };
|
|
3318
|
+
import go_wasm from "tree-sitter-wasms/out/tree-sitter-go.wasm" with { type: "file" };
|
|
3319
|
+
import html_wasm from "tree-sitter-wasms/out/tree-sitter-html.wasm" with { type: "file" };
|
|
3320
|
+
import java_wasm from "tree-sitter-wasms/out/tree-sitter-java.wasm" with { type: "file" };
|
|
3321
|
+
import javascript_wasm from "tree-sitter-wasms/out/tree-sitter-javascript.wasm" with { type: "file" };
|
|
3322
|
+
import json_wasm from "tree-sitter-wasms/out/tree-sitter-json.wasm" with { type: "file" };
|
|
3323
|
+
import kotlin_wasm from "tree-sitter-wasms/out/tree-sitter-kotlin.wasm" with { type: "file" };
|
|
3324
|
+
import lua_wasm from "tree-sitter-wasms/out/tree-sitter-lua.wasm" with { type: "file" };
|
|
3325
|
+
import objc_wasm from "tree-sitter-wasms/out/tree-sitter-objc.wasm" with { type: "file" };
|
|
3326
|
+
import ocaml_wasm from "tree-sitter-wasms/out/tree-sitter-ocaml.wasm" with { type: "file" };
|
|
3327
|
+
import php_wasm from "tree-sitter-wasms/out/tree-sitter-php.wasm" with { type: "file" };
|
|
3328
|
+
import python_wasm from "tree-sitter-wasms/out/tree-sitter-python.wasm" with { type: "file" };
|
|
3329
|
+
import ql_wasm from "tree-sitter-wasms/out/tree-sitter-ql.wasm" with { type: "file" };
|
|
3330
|
+
import ruby_wasm from "tree-sitter-wasms/out/tree-sitter-ruby.wasm" with { type: "file" };
|
|
3331
|
+
import rust_wasm from "tree-sitter-wasms/out/tree-sitter-rust.wasm" with { type: "file" };
|
|
3332
|
+
import scala_wasm from "tree-sitter-wasms/out/tree-sitter-scala.wasm" with { type: "file" };
|
|
3333
|
+
import swift_wasm from "tree-sitter-wasms/out/tree-sitter-swift.wasm" with { type: "file" };
|
|
3334
|
+
import toml_wasm from "tree-sitter-wasms/out/tree-sitter-toml.wasm" with { type: "file" };
|
|
3335
|
+
import tsx_wasm from "tree-sitter-wasms/out/tree-sitter-tsx.wasm" with { type: "file" };
|
|
3336
|
+
import typescript_wasm from "tree-sitter-wasms/out/tree-sitter-typescript.wasm" with { type: "file" };
|
|
3337
|
+
import vue_wasm from "tree-sitter-wasms/out/tree-sitter-vue.wasm" with { type: "file" };
|
|
3338
|
+
import yaml_wasm from "tree-sitter-wasms/out/tree-sitter-yaml.wasm" with { type: "file" };
|
|
3339
|
+
import zig_wasm from "tree-sitter-wasms/out/tree-sitter-zig.wasm" with { type: "file" };
|
|
3340
|
+
|
|
3341
|
+
// parsers/queries/bash.scm
|
|
3342
|
+
var bash_default = "./bash-nbhfht5r.scm";
|
|
3343
|
+
|
|
3344
|
+
// parsers/queries/c.scm
|
|
3345
|
+
var c_default = "./c-4zy7d3fw.scm";
|
|
3346
|
+
|
|
3347
|
+
// parsers/queries/cpp.scm
|
|
3348
|
+
var cpp_default = "./cpp-stxr9ffp.scm";
|
|
3349
|
+
|
|
3350
|
+
// parsers/queries/c_sharp.scm
|
|
3351
|
+
var c_sharp_default = "./c_sharp-tb7n62t2.scm";
|
|
3352
|
+
|
|
3353
|
+
// parsers/queries/css.scm
|
|
3354
|
+
var css_default = "./css-b1p1h6ys.scm";
|
|
3355
|
+
|
|
3356
|
+
// parsers/queries/dart.scm
|
|
3357
|
+
var dart_default = "./dart-t89b32ej.scm";
|
|
3358
|
+
|
|
3359
|
+
// parsers/queries/elisp.scm
|
|
3360
|
+
var elisp_default = "./elisp-ax9wx1b9.scm";
|
|
3361
|
+
|
|
3362
|
+
// parsers/queries/elixir.scm
|
|
3363
|
+
var elixir_default = "./elixir-77300bss.scm";
|
|
3364
|
+
|
|
3365
|
+
// parsers/queries/elm.scm
|
|
3366
|
+
var elm_default = "./elm-k2q5knqb.scm";
|
|
3367
|
+
|
|
3368
|
+
// parsers/queries/embedded_template.scm
|
|
3369
|
+
var embedded_template_default = "./embedded_template-k7ypjkba.scm";
|
|
3370
|
+
|
|
3371
|
+
// parsers/queries/go.scm
|
|
3372
|
+
var go_default = "./go-9wx8m64k.scm";
|
|
3373
|
+
|
|
3374
|
+
// parsers/queries/html.scm
|
|
3375
|
+
var html_default = "./html-g95aq3vw.scm";
|
|
3376
|
+
|
|
3377
|
+
// parsers/queries/java.scm
|
|
3378
|
+
var java_default = "./java-wasz6t7a.scm";
|
|
3379
|
+
|
|
3380
|
+
// parsers/queries/javascript.scm
|
|
3381
|
+
var javascript_default = "./javascript-cswtymww.scm";
|
|
3382
|
+
|
|
3383
|
+
// parsers/queries/json.scm
|
|
3384
|
+
var json_default = "./json-pf4aw8w1.scm";
|
|
3385
|
+
|
|
3386
|
+
// parsers/queries/kotlin.scm
|
|
3387
|
+
var kotlin_default = "./kotlin-m6cj2y7m.scm";
|
|
3388
|
+
|
|
3389
|
+
// parsers/queries/lua.scm
|
|
3390
|
+
var lua_default = "./lua-7h6jk3pd.scm";
|
|
3391
|
+
|
|
3392
|
+
// parsers/queries/objc.scm
|
|
3393
|
+
var objc_default = "./objc-6yjdwy1z.scm";
|
|
3394
|
+
|
|
3395
|
+
// parsers/queries/ocaml.scm
|
|
3396
|
+
var ocaml_default = "./ocaml-jfj520qk.scm";
|
|
3397
|
+
|
|
3398
|
+
// parsers/queries/php.scm
|
|
3399
|
+
var php_default = "./php-bds7v5h1.scm";
|
|
3400
|
+
|
|
3401
|
+
// parsers/queries/python.scm
|
|
3402
|
+
var python_default = "./python-k2cbtg72.scm";
|
|
3403
|
+
|
|
3404
|
+
// parsers/queries/ql.scm
|
|
3405
|
+
var ql_default = "./ql-qs2vxbeq.scm";
|
|
3406
|
+
|
|
3407
|
+
// parsers/queries/ruby.scm
|
|
3408
|
+
var ruby_default = "./ruby-3ybh5bp2.scm";
|
|
3409
|
+
|
|
3410
|
+
// parsers/queries/rust.scm
|
|
3411
|
+
var rust_default = "./rust-edtnatpk.scm";
|
|
3412
|
+
|
|
3413
|
+
// parsers/queries/scala.scm
|
|
3414
|
+
var scala_default = "./scala-91f2sb1z.scm";
|
|
3415
|
+
|
|
3416
|
+
// parsers/queries/swift.scm
|
|
3417
|
+
var swift_default = "./swift-a4tt704b.scm";
|
|
3418
|
+
|
|
3419
|
+
// parsers/queries/toml.scm
|
|
3420
|
+
var toml_default = "./toml-hcqfz0bg.scm";
|
|
3421
|
+
|
|
3422
|
+
// parsers/queries/tsx.scm
|
|
3423
|
+
var tsx_default = "./tsx-c3k3mmc9.scm";
|
|
3424
|
+
|
|
3425
|
+
// parsers/queries/typescript.scm
|
|
3426
|
+
var typescript_default = "./typescript-hz4pg6ze.scm";
|
|
3427
|
+
|
|
3428
|
+
// parsers/queries/vue.scm
|
|
3429
|
+
var vue_default = "./vue-xbeea1aj.scm";
|
|
3430
|
+
|
|
3431
|
+
// parsers/queries/yaml.scm
|
|
3432
|
+
var yaml_default = "./yaml-mbfb25g0.scm";
|
|
3433
|
+
|
|
3434
|
+
// parsers/queries/zig.scm
|
|
3435
|
+
var zig_default = "./zig-ds3q8m26.scm";
|
|
3436
|
+
|
|
3437
|
+
// parsers/index.ts
|
|
3438
|
+
var parsers = [
|
|
3439
|
+
{
|
|
3440
|
+
filetype: "bash",
|
|
3441
|
+
wasm: bash_wasm,
|
|
3442
|
+
queries: { highlights: [bash_default] }
|
|
3443
|
+
},
|
|
3444
|
+
{ filetype: "c", wasm: c_wasm, queries: { highlights: [c_default] } },
|
|
3445
|
+
{
|
|
3446
|
+
filetype: "cpp",
|
|
3447
|
+
wasm: cpp_wasm,
|
|
3448
|
+
queries: { highlights: [cpp_default] }
|
|
3449
|
+
},
|
|
3450
|
+
{
|
|
3451
|
+
filetype: "c_sharp",
|
|
3452
|
+
wasm: c_sharp_wasm,
|
|
3453
|
+
queries: { highlights: [c_sharp_default] }
|
|
3454
|
+
},
|
|
3455
|
+
{
|
|
3456
|
+
filetype: "css",
|
|
3457
|
+
wasm: css_wasm,
|
|
3458
|
+
queries: { highlights: [css_default] }
|
|
3459
|
+
},
|
|
3460
|
+
{
|
|
3461
|
+
filetype: "dart",
|
|
3462
|
+
wasm: dart_wasm,
|
|
3463
|
+
queries: { highlights: [dart_default] }
|
|
3464
|
+
},
|
|
3465
|
+
{
|
|
3466
|
+
filetype: "elisp",
|
|
3467
|
+
wasm: elisp_wasm,
|
|
3468
|
+
queries: { highlights: [elisp_default] }
|
|
3469
|
+
},
|
|
3470
|
+
{
|
|
3471
|
+
filetype: "elixir",
|
|
3472
|
+
wasm: elixir_wasm,
|
|
3473
|
+
queries: { highlights: [elixir_default] }
|
|
3474
|
+
},
|
|
3475
|
+
{
|
|
3476
|
+
filetype: "elm",
|
|
3477
|
+
wasm: elm_wasm,
|
|
3478
|
+
queries: { highlights: [elm_default] }
|
|
3479
|
+
},
|
|
3480
|
+
{
|
|
3481
|
+
filetype: "embedded_template",
|
|
3482
|
+
wasm: embedded_template_wasm,
|
|
3483
|
+
queries: { highlights: [embedded_template_default] }
|
|
3484
|
+
},
|
|
3485
|
+
{ filetype: "go", wasm: go_wasm, queries: { highlights: [go_default] } },
|
|
3486
|
+
{
|
|
3487
|
+
filetype: "html",
|
|
3488
|
+
wasm: html_wasm,
|
|
3489
|
+
queries: { highlights: [html_default] }
|
|
3490
|
+
},
|
|
3491
|
+
{
|
|
3492
|
+
filetype: "java",
|
|
3493
|
+
wasm: java_wasm,
|
|
3494
|
+
queries: { highlights: [java_default] }
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
filetype: "javascript",
|
|
3498
|
+
wasm: javascript_wasm,
|
|
3499
|
+
queries: { highlights: [javascript_default] }
|
|
3500
|
+
},
|
|
3501
|
+
{
|
|
3502
|
+
filetype: "json",
|
|
3503
|
+
wasm: json_wasm,
|
|
3504
|
+
queries: { highlights: [json_default] }
|
|
3505
|
+
},
|
|
3506
|
+
{
|
|
3507
|
+
filetype: "kotlin",
|
|
3508
|
+
wasm: kotlin_wasm,
|
|
3509
|
+
queries: { highlights: [kotlin_default] }
|
|
3510
|
+
},
|
|
3511
|
+
{
|
|
3512
|
+
filetype: "lua",
|
|
3513
|
+
wasm: lua_wasm,
|
|
3514
|
+
queries: { highlights: [lua_default] }
|
|
3515
|
+
},
|
|
3516
|
+
{
|
|
3517
|
+
filetype: "objc",
|
|
3518
|
+
wasm: objc_wasm,
|
|
3519
|
+
queries: { highlights: [objc_default] }
|
|
3520
|
+
},
|
|
3521
|
+
{
|
|
3522
|
+
filetype: "ocaml",
|
|
3523
|
+
wasm: ocaml_wasm,
|
|
3524
|
+
queries: { highlights: [ocaml_default] }
|
|
3525
|
+
},
|
|
3526
|
+
{
|
|
3527
|
+
filetype: "php",
|
|
3528
|
+
wasm: php_wasm,
|
|
3529
|
+
queries: { highlights: [php_default] }
|
|
3530
|
+
},
|
|
3531
|
+
{
|
|
3532
|
+
filetype: "python",
|
|
3533
|
+
wasm: python_wasm,
|
|
3534
|
+
queries: { highlights: [python_default] }
|
|
3535
|
+
},
|
|
3536
|
+
{ filetype: "ql", wasm: ql_wasm, queries: { highlights: [ql_default] } },
|
|
3537
|
+
{
|
|
3538
|
+
filetype: "ruby",
|
|
3539
|
+
wasm: ruby_wasm,
|
|
3540
|
+
queries: { highlights: [ruby_default] }
|
|
3541
|
+
},
|
|
3542
|
+
{
|
|
3543
|
+
filetype: "rust",
|
|
3544
|
+
wasm: rust_wasm,
|
|
3545
|
+
queries: { highlights: [rust_default] }
|
|
3546
|
+
},
|
|
3547
|
+
{
|
|
3548
|
+
filetype: "scala",
|
|
3549
|
+
wasm: scala_wasm,
|
|
3550
|
+
queries: { highlights: [scala_default] }
|
|
3551
|
+
},
|
|
3552
|
+
{
|
|
3553
|
+
filetype: "swift",
|
|
3554
|
+
wasm: swift_wasm,
|
|
3555
|
+
queries: { highlights: [swift_default] }
|
|
3556
|
+
},
|
|
3557
|
+
{
|
|
3558
|
+
filetype: "toml",
|
|
3559
|
+
wasm: toml_wasm,
|
|
3560
|
+
queries: { highlights: [toml_default] }
|
|
3561
|
+
},
|
|
3562
|
+
{
|
|
3563
|
+
filetype: "tsx",
|
|
3564
|
+
wasm: tsx_wasm,
|
|
3565
|
+
queries: { highlights: [tsx_default] }
|
|
3566
|
+
},
|
|
3567
|
+
{
|
|
3568
|
+
filetype: "typescript",
|
|
3569
|
+
wasm: typescript_wasm,
|
|
3570
|
+
queries: { highlights: [typescript_default] }
|
|
3571
|
+
},
|
|
3572
|
+
{
|
|
3573
|
+
filetype: "vue",
|
|
3574
|
+
wasm: vue_wasm,
|
|
3575
|
+
queries: { highlights: [vue_default] }
|
|
3576
|
+
},
|
|
3577
|
+
{
|
|
3578
|
+
filetype: "yaml",
|
|
3579
|
+
wasm: yaml_wasm,
|
|
3580
|
+
queries: { highlights: [yaml_default] }
|
|
3581
|
+
},
|
|
3582
|
+
{
|
|
3583
|
+
filetype: "zig",
|
|
3584
|
+
wasm: zig_wasm,
|
|
3585
|
+
queries: { highlights: [zig_default] }
|
|
3586
|
+
}
|
|
3587
|
+
];
|
|
3588
|
+
|
|
3589
|
+
// src/App.tsx
|
|
3590
|
+
import { jsxDEV as jsxDEV20 } from "@opentui/solid/jsx-dev-runtime";
|
|
3591
|
+
exports_db.init();
|
|
3592
|
+
addDefaultParsers(parsers);
|
|
3593
|
+
var originalSliderRenderSelf = SliderRenderable.prototype.renderSelf;
|
|
3594
|
+
SliderRenderable.prototype.renderSelf = function(buffer) {
|
|
3595
|
+
if (this.width <= 0 || this.height <= 0)
|
|
3596
|
+
return;
|
|
3597
|
+
if (this.orientation === "vertical" && this.height <= 1)
|
|
3598
|
+
return;
|
|
3599
|
+
if (this.orientation === "horizontal" && this.width <= 1)
|
|
3600
|
+
return;
|
|
3601
|
+
originalSliderRenderSelf.call(this, buffer);
|
|
3602
|
+
};
|
|
3603
|
+
var RECORD_FRAMES = process.env.RECORD_FRAMES === "1";
|
|
3604
|
+
var frameNumber = 0;
|
|
3605
|
+
var decoder = new TextDecoder;
|
|
3606
|
+
async function tui(options = {}) {
|
|
3607
|
+
const repoPath = options.repoPath ?? process.cwd();
|
|
3608
|
+
await render(() => /* @__PURE__ */ jsxDEV20(ErrorBoundary, {
|
|
3609
|
+
fallback: (err, reset) => /* @__PURE__ */ jsxDEV20(ErrorFallback, {
|
|
3610
|
+
error: err,
|
|
3611
|
+
reset
|
|
3612
|
+
}, undefined, false, undefined, this),
|
|
3613
|
+
children: /* @__PURE__ */ jsxDEV20(Review, {
|
|
3614
|
+
repoPath
|
|
3615
|
+
}, undefined, false, undefined, this)
|
|
3616
|
+
}, undefined, false, undefined, this), {
|
|
3617
|
+
exitOnCtrlC: false,
|
|
3618
|
+
useKittyKeyboard: {},
|
|
3619
|
+
postProcessFns: RECORD_FRAMES ? [
|
|
3620
|
+
(buffer) => {
|
|
3621
|
+
const frameBytes = buffer.getRealCharBytes(true);
|
|
3622
|
+
const frame = decoder.decode(frameBytes);
|
|
3623
|
+
mkdirSync2("frames", { recursive: true });
|
|
3624
|
+
writeFileSync2(`frames/frame-${String(frameNumber++).padStart(5, "0")}.txt`, frame);
|
|
3625
|
+
}
|
|
3626
|
+
] : undefined
|
|
3627
|
+
});
|
|
3628
|
+
}
|
|
3629
|
+
if (import.meta.main) {
|
|
3630
|
+
tui();
|
|
3631
|
+
}
|
|
3632
|
+
export {
|
|
3633
|
+
tui
|
|
3634
|
+
};
|
|
3635
|
+
|
|
3636
|
+
//# debugId=D41DC668FD4D6FC664756E2164756E21
|