115 lines · 3.7 KB · TypeScript
RawDownload
import { EDITORS, EditorId, EnvironmentId } from "@t3tools/contracts";import {  mapAtomCommandResult,  type AtomCommandFailure,  type AtomCommandResult,} from "@t3tools/client-runtime/state/runtime";import * as Cause from "effect/Cause";import * as Schema from "effect/Schema";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 PreferredEditorEnvironmentRequiredError extends Schema.TaggedError<PreferredEditorEnvironmentRequiredError>()(  "PreferredEditorEnvironmentRequiredError",  {    targetPath: Schema.String,  },) {  override get message(): string {    return `Cannot open ${this.targetPath} because no environment is selected.`;  }} export class PreferredEditorUnavailableError extends Schema.TaggedError<PreferredEditorUnavailableError>()(  "PreferredEditorUnavailableError",  {    environmentId: EnvironmentId,    targetPath: Schema.String,    availableEditorIds: Schema.Array(EditorId),  },) {  override get message(): string {    return `No available editor can open ${this.targetPath} in environment ${this.environmentId}.`;  }} 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        | PreferredEditorEnvironmentRequiredError        | PreferredEditorUnavailableError      >    > => {      if (environmentId === null) {        return AsyncResult.failure(          Cause.fail(            new PreferredEditorEnvironmentRequiredError({              targetPath,            }),          ),        );      }      const editor = resolveAndPersistPreferredEditor(availableEditors);      if (!editor) {        return AsyncResult.failure(          Cause.fail(            new PreferredEditorUnavailableError({              environmentId,              targetPath,              availableEditorIds: availableEditors,            }),          ),        );      }      const result = await openInEditor({        environmentId,        input: {          cwd: targetPath,          editor,        },      });      return mapAtomCommandResult(result, () => editor);    },    [availableEditors, environmentId, openInEditor],  );}