From 93a820d2c1919c135d192115c3b5ff25d982429c Mon Sep 17 00:00:00 2001 From: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> Date: Thu, 9 Jul 2026 20:55:09 -0400 Subject: [PATCH] fix(windows): support VS Code Insiders WSL launch --- docs/issue-7649-vscode-wsl-launch.md | 4 +- src/main/external-editor-launch.test.ts | 58 +++++++++++++++---------- src/main/external-editor-launch.ts | 6 ++- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/docs/issue-7649-vscode-wsl-launch.md b/docs/issue-7649-vscode-wsl-launch.md index 91643a8fdcf..4643672e41e 100644 --- a/docs/issue-7649-vscode-wsl-launch.md +++ b/docs/issue-7649-vscode-wsl-launch.md @@ -20,7 +20,7 @@ The deterministic reproduction for `\\wsl.localhost\Ubuntu\home\aliuq\project` p ## Design 1. In the external-editor launch-spec builder, parse the target path with the shared WSL UNC parser when the host platform is Windows. -2. For direct/executable VS Code launchers only, reuse the existing normalized launcher-basename check and translate a recognized WSL target into `['--remote', 'wsl+', '']`. The match is case-insensitive and strips every Windows launcher suffix already supported by Orca (`.cmd`, `.exe`, and `.bat`), so direct paths such as `Code.exe` and resolved shims such as `CODE.CMD` or `code.bat` all identify as basename `code`. +2. For direct/executable VS Code Stable or Insiders launchers only, reuse the existing normalized launcher-basename check and translate a recognized WSL target into `['--remote', 'wsl+', '']`. The exact allowlist recognizes Stable's `code`, Insiders' `code-insiders`, and the direct `Code - Insiders.exe` basename without matching unrelated `code-*` editors. Matching is case-insensitive and strips every Windows launcher suffix already supported by Orca (`.cmd`, `.exe`, and `.bat`). 3. Keep local paths, non-Windows hosts, non-VS-Code applications, and compound commands on their existing argument paths. The existing main-process spawn and Windows shim handling remain unchanged. 4. Replace the temporary reproduction harness with focused regression cases in `src/main/external-editor-launch.test.ts` covering modern and legacy WSL UNC forms plus unaffected local/custom-editor behavior. @@ -49,7 +49,7 @@ The deterministic reproduction for `\\wsl.localhost\Ubuntu\home\aliuq\project` p - Unit: demonstrate the pre-fix launch spec lacks `--remote` for a modern WSL UNC path. - Unit: assert the fixed modern UNC launch is `--remote`, `wsl+Ubuntu`, `/home/...`. - Unit: assert legacy `\\wsl$` and distro-root paths produce the same Remote - WSL form. -- Unit: assert direct and resolved Windows VS Code launchers are matched case-insensitively across `.exe`, `.cmd`, and `.bat` suffixes. +- Unit: assert direct and resolved Windows VS Code Stable and Insiders launchers are matched case-insensitively across `.exe`, `.cmd`, and `.bat` suffixes. - Unit: assert distro names and Linux folder paths containing spaces remain intact arguments through launch-spec construction and Windows shim forwarding. - Unit: assert a Windows local path remains unchanged and explicit `darwin` and `linux` hosts do not acquire WSL remote arguments. - Unit: assert Cursor and another custom editor are not given VS Code remote arguments. diff --git a/src/main/external-editor-launch.test.ts b/src/main/external-editor-launch.test.ts index abb1e0fb757..8ced95ffd2f 100644 --- a/src/main/external-editor-launch.test.ts +++ b/src/main/external-editor-launch.test.ts @@ -136,13 +136,18 @@ describe('resolveExternalEditorLaunchSpec', () => { }) }) - it('opens modern WSL UNC workspaces in the matching VS Code remote', () => { - expect( - resolveExternalEditorLaunchSpec('code', '\\\\wsl.localhost\\Ubuntu\\home\\aliuq\\project', { - platform: 'win32' - }).spawnArgs - ).toEqual(['--remote', 'wsl+Ubuntu', '/home/aliuq/project']) - }) + it.each(['code', 'code-insiders'])( + 'opens modern WSL UNC workspaces with %s in the matching VS Code remote', + (editorCommand) => { + expect( + resolveExternalEditorLaunchSpec( + editorCommand, + '\\\\wsl.localhost\\Ubuntu\\home\\aliuq\\project', + { platform: 'win32' } + ).spawnArgs + ).toEqual(['--remote', 'wsl+Ubuntu', '/home/aliuq/project']) + } + ) it.each([ [ @@ -161,8 +166,10 @@ describe('resolveExternalEditorLaunchSpec', () => { it.each([ 'C:\\Program Files\\Microsoft VS Code\\Code.exe', + 'C:\\Program Files\\Microsoft VS Code Insiders\\Code - Insiders.exe', 'C:\\Tools\\CODE.CMD', - 'C:\\Tools\\code.bat' + 'C:\\Tools\\code.bat', + 'C:\\Tools\\code-insiders.cmd' ])('recognizes the direct Windows VS Code launcher %s', (editorCommand) => { expect( resolveExternalEditorLaunchSpec( @@ -173,23 +180,26 @@ describe('resolveExternalEditorLaunchSpec', () => { ).toEqual(['--remote', 'wsl+Ubuntu', '/home/ada/project']) }) - it.each(['C:\\Tools\\CODE.CMD', 'C:\\Tools\\code.bat'])( - 'recognizes the resolved Windows VS Code launcher %s', - (resolvedCommand) => { - resolveCliCommandMock.mockReturnValueOnce(resolvedCommand) + it.each([ + ['code', 'C:\\Tools\\CODE.CMD'], + ['code', 'C:\\Tools\\code.bat'], + ['code-insiders', 'C:\\Tools\\code-insiders.cmd'] + ])('recognizes the resolved Windows VS Code launcher %s', (editorCommand, resolvedCommand) => { + resolveCliCommandMock.mockReturnValueOnce(resolvedCommand) - expect( - resolveExternalEditorLaunchSpec('code', '\\\\wsl.localhost\\Ubuntu\\home\\ada\\project', { - platform: 'win32' - }) - ).toEqual({ - kind: 'executable', - hideWindowsConsole: true, - spawnCmd: resolvedCommand, - spawnArgs: ['--remote', 'wsl+Ubuntu', '/home/ada/project'] - }) - } - ) + expect( + resolveExternalEditorLaunchSpec( + editorCommand, + '\\\\wsl.localhost\\Ubuntu\\home\\ada\\project', + { platform: 'win32' } + ) + ).toEqual({ + kind: 'executable', + hideWindowsConsole: true, + spawnCmd: resolvedCommand, + spawnArgs: ['--remote', 'wsl+Ubuntu', '/home/ada/project'] + }) + }) it('preserves spaces in WSL distro and folder arguments', () => { expect( diff --git a/src/main/external-editor-launch.ts b/src/main/external-editor-launch.ts index 6a7badc9f82..b86ca3e896f 100644 --- a/src/main/external-editor-launch.ts +++ b/src/main/external-editor-launch.ts @@ -6,6 +6,7 @@ import { getCmdExePath } from './win32-utils' export const EXTERNAL_EDITOR_CLI_COMMAND = 'code' const WINDOWS_CONSOLE_EDITORS = new Set(['nvim', 'vim']) +const VSCODE_REMOTE_EDITORS = new Set(['code', 'code-insiders', 'code - insiders']) export type ExternalEditorLaunchSpec = | { @@ -112,12 +113,13 @@ function buildExecutableArgs( pathValue: string, platform: NodeJS.Platform ): string[] { - if (getLauncherBaseName(editorCommand) === 'cursor') { + const launcherBaseName = getLauncherBaseName(editorCommand) + if (launcherBaseName === 'cursor') { // Why: Cursor can route bare folder launches through the last active // workbench. A new window keeps "Open in Cursor" scoped to this worktree. return ['--new-window', pathValue] } - if (platform === 'win32' && getLauncherBaseName(editorCommand) === 'code') { + if (platform === 'win32' && VSCODE_REMOTE_EDITORS.has(launcherBaseName)) { const wslPath = parseWslUncPath(pathValue) if (wslPath) { // Why: VS Code otherwise treats a WSL UNC path as a local Windows folder.