| author | |
| committer | |
| log | 20ef25037f455a570426ac2a93fcf499d1f91167 |
| tree | bfb6606e8eb27f6b6bb77aa7a3f0fc21c4421a0a |
| parent | 5735693d4acb949c2b0daf1219b4f62845115435 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>4 files changed, 31 insertions(+), 9 deletions(-)
apps/desktop/src/electron/ElectronShell.test.ts+9-3| ... | ... | @@ -93,10 +93,16 @@ describe("ElectronShell", () => { |
| 93 | 93 | openExternalMock.mockResolvedValue(undefined); |
| 94 | 94 | |
| 95 | 95 | const electronShell = yield* ElectronShell.ElectronShell; |
| 96 | const result = yield* electronShell.openExternal("zed://ssh/example.com/home/user/project"); | |
| 96 | const results = yield* Effect.all([ | |
| 97 | electronShell.openExternal("zed://ssh/example.com/home/user/project"), | |
| 98 | electronShell.openExternal("zed://ssh/example.com/"), | |
| 99 | ]); | |
| 97 | 100 | |
| 98 | assert.equal(result, true); | |
| 99 | assert.deepEqual(openExternalMock.mock.calls, [["zed://ssh/example.com/home/user/project"]]); | |
| 101 | assert.deepEqual(results, [true, true]); | |
| 102 | assert.deepEqual(openExternalMock.mock.calls, [ | |
| 103 | ["zed://ssh/example.com/home/user/project"], | |
| 104 | ["zed://ssh/example.com/"], | |
| 105 | ]); | |
| 100 | 106 | }).pipe(Effect.provide(ElectronShell.layer)), |
| 101 | 107 | ); |
| 102 | 108 |
apps/desktop/src/electron/ElectronShell.ts+1-1| ... | ... | @@ -36,7 +36,7 @@ const REMOTE_EDITOR_PROTOCOLS = new Set( |
| 36 | 36 | ); |
| 37 | 37 | |
| 38 | 38 | // Zed's host sits in the first path segment, so it needs its own userinfo ban. |
| 39 | const ZED_SSH_PATHNAME = /^\/[^/@:]+\/.+$/; | |
| 39 | const ZED_SSH_PATHNAME = /^\/[^/@:]+\/.*$/; | |
| 40 | 40 | |
| 41 | 41 | const isRemoteEditorUrl = (url: URL) => |
| 42 | 42 | REMOTE_EDITOR_PROTOCOLS.has(url.protocol) && |
apps/web/src/remoteOpen.test.ts+9| ... | ... | @@ -151,6 +151,15 @@ describe("buildRemoteOpenUrl", () => { |
| 151 | 151 | ).toBe("zed://ssh/sol.tail1234.ts.net/home/theo/code/my%20repo"); |
| 152 | 152 | }); |
| 153 | 153 | |
| 154 | it("drops the Windows drive letter for Zed", () => { | |
| 155 | expect( | |
| 156 | buildRemoteOpenUrl({ editor: "zed", host: "sol", absolutePath: "C:\\Users\\theo" }), | |
| 157 | ).toBe("zed://ssh/sol/Users/theo"); | |
| 158 | expect(buildRemoteOpenUrl({ editor: "zed", host: "sol", absolutePath: "/C:/project" })).toBe( | |
| 159 | "zed://ssh/sol/C%3A/project", | |
| 160 | ); | |
| 161 | }); | |
| 162 | ||
| 154 | 163 | it("returns undefined for editors without remote support", () => { |
| 155 | 164 | expect(buildRemoteOpenUrl({ editor: "idea", host: "sol", absolutePath: "/tmp/x" })).toBe( |
| 156 | 165 | undefined, |
packages/contracts/src/editor.ts+12-5| ... | ... | @@ -91,7 +91,7 @@ export type LaunchEditorInput = typeof LaunchEditorInput.Type; |
| 91 | 91 | |
| 92 | 92 | const remoteSchemeOf = (editor: EditorDefinition): string | undefined => editor.remoteScheme; |
| 93 | 93 | |
| 94 | /** Editors that can open a remote workspace via `vscode-remote` deep links. */ | |
| 94 | /** Editors that can open a remote workspace via an SSH deep link. */ | |
| 95 | 95 | export const REMOTE_CAPABLE_EDITOR_IDS: ReadonlyArray<EditorId> = EDITORS.flatMap((editor) => |
| 96 | 96 | remoteSchemeOf(editor) !== undefined ? [editor.id] : [], |
| 97 | 97 | ); |
| ... | ... | @@ -119,11 +119,18 @@ export const buildRemoteOpenUrl = (input: { |
| 119 | 119 | // Windows server paths (`C:\...`) appear as `/C:/...` in vscode-remote URIs. |
| 120 | 120 | const posixPath = input.absolutePath.replaceAll("\\", "/"); |
| 121 | 121 | const rootedPath = posixPath.startsWith("/") ? posixPath : `/${posixPath}`; |
| 122 | const encodedPath = rootedPath.split("/").map(encodeURIComponent).join("/"); | |
| 123 | 122 | const encodedHost = encodeURIComponent(input.host); |
| 124 | return input.editor === "zed" | |
| 125 | ? `${scheme}://ssh/${encodedHost}${encodedPath}` | |
| 126 | : `${scheme}://vscode-remote/ssh-remote+${encodedHost}${encodedPath}`; | |
| 123 | if (input.editor === "zed") { | |
| 124 | // Zed's remote server resolves a rooted path on the system drive, so a | |
| 125 | // Windows `C:\Users\x` must become `/Users/x` (verified in #8938). Other | |
| 126 | // drives are untested and kept as is rather than silently remapped, and a | |
| 127 | // POSIX path that happens to start with `/C:` is left alone. | |
| 128 | const zedPath = /^[Cc]:[\\/]/.test(input.absolutePath) ? rootedPath.slice(3) : rootedPath; | |
| 129 | const encodedZedPath = zedPath.split("/").map(encodeURIComponent).join("/"); | |
| 130 | return `${scheme}://ssh/${encodedHost}${encodedZedPath}`; | |
| 131 | } | |
| 132 | const encodedPath = rootedPath.split("/").map(encodeURIComponent).join("/"); | |
| 133 | return `${scheme}://vscode-remote/ssh-remote+${encodedHost}${encodedPath}`; | |
| 127 | 134 | }; |
| 128 | 135 | |
| 129 | 136 | /** |