diff options
| author | haoyuren <13851610112@163.com> | 2026-03-13 17:28:27 -0500 |
|---|---|---|
| committer | haoyuren <13851610112@163.com> | 2026-03-13 17:28:27 -0500 |
| commit | e377dabf99595a6783fd962a8765d2214a635ac2 (patch) | |
| tree | b6f8654e13579552f31326d7f2780005b13cfa7e /src/main | |
| parent | c069e833b98253f31ef153317a6212cefde07c9a (diff) | |
Separate Terminal and Claude into independent pty instances
- Support multiple named pty instances via ID-based IPC channels
- Terminal tab spawns a shell, Claude tab spawns `claude` CLI separately
- Fix pty race condition: old instance's onExit callback could delete
the replacement instance from the Map during React StrictMode re-mount
- Guard against StrictMode double-initialization in TerminalInstance
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Diffstat (limited to 'src/main')
| -rw-r--r-- | src/main/index.ts | 46 |
1 files changed, 29 insertions, 17 deletions
diff --git a/src/main/index.ts b/src/main/index.ts index 0d93b17..9be1a74 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -11,7 +11,7 @@ import { CompilationManager } from './compilationManager' import { FileSyncBridge } from './fileSyncBridge' let mainWindow: BrowserWindow | null = null -let ptyInstance: pty.IPty | null = null +const ptyInstances = new Map<string, pty.IPty>() let overleafSock: OverleafSocket | null = null let compilationManager: CompilationManager | null = null let fileSyncBridge: FileSyncBridge | null = null @@ -91,13 +91,16 @@ ipcMain.handle('synctex:editFromPdf', async (_e, pdfPath: string, page: number, // ── Terminal / PTY ─────────────────────────────────────────────── -ipcMain.handle('pty:spawn', async (_e, cwd: string) => { - if (ptyInstance) { - ptyInstance.kill() +ipcMain.handle('pty:spawn', async (_e, id: string, cwd: string, cmd?: string, args?: string[]) => { + const existing = ptyInstances.get(id) + if (existing) { + existing.kill() + ptyInstances.delete(id) } - const shellPath = process.env.SHELL || '/bin/zsh' - ptyInstance = pty.spawn(shellPath, ['-l'], { + const shellPath = cmd || process.env.SHELL || '/bin/zsh' + const shellArgs = args || ['-l'] + const instance = pty.spawn(shellPath, shellArgs, { name: 'xterm-256color', cols: 80, rows: 24, @@ -105,28 +108,37 @@ ipcMain.handle('pty:spawn', async (_e, cwd: string) => { env: process.env as Record<string, string> }) - ptyInstance.onData((data) => { - sendToRenderer('pty:data', data) + ptyInstances.set(id, instance) + + instance.onData((data) => { + sendToRenderer(`pty:data:${id}`, data) }) - ptyInstance.onExit(() => { - sendToRenderer('pty:exit') + instance.onExit(() => { + // Only delete if this is still the current instance (avoid race with re-spawn) + if (ptyInstances.get(id) === instance) { + sendToRenderer(`pty:exit:${id}`) + ptyInstances.delete(id) + } }) }) -ipcMain.handle('pty:write', async (_e, data: string) => { - ptyInstance?.write(data) +ipcMain.handle('pty:write', async (_e, id: string, data: string) => { + ptyInstances.get(id)?.write(data) }) -ipcMain.handle('pty:resize', async (_e, cols: number, rows: number) => { +ipcMain.handle('pty:resize', async (_e, id: string, cols: number, rows: number) => { try { - ptyInstance?.resize(cols, rows) + ptyInstances.get(id)?.resize(cols, rows) } catch { /* ignore resize errors */ } }) -ipcMain.handle('pty:kill', async () => { - ptyInstance?.kill() - ptyInstance = null +ipcMain.handle('pty:kill', async (_e, id: string) => { + const instance = ptyInstances.get(id) + if (instance) { + instance.kill() + ptyInstances.delete(id) + } }) // ── Overleaf Web Session (for comments) ───────────────────────── |
