import { EditorId, type EnvironmentId, type ResolvedKeybindingsConfig } from "@t3tools/contracts"; import { memo, useCallback, useEffect, useMemo } from "react"; import { isOpenFavoriteEditorShortcut, shortcutLabelForCommand } from "../../keybindings"; import { usePreferredEditor } from "../../editorPreferences"; import { ChevronDownIcon, FolderClosedIcon } from "lucide-react"; import { Button } from "../ui/button"; import { Group, GroupSeparator } from "../ui/group"; import { Menu, MenuItem, MenuPopup, MenuShortcut, MenuTrigger } from "../ui/menu"; import { AntigravityIcon, CursorIcon, Icon, KiroIcon, TraeIcon, VisualStudioCode, VisualStudioCodeInsiders, VSCodium, Zed, } from "../Icons"; import { AquaIcon, CLionIcon, DataGripIcon, DataSpellIcon, GoLandIcon, IntelliJIdeaIcon, PhpStormIcon, PyCharmIcon, RiderIcon, RubyMineIcon, RustRoverIcon, WebStormIcon, } from "../JetBrainsIcons"; import { cn, isMacPlatform, isWindowsPlatform } from "~/lib/utils"; import { shellEnvironment } from "~/state/shell"; import { useAtomCommand } from "~/state/use-atom-command"; type OpenInOption = { label: string; Icon: Icon; value: EditorId; kind: "brand" | "generic"; }; const resolveOptions = (platform: string, availableEditors: ReadonlyArray) => { const baseOptions: ReadonlyArray = [ { label: "Cursor", Icon: CursorIcon, value: "cursor", kind: "brand", }, { label: "Trae", Icon: TraeIcon, value: "trae", kind: "brand", }, { label: "Kiro", Icon: KiroIcon, value: "kiro", kind: "brand", }, { label: "VS Code", Icon: VisualStudioCode, value: "vscode", kind: "brand", }, { label: "VS Code Insiders", Icon: VisualStudioCodeInsiders, value: "vscode-insiders", kind: "brand", }, { label: "VSCodium", Icon: VSCodium, value: "vscodium", kind: "brand", }, { label: "Zed", Icon: Zed, value: "zed", kind: "brand", }, { label: "Antigravity", Icon: AntigravityIcon, value: "antigravity", kind: "brand", }, { label: "IntelliJ IDEA", Icon: IntelliJIdeaIcon, value: "idea", kind: "brand", }, { label: "Aqua", Icon: AquaIcon, value: "aqua", kind: "brand", }, { label: "CLion", Icon: CLionIcon, value: "clion", kind: "brand", }, { label: "DataGrip", Icon: DataGripIcon, value: "datagrip", kind: "brand", }, { label: "DataSpell", Icon: DataSpellIcon, value: "dataspell", kind: "brand", }, { label: "GoLand", Icon: GoLandIcon, value: "goland", kind: "brand", }, { label: "PhpStorm", Icon: PhpStormIcon, value: "phpstorm", kind: "brand", }, { label: "PyCharm", Icon: PyCharmIcon, value: "pycharm", kind: "brand", }, { label: "Rider", Icon: RiderIcon, value: "rider", kind: "brand", }, { label: "RubyMine", Icon: RubyMineIcon, value: "rubymine", kind: "brand", }, { label: "RustRover", Icon: RustRoverIcon, value: "rustrover", kind: "brand", }, { label: "WebStorm", Icon: WebStormIcon, value: "webstorm", kind: "brand", }, { label: isMacPlatform(platform) ? "Finder" : isWindowsPlatform(platform) ? "Explorer" : "Files", Icon: FolderClosedIcon, value: "file-manager", kind: "generic", }, ]; const availableEditorSet = new Set(availableEditors); return baseOptions.filter((option) => availableEditorSet.has(option.value)); }; function getOpenInIconClass(kind: OpenInOption["kind"]) { return cn(kind === "brand" ? "text-foreground opacity-100" : "text-muted-foreground"); } export const OpenInPicker = memo(function OpenInPicker({ environmentId, keybindings, availableEditors, openInCwd, compact = false, enableShortcut = true, }: { environmentId: EnvironmentId; keybindings: ResolvedKeybindingsConfig; availableEditors: ReadonlyArray; openInCwd: string | null; compact?: boolean; enableShortcut?: boolean; }) { const openInEditorMutation = useAtomCommand(shellEnvironment.openInEditor, "open in editor"); const [preferredEditor, setPreferredEditor] = usePreferredEditor(availableEditors); const options = useMemo( () => resolveOptions(navigator.platform, availableEditors), [availableEditors], ); const primaryOption = options.find(({ value }) => value === preferredEditor) ?? null; const openInEditor = useCallback( (editorId: EditorId | null) => { if (!openInCwd) return; const editor = editorId ?? preferredEditor; if (!editor) return; const result = openInEditorMutation({ environmentId, input: { cwd: openInCwd, editor, }, }); setPreferredEditor(editor); return result; }, [environmentId, openInCwd, openInEditorMutation, preferredEditor, setPreferredEditor], ); const openFavoriteEditorShortcutLabel = useMemo( () => shortcutLabelForCommand(keybindings, "editor.openFavorite"), [keybindings], ); useEffect(() => { if (!enableShortcut) return; const handler = (e: globalThis.KeyboardEvent) => { if (!isOpenFavoriteEditorShortcut(e, keybindings)) return; if (!openInCwd) return; if (!preferredEditor) return; e.preventDefault(); void openInEditorMutation({ environmentId, input: { cwd: openInCwd, editor: preferredEditor, }, }); }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }, [ enableShortcut, environmentId, keybindings, openInCwd, openInEditorMutation, preferredEditor, ]); return ( } > {options.length === 0 && No installed editors found} {options.map(({ label, Icon, value, kind }) => ( openInEditor(value)}> ))} ); });