honeytree 1.1.6 → 1.2.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/LICENSE +21 -0
- package/README.md +43 -141
- package/bin/honeydew.js +4 -3
- package/package.json +9 -9
- package/src/animation-keyframes.js +890 -0
- package/src/animation.js +151 -0
- package/src/camera.js +54 -0
- package/src/diffactions.js +32 -0
- package/src/diffpanel.js +83 -0
- package/src/diffparser.js +44 -0
- package/src/diffwatch.js +52 -0
- package/src/pointcloud.js +193 -0
- package/src/renderer.js +49 -3
- package/src/renderer3d.js +135 -0
- package/src/scanner.js +102 -0
- package/src/sprites.js +2 -1
- package/src/viewer.js +375 -148
package/src/viewer.js
CHANGED
|
@@ -1,9 +1,27 @@
|
|
|
1
|
-
import
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { scanCodebase } from "./scanner.js";
|
|
3
|
+
import { generateForestCloud, generateGroundPlane } from "./pointcloud.js";
|
|
4
|
+
import { createCamera, rotatePoint, projectPoint, clampElevation, clampAzimuth } from "./camera.js";
|
|
5
|
+
import { createFrameBuffer, rasterize, renderBufferToString, renderTopBar, renderStatusBar } from "./renderer3d.js";
|
|
6
|
+
import { getChangedFiles, getFileDiff, watchForChanges } from "./diffwatch.js";
|
|
7
|
+
import { parseDiff } from "./diffparser.js";
|
|
8
|
+
import { createDiffPanel, renderDiffPanel } from "./diffpanel.js";
|
|
9
|
+
import { stageHunk, revertHunk } from "./diffactions.js";
|
|
10
|
+
|
|
11
|
+
export function createForestWatcher(filePath, onChange) {
|
|
12
|
+
try {
|
|
13
|
+
const stat = fs.statSync(filePath);
|
|
14
|
+
if (!stat.isFile()) return null;
|
|
2
15
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
16
|
+
const watcher = fs.watch(filePath, onChange);
|
|
17
|
+
watcher.on("error", () => {
|
|
18
|
+
try { watcher.close(); } catch {}
|
|
19
|
+
});
|
|
20
|
+
return watcher;
|
|
21
|
+
} catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
7
25
|
|
|
8
26
|
function writeAnsi(code) {
|
|
9
27
|
process.stdout.write(code);
|
|
@@ -25,210 +43,419 @@ function moveHome() {
|
|
|
25
43
|
writeAnsi("\x1b[H");
|
|
26
44
|
}
|
|
27
45
|
|
|
28
|
-
function
|
|
29
|
-
|
|
46
|
+
function enableMouse() {
|
|
47
|
+
writeAnsi("\x1b[?1000h");
|
|
48
|
+
writeAnsi("\x1b[?1002h");
|
|
49
|
+
writeAnsi("\x1b[?1006h");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function disableMouse() {
|
|
53
|
+
writeAnsi("\x1b[?1006l");
|
|
54
|
+
writeAnsi("\x1b[?1002l");
|
|
55
|
+
writeAnsi("\x1b[?1000l");
|
|
30
56
|
}
|
|
31
57
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
58
|
+
function enableMouseMotion() {
|
|
59
|
+
writeAnsi("\x1b[?1003h");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function disableMouseMotion() {
|
|
63
|
+
writeAnsi("\x1b[?1003l");
|
|
64
|
+
}
|
|
35
65
|
|
|
36
|
-
|
|
37
|
-
|
|
66
|
+
export async function viewer(targetDir) {
|
|
67
|
+
const dir = targetDir || process.cwd();
|
|
68
|
+
|
|
69
|
+
process.stdout.write("Scanning codebase...\n");
|
|
70
|
+
const files = scanCodebase(dir);
|
|
71
|
+
|
|
72
|
+
if (files.length === 0) {
|
|
73
|
+
console.error("No source files found in", dir);
|
|
38
74
|
process.exit(1);
|
|
39
75
|
}
|
|
40
76
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
77
|
+
const { points: treePoints, filePaths } = generateForestCloud(files);
|
|
78
|
+
const groundRadius = Math.max(25, Math.sqrt(files.length) * 7);
|
|
79
|
+
const groundPoints = generateGroundPlane(groundRadius);
|
|
80
|
+
const allPoints = [...treePoints, ...groundPoints];
|
|
81
|
+
|
|
82
|
+
const camera = createCamera();
|
|
83
|
+
let mouseDown = false;
|
|
84
|
+
let lastMouseX = 0;
|
|
85
|
+
let lastMouseY = 0;
|
|
86
|
+
let hoveredFile = "";
|
|
87
|
+
let hoveredModified = false;
|
|
88
|
+
let needsRedraw = true;
|
|
89
|
+
|
|
90
|
+
let changedFiles = new Set();
|
|
91
|
+
let diffPanel = null;
|
|
92
|
+
let diffMode = false;
|
|
93
|
+
let polling = false;
|
|
94
|
+
let watcher = null;
|
|
95
|
+
|
|
96
|
+
let screenWidth = process.stdout.columns || 80;
|
|
97
|
+
let screenHeight = (process.stdout.rows || 24) - 2;
|
|
98
|
+
|
|
99
|
+
function renderFrame() {
|
|
100
|
+
screenWidth = process.stdout.columns || 80;
|
|
101
|
+
screenHeight = (process.stdout.rows || 24) - 2;
|
|
102
|
+
|
|
103
|
+
const forestWidth = diffPanel ? Math.floor(screenWidth * 0.4) : screenWidth;
|
|
104
|
+
const buf = createFrameBuffer(forestWidth, screenHeight);
|
|
105
|
+
|
|
106
|
+
const projected = [];
|
|
107
|
+
for (const p of allPoints) {
|
|
108
|
+
const [rx, ry, rz] = rotatePoint(p.x, p.y, p.z, camera.azimuth, camera.elevation);
|
|
109
|
+
const proj = projectPoint(rx, ry, rz, forestWidth, screenHeight, camera.distance);
|
|
110
|
+
if (proj.visible) {
|
|
111
|
+
projected.push({
|
|
112
|
+
...proj,
|
|
113
|
+
color: p.color,
|
|
114
|
+
fileIndex: p.fileIndex,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
rasterize(buf, projected);
|
|
120
|
+
|
|
121
|
+
const changedIndices = diffMode
|
|
122
|
+
? new Set(files.map((f, i) => f.changed ? i : -1).filter(i => i >= 0))
|
|
123
|
+
: null;
|
|
124
|
+
|
|
125
|
+
moveHome();
|
|
126
|
+
process.stdout.write(renderTopBar(hoveredFile, screenWidth, hoveredModified));
|
|
127
|
+
process.stdout.write("\n");
|
|
128
|
+
|
|
129
|
+
const forestLines = renderBufferToString(buf, undefined, changedIndices).split("\n");
|
|
47
130
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
131
|
+
if (diffPanel) {
|
|
132
|
+
const panelWidth = screenWidth - forestWidth - 1;
|
|
133
|
+
const panelLines = renderDiffPanel(diffPanel, panelWidth, screenHeight);
|
|
134
|
+
const border = chalk.hex("#555555")("│");
|
|
135
|
+
|
|
136
|
+
for (let y = 0; y < screenHeight; y++) {
|
|
137
|
+
const fLine = forestLines[y] || "";
|
|
138
|
+
const pLine = panelLines[y] || "";
|
|
139
|
+
process.stdout.write(fLine + border + pLine);
|
|
140
|
+
if (y < screenHeight - 1) process.stdout.write("\n");
|
|
141
|
+
}
|
|
142
|
+
} else {
|
|
143
|
+
process.stdout.write(forestLines.join("\n"));
|
|
56
144
|
}
|
|
57
|
-
}
|
|
58
145
|
|
|
59
|
-
|
|
60
|
-
let lastTotalPrompts = forest.totalPrompts;
|
|
61
|
-
let animating = false;
|
|
146
|
+
process.stdout.write("\n");
|
|
62
147
|
|
|
63
|
-
|
|
64
|
-
|
|
148
|
+
const changedCount = changedFiles.size;
|
|
149
|
+
const changeText = changedCount > 0 ? `${changedCount} files changed | ` : "";
|
|
150
|
+
process.stdout.write(renderStatusBar(files.length, screenWidth, changeText));
|
|
65
151
|
|
|
66
|
-
|
|
67
|
-
return process.stdout.columns || 80;
|
|
152
|
+
return buf;
|
|
68
153
|
}
|
|
69
154
|
|
|
70
|
-
function
|
|
71
|
-
const
|
|
72
|
-
|
|
155
|
+
function regeneratePoints() {
|
|
156
|
+
const { points: newTreePoints, filePaths: newPaths } = generateForestCloud(files);
|
|
157
|
+
const newGround = generateGroundPlane(groundRadius);
|
|
158
|
+
allPoints.length = 0;
|
|
159
|
+
allPoints.push(...newTreePoints, ...newGround);
|
|
160
|
+
filePaths.length = 0;
|
|
161
|
+
filePaths.push(...newPaths);
|
|
73
162
|
}
|
|
74
163
|
|
|
75
|
-
function
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
164
|
+
async function pollChanges() {
|
|
165
|
+
if (polling) return;
|
|
166
|
+
polling = true;
|
|
167
|
+
try {
|
|
168
|
+
const newChanged = await getChangedFiles(dir);
|
|
169
|
+
const changed = newChanged.size !== changedFiles.size ||
|
|
170
|
+
[...newChanged].some(f => !changedFiles.has(f));
|
|
171
|
+
changedFiles = newChanged;
|
|
172
|
+
|
|
173
|
+
for (const file of files) {
|
|
174
|
+
file.changed = changedFiles.has(file.relativePath);
|
|
175
|
+
}
|
|
85
176
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
177
|
+
if (changed) {
|
|
178
|
+
regeneratePoints();
|
|
179
|
+
needsRedraw = true;
|
|
180
|
+
redraw();
|
|
181
|
+
}
|
|
182
|
+
} catch {
|
|
183
|
+
// ignore poll errors
|
|
91
184
|
}
|
|
185
|
+
polling = false;
|
|
186
|
+
}
|
|
92
187
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
);
|
|
188
|
+
function checkAllResolved() {
|
|
189
|
+
if (!diffPanel) return;
|
|
190
|
+
const allDone = diffPanel.hunkStatus.every(s => s !== "pending");
|
|
191
|
+
if (!allDone) return;
|
|
97
192
|
|
|
98
|
-
for (let
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
193
|
+
for (let i = 0; i < diffPanel.hunks.length; i++) {
|
|
194
|
+
if (diffPanel.hunkStatus[i] === "accepted") {
|
|
195
|
+
stageHunk(dir, diffPanel.filePath, diffPanel.hunks[i]);
|
|
196
|
+
} else if (diffPanel.hunkStatus[i] === "rejected") {
|
|
197
|
+
revertHunk(dir, diffPanel.filePath, diffPanel.hunks[i]);
|
|
198
|
+
}
|
|
102
199
|
}
|
|
103
200
|
|
|
104
|
-
|
|
105
|
-
|
|
201
|
+
diffPanel = null;
|
|
202
|
+
clearScreen();
|
|
203
|
+
pollChanges();
|
|
106
204
|
}
|
|
107
205
|
|
|
108
|
-
syncWidth();
|
|
109
206
|
hideCursor();
|
|
110
207
|
clearScreen();
|
|
111
|
-
|
|
208
|
+
enableMouse();
|
|
209
|
+
enableMouseMotion();
|
|
210
|
+
|
|
211
|
+
// Render first frame before starting change detection
|
|
212
|
+
let currentBuf = renderFrame();
|
|
213
|
+
needsRedraw = false;
|
|
214
|
+
|
|
215
|
+
// Start async change detection after first render
|
|
216
|
+
pollChanges();
|
|
217
|
+
|
|
218
|
+
// Watch filesystem for instant change detection
|
|
219
|
+
watcher = watchForChanges(dir, () => pollChanges());
|
|
220
|
+
|
|
221
|
+
// Fallback poll every 3 seconds in case fs.watch misses events
|
|
222
|
+
const pollTimer = setInterval(() => pollChanges(), 3000);
|
|
223
|
+
|
|
224
|
+
function redraw() {
|
|
225
|
+
if (!needsRedraw) return;
|
|
226
|
+
needsRedraw = false;
|
|
227
|
+
try {
|
|
228
|
+
currentBuf = renderFrame();
|
|
229
|
+
} catch {
|
|
230
|
+
// swallow render errors to prevent terminal corruption
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function parseMouseEvent(data) {
|
|
235
|
+
const str = data.toString();
|
|
236
|
+
const match = str.match(/\x1b\[<(\d+);(\d+);(\d+)([Mm])/);
|
|
237
|
+
if (!match) return null;
|
|
238
|
+
|
|
239
|
+
const button = parseInt(match[1]);
|
|
240
|
+
const x = parseInt(match[2]) - 1;
|
|
241
|
+
const y = parseInt(match[3]) - 1;
|
|
242
|
+
const released = match[4] === "m";
|
|
243
|
+
|
|
244
|
+
return { button, x, y, released };
|
|
245
|
+
}
|
|
112
246
|
|
|
113
247
|
const cleanup = () => {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
248
|
+
clearInterval(pollTimer);
|
|
249
|
+
if (watcher) watcher.close();
|
|
250
|
+
disableMouseMotion();
|
|
251
|
+
disableMouse();
|
|
118
252
|
showCursor();
|
|
119
253
|
clearScreen();
|
|
120
|
-
console.log(
|
|
121
|
-
`Forest summary: ${forest.trees.length} trees across ${forest.totalPrompts} prompts`,
|
|
122
|
-
);
|
|
254
|
+
console.log(`Scanned ${files.length} files in ${dir}`);
|
|
123
255
|
process.exit(0);
|
|
124
256
|
};
|
|
125
257
|
|
|
126
258
|
process.on("SIGINT", cleanup);
|
|
127
259
|
process.on("SIGTERM", cleanup);
|
|
128
260
|
process.stdout.on("resize", () => {
|
|
129
|
-
|
|
261
|
+
needsRedraw = true;
|
|
130
262
|
clearScreen();
|
|
131
|
-
|
|
263
|
+
redraw();
|
|
132
264
|
});
|
|
133
265
|
|
|
134
|
-
// Enable raw mode for keypress handling
|
|
135
266
|
if (process.stdin.isTTY) {
|
|
136
267
|
process.stdin.setRawMode(true);
|
|
137
268
|
process.stdin.resume();
|
|
138
269
|
process.stdin.on("data", (data) => {
|
|
139
270
|
const key = data.toString();
|
|
140
|
-
|
|
271
|
+
|
|
141
272
|
if (key === "\x03" || key === "q") {
|
|
142
273
|
cleanup();
|
|
143
274
|
return;
|
|
144
275
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
276
|
+
|
|
277
|
+
// Panel keybindings (when panel is open)
|
|
278
|
+
if (diffPanel) {
|
|
279
|
+
if (key === "\x1b" || key === "\x1b\x1b") {
|
|
280
|
+
diffPanel = null;
|
|
281
|
+
needsRedraw = true;
|
|
282
|
+
clearScreen();
|
|
283
|
+
redraw();
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (key === "j" || key === "\x1b[B") {
|
|
287
|
+
diffPanel.currentHunk = Math.min(diffPanel.currentHunk + 1, diffPanel.hunks.length - 1);
|
|
288
|
+
needsRedraw = true;
|
|
289
|
+
redraw();
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (key === "k" || key === "\x1b[A") {
|
|
293
|
+
diffPanel.currentHunk = Math.max(diffPanel.currentHunk - 1, 0);
|
|
294
|
+
needsRedraw = true;
|
|
295
|
+
redraw();
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
if (key === "a") {
|
|
299
|
+
diffPanel.hunkStatus[diffPanel.currentHunk] = "accepted";
|
|
300
|
+
const next = diffPanel.hunkStatus.findIndex((s, i) => i > diffPanel.currentHunk && s === "pending");
|
|
301
|
+
if (next >= 0) diffPanel.currentHunk = next;
|
|
302
|
+
checkAllResolved();
|
|
303
|
+
needsRedraw = true;
|
|
304
|
+
redraw();
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (key === "r") {
|
|
308
|
+
diffPanel.hunkStatus[diffPanel.currentHunk] = "rejected";
|
|
309
|
+
const next = diffPanel.hunkStatus.findIndex((s, i) => i > diffPanel.currentHunk && s === "pending");
|
|
310
|
+
if (next >= 0) diffPanel.currentHunk = next;
|
|
311
|
+
checkAllResolved();
|
|
312
|
+
needsRedraw = true;
|
|
313
|
+
redraw();
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (key === "A") {
|
|
317
|
+
for (let i = 0; i < diffPanel.hunkStatus.length; i++) {
|
|
318
|
+
if (diffPanel.hunkStatus[i] === "pending") diffPanel.hunkStatus[i] = "accepted";
|
|
319
|
+
}
|
|
320
|
+
checkAllResolved();
|
|
321
|
+
needsRedraw = true;
|
|
322
|
+
redraw();
|
|
323
|
+
return;
|
|
324
|
+
}
|
|
325
|
+
if (key === "R") {
|
|
326
|
+
for (let i = 0; i < diffPanel.hunkStatus.length; i++) {
|
|
327
|
+
if (diffPanel.hunkStatus[i] === "pending") diffPanel.hunkStatus[i] = "rejected";
|
|
328
|
+
}
|
|
329
|
+
checkAllResolved();
|
|
330
|
+
needsRedraw = true;
|
|
331
|
+
redraw();
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
150
334
|
return;
|
|
151
335
|
}
|
|
152
|
-
|
|
153
|
-
if (key === "
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
336
|
+
|
|
337
|
+
if (key === "d") {
|
|
338
|
+
diffMode = !diffMode;
|
|
339
|
+
needsRedraw = true;
|
|
340
|
+
redraw();
|
|
157
341
|
return;
|
|
158
342
|
}
|
|
159
|
-
});
|
|
160
|
-
}
|
|
161
343
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
344
|
+
if (key === "r") {
|
|
345
|
+
process.stdout.write("\x1b[H");
|
|
346
|
+
process.stdout.write("Rescanning...");
|
|
347
|
+
const newFiles = scanCodebase(dir);
|
|
348
|
+
files.length = 0;
|
|
349
|
+
files.push(...newFiles);
|
|
350
|
+
regeneratePoints();
|
|
351
|
+
needsRedraw = true;
|
|
352
|
+
clearScreen();
|
|
353
|
+
redraw();
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
170
356
|
|
|
171
|
-
|
|
172
|
-
|
|
357
|
+
if (key === "\x1b[D") {
|
|
358
|
+
camera.azimuth = clampAzimuth(camera.azimuth - 5);
|
|
359
|
+
needsRedraw = true;
|
|
360
|
+
redraw();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
if (key === "\x1b[C") {
|
|
364
|
+
camera.azimuth = clampAzimuth(camera.azimuth + 5);
|
|
365
|
+
needsRedraw = true;
|
|
366
|
+
redraw();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
if (key === "\x1b[A") {
|
|
370
|
+
camera.elevation = clampElevation(camera.elevation + 5);
|
|
371
|
+
needsRedraw = true;
|
|
372
|
+
redraw();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
if (key === "\x1b[B") {
|
|
376
|
+
camera.elevation = clampElevation(camera.elevation - 5);
|
|
377
|
+
needsRedraw = true;
|
|
378
|
+
redraw();
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
173
381
|
|
|
174
|
-
|
|
175
|
-
|
|
382
|
+
if (key === "+" || key === "=") {
|
|
383
|
+
camera.distance = Math.max(10, camera.distance - 5);
|
|
384
|
+
needsRedraw = true;
|
|
385
|
+
redraw();
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
if (key === "-" || key === "_") {
|
|
389
|
+
camera.distance = Math.min(120, camera.distance + 5);
|
|
390
|
+
needsRedraw = true;
|
|
391
|
+
redraw();
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
176
394
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
lastTotalPrompts = forest.totalPrompts;
|
|
395
|
+
const mouse = parseMouseEvent(data);
|
|
396
|
+
if (!mouse) return;
|
|
180
397
|
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const termWidth = getViewportWidth();
|
|
187
|
-
viewportX = clampViewport(newTree.x - Math.floor(termWidth / 2));
|
|
398
|
+
if (mouse.button === 0 && !mouse.released) {
|
|
399
|
+
mouseDown = true;
|
|
400
|
+
lastMouseX = mouse.x;
|
|
401
|
+
lastMouseY = mouse.y;
|
|
402
|
+
return;
|
|
188
403
|
}
|
|
189
|
-
animating = true;
|
|
190
|
-
await animateNewTree(forest, nextMaxId);
|
|
191
|
-
animating = false;
|
|
192
|
-
} else {
|
|
193
|
-
renderForest(forest);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
404
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
405
|
+
if (mouse.released && mouse.button === 0) {
|
|
406
|
+
const sy = mouse.y - 1;
|
|
407
|
+
const sx = mouse.x;
|
|
408
|
+
if (!diffPanel && currentBuf && sy >= 0 && sy < currentBuf.height && sx >= 0 && sx < currentBuf.width) {
|
|
409
|
+
const fi = currentBuf.fileIndices[sy][sx];
|
|
410
|
+
if (fi >= 0 && files[fi] && files[fi].changed) {
|
|
411
|
+
const filePath = filePaths[fi];
|
|
412
|
+
getFileDiff(dir, filePath).then((diff) => {
|
|
413
|
+
if (diff) {
|
|
414
|
+
const hunks = parseDiff(diff);
|
|
415
|
+
if (hunks.length > 0) {
|
|
416
|
+
diffPanel = createDiffPanel(filePath, hunks);
|
|
417
|
+
needsRedraw = true;
|
|
418
|
+
clearScreen();
|
|
419
|
+
redraw();
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
mouseDown = false;
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
mouseDown = false;
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
210
430
|
|
|
211
|
-
|
|
431
|
+
if (mouse.button === 32 && mouseDown) {
|
|
432
|
+
const dx = mouse.x - lastMouseX;
|
|
433
|
+
const dy = mouse.y - lastMouseY;
|
|
434
|
+
lastMouseX = mouse.x;
|
|
435
|
+
lastMouseY = mouse.y;
|
|
212
436
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
437
|
+
camera.azimuth = clampAzimuth(camera.azimuth + dx * 0.8);
|
|
438
|
+
camera.elevation = clampElevation(camera.elevation - dy * 0.8);
|
|
439
|
+
needsRedraw = true;
|
|
440
|
+
redraw();
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
218
443
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
444
|
+
if (mouse.button === 35 || (mouse.button >= 32 && mouse.released === false && !mouseDown)) {
|
|
445
|
+
const sy = mouse.y - 1;
|
|
446
|
+
const sx = mouse.x;
|
|
447
|
+
if (currentBuf && sy >= 0 && sy < currentBuf.height && sx >= 0 && sx < currentBuf.width) {
|
|
448
|
+
const fi = currentBuf.fileIndices[sy][sx];
|
|
449
|
+
const newHover = fi >= 0 ? filePaths[fi] : "";
|
|
450
|
+
const isModified = fi >= 0 && files[fi] && files[fi].changed;
|
|
451
|
+
if (newHover !== hoveredFile) {
|
|
452
|
+
hoveredFile = newHover;
|
|
453
|
+
hoveredModified = isModified;
|
|
454
|
+
writeAnsi(`\x1b[1;1H`);
|
|
455
|
+
process.stdout.write(renderTopBar(hoveredFile, screenWidth, hoveredModified));
|
|
456
|
+
}
|
|
229
457
|
}
|
|
230
|
-
watcher = startWatcher();
|
|
231
458
|
}
|
|
232
|
-
}
|
|
233
|
-
}
|
|
459
|
+
});
|
|
460
|
+
}
|
|
234
461
|
}
|