import { EDITORS, EditorId, type EnvironmentId } from "@t3tools/contracts";import { mapAtomCommandResult, type AtomCommandFailure, type AtomCommandResult,} from "@t3tools/client-runtime/state/runtime";import * as Cause from "effect/Cause";import * as Data from "effect/Data";import { AsyncResult } from "effect/unstable/reactivity";import { getLocalStorageItem, setLocalStorageItem, useLocalStorage } from "./hooks/useLocalStorage";import { useCallback, useMemo } from "react";import { shellEnvironment } from "./state/shell";import { useAtomCommand } from "./state/use-atom-command"; const LAST_EDITOR_KEY = "t3code:last-editor"; export class PreferredEditorUnavailableError extends Data.TaggedError( "PreferredEditorUnavailableError",)<{ readonly message: string;}> {} 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; }, [lastEditor, availableEditors]); return [effectiveEditor, setLastEditor] as const;} export function resolveAndPersistPreferredEditor( availableEditors: readonly EditorId[],): EditorId | null { 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; if (editor) setLocalStorageItem(LAST_EDITOR_KEY, editor, EditorId); return editor ?? null;} export function useOpenInPreferredEditor( environmentId: EnvironmentId | null, availableEditors: readonly EditorId[],) { const openInEditor = useAtomCommand(shellEnvironment.openInEditor, { reportFailure: false, }); type OpenInEditorError = AtomCommandFailure<Awaited<ReturnType<typeof openInEditor>>>; return useCallback( async ( targetPath: string, ): Promise< AtomCommandResult<EditorId, OpenInEditorError | PreferredEditorUnavailableError> > => { if (environmentId === null) { return AsyncResult.failure( Cause.fail( new PreferredEditorUnavailableError({ message: "No environment is selected.", }), ), ); } const editor = resolveAndPersistPreferredEditor(availableEditors); if (!editor) { return AsyncResult.failure( Cause.fail( new PreferredEditorUnavailableError({ message: "No available editors found.", }), ), ); } const result = await openInEditor({ environmentId, input: { cwd: targetPath, editor, }, }); return mapAtomCommandResult(result, () => editor); }, [availableEditors, environmentId, openInEditor], );}
import { EDITORS, EditorId, type EnvironmentId } from "@t3tools/contracts";
import {
mapAtomCommandResult,
type AtomCommandFailure,
type AtomCommandResult,
} from "@t3tools/client-runtime/state/runtime";
import * as Cause from "effect/Cause";
import * as Data from "effect/Data";
import { AsyncResult } from "effect/unstable/reactivity";
import { getLocalStorageItem, setLocalStorageItem, useLocalStorage } from "./hooks/useLocalStorage";
import { useCallback, useMemo } from "react";
import { shellEnvironment } from "./state/shell";
import { useAtomCommand } from "./state/use-atom-command";
const LAST_EDITOR_KEY = "t3code:last-editor";
export class PreferredEditorUnavailableError extends Data.TaggedError(
"PreferredEditorUnavailableError",
)<{
readonly message: string;
}> {}
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;
}, [lastEditor, availableEditors]);
return [effectiveEditor, setLastEditor] as const;
}
export function resolveAndPersistPreferredEditor(
availableEditors: readonly EditorId[],
): EditorId | null {
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;
if (editor) setLocalStorageItem(LAST_EDITOR_KEY, editor, EditorId);
return editor ?? null;
}
export function useOpenInPreferredEditor(
environmentId: EnvironmentId | null,
availableEditors: readonly EditorId[],
) {
const openInEditor = useAtomCommand(shellEnvironment.openInEditor, {
reportFailure: false,
});
type OpenInEditorError = AtomCommandFailure<Awaited<ReturnType<typeof openInEditor>>>;
return useCallback(
async (
targetPath: string,
): Promise<
AtomCommandResult<EditorId, OpenInEditorError | PreferredEditorUnavailableError>
> => {
if (environmentId === null) {
return AsyncResult.failure(
Cause.fail(
new PreferredEditorUnavailableError({
message: "No environment is selected.",
}),
),
);
}
const editor = resolveAndPersistPreferredEditor(availableEditors);
if (!editor) {
return AsyncResult.failure(
Cause.fail(
new PreferredEditorUnavailableError({
message: "No available editors found.",
}),
),
);
}
const result = await openInEditor({
environmentId,
input: {
cwd: targetPath,
editor,
},
});
return mapAtomCommandResult(result, () => editor);
},
[availableEditors, environmentId, openInEditor],
);
}