/* Copyright 2026 Marimo. All rights reserved. */ import { useAtom, useAtomValue } from "jotai"; import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator, CommandShortcut, } from "@/components/ui/command"; import { lastFocusedCellAtom } from "@/core/cells/focus"; import { hotkeysAtom } from "@/core/config/config"; import { type HotkeyAction, isHotkeyAction } from "@/core/hotkeys/hotkeys"; import { parseShortcut } from "@/core/hotkeys/shortcuts"; import { useEventListener } from "@/hooks/useEventListener"; import { Objects } from "@/utils/objects"; import { useRegisteredActions } from "../../../core/hotkeys/actions"; import { useRecentCommands } from "../../../hooks/useRecentCommands"; import { KeyboardHotkeys } from "../../shortcuts/renderShortcut"; import { type ActionButton, flattenActions, isParentAction, } from "../actions/types"; import { useCellActionButtons } from "../actions/useCellActionButton"; import { useConfigActions } from "../actions/useConfigActions"; import { useNotebookActions } from "../actions/useNotebookActions"; import { commandPaletteAtom } from "./state"; const CommandPalette = () => { const [open, setOpen] = useAtom(commandPaletteAtom); const registeredActions = useRegisteredActions(); const lastFocusedCell = useAtomValue(lastFocusedCellAtom); const hotkeys = useAtomValue(hotkeysAtom); // Cell actions let cellActions = useCellActionButtons({ cell: lastFocusedCell }).flat(); cellActions = flattenActions(cellActions); // Notebook actions const configActions = useConfigActions(); let notebookActions = useNotebookActions(); notebookActions = [ ...flattenActions(notebookActions), ...flattenActions(configActions), ]; const notebookActionsWithoutHotkeys = notebookActions.filter( (action) => !action.hotkey, ); const keyedNotebookActions: Record = Objects.keyBy(notebookActionsWithoutHotkeys, (action) => action.label); const { recentCommands, addRecentCommand } = useRecentCommands(); const recentCommandsSet = new Set(recentCommands); useEventListener(document, "keydown", (e) => { if (parseShortcut(hotkeys.getHotkey("global.commandPalette").key)(e)) { e.preventDefault(); setOpen((open) => !open); } }); const renderShortcutCommandItem = ( shortcut: HotkeyAction, props: { disabled?: boolean; tooltip?: React.ReactNode; }, ) => { const action = registeredActions[shortcut]; if (!action) { return null; } const hotkey = hotkeys.getHotkey(shortcut); return ( { addRecentCommand(shortcut); // Close first and then run the action, so the dialog doesn't steal focus setOpen(false); requestAnimationFrame(() => { action(); }); }} key={shortcut} value={hotkey.name} > {hotkey.name} {props.tooltip && {props.tooltip}} ); }; const renderCommandItem = ({ label, handle, props = {}, hotkey, additionalKeywords, }: { label: string; handle: () => void; props?: { disabled?: boolean; tooltip?: React.ReactNode }; hotkey?: HotkeyAction; additionalKeywords?: string[]; }) => { return ( { addRecentCommand(label); setOpen(false); requestAnimationFrame(() => { handle(); }); }} key={label} value={label} > {label} {props.tooltip && ({props.tooltip})} {hotkey && ( )} ); }; return ( No results found. {recentCommands.length > 0 && ( <> {recentCommands.map((shortcut) => { const action = keyedNotebookActions[shortcut]; // Hotkey if (isHotkeyAction(shortcut)) { return renderShortcutCommandItem(shortcut, { disabled: action?.disabled, tooltip: action?.tooltip, }); } // Other action if (action && !isParentAction(action)) { return renderCommandItem({ label: action.label, handle: action.handleHeadless || action.handle, props: { disabled: action.disabled, tooltip: action.tooltip, }, additionalKeywords: action.additionalKeywords, }); } return null; })} )} {hotkeys.iterate().map((shortcut) => { if (recentCommandsSet.has(shortcut)) { return null; // Don't show recent commands in the main list } const action = keyedNotebookActions[shortcut]; return renderShortcutCommandItem(shortcut, { disabled: action?.disabled, tooltip: action?.tooltip, }); })} {notebookActionsWithoutHotkeys.map((action) => { if (recentCommandsSet.has(action.label)) { return null; // Don't show recent commands in the main list } return renderCommandItem({ label: action.label, handle: action.handleHeadless || action.handle, props: { disabled: action.disabled, tooltip: action.tooltip }, additionalKeywords: action.additionalKeywords, }); })} {cellActions.map((action) => { if (recentCommandsSet.has(action.label)) { return null; // Don't show recent commands in the main list } return renderCommandItem({ label: `Cell > ${action.label}`, handle: action.handleHeadless || action.handle, props: { disabled: action.disabled, tooltip: action.tooltip }, additionalKeywords: action.additionalKeywords, }); })} ); }; export default CommandPalette;