From 11fe73789110d5153bdc78983b1b685855484ade Mon Sep 17 00:00:00 2001 From: Bil0000 <62337003+Bil0000@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:08:01 +0200 Subject: [PATCH 1/4] fix: detect installed editors outside PATH --- apps/desktop/src/ipc/methods/window.test.ts | 36 +++++ apps/desktop/src/ipc/methods/window.ts | 18 +-- .../src/process/externalLauncher.test.ts | 146 ++++++++++++++++++ apps/server/src/process/externalLauncher.ts | 40 ++--- packages/shared/package.json | 4 + packages/shared/src/editor.ts | 125 +++++++++++++++ 6 files changed, 332 insertions(+), 37 deletions(-) create mode 100644 packages/shared/src/editor.ts diff --git a/apps/desktop/src/ipc/methods/window.test.ts b/apps/desktop/src/ipc/methods/window.test.ts index eca3db4ddf85..764056742372 100644 --- a/apps/desktop/src/ipc/methods/window.test.ts +++ b/apps/desktop/src/ipc/methods/window.test.ts @@ -1,4 +1,8 @@ import { assert, describe, it } from "@effect/vitest"; +import * as NodeServices from "@effect/platform-node/NodeServices"; +import { HostProcessEnvironment, HostProcessPlatform } from "@t3tools/shared/hostProcess"; +import * as FileSystem from "effect/FileSystem"; +import * as Path from "effect/Path"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; @@ -26,6 +30,7 @@ import { getWindowFullscreenState, pasteAsText, pickProjectFavicon, + probeRemoteEditors, } from "./window.ts"; const readyWslConfig: DesktopBackendManager.DesktopBackendStartConfig = { @@ -262,3 +267,34 @@ describe("pickProjectFavicon", () => { }), ); }); + +it.effect.skipIf(HostProcessPlatform.defaultValue() === "win32")( + "finds remote editors installed without PATH launchers", + () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const home = yield* fs.makeTempDirectoryScoped({ prefix: "t3-remote-editors-" }); + for (const app of ["Cursor", "Visual Studio Code", "WebStorm"]) { + const executable = path.join( + home, + "Applications", + `${app}.app`, + app === "WebStorm" ? "Contents/MacOS/webstorm" : "Contents/Resources/app/bin/code", + ); + yield* fs.makeDirectory(path.dirname(executable), { recursive: true }); + yield* fs.writeFileString(executable, "#!/bin/sh\n"); + yield* fs.chmod(executable, 0o755); + } + const editors = yield* probeRemoteEditors.handler(undefined).pipe( + Effect.provideService(HostProcessEnvironment, { + HOME: home, + PATH: path.join(home, "empty"), + }), + Effect.provideService(HostProcessPlatform, "darwin"), + ); + assert.include(editors, "cursor"); + assert.include(editors, "vscode"); + assert.notInclude(editors, "webstorm"); + }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), +); diff --git a/apps/desktop/src/ipc/methods/window.ts b/apps/desktop/src/ipc/methods/window.ts index 81361db37303..a7d80c141cfd 100644 --- a/apps/desktop/src/ipc/methods/window.ts +++ b/apps/desktop/src/ipc/methods/window.ts @@ -14,7 +14,8 @@ import { type PickedThemeFile, } from "@t3tools/contracts"; import { WORKSPACE_IMAGE_PREVIEW_EXTENSIONS } from "@t3tools/shared/filePreview"; -import { isCommandAvailable } from "@t3tools/shared/shell"; +import { resolveEditorCommand } from "@t3tools/shared/editor"; +import { HostProcessEnvironment } from "@t3tools/shared/hostProcess"; import * as NodeOS from "node:os"; import * as FileSystem from "effect/FileSystem"; import * as Path from "effect/Path"; @@ -333,20 +334,13 @@ export const probeRemoteEditors = DesktopIpc.makeIpcMethod({ channel: IpcChannels.PROBE_REMOTE_EDITORS_CHANNEL, payload: Schema.Undefined, result: Schema.Array(EditorId), - // Probes THIS machine (where the renderer runs) for remote-capable editor - // CLIs, unlike the server's probe which walks the environment host's PATH. - // A Finder-launched app can miss PATH entries; an empty result makes the - // renderer fall back to VS Code only, so that fails soft. handler: Effect.fn("desktop.ipc.window.probeRemoteEditors")(function* () { const available: Array = []; + const env = yield* HostProcessEnvironment; for (const editorId of REMOTE_CAPABLE_EDITOR_IDS) { - const commands = EDITORS.find((editor) => editor.id === editorId)?.commands; - if (!commands) continue; - for (const command of commands) { - if (yield* isCommandAvailable(command, { env: process.env })) { - available.push(editorId); - break; - } + const editor = EDITORS.find((editor) => editor.id === editorId); + if (editor && Option.isSome(yield* resolveEditorCommand(editor, env))) { + available.push(editorId); } } return available; diff --git a/apps/server/src/process/externalLauncher.test.ts b/apps/server/src/process/externalLauncher.test.ts index b7092d70f0e2..618f26deaba5 100644 --- a/apps/server/src/process/externalLauncher.test.ts +++ b/apps/server/src/process/externalLauncher.test.ts @@ -922,6 +922,152 @@ it.effect("discovers editors through the service API", () => }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), ); +for (const { platform, installPath, editor, args } of [ + { + platform: "darwin", + installPath: "Applications/Cursor.app/Contents/Resources/app/bin/code", + editor: "cursor", + args: ["--classic", "--goto", "/workspace with spaces/file.ts:12:4"], + }, + { + platform: "darwin", + installPath: "Applications/Kiro.app/Contents/Resources/app/bin/code", + editor: "kiro", + args: ["--goto", "/workspace with spaces/file.ts:12:4"], + }, + { + platform: "darwin", + installPath: "Applications/WebStorm.app/Contents/MacOS/webstorm", + editor: "webstorm", + args: ["--line", "12", "--column", "4", "/workspace with spaces/file.ts"], + }, + { + platform: "darwin", + installPath: "Applications/Zed.app/Contents/MacOS/cli", + editor: "zed", + args: ["/workspace with spaces/file.ts:12:4"], + }, + { + platform: "win32", + installPath: "Programs/Microsoft VS Code/bin/code.cmd", + editor: "vscode", + args: ['^"--goto^"', '^"/workspace^ with^ spaces/file.ts:12:4^"'], + }, + { + platform: "win32", + installPath: "Programs/JetBrains/WebStorm 2026.2/bin/webstorm64.exe", + editor: "webstorm", + args: ["--line", "12", "--column", "4", "/workspace with spaces/file.ts"], + }, + { + platform: "win32", + installPath: "Programs/WebStorm/bin/webstorm64.exe", + editor: "webstorm", + args: ["--line", "12", "--column", "4", "/workspace with spaces/file.ts"], + }, + { + platform: "win32", + installPath: "Programs/Zed/bin/zed.exe", + editor: "zed", + args: ["/workspace with spaces/file.ts:12:4"], + }, + { + platform: "linux", + installPath: ".local/share/JetBrains/Toolbox/scripts/idea", + editor: "idea", + args: ["--line", "12", "--column", "4", "/workspace with spaces/file.ts"], + }, + { + platform: "linux", + installPath: ".local/bin/zed", + editor: "zed", + args: ["/workspace with spaces/file.ts:12:4"], + }, +] as const) { + it.effect.skipIf(windowsHost && platform !== "win32")( + `discovers and launches ${editor} outside PATH on ${platform}`, + () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const home = yield* fs.makeTempDirectoryScoped({ prefix: "t3-editor installs-" }); + const executable = path.join(home, installPath); + yield* fs.makeDirectory(path.dirname(executable), { recursive: true }); + yield* fs.writeFileString(executable, "#!/bin/sh\n"); + yield* fs.chmod(executable, 0o755); + let spawned: ChildProcess.StandardCommand | undefined; + yield* Effect.gen(function* () { + const launcher = yield* ExternalLauncher.ExternalLauncher; + assert.include(yield* launcher.resolveAvailableEditors(), editor); + yield* launcher.launchEditor({ editor, cwd: "/workspace with spaces/file.ts:12:4" }); + }).pipe( + Effect.provide( + testLayer({ + platform, + env: { HOME: home, LOCALAPPDATA: home, PATH: path.join(home, "empty") }, + onSpawn: (command) => { + spawned = command; + }, + }), + ), + ); + assert.ok(spawned); + assert.equal( + spawned.command, + executable.endsWith(".cmd") ? `^"${executable.replaceAll(" ", "^ ")}^"` : executable, + ); + assert.deepEqual(spawned.args, args); + assert.equal(spawned.options.shell, executable.endsWith(".cmd")); + }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), + ); +} + +it.effect.skipIf(windowsHost)("ignores unusable app bundles and keeps PATH launchers first", () => + Effect.gen(function* () { + const fs = yield* FileSystem.FileSystem; + const path = yield* Path.Path; + const home = yield* fs.makeTempDirectoryScoped({ prefix: "t3-editor-priority-" }); + const executable = path.join(home, "Applications/Cursor.app/Contents/Resources/app/bin/code"); + yield* fs.makeDirectory(executable, { recursive: true }); + const env = { HOME: home, PATH: path.join(home, "bin") }; + const discover = Effect.gen(function* () { + const launcher = yield* ExternalLauncher.ExternalLauncher; + return yield* launcher.resolveAvailableEditors(); + }); + assert.notInclude( + yield* discover.pipe(Effect.provide(testLayer({ platform: "darwin", env }))), + "cursor", + ); + yield* fs.remove(executable, { recursive: true }); + yield* fs.writeFileString(executable, "#!/bin/sh\n"); + yield* fs.chmod(executable, 0o644); + assert.notInclude( + yield* discover.pipe(Effect.provide(testLayer({ platform: "darwin", env }))), + "cursor", + ); + yield* fs.chmod(executable, 0o755); + yield* fs.makeDirectory(env.PATH); + yield* fs.writeFileString(path.join(env.PATH, "cursor"), "#!/bin/sh\n"); + yield* fs.chmod(path.join(env.PATH, "cursor"), 0o755); + let spawned: ChildProcess.StandardCommand | undefined; + yield* Effect.gen(function* () { + const launcher = yield* ExternalLauncher.ExternalLauncher; + yield* launcher.launchEditor({ editor: "cursor", cwd: "/workspace" }); + }).pipe( + Effect.provide( + testLayer({ + platform: "darwin", + env: { ...env, PATH: `${env.PATH}:/usr/bin` }, + onSpawn: (command) => { + spawned = command; + }, + }), + ), + ); + assert.equal(spawned?.command, "cursor"); + }).pipe(Effect.scoped, Effect.provide(NodeServices.layer)), +); + it.effect("memoizes editor discovery and refreshes after the cache window", () => { let statCalls = 0; const fileInfo = { type: "File" } as FileSystem.File.Info; diff --git a/apps/server/src/process/externalLauncher.ts b/apps/server/src/process/externalLauncher.ts index 56d729b52fbb..29c25e790c61 100644 --- a/apps/server/src/process/externalLauncher.ts +++ b/apps/server/src/process/externalLauncher.ts @@ -18,6 +18,7 @@ import { type FileManagerRevealKind, type LaunchEditorInput, } from "@t3tools/contracts"; +import { resolveEditorCommand } from "@t3tools/shared/editor"; import { HostProcessPlatform } from "@t3tools/shared/hostProcess"; import { isCommandAvailable, resolveSpawnCommand } from "@t3tools/shared/shell"; import * as Clock from "effect/Clock"; @@ -109,6 +110,12 @@ const CommandLookupEnvConfig = Config.all({ Path: Config.String("Path").pipe(Config.option), path: Config.String("path").pipe(Config.option), PATHEXT: Config.String("PATHEXT").pipe(Config.option), + HOME: Config.String("HOME").pipe(Config.option), + LOCALAPPDATA: Config.String("LOCALAPPDATA").pipe(Config.option), + ProgramFiles: Config.String("ProgramFiles").pipe(Config.option), + ProgramW6432: Config.String("ProgramW6432").pipe(Config.option), + XDG_DATA_HOME: Config.String("XDG_DATA_HOME").pipe(Config.option), + "ProgramFiles(x86)": Config.String("ProgramFiles(x86)").pipe(Config.option), }).pipe(Config.map(compactEnv)); const readBrowserLaunchEnv = BrowserLaunchEnvConfig.pipe(Effect.orElseSucceed(() => ({}))); @@ -154,26 +161,6 @@ function resolveCommandEditorArgs( } } -function resolveEditorArgs( - editor: (typeof EDITORS)[number], - target: string, -): ReadonlyArray { - const baseArgs = "baseArgs" in editor ? editor.baseArgs : []; - return [...baseArgs, ...resolveCommandEditorArgs(editor, target)]; -} - -const resolveAvailableCommand = Effect.fn("externalLauncher.resolveAvailableCommand")(function* ( - commands: ReadonlyArray, - env: NodeJS.ProcessEnv, -): Effect.fn.Return, never, FileSystem.FileSystem | Path.Path> { - for (const command of commands) { - if (yield* isCommandAvailable(command, { env })) { - return Option.some(command); - } - } - return Option.none(); -}); - function encodeUtf16LeBase64(input: string): string { const bytes = new Uint8Array(input.length * 2); for (let index = 0; index < input.length; index += 1) { @@ -435,7 +422,7 @@ const buildAvailableEditors = Effect.fn("externalLauncher.buildAvailableEditors" continue; } - const command = yield* resolveAvailableCommand(editor.commands, env); + const command = yield* resolveEditorCommand(editor, env); if (Option.isSome(command)) { available.push(editor.id); } @@ -538,15 +525,18 @@ const resolveEditorLaunch = Effect.fn("resolveEditorLaunch")(function* ( } if (editorDef.commands) { - const command = Option.getOrElse( - yield* resolveAvailableCommand(editorDef.commands, env), - () => editorDef.commands[0], + const { command, baseArgs } = Option.getOrElse( + yield* resolveEditorCommand(editorDef, env), + () => ({ + command: editorDef.commands[0], + baseArgs: "baseArgs" in editorDef ? editorDef.baseArgs : [], + }), ); return { editor: editorDef.id, target: input.cwd, command, - args: resolveEditorArgs(editorDef, input.cwd), + args: [...baseArgs, ...resolveCommandEditorArgs(editorDef, input.cwd)], }; } diff --git a/packages/shared/package.json b/packages/shared/package.json index 97452f4d4a9f..1cd162593160 100644 --- a/packages/shared/package.json +++ b/packages/shared/package.json @@ -63,6 +63,10 @@ "types": "./src/httpObservability.ts", "import": "./src/httpObservability.ts" }, + "./editor": { + "types": "./src/editor.ts", + "import": "./src/editor.ts" + }, "./shell": { "types": "./src/shell.ts", "import": "./src/shell.ts" diff --git a/packages/shared/src/editor.ts b/packages/shared/src/editor.ts new file mode 100644 index 000000000000..d0bf99ed8664 --- /dev/null +++ b/packages/shared/src/editor.ts @@ -0,0 +1,125 @@ +import type { EDITORS } from "@t3tools/contracts"; +import * as Effect from "effect/Effect"; +import * as FileSystem from "effect/FileSystem"; +import * as Option from "effect/Option"; +import * as Path from "effect/Path"; + +import { HostProcessPlatform } from "./hostProcess.ts"; +import { isCommandAvailable } from "./shell.ts"; + +type Editor = (typeof EDITORS)[number]; + +const installNames: Partial>> = { + vscode: ["Visual Studio Code"], + "vscode-insiders": ["Visual Studio Code - Insiders"], + idea: ["IntelliJ IDEA", "IntelliJ IDEA CE", "IntelliJ IDEA Ultimate"], + pycharm: ["PyCharm", "PyCharm CE"], + rider: ["Rider", "JetBrains Rider"], +}; + +export const resolveEditorCommand = Effect.fn("editor.resolveEditorCommand")(function* ( + editor: Editor, + env: NodeJS.ProcessEnv, +) { + if (editor.commands === null) return Option.none(); + const baseArgs = "baseArgs" in editor ? editor.baseArgs : []; + for (const command of editor.commands) { + if (yield* isCommandAvailable(command, { env })) return Option.some({ command, baseArgs }); + } + + const platform = yield* HostProcessPlatform; + const path = yield* Path.Path; + const fs = yield* FileSystem.FileSystem; + const home = env.HOME; + const names = installNames[editor.id] ?? [editor.label]; + const command = editor.commands[0]; + const jetbrains = editor.launchStyle === "line-column"; + const candidates: string[] = []; + + if (platform === "darwin") { + const roots = [...(home ? [path.join(home, "Applications")] : []), "/Applications"]; + for (const root of roots) { + for (const name of names) { + const contents = path.join(root, `${name}.app`, "Contents"); + candidates.push( + ...(jetbrains || editor.id === "zed" + ? [path.join(contents, "MacOS", editor.id === "zed" ? "cli" : command)] + : [ + path.join(contents, "Resources/app/bin", command), + path.join(contents, "Resources/app/bin/code"), + ]), + ); + } + } + if (home && jetbrains) { + candidates.push( + path.join(home, "Library/Application Support/JetBrains/Toolbox/scripts", command), + ); + } + } else if (platform === "win32") { + const roots = [ + ...(env.LOCALAPPDATA ? [path.join(env.LOCALAPPDATA, "Programs")] : []), + ...[env.ProgramFiles, env["ProgramFiles(x86)"], env.ProgramW6432].filter( + (root): root is string => !!root, + ), + ]; + if (jetbrains) { + if (env.LOCALAPPDATA) { + candidates.push(path.join(env.LOCALAPPDATA, "JetBrains/Toolbox/scripts", `${command}.cmd`)); + } + for (const directory of roots.flatMap((root) => [root, path.join(root, "JetBrains")])) { + const entries = yield* fs.readDirectory(directory).pipe(Effect.orElseSucceed(() => [])); + for (const entry of entries) { + if (names.some((name) => entry === name || entry.startsWith(`${name} `))) { + candidates.push(path.join(directory, entry, "bin", `${command}64.exe`)); + candidates.push(path.join(directory, entry, "bin", `${command}.exe`)); + } + } + } + } else { + const name = + editor.id === "vscode" + ? "Microsoft VS Code" + : editor.id === "vscode-insiders" + ? "Microsoft VS Code Insiders" + : editor.label; + for (const root of roots) { + candidates.push( + path.join(root, name, "bin", `${command}.cmd`), + path.join(root, name, "bin/code.cmd"), + ); + if (editor.id === "zed") { + candidates.push( + path.join(root, name, "bin", "zed.exe"), + path.join(root, name, "zed.exe"), + ); + } + } + } + } else if (platform === "linux") { + const dirs = [ + ...(home ? [path.join(home, ".local/bin")] : []), + "/usr/local/bin", + "/usr/bin", + "/snap/bin", + ]; + if (jetbrains) { + const dataHome = env.XDG_DATA_HOME || (home ? path.join(home, ".local/share") : undefined); + if (dataHome) dirs.push(path.join(dataHome, "JetBrains/Toolbox/scripts")); + } + for (const dir of dirs) { + for (const name of editor.commands) candidates.push(path.join(dir, name)); + } + } + + for (const candidate of candidates) { + if (yield* isCommandAvailable(candidate, { env })) { + return Option.some({ + command: candidate, + baseArgs: + editor.id === "kiro" && (platform === "darwin" || platform === "win32") ? [] : baseArgs, + }); + } + } + return Option.none(); +}); From 89703dece210556a7aa5360c49e105051dbc7124 Mon Sep 17 00:00:00 2001 From: Bil0000 <62337003+Bil0000@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:10:24 +0200 Subject: [PATCH 2/4] test: allow existing system editors in discovery fixtures --- apps/server/src/process/externalLauncher.test.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/apps/server/src/process/externalLauncher.test.ts b/apps/server/src/process/externalLauncher.test.ts index 618f26deaba5..00ba242e1f12 100644 --- a/apps/server/src/process/externalLauncher.test.ts +++ b/apps/server/src/process/externalLauncher.test.ts @@ -1028,22 +1028,23 @@ it.effect.skipIf(windowsHost)("ignores unusable app bundles and keeps PATH launc const path = yield* Path.Path; const home = yield* fs.makeTempDirectoryScoped({ prefix: "t3-editor-priority-" }); const executable = path.join(home, "Applications/Cursor.app/Contents/Resources/app/bin/code"); - yield* fs.makeDirectory(executable, { recursive: true }); const env = { HOME: home, PATH: path.join(home, "bin") }; const discover = Effect.gen(function* () { const launcher = yield* ExternalLauncher.ExternalLauncher; return yield* launcher.resolveAvailableEditors(); }); - assert.notInclude( + const before = yield* discover.pipe(Effect.provide(testLayer({ platform: "darwin", env }))); + yield* fs.makeDirectory(executable, { recursive: true }); + assert.deepEqual( yield* discover.pipe(Effect.provide(testLayer({ platform: "darwin", env }))), - "cursor", + before, ); yield* fs.remove(executable, { recursive: true }); yield* fs.writeFileString(executable, "#!/bin/sh\n"); yield* fs.chmod(executable, 0o644); - assert.notInclude( + assert.deepEqual( yield* discover.pipe(Effect.provide(testLayer({ platform: "darwin", env }))), - "cursor", + before, ); yield* fs.chmod(executable, 0o755); yield* fs.makeDirectory(env.PATH); From 76a54e68f25dd25436fbe82702d9b5452a64196a Mon Sep 17 00:00:00 2001 From: Bil0000 <62337003+Bil0000@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:14:43 +0200 Subject: [PATCH 3/4] fix: preserve host process service namespaces --- apps/desktop/src/ipc/methods/window.ts | 4 ++-- packages/shared/src/editor.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/apps/desktop/src/ipc/methods/window.ts b/apps/desktop/src/ipc/methods/window.ts index a7d80c141cfd..75f34ab07ab1 100644 --- a/apps/desktop/src/ipc/methods/window.ts +++ b/apps/desktop/src/ipc/methods/window.ts @@ -15,7 +15,7 @@ import { } from "@t3tools/contracts"; import { WORKSPACE_IMAGE_PREVIEW_EXTENSIONS } from "@t3tools/shared/filePreview"; import { resolveEditorCommand } from "@t3tools/shared/editor"; -import { HostProcessEnvironment } from "@t3tools/shared/hostProcess"; +import * as HostProcess from "@t3tools/shared/hostProcess"; import * as NodeOS from "node:os"; import * as FileSystem from "effect/FileSystem"; import * as Path from "effect/Path"; @@ -336,7 +336,7 @@ export const probeRemoteEditors = DesktopIpc.makeIpcMethod({ result: Schema.Array(EditorId), handler: Effect.fn("desktop.ipc.window.probeRemoteEditors")(function* () { const available: Array = []; - const env = yield* HostProcessEnvironment; + const env = yield* HostProcess.HostProcessEnvironment; for (const editorId of REMOTE_CAPABLE_EDITOR_IDS) { const editor = EDITORS.find((editor) => editor.id === editorId); if (editor && Option.isSome(yield* resolveEditorCommand(editor, env))) { diff --git a/packages/shared/src/editor.ts b/packages/shared/src/editor.ts index d0bf99ed8664..60f3216dc809 100644 --- a/packages/shared/src/editor.ts +++ b/packages/shared/src/editor.ts @@ -4,7 +4,7 @@ import * as FileSystem from "effect/FileSystem"; import * as Option from "effect/Option"; import * as Path from "effect/Path"; -import { HostProcessPlatform } from "./hostProcess.ts"; +import * as HostProcess from "./hostProcess.ts"; import { isCommandAvailable } from "./shell.ts"; type Editor = (typeof EDITORS)[number]; @@ -27,7 +27,7 @@ export const resolveEditorCommand = Effect.fn("editor.resolveEditorCommand")(fun if (yield* isCommandAvailable(command, { env })) return Option.some({ command, baseArgs }); } - const platform = yield* HostProcessPlatform; + const platform = yield* HostProcess.HostProcessPlatform; const path = yield* Path.Path; const fs = yield* FileSystem.FileSystem; const home = env.HOME; From 1e3291b2efd1baf5960b8fa1ad8bab89243fc480 Mon Sep 17 00:00:00 2001 From: Bil0000 <62337003+Bil0000@users.noreply.github.com> Date: Fri, 18 Sep 2026 12:28:40 +0200 Subject: [PATCH 4/4] fix: find Windows Electron editor launchers --- apps/server/src/process/externalLauncher.test.ts | 6 ++++++ packages/shared/src/editor.ts | 2 ++ 2 files changed, 8 insertions(+) diff --git a/apps/server/src/process/externalLauncher.test.ts b/apps/server/src/process/externalLauncher.test.ts index 00ba242e1f12..f714a70f783d 100644 --- a/apps/server/src/process/externalLauncher.test.ts +++ b/apps/server/src/process/externalLauncher.test.ts @@ -947,6 +947,12 @@ for (const { platform, installPath, editor, args } of [ editor: "zed", args: ["/workspace with spaces/file.ts:12:4"], }, + { + platform: "win32", + installPath: "Programs/Cursor/resources/app/bin/cursor.cmd", + editor: "cursor", + args: ['^"--classic^"', '^"--goto^"', '^"/workspace^ with^ spaces/file.ts:12:4^"'], + }, { platform: "win32", installPath: "Programs/Microsoft VS Code/bin/code.cmd", diff --git a/packages/shared/src/editor.ts b/packages/shared/src/editor.ts index 60f3216dc809..cc6def9d01e9 100644 --- a/packages/shared/src/editor.ts +++ b/packages/shared/src/editor.ts @@ -85,6 +85,8 @@ export const resolveEditorCommand = Effect.fn("editor.resolveEditorCommand")(fun : editor.label; for (const root of roots) { candidates.push( + path.join(root, name, "resources/app/bin", `${command}.cmd`), + path.join(root, name, "resources/app/bin/code.cmd"), path.join(root, name, "bin", `${command}.cmd`), path.join(root, name, "bin/code.cmd"), );