import { EditorId, 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 { isMacPlatform, isWindowsPlatform } from "~/lib/utils"; import { readLocalApi } from "~/localApi"; const resolveOptions = (platform: string, availableEditors: ReadonlyArray) => { const baseOptions: ReadonlyArray<{ label: string; Icon: Icon; value: EditorId }> = [ { label: "Cursor", Icon: CursorIcon, value: "cursor", }, { label: "Trae", Icon: TraeIcon, value: "trae", }, { label: "Kiro", Icon: KiroIcon, value: "kiro", }, { label: "VS Code", Icon: VisualStudioCode, value: "vscode", }, { label: "VS Code Insiders", Icon: VisualStudioCodeInsiders, value: "vscode-insiders", }, { label: "VSCodium", Icon: VSCodium, value: "vscodium", }, { label: "Zed", Icon: Zed, value: "zed", }, { label: "Antigravity", Icon: AntigravityIcon, value: "antigravity", }, { label: "IntelliJ IDEA", Icon: IntelliJIdeaIcon, value: "idea", }, { label: "Aqua", Icon: AquaIcon, value: "aqua", }, { label: "CLion", Icon: CLionIcon, value: "clion", }, { label: "DataGrip", Icon: DataGripIcon, value: "datagrip", }, { label: "DataSpell", Icon: DataSpellIcon, value: "dataspell", }, { label: "GoLand", Icon: GoLandIcon, value: "goland", }, { label: "PhpStorm", Icon: PhpStormIcon, value: "phpstorm", }, { label: "PyCharm", Icon: PyCharmIcon, value: "pycharm", }, { label: "Rider", Icon: RiderIcon, value: "rider", }, { label: "RubyMine", Icon: RubyMineIcon, value: "rubymine", }, { label: "RustRover", Icon: RustRoverIcon, value: "rustrover", }, { label: "WebStorm", Icon: WebStormIcon, value: "webstorm", }, { label: isMacPlatform(platform) ? "Finder" : isWindowsPlatform(platform) ? "Explorer" : "Files", Icon: FolderClosedIcon, value: "file-manager", }, ]; const availableEditorSet = new Set(availableEditors); return baseOptions.filter((option) => availableEditorSet.has(option.value)); }; export const OpenInPicker = memo(function OpenInPicker({ keybindings, availableEditors, openInCwd, }: { keybindings: ResolvedKeybindingsConfig; availableEditors: ReadonlyArray; openInCwd: string | null; }) { 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) => { const api = readLocalApi(); if (!api || !openInCwd) return; const editor = editorId ?? preferredEditor; if (!editor) return; void api.shell.openInEditor(openInCwd, editor); setPreferredEditor(editor); }, [preferredEditor, openInCwd, setPreferredEditor], ); const openFavoriteEditorShortcutLabel = useMemo( () => shortcutLabelForCommand(keybindings, "editor.openFavorite"), [keybindings], ); useEffect(() => { const handler = (e: globalThis.KeyboardEvent) => { const api = readLocalApi(); if (!isOpenFavoriteEditorShortcut(e, keybindings)) return; if (!api || !openInCwd) return; if (!preferredEditor) return; e.preventDefault(); void api.shell.openInEditor(openInCwd, preferredEditor); }; window.addEventListener("keydown", handler); return () => window.removeEventListener("keydown", handler); }, [preferredEditor, keybindings, openInCwd]); return ( }> {options.length === 0 && No installed editors found} {options.map(({ label, Icon, value }) => ( openInEditor(value)}> ))} ); });