From bee7a9bdf4f719872021d008b43ce2d45ff0f5fc Mon Sep 17 00:00:00 2001 From: Anthony Kim Date: Wed, 11 Feb 2026 22:46:04 -0800 Subject: [PATCH] Ghostty for linux as well --- .../node/externalTerminalService.ts | 97 ++++++++++++------- .../test/node/externalTerminalService.test.ts | 21 ++++ 2 files changed, 82 insertions(+), 36 deletions(-) diff --git a/src/vs/platform/externalTerminal/node/externalTerminalService.ts b/src/vs/platform/externalTerminal/node/externalTerminalService.ts index 141bff9898e..8f31f4175e6 100644 --- a/src/vs/platform/externalTerminal/node/externalTerminalService.ts +++ b/src/vs/platform/externalTerminal/node/externalTerminalService.ts @@ -239,11 +239,24 @@ export class LinuxExternalTerminalService extends ExternalTerminalService implem const execPromise = settings.linuxExec ? Promise.resolve(settings.linuxExec) : LinuxExternalTerminalService.getDefaultTerminalLinuxReady(); return new Promise((resolve, reject) => { - - const termArgs: string[] = []; - //termArgs.push('--title'); - //termArgs.push(`"${TERMINAL_TITLE}"`); execPromise.then(exec => { + const basename = path.basename(exec).toLowerCase(); + if (basename === 'ghostty') { + const ghosttyArgs: string[] = []; + if (dir) { + ghosttyArgs.push(`--working-directory=${dir}`); + } + ghosttyArgs.push('--wait-after-command=true'); + if (args.length) { + ghosttyArgs.push('-e', ...args); + } + LinuxExternalTerminalService.spawnTerminalWithEnv(exec, ghosttyArgs, dir, envVars, resolve, reject); + return; + } + + const termArgs: string[] = []; + //termArgs.push('--title'); + //termArgs.push(`"${TERMINAL_TITLE}"`); if (exec.indexOf('gnome-terminal') >= 0) { termArgs.push('-x'); } else { @@ -256,41 +269,51 @@ export class LinuxExternalTerminalService extends ExternalTerminalService implem termArgs.push(`''${bashCommand}''`); // wrapping argument in two sets of ' because node is so "friendly" that it removes one set... - // merge environment variables into a copy of the process.env - const env = Object.assign({}, getSanitizedEnvironment(process), envVars); - - // delete environment variables that have a null value - Object.keys(env).filter(v => env[v] === null).forEach(key => delete env[key]); - - const options = { - cwd: dir, - env: env - }; - - let stderr = ''; - const cmd = cp.spawn(exec, termArgs, options); - cmd.on('error', err => { - reject(improveError(err)); - }); - cmd.stderr.on('data', (data) => { - stderr += data.toString(); - }); - cmd.on('exit', (code: number) => { - if (code === 0) { // OK - resolve(undefined); - } else { - if (stderr) { - const lines = stderr.split('\n', 1); - reject(new Error(lines[0])); - } else { - reject(new Error(nls.localize('linux.term.failed', "'{0}' failed with exit code {1}", exec, code))); - } - } - }); + LinuxExternalTerminalService.spawnTerminalWithEnv(exec, termArgs, dir, envVars, resolve, reject); }); }); } + private static spawnTerminalWithEnv( + exec: string, + args: string[], + dir: string, + envVars: ITerminalEnvironment, + resolve: (value: number | PromiseLike | undefined) => void, + reject: (reason?: unknown) => void + ): void { + const env = Object.assign({}, getSanitizedEnvironment(process), envVars); + + // delete environment variables that have a null value + Object.keys(env).filter(v => env[v] === null).forEach(key => delete env[key]); + + const options = { + cwd: dir, + env: env + }; + + let stderr = ''; + const cmd = cp.spawn(exec, args, options); + cmd.on('error', err => { + reject(improveError(err)); + }); + cmd.stderr.on('data', (data) => { + stderr += data.toString(); + }); + cmd.on('exit', (code: number) => { + if (code === 0) { + resolve(undefined); + } else { + if (stderr) { + const lines = stderr.split('\n', 1); + reject(new Error(lines[0])); + } else { + reject(new Error(nls.localize('linux.term.failed', "'{0}' failed with exit code {1}", exec, code))); + } + } + }); + } + private static _DEFAULT_TERMINAL_LINUX_READY: Promise; public static async getDefaultTerminalLinuxReady(): Promise { @@ -325,7 +348,9 @@ export class LinuxExternalTerminalService extends ExternalTerminalService implem return new Promise((c, e) => { execPromise.then(exec => { const env = getSanitizedEnvironment(process); - const child = spawner.spawn(exec, [], { cwd, env }); + const basename = path.basename(exec).toLowerCase(); + const args = basename === 'ghostty' && cwd ? [`--working-directory=${cwd}`] : []; + const child = spawner.spawn(exec, args, { cwd, env }); child.on('error', e); child.on('exit', () => c()); }); diff --git a/src/vs/platform/externalTerminal/test/node/externalTerminalService.test.ts b/src/vs/platform/externalTerminal/test/node/externalTerminalService.test.ts index e3647666227..52a7093d67a 100644 --- a/src/vs/platform/externalTerminal/test/node/externalTerminalService.test.ts +++ b/src/vs/platform/externalTerminal/test/node/externalTerminalService.test.ts @@ -208,6 +208,27 @@ suite('ExternalTerminalService', () => { ); }); + test(`LinuxTerminalService - Ghostty should be spawned with working directory`, done => { + const testCwd = 'path/to/workspace'; + const mockSpawner: any = { + spawn: (command: any, args: any, opts: any) => { + strictEqual(command, 'ghostty'); + deepStrictEqual(args, [`--working-directory=${testCwd}`]); + strictEqual(opts.cwd, testCwd); + done(); + return { + on: (evt: any) => evt + }; + } + }; + const testService = new LinuxExternalTerminalService(); + testService.spawnTerminal( + mockSpawner, + { linuxExec: 'ghostty' }, + testCwd + ); + }); + test(`LinuxTerminalService - uses default terminal when configuration.terminal.external.linuxExec is undefined`, done => { LinuxExternalTerminalService.getDefaultTerminalLinuxReady().then(defaultTerminalLinux => { const testCwd = 'path/to/workspace';