Skip to content

Commit eca6618

Browse files
Support Zed alias command detection
- Resolve editor launches against multiple command aliases - Include zeditor as a fallback for Zed and update availability checks - Refresh MSW worker version
1 parent 9e29c9d commit eca6618

4 files changed

Lines changed: 89 additions & 11 deletions

File tree

apps/server/src/open.test.ts

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
1616
const antigravityLaunch = yield* resolveEditorLaunch(
1717
{ cwd: "/tmp/workspace", editor: "antigravity" },
1818
"darwin",
19+
{ PATH: "" },
1920
);
2021
assert.deepEqual(antigravityLaunch, {
2122
command: "agy",
@@ -25,6 +26,7 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
2526
const cursorLaunch = yield* resolveEditorLaunch(
2627
{ cwd: "/tmp/workspace", editor: "cursor" },
2728
"darwin",
29+
{ PATH: "" },
2830
);
2931
assert.deepEqual(cursorLaunch, {
3032
command: "cursor",
@@ -34,6 +36,7 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
3436
const vscodeLaunch = yield* resolveEditorLaunch(
3537
{ cwd: "/tmp/workspace", editor: "vscode" },
3638
"darwin",
39+
{ PATH: "" },
3740
);
3841
assert.deepEqual(vscodeLaunch, {
3942
command: "code",
@@ -43,6 +46,7 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
4346
const zedLaunch = yield* resolveEditorLaunch(
4447
{ cwd: "/tmp/workspace", editor: "zed" },
4548
"darwin",
49+
{ PATH: "" },
4650
);
4751
assert.deepEqual(zedLaunch, {
4852
command: "zed",
@@ -56,6 +60,7 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
5660
const lineOnly = yield* resolveEditorLaunch(
5761
{ cwd: "/tmp/workspace/AGENTS.md:48", editor: "cursor" },
5862
"darwin",
63+
{ PATH: "" },
5964
);
6065
assert.deepEqual(lineOnly, {
6166
command: "cursor",
@@ -65,6 +70,7 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
6570
const lineAndColumn = yield* resolveEditorLaunch(
6671
{ cwd: "/tmp/workspace/src/open.ts:71:5", editor: "cursor" },
6772
"darwin",
73+
{ PATH: "" },
6874
);
6975
assert.deepEqual(lineAndColumn, {
7076
command: "cursor",
@@ -74,6 +80,7 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
7480
const vscodeLineAndColumn = yield* resolveEditorLaunch(
7581
{ cwd: "/tmp/workspace/src/open.ts:71:5", editor: "vscode" },
7682
"darwin",
83+
{ PATH: "" },
7784
);
7885
assert.deepEqual(vscodeLineAndColumn, {
7986
command: "code",
@@ -83,6 +90,7 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
8390
const zedLineAndColumn = yield* resolveEditorLaunch(
8491
{ cwd: "/tmp/workspace/src/open.ts:71:5", editor: "zed" },
8592
"darwin",
93+
{ PATH: "" },
8694
);
8795
assert.deepEqual(zedLineAndColumn, {
8896
command: "zed",
@@ -91,11 +99,43 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
9199
}),
92100
);
93101

102+
it.effect("falls back to zeditor when zed is not installed", () =>
103+
Effect.gen(function* () {
104+
const fs = yield* FileSystem.FileSystem;
105+
const path = yield* Path.Path;
106+
const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-open-test-" });
107+
yield* fs.writeFileString(path.join(dir, "zeditor"), "#!/bin/sh\nexit 0\n");
108+
yield* fs.chmod(path.join(dir, "zeditor"), 0o755);
109+
110+
const result = yield* resolveEditorLaunch({ cwd: "/tmp/workspace", editor: "zed" }, "linux", {
111+
PATH: dir,
112+
});
113+
114+
assert.deepEqual(result, {
115+
command: "zeditor",
116+
args: ["/tmp/workspace"],
117+
});
118+
}),
119+
);
120+
121+
it.effect("falls back to the primary command when no alias is installed", () =>
122+
Effect.gen(function* () {
123+
const result = yield* resolveEditorLaunch({ cwd: "/tmp/workspace", editor: "zed" }, "linux", {
124+
PATH: "",
125+
});
126+
assert.deepEqual(result, {
127+
command: "zed",
128+
args: ["/tmp/workspace"],
129+
});
130+
}),
131+
);
132+
94133
it.effect("maps file-manager editor to OS open commands", () =>
95134
Effect.gen(function* () {
96135
const launch1 = yield* resolveEditorLaunch(
97136
{ cwd: "/tmp/workspace", editor: "file-manager" },
98137
"darwin",
138+
{ PATH: "" },
99139
);
100140
assert.deepEqual(launch1, {
101141
command: "open",
@@ -105,6 +145,7 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
105145
const launch2 = yield* resolveEditorLaunch(
106146
{ cwd: "C:\\workspace", editor: "file-manager" },
107147
"win32",
148+
{ PATH: "" },
108149
);
109150
assert.deepEqual(launch2, {
110151
command: "explorer",
@@ -114,6 +155,7 @@ it.layer(NodeServices.layer)("resolveEditorLaunch", (it) => {
114155
const launch3 = yield* resolveEditorLaunch(
115156
{ cwd: "/tmp/workspace", editor: "file-manager" },
116157
"linux",
158+
{ PATH: "" },
117159
);
118160
assert.deepEqual(launch3, {
119161
command: "xdg-open",
@@ -229,4 +271,22 @@ it.layer(NodeServices.layer)("resolveAvailableEditors", (it) => {
229271
assert.deepEqual(editors, ["cursor", "file-manager"]);
230272
}),
231273
);
274+
275+
it.effect("includes zed when only the zeditor command is installed", () =>
276+
Effect.gen(function* () {
277+
const fs = yield* FileSystem.FileSystem;
278+
const path = yield* Path.Path;
279+
const dir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-editors-" });
280+
281+
yield* fs.writeFileString(path.join(dir, "zeditor"), "#!/bin/sh\nexit 0\n");
282+
yield* fs.writeFileString(path.join(dir, "xdg-open"), "#!/bin/sh\nexit 0\n");
283+
yield* fs.chmod(path.join(dir, "zeditor"), 0o755);
284+
yield* fs.chmod(path.join(dir, "xdg-open"), 0o755);
285+
286+
const editors = resolveAvailableEditors("linux", {
287+
PATH: dir,
288+
});
289+
assert.deepEqual(editors, ["zed", "file-manager"]);
290+
}),
291+
);
232292
});

apps/server/src/open.ts

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,18 @@ function shouldUseGotoFlag(editorId: EditorId, target: string): boolean {
4545
);
4646
}
4747

48+
function resolveAvailableCommand(
49+
commands: ReadonlyArray<string>,
50+
options: CommandAvailabilityOptions = {},
51+
): string | null {
52+
for (const command of commands) {
53+
if (isCommandAvailable(command, options)) {
54+
return command;
55+
}
56+
}
57+
return null;
58+
}
59+
4860
function fileManagerCommandForPlatform(platform: NodeJS.Platform): string {
4961
switch (platform) {
5062
case "darwin":
@@ -168,8 +180,11 @@ export function resolveAvailableEditors(
168180
const available: EditorId[] = [];
169181

170182
for (const editor of EDITORS) {
171-
const command = editor.command ?? fileManagerCommandForPlatform(platform);
172-
if (isCommandAvailable(command, { platform, env })) {
183+
const command =
184+
editor.commands === null
185+
? fileManagerCommandForPlatform(platform)
186+
: resolveAvailableCommand(editor.commands, { platform, env });
187+
if (command !== null) {
173188
available.push(editor.id);
174189
}
175190
}
@@ -206,16 +221,19 @@ export class Open extends ServiceMap.Service<Open, OpenShape>()("t3/open") {}
206221
export const resolveEditorLaunch = Effect.fnUntraced(function* (
207222
input: OpenInEditorInput,
208223
platform: NodeJS.Platform = process.platform,
224+
env: NodeJS.ProcessEnv = process.env,
209225
): Effect.fn.Return<EditorLaunch, OpenError> {
210226
const editorDef = EDITORS.find((editor) => editor.id === input.editor);
211227
if (!editorDef) {
212228
return yield* new OpenError({ message: `Unknown editor: ${input.editor}` });
213229
}
214230

215-
if (editorDef.command) {
231+
if (editorDef.commands) {
232+
const command =
233+
resolveAvailableCommand(editorDef.commands, { platform, env }) ?? editorDef.commands[0];
216234
return shouldUseGotoFlag(editorDef.id, input.cwd)
217-
? { command: editorDef.command, args: ["--goto", input.cwd] }
218-
: { command: editorDef.command, args: [input.cwd] };
235+
? { command, args: ["--goto", input.cwd] }
236+
: { command, args: [input.cwd] };
219237
}
220238

221239
if (editorDef.id !== "file-manager") {

apps/web/public/mockServiceWorker.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
* - Please do NOT modify this file.
88
*/
99

10-
const PACKAGE_VERSION = '2.12.10'
10+
const PACKAGE_VERSION = '2.12.11'
1111
const INTEGRITY_CHECKSUM = '4db4a41e972cec1b64cc569c66952d82'
1212
const IS_MOCKED_RESPONSE = Symbol('isMockedResponse')
1313
const activeClientIds = new Set()

packages/contracts/src/editor.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import { Schema } from "effect";
22
import { TrimmedNonEmptyString } from "./baseSchemas";
33

44
export const EDITORS = [
5-
{ id: "cursor", label: "Cursor", command: "cursor" },
6-
{ id: "vscode", label: "VS Code", command: "code" },
7-
{ id: "zed", label: "Zed", command: "zed" },
8-
{ id: "antigravity", label: "Antigravity", command: "agy" },
9-
{ id: "file-manager", label: "File Manager", command: null },
5+
{ id: "cursor", label: "Cursor", commands: ["cursor"] },
6+
{ id: "vscode", label: "VS Code", commands: ["code"] },
7+
{ id: "zed", label: "Zed", commands: ["zed", "zeditor"] },
8+
{ id: "antigravity", label: "Antigravity", commands: ["agy"] },
9+
{ id: "file-manager", label: "File Manager", commands: null },
1010
] as const;
1111

1212
export const EditorId = Schema.Literals(EDITORS.map((e) => e.id));

0 commit comments

Comments
 (0)