Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions apps/web/src/editorPreferences.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { EDITORS, EditorId, EnvironmentId } from "@t3tools/contracts";
import { EditorId, EnvironmentId } from "@t3tools/contracts";
import {
mapAtomCommandResult,
type AtomCommandFailure,
Expand Down Expand Up @@ -38,12 +38,17 @@ export class PreferredEditorUnavailableError extends Schema.TaggedError<Preferre
}
}

/**
* The remembered editor when it is available, else the first available one.
* Callers order `availableEditors` by preference (the server's PATH probe
* follows the EDITORS catalog; the browser remote list leads with VS Code).
*/
export function usePreferredEditor(availableEditors: ReadonlyArray<EditorId>) {
const [lastEditor, setLastEditor] = useLocalStorage(LAST_EDITOR_KEY, null, EditorId);

const effectiveEditor = useMemo(() => {
if (lastEditor && availableEditors.includes(lastEditor)) return lastEditor;
return EDITORS.find((editor) => availableEditors.includes(editor.id))?.id ?? null;
return availableEditors[0] ?? null;
}, [lastEditor, availableEditors]);

return [effectiveEditor, setLastEditor] as const;
Expand All @@ -55,9 +60,9 @@ export function resolveAndPersistPreferredEditor(
const availableEditorIds = new Set(availableEditors);
const stored = getLocalStorageItem(LAST_EDITOR_KEY, EditorId);
if (stored && availableEditorIds.has(stored)) return stored;
const editor = EDITORS.find((editor) => availableEditorIds.has(editor.id))?.id ?? null;
const editor = availableEditors[0] ?? null;
if (editor) setLocalStorageItem(LAST_EDITOR_KEY, editor, EditorId);
return editor ?? null;
return editor;
}

export function useOpenInPreferredEditor(
Expand Down
10 changes: 9 additions & 1 deletion apps/web/src/remoteOpen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
import { buildRemoteOpenUrl, EnvironmentId } from "@t3tools/contracts";
import { describe, expect, it } from "vite-plus/test";

import { resolveRemoteOpenState } from "./remoteOpen";
import { BROWSER_REMOTE_EDITORS, resolveRemoteOpenState } from "./remoteOpen";

const environmentId = EnvironmentId.make("environment-1");

Expand Down Expand Up @@ -118,6 +118,14 @@ describe("resolveRemoteOpenState", () => {
});
});

describe("BROWSER_REMOTE_EDITORS", () => {
it("leads with VS Code and offers the other remote-capable editors", () => {
expect(BROWSER_REMOTE_EDITORS[0]).toBe("vscode");
expect(BROWSER_REMOTE_EDITORS).toEqual(expect.arrayContaining(["vscodium", "cursor", "zed"]));
expect(BROWSER_REMOTE_EDITORS).not.toContain("idea");
});
});

describe("buildRemoteOpenUrl", () => {
it("builds a vscode-remote deep link", () => {
expect(
Expand Down
22 changes: 17 additions & 5 deletions apps/web/src/remoteOpen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -124,24 +124,36 @@ export function useRemoteOpenState(environmentId: EnvironmentId | null): RemoteO

/**
* Editors offered in remote-link mode. The desktop app probes the machine the
* renderer runs on; a browser cannot, so it offers VS Code only.
* renderer runs on. A browser cannot, so it offers every remote-capable
* editor and remembers the pick; VS Code leads as the default.
*/
export const BROWSER_REMOTE_EDITORS: ReadonlyArray<EditorId> = [
"vscode",
...REMOTE_CAPABLE_EDITOR_IDS.filter((id) => id !== "vscode"),
];

/**
* Desktop fallback when the probe fails, finds nothing, or is missing from an
* older desktop shell (the bridge contract documents VS Code only there).
*/
const REMOTE_FALLBACK_EDITORS: ReadonlyArray<EditorId> = ["vscode"];

let cachedProbedEditors: ReadonlyArray<EditorId> | null = null;

export function useRemoteCapableEditors(): ReadonlyArray<EditorId> {
const bridge = window.desktopBridge;
const unprobedEditors = bridge === undefined ? BROWSER_REMOTE_EDITORS : REMOTE_FALLBACK_EDITORS;
const [editors, setEditors] = useState<ReadonlyArray<EditorId>>(
() => cachedProbedEditors ?? REMOTE_FALLBACK_EDITORS,
() => cachedProbedEditors ?? unprobedEditors,
);

useEffect(() => {
if (cachedProbedEditors !== null) {
return;
}
const probe = window.desktopBridge?.probeRemoteEditors;
const probe = bridge?.probeRemoteEditors;
if (probe === undefined) {
cachedProbedEditors = REMOTE_FALLBACK_EDITORS;
cachedProbedEditors = unprobedEditors;
return;
}
let cancelled = false;
Expand All @@ -160,7 +172,7 @@ export function useRemoteCapableEditors(): ReadonlyArray<EditorId> {
return () => {
cancelled = true;
};
}, []);
}, [bridge, unprobedEditors]);

return editors;
}
Expand Down
Loading