summaryrefslogtreecommitdiff
path: root/src/main
diff options
context:
space:
mode:
authorhaoyuren <13851610112@163.com>2026-03-13 17:28:27 -0500
committerhaoyuren <13851610112@163.com>2026-03-13 17:28:27 -0500
commite377dabf99595a6783fd962a8765d2214a635ac2 (patch)
treeb6f8654e13579552f31326d7f2780005b13cfa7e /src/main
parentc069e833b98253f31ef153317a6212cefde07c9a (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.ts46
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) ─────────────────────────