Skip to content

Commit 2930290

Browse files
committed
resizing works, delete git branch when deleting session
1 parent 0de5e77 commit 2930290

File tree

3 files changed

+59
-115
lines changed

3 files changed

+59
-115
lines changed

main.ts

Lines changed: 29 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,17 @@
1-
import { app, BrowserWindow, ipcMain, dialog } from "electron";
1+
import {exec} from "child_process";
2+
import {app, BrowserWindow, dialog, ipcMain} from "electron";
3+
import Store from "electron-store";
4+
import * as fs from "fs";
25
import * as pty from "node-pty";
36
import * as os from "os";
47
import * as path from "path";
5-
import * as fs from "fs";
6-
import { simpleGit } from "simple-git";
7-
import Store from "electron-store";
8-
import { exec } from "child_process";
9-
import { promisify } from "util";
10-
import { v4 as uuidv4 } from "uuid";
8+
import {simpleGit} from "simple-git";
9+
import {promisify} from "util";
10+
import {v4 as uuidv4} from "uuid";
11+
import {PersistedSession, SessionConfig} from "./types";
1112

1213
const execAsync = promisify(exec);
1314

14-
interface SessionConfig {
15-
projectDir: string;
16-
parentBranch: string;
17-
branchName?: string;
18-
codingAgent: string;
19-
skipPermissions: boolean;
20-
setupCommands?: string[];
21-
}
22-
23-
interface PersistedSession {
24-
id: string;
25-
number: number;
26-
name: string;
27-
config: SessionConfig;
28-
worktreePath: string;
29-
createdAt: number;
30-
sessionUuid: string;
31-
mcpConfigPath?: string;
32-
}
33-
3415
let mainWindow: BrowserWindow;
3516
const activePtyProcesses = new Map<string, pty.IPty>();
3617
const mcpPollerPtyProcesses = new Map<string, pty.IPty>();
@@ -344,10 +325,10 @@ async function ensureFleetcodeExcluded(projectDir: string) {
344325
}
345326
}
346327

347-
async function createWorktree(projectDir: string, parentBranch: string, sessionNumber: number, sessionUuid: string, customBranchName?: string): Promise<string> {
328+
async function createWorktree(projectDir: string, parentBranch: string, sessionNumber: number, sessionUuid: string, customBranchName?: string): Promise<{ worktreePath: string; branchName: string }> {
348329
const git = simpleGit(projectDir);
349330
const fleetcodeDir = path.join(projectDir, ".fleetcode");
350-
const worktreeName = `session${sessionNumber}`;
331+
const worktreeName = customBranchName || `session${sessionNumber}`;
351332
const worktreePath = path.join(fleetcodeDir, worktreeName);
352333

353334
// Use custom branch name if provided, otherwise generate default
@@ -357,7 +338,7 @@ async function createWorktree(projectDir: string, parentBranch: string, sessionN
357338
} else {
358339
// Include short UUID to ensure branch uniqueness across deletes/recreates
359340
const shortUuid = sessionUuid.split('-')[0];
360-
branchName = `fleetcode/session${sessionNumber}-${shortUuid}`;
341+
branchName = `fleetcode/${worktreeName}-${shortUuid}`;
361342
}
362343

363344
// Create .fleetcode directory if it doesn't exist
@@ -385,7 +366,7 @@ async function createWorktree(projectDir: string, parentBranch: string, sessionN
385366
// This creates a new branch named "fleetcode/session<N>" starting from the parent branch
386367
await git.raw(["worktree", "add", "-b", branchName, worktreePath, parentBranch]);
387368

388-
return worktreePath;
369+
return { worktreePath, branchName };
389370
}
390371

391372
async function removeWorktree(projectDir: string, worktreePath: string) {
@@ -397,6 +378,15 @@ async function removeWorktree(projectDir: string, worktreePath: string) {
397378
}
398379
}
399380

381+
async function removeGitBranch(projectDir: string, branchName: string) {
382+
const git = simpleGit(projectDir);
383+
try {
384+
await git.raw(["branch", "-D", branchName]);
385+
} catch (error) {
386+
console.error("Error removing git branch:", error);
387+
}
388+
}
389+
400390
// Open directory picker
401391
ipcMain.handle("select-directory", async () => {
402392
const result = await dialog.showOpenDialog(mainWindow, {
@@ -453,7 +443,7 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => {
453443
ensureFleetcodeExcluded(config.projectDir);
454444

455445
// Create git worktree with custom or default branch name
456-
const worktreePath = await createWorktree(config.projectDir, config.parentBranch, sessionNumber, sessionUuid, config.branchName);
446+
const { worktreePath, branchName } = await createWorktree(config.projectDir, config.parentBranch, sessionNumber, sessionUuid, config.branchName);
457447

458448
// Extract and write MCP config
459449
const mcpServers = extractProjectMcpConfig(config.projectDir);
@@ -469,6 +459,7 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => {
469459
createdAt: Date.now(),
470460
sessionUuid,
471461
mcpConfigPath: mcpConfigPath || undefined,
462+
gitBranch: branchName,
472463
};
473464

474465
// Save to store
@@ -572,6 +563,11 @@ ipcMain.on("delete-session", async (_event, sessionId: string) => {
572563
// Remove git worktree
573564
await removeWorktree(session.config.projectDir, session.worktreePath);
574565

566+
// Remove git branch if it exists
567+
if (session.gitBranch) {
568+
await removeGitBranch(session.config.projectDir, session.gitBranch);
569+
}
570+
575571
// Remove from store
576572
sessions.splice(sessionIndex, 1);
577573
savePersistedSessions(sessions);
@@ -778,6 +774,7 @@ const createWindow = () => {
778774
app.whenReady().then(() => {
779775
createWindow();
780776

777+
// Handles launch from dock on macos
781778
app.on("activate", () => {
782779
if (BrowserWindow.getAllWindows().length === 0) {
783780
createWindow();

renderer.ts

Lines changed: 10 additions & 83 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,7 @@
1-
import { ipcRenderer } from "electron";
2-
import { Terminal } from "xterm";
3-
import { FitAddon } from "@xterm/addon-fit";
4-
5-
interface SessionConfig {
6-
projectDir: string;
7-
parentBranch: string;
8-
branchName?: string;
9-
codingAgent: string;
10-
skipPermissions: boolean;
11-
setupCommands?: string[];
12-
}
13-
14-
interface PersistedSession {
15-
id: string;
16-
number: number;
17-
name: string;
18-
config: SessionConfig;
19-
worktreePath: string;
20-
createdAt: number;
21-
sessionUuid: string;
22-
}
1+
import {FitAddon} from "@xterm/addon-fit";
2+
import {ipcRenderer} from "electron";
3+
import {Terminal} from "xterm";
4+
import {PersistedSession, SessionConfig} from "./types";
235

246
interface Session {
257
id: string;
@@ -252,7 +234,6 @@ function createTerminalUI(sessionId: string) {
252234
}
253235

254236
term.open(sessionElement);
255-
fitAddon.fit();
256237

257238
term.onData((data) => {
258239
ipcRenderer.send("session-input", sessionId, data);
@@ -265,53 +246,13 @@ function createTerminalUI(sessionId: string) {
265246
}
266247
});
267248

268-
// Handle resize - only refit if dimensions actually changed
269-
let lastCols = term.cols;
270-
let lastRows = term.rows;
271-
let resizeTimeout: NodeJS.Timeout | null = null;
272-
273249
const resizeHandler = () => {
274250
if (activeSessionId === sessionId) {
275-
// Clear any pending resize
276-
if (resizeTimeout) {
277-
clearTimeout(resizeTimeout);
251+
const proposedDimensions = fitAddon.proposeDimensions();
252+
if (proposedDimensions) {
253+
fitAddon.fit();
254+
ipcRenderer.send("session-resize", sessionId, proposedDimensions.cols, proposedDimensions.rows);
278255
}
279-
280-
// Debounce the fit call
281-
resizeTimeout = setTimeout(() => {
282-
// Calculate what the new dimensions would be
283-
const container = sessionElement;
284-
if (!container) return;
285-
286-
const rect = container.getBoundingClientRect();
287-
const core = (term as any)._core;
288-
if (!core) return;
289-
290-
// Estimate new dimensions based on container size
291-
const newCols = Math.floor(rect.width / core._renderService.dimensions.actualCellWidth);
292-
const newRows = Math.floor(rect.height / core._renderService.dimensions.actualCellHeight);
293-
294-
// Only fit if dimensions actually changed significantly (more than 1 char difference)
295-
if (Math.abs(newCols - lastCols) > 1 || Math.abs(newRows - lastRows) > 1) {
296-
// Save scroll position before fitting
297-
const wasAtBottom = term.buffer.active.viewportY === term.buffer.active.baseY;
298-
const savedScrollPosition = term.buffer.active.viewportY;
299-
300-
fitAddon.fit();
301-
302-
lastCols = term.cols;
303-
lastRows = term.rows;
304-
305-
// Restore scroll position unless we were at the bottom (in which case stay at bottom)
306-
if (!wasAtBottom && savedScrollPosition !== term.buffer.active.viewportY) {
307-
term.scrollToLine(savedScrollPosition);
308-
}
309-
310-
ipcRenderer.send("session-resize", sessionId, term.cols, term.rows);
311-
}
312-
313-
resizeTimeout = null;
314-
}, 100); // 100ms debounce
315256
}
316257
};
317258
window.addEventListener("resize", resizeHandler);
@@ -599,22 +540,8 @@ function switchToSession(sessionId: string) {
599540

600541
// Focus and resize
601542
session.terminal.focus();
602-
setTimeout(() => {
603-
if (session.fitAddon && session.terminal) {
604-
// Save scroll position before fitting
605-
const wasAtBottom = session.terminal.buffer.active.viewportY === session.terminal.buffer.active.baseY;
606-
const savedScrollPosition = session.terminal.buffer.active.viewportY;
607-
608-
session.fitAddon.fit();
609-
610-
// Restore scroll position unless we were at the bottom
611-
if (!wasAtBottom && savedScrollPosition !== session.terminal.buffer.active.viewportY) {
612-
session.terminal.scrollToLine(savedScrollPosition);
613-
}
614-
615-
ipcRenderer.send("session-resize", sessionId, session.terminal.cols, session.terminal.rows);
616-
}
617-
}, 0);
543+
// Dispatch resize event to trigger terminal resize
544+
window.dispatchEvent(new Event("resize"));
618545
}
619546
}
620547

types.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
export interface SessionConfig {
2+
projectDir: string;
3+
parentBranch: string;
4+
branchName?: string;
5+
codingAgent: string;
6+
skipPermissions: boolean;
7+
setupCommands?: string[];
8+
}
9+
10+
export interface PersistedSession {
11+
id: string;
12+
number: number;
13+
name: string;
14+
config: SessionConfig;
15+
worktreePath: string;
16+
createdAt: number;
17+
sessionUuid: string;
18+
mcpConfigPath?: string;
19+
gitBranch?: string;
20+
}

0 commit comments

Comments
 (0)