fix(windows): support VS Code Insiders WSL launch

This commit is contained in:
Jinwoo Hong
2026-07-09 20:55:09 -04:00
parent dd6a9ed9de
commit 93a820d2c1
3 changed files with 40 additions and 28 deletions
+2 -2
View File
@@ -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+<distro>', '<linuxPath>']`. 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+<distro>', '<linuxPath>']`. 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.
+34 -24
View File
@@ -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(
+4 -2
View File
@@ -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.