resizing works, delete git branch when deleting session
This commit is contained in:
parent
0de5e77e99
commit
2930290662
61
main.ts
61
main.ts
|
|
@ -1,36 +1,17 @@
|
|||
import { app, BrowserWindow, ipcMain, dialog } from "electron";
|
||||
import {exec} from "child_process";
|
||||
import {app, BrowserWindow, dialog, ipcMain} from "electron";
|
||||
import Store from "electron-store";
|
||||
import * as fs from "fs";
|
||||
import * as pty from "node-pty";
|
||||
import * as os from "os";
|
||||
import * as path from "path";
|
||||
import * as fs from "fs";
|
||||
import { simpleGit } from "simple-git";
|
||||
import Store from "electron-store";
|
||||
import { exec } from "child_process";
|
||||
import { promisify } from "util";
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import {simpleGit} from "simple-git";
|
||||
import {promisify} from "util";
|
||||
import {v4 as uuidv4} from "uuid";
|
||||
import {PersistedSession, SessionConfig} from "./types";
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
interface SessionConfig {
|
||||
projectDir: string;
|
||||
parentBranch: string;
|
||||
branchName?: string;
|
||||
codingAgent: string;
|
||||
skipPermissions: boolean;
|
||||
setupCommands?: string[];
|
||||
}
|
||||
|
||||
interface PersistedSession {
|
||||
id: string;
|
||||
number: number;
|
||||
name: string;
|
||||
config: SessionConfig;
|
||||
worktreePath: string;
|
||||
createdAt: number;
|
||||
sessionUuid: string;
|
||||
mcpConfigPath?: string;
|
||||
}
|
||||
|
||||
let mainWindow: BrowserWindow;
|
||||
const activePtyProcesses = new Map<string, pty.IPty>();
|
||||
const mcpPollerPtyProcesses = new Map<string, pty.IPty>();
|
||||
|
|
@ -344,10 +325,10 @@ async function ensureFleetcodeExcluded(projectDir: string) {
|
|||
}
|
||||
}
|
||||
|
||||
async function createWorktree(projectDir: string, parentBranch: string, sessionNumber: number, sessionUuid: string, customBranchName?: string): Promise<string> {
|
||||
async function createWorktree(projectDir: string, parentBranch: string, sessionNumber: number, sessionUuid: string, customBranchName?: string): Promise<{ worktreePath: string; branchName: string }> {
|
||||
const git = simpleGit(projectDir);
|
||||
const fleetcodeDir = path.join(projectDir, ".fleetcode");
|
||||
const worktreeName = `session${sessionNumber}`;
|
||||
const worktreeName = customBranchName || `session${sessionNumber}`;
|
||||
const worktreePath = path.join(fleetcodeDir, worktreeName);
|
||||
|
||||
// Use custom branch name if provided, otherwise generate default
|
||||
|
|
@ -357,7 +338,7 @@ async function createWorktree(projectDir: string, parentBranch: string, sessionN
|
|||
} else {
|
||||
// Include short UUID to ensure branch uniqueness across deletes/recreates
|
||||
const shortUuid = sessionUuid.split('-')[0];
|
||||
branchName = `fleetcode/session${sessionNumber}-${shortUuid}`;
|
||||
branchName = `fleetcode/${worktreeName}-${shortUuid}`;
|
||||
}
|
||||
|
||||
// Create .fleetcode directory if it doesn't exist
|
||||
|
|
@ -385,7 +366,7 @@ async function createWorktree(projectDir: string, parentBranch: string, sessionN
|
|||
// This creates a new branch named "fleetcode/session<N>" starting from the parent branch
|
||||
await git.raw(["worktree", "add", "-b", branchName, worktreePath, parentBranch]);
|
||||
|
||||
return worktreePath;
|
||||
return { worktreePath, branchName };
|
||||
}
|
||||
|
||||
async function removeWorktree(projectDir: string, worktreePath: string) {
|
||||
|
|
@ -397,6 +378,15 @@ async function removeWorktree(projectDir: string, worktreePath: string) {
|
|||
}
|
||||
}
|
||||
|
||||
async function removeGitBranch(projectDir: string, branchName: string) {
|
||||
const git = simpleGit(projectDir);
|
||||
try {
|
||||
await git.raw(["branch", "-D", branchName]);
|
||||
} catch (error) {
|
||||
console.error("Error removing git branch:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// Open directory picker
|
||||
ipcMain.handle("select-directory", async () => {
|
||||
const result = await dialog.showOpenDialog(mainWindow, {
|
||||
|
|
@ -453,7 +443,7 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => {
|
|||
ensureFleetcodeExcluded(config.projectDir);
|
||||
|
||||
// Create git worktree with custom or default branch name
|
||||
const worktreePath = await createWorktree(config.projectDir, config.parentBranch, sessionNumber, sessionUuid, config.branchName);
|
||||
const { worktreePath, branchName } = await createWorktree(config.projectDir, config.parentBranch, sessionNumber, sessionUuid, config.branchName);
|
||||
|
||||
// Extract and write MCP config
|
||||
const mcpServers = extractProjectMcpConfig(config.projectDir);
|
||||
|
|
@ -469,6 +459,7 @@ ipcMain.on("create-session", async (event, config: SessionConfig) => {
|
|||
createdAt: Date.now(),
|
||||
sessionUuid,
|
||||
mcpConfigPath: mcpConfigPath || undefined,
|
||||
gitBranch: branchName,
|
||||
};
|
||||
|
||||
// Save to store
|
||||
|
|
@ -572,6 +563,11 @@ ipcMain.on("delete-session", async (_event, sessionId: string) => {
|
|||
// Remove git worktree
|
||||
await removeWorktree(session.config.projectDir, session.worktreePath);
|
||||
|
||||
// Remove git branch if it exists
|
||||
if (session.gitBranch) {
|
||||
await removeGitBranch(session.config.projectDir, session.gitBranch);
|
||||
}
|
||||
|
||||
// Remove from store
|
||||
sessions.splice(sessionIndex, 1);
|
||||
savePersistedSessions(sessions);
|
||||
|
|
@ -778,6 +774,7 @@ const createWindow = () => {
|
|||
app.whenReady().then(() => {
|
||||
createWindow();
|
||||
|
||||
// Handles launch from dock on macos
|
||||
app.on("activate", () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow();
|
||||
|
|
|
|||
91
renderer.ts
91
renderer.ts
|
|
@ -1,25 +1,7 @@
|
|||
import { ipcRenderer } from "electron";
|
||||
import { Terminal } from "xterm";
|
||||
import { FitAddon } from "@xterm/addon-fit";
|
||||
|
||||
interface SessionConfig {
|
||||
projectDir: string;
|
||||
parentBranch: string;
|
||||
branchName?: string;
|
||||
codingAgent: string;
|
||||
skipPermissions: boolean;
|
||||
setupCommands?: string[];
|
||||
}
|
||||
|
||||
interface PersistedSession {
|
||||
id: string;
|
||||
number: number;
|
||||
name: string;
|
||||
config: SessionConfig;
|
||||
worktreePath: string;
|
||||
createdAt: number;
|
||||
sessionUuid: string;
|
||||
}
|
||||
import {FitAddon} from "@xterm/addon-fit";
|
||||
import {ipcRenderer} from "electron";
|
||||
import {Terminal} from "xterm";
|
||||
import {PersistedSession, SessionConfig} from "./types";
|
||||
|
||||
interface Session {
|
||||
id: string;
|
||||
|
|
@ -252,7 +234,6 @@ function createTerminalUI(sessionId: string) {
|
|||
}
|
||||
|
||||
term.open(sessionElement);
|
||||
fitAddon.fit();
|
||||
|
||||
term.onData((data) => {
|
||||
ipcRenderer.send("session-input", sessionId, data);
|
||||
|
|
@ -265,53 +246,13 @@ function createTerminalUI(sessionId: string) {
|
|||
}
|
||||
});
|
||||
|
||||
// Handle resize - only refit if dimensions actually changed
|
||||
let lastCols = term.cols;
|
||||
let lastRows = term.rows;
|
||||
let resizeTimeout: NodeJS.Timeout | null = null;
|
||||
|
||||
const resizeHandler = () => {
|
||||
if (activeSessionId === sessionId) {
|
||||
// Clear any pending resize
|
||||
if (resizeTimeout) {
|
||||
clearTimeout(resizeTimeout);
|
||||
}
|
||||
|
||||
// Debounce the fit call
|
||||
resizeTimeout = setTimeout(() => {
|
||||
// Calculate what the new dimensions would be
|
||||
const container = sessionElement;
|
||||
if (!container) return;
|
||||
|
||||
const rect = container.getBoundingClientRect();
|
||||
const core = (term as any)._core;
|
||||
if (!core) return;
|
||||
|
||||
// Estimate new dimensions based on container size
|
||||
const newCols = Math.floor(rect.width / core._renderService.dimensions.actualCellWidth);
|
||||
const newRows = Math.floor(rect.height / core._renderService.dimensions.actualCellHeight);
|
||||
|
||||
// Only fit if dimensions actually changed significantly (more than 1 char difference)
|
||||
if (Math.abs(newCols - lastCols) > 1 || Math.abs(newRows - lastRows) > 1) {
|
||||
// Save scroll position before fitting
|
||||
const wasAtBottom = term.buffer.active.viewportY === term.buffer.active.baseY;
|
||||
const savedScrollPosition = term.buffer.active.viewportY;
|
||||
|
||||
const proposedDimensions = fitAddon.proposeDimensions();
|
||||
if (proposedDimensions) {
|
||||
fitAddon.fit();
|
||||
|
||||
lastCols = term.cols;
|
||||
lastRows = term.rows;
|
||||
|
||||
// Restore scroll position unless we were at the bottom (in which case stay at bottom)
|
||||
if (!wasAtBottom && savedScrollPosition !== term.buffer.active.viewportY) {
|
||||
term.scrollToLine(savedScrollPosition);
|
||||
ipcRenderer.send("session-resize", sessionId, proposedDimensions.cols, proposedDimensions.rows);
|
||||
}
|
||||
|
||||
ipcRenderer.send("session-resize", sessionId, term.cols, term.rows);
|
||||
}
|
||||
|
||||
resizeTimeout = null;
|
||||
}, 100); // 100ms debounce
|
||||
}
|
||||
};
|
||||
window.addEventListener("resize", resizeHandler);
|
||||
|
|
@ -599,22 +540,8 @@ function switchToSession(sessionId: string) {
|
|||
|
||||
// Focus and resize
|
||||
session.terminal.focus();
|
||||
setTimeout(() => {
|
||||
if (session.fitAddon && session.terminal) {
|
||||
// Save scroll position before fitting
|
||||
const wasAtBottom = session.terminal.buffer.active.viewportY === session.terminal.buffer.active.baseY;
|
||||
const savedScrollPosition = session.terminal.buffer.active.viewportY;
|
||||
|
||||
session.fitAddon.fit();
|
||||
|
||||
// Restore scroll position unless we were at the bottom
|
||||
if (!wasAtBottom && savedScrollPosition !== session.terminal.buffer.active.viewportY) {
|
||||
session.terminal.scrollToLine(savedScrollPosition);
|
||||
}
|
||||
|
||||
ipcRenderer.send("session-resize", sessionId, session.terminal.cols, session.terminal.rows);
|
||||
}
|
||||
}, 0);
|
||||
// Dispatch resize event to trigger terminal resize
|
||||
window.dispatchEvent(new Event("resize"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
20
types.ts
Normal file
20
types.ts
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
export interface SessionConfig {
|
||||
projectDir: string;
|
||||
parentBranch: string;
|
||||
branchName?: string;
|
||||
codingAgent: string;
|
||||
skipPermissions: boolean;
|
||||
setupCommands?: string[];
|
||||
}
|
||||
|
||||
export interface PersistedSession {
|
||||
id: string;
|
||||
number: number;
|
||||
name: string;
|
||||
config: SessionConfig;
|
||||
worktreePath: string;
|
||||
createdAt: number;
|
||||
sessionUuid: string;
|
||||
mcpConfigPath?: string;
|
||||
gitBranch?: string;
|
||||
}
|
||||
Loading…
Reference in a new issue