diff --git a/packages/tui/src/component/command-palette.tsx b/packages/tui/src/component/command-palette.tsx index d9db1a1cd39..e801a8093a6 100644 --- a/packages/tui/src/component/command-palette.tsx +++ b/packages/tui/src/component/command-palette.tsx @@ -2,6 +2,7 @@ import { createMemo } from "solid-js" import { DialogSelect, type DialogSelectRef } from "../ui/dialog-select" import { type DialogContext } from "../ui/dialog" import { COMMAND_PALETTE_COMMAND, Keymap, type KeymapCommand } from "../context/keymap" +import { DialogConfig, settingID, settings } from "./dialog-config" function isSuggestedPaletteCommand(command: KeymapCommand) { const suggested = command.suggested @@ -16,11 +17,14 @@ export function CommandPaletteDialog() { const options = createMemo(() => commands().flatMap((command) => { if (!command.id || !command.palette || command.id === COMMAND_PALETTE_COMMAND) return [] + const footer = shortcuts.all(command.id) return { title: command.title ?? command.id, description: command.description, category: command.group, - footer: shortcuts.all(command.id), + searchText: [command.id, command.description].filter(Boolean).join(" "), + searchFooter: [command.group, footer].filter(Boolean).join(" · "), + footer, value: command.id, suggested: isSuggestedPaletteCommand(command), onSelect: (dialog: DialogContext) => { @@ -30,10 +34,20 @@ export function CommandPaletteDialog() { } }), ) + const settingOptions = settings.map((setting) => ({ + title: setting.title, + category: setting.category, + searchText: setting.keywords?.join(" "), + searchFooter: `Settings · ${setting.category}`, + value: `setting:${settingID(setting)}`, + onSelect: (dialog: DialogContext) => { + dialog.replace(() => ) + }, + })) let ref: DialogSelectRef const list = () => { - if (ref?.filter) return options() + if (ref?.filter) return [...options(), ...settingOptions] return [ ...options() .filter((option) => option.suggested) @@ -46,5 +60,7 @@ export function CommandPaletteDialog() { ] } - return (ref = value)} title="Commands" options={list()} /> + return ( + (ref = value)} title="Commands" options={list()} flat={true} filterThreshold={0.7} /> + ) } diff --git a/packages/tui/src/component/dialog-config.tsx b/packages/tui/src/component/dialog-config.tsx index 1ca585ec137..0cf13f88ac8 100644 --- a/packages/tui/src/component/dialog-config.tsx +++ b/packages/tui/src/component/dialog-config.tsx @@ -15,14 +15,16 @@ type Setting = { min?: number max?: number format?: (value: unknown) => string + keywords?: readonly string[] } -const settings: Setting[] = [ +export const settings: Setting[] = [ { title: "Theme", category: "Appearance", path: ["theme", "name"], default: "opencode", + keywords: ["color scheme", "colors"], }, { title: "Color mode", @@ -30,6 +32,7 @@ const settings: Setting[] = [ path: ["theme", "mode"], default: "system", values: ["system", "dark", "light"], + keywords: ["dark mode", "light mode", "system theme"], }, { title: "Animations", @@ -38,6 +41,7 @@ const settings: Setting[] = [ default: false, values: [false, true], labels: ["off", "on"], + keywords: ["motion", "effects"], }, { title: "Onboarding", @@ -46,6 +50,7 @@ const settings: Setting[] = [ default: true, values: [false, true], labels: ["off", "on"], + keywords: ["hints", "getting started", "guidance"], }, { title: "Sidebar", @@ -53,6 +58,7 @@ const settings: Setting[] = [ path: ["session", "sidebar"], default: "auto", values: ["hide", "auto"], + keywords: ["side panel"], }, { title: "Scrollbar", @@ -61,6 +67,7 @@ const settings: Setting[] = [ default: false, values: [false, true], labels: ["off", "on"], + keywords: ["scroll bar"], }, { title: "Thinking", @@ -68,6 +75,7 @@ const settings: Setting[] = [ path: ["session", "thinking"], default: "hide", values: ["hide", "show"], + keywords: ["reasoning", "chain of thought"], }, { title: "Markdown", @@ -75,6 +83,7 @@ const settings: Setting[] = [ path: ["session", "markdown"], default: "rendered", values: ["source", "rendered"], + keywords: ["syntax", "concealment", "rendering"], }, { title: "Grouping", @@ -82,6 +91,7 @@ const settings: Setting[] = [ path: ["session", "grouping"], default: "auto", values: ["none", "auto"], + keywords: ["transcript", "messages"], }, { title: "Layout", @@ -89,6 +99,7 @@ const settings: Setting[] = [ path: ["diffs", "view"], default: "auto", values: ["auto", "split", "unified"], + keywords: ["diff layout", "split diff", "unified diff"], }, { title: "Wrapping", @@ -96,6 +107,7 @@ const settings: Setting[] = [ path: ["diffs", "wrap"], default: "word", values: ["none", "word"], + keywords: ["diff wrap", "word wrap", "line wrap"], }, { title: "File tree", @@ -104,6 +116,7 @@ const settings: Setting[] = [ default: true, values: [false, true], labels: ["off", "on"], + keywords: ["diff files"], }, { title: "Single patch", @@ -112,6 +125,7 @@ const settings: Setting[] = [ default: false, values: [false, true], labels: ["off", "on"], + keywords: ["one file", "selected file"], }, { title: "Scroll speed", @@ -122,6 +136,7 @@ const settings: Setting[] = [ min: 0.25, max: 10, format: (value) => Number(value).toFixed(2), + keywords: ["scrolling"], }, { title: "Acceleration", @@ -130,6 +145,7 @@ const settings: Setting[] = [ default: false, values: [false, true], labels: ["off", "on"], + keywords: ["scroll acceleration"], }, { title: "Mouse", @@ -138,6 +154,7 @@ const settings: Setting[] = [ default: true, values: [false, true], labels: ["off", "on"], + keywords: ["mouse capture"], }, { title: "Editor context", @@ -146,6 +163,7 @@ const settings: Setting[] = [ default: true, values: [false, true], labels: ["off", "on"], + keywords: ["file context", "prompt context", "editor selection"], }, { title: "Large pastes", @@ -153,6 +171,7 @@ const settings: Setting[] = [ path: ["prompt", "paste"], default: "compact", values: ["compact", "full"], + keywords: ["paste summary", "clipboard", "pasted content"], }, { title: "Leader timeout", @@ -163,6 +182,7 @@ const settings: Setting[] = [ min: 250, max: 10000, format: (value) => `${value} ms`, + keywords: ["leader key", "shortcut timeout"], }, { title: "Attention", @@ -171,6 +191,7 @@ const settings: Setting[] = [ default: false, values: [false, true], labels: ["off", "on"], + keywords: ["alerts"], }, { title: "Notifications", @@ -179,6 +200,7 @@ const settings: Setting[] = [ default: true, values: [false, true], labels: ["off", "on"], + keywords: ["system notifications", "desktop notifications", "alerts"], }, { title: "Sounds", @@ -187,6 +209,7 @@ const settings: Setting[] = [ default: true, values: [false, true], labels: ["off", "on"], + keywords: ["audio", "sound effects"], }, { title: "Volume", @@ -197,6 +220,7 @@ const settings: Setting[] = [ min: 0, max: 1, format: (value) => `${Math.round(Number(value) * 100)}%`, + keywords: ["sound volume", "audio volume"], }, { title: "Window title", @@ -205,6 +229,7 @@ const settings: Setting[] = [ default: true, values: [false, true], labels: ["off", "on"], + keywords: ["terminal title", "tab title"], }, { title: "Copy on select", @@ -213,6 +238,7 @@ const settings: Setting[] = [ default: process.platform !== "win32", values: [false, true], labels: ["off", "on"], + keywords: ["selection", "clipboard"], }, { title: "DevTools", @@ -221,6 +247,7 @@ const settings: Setting[] = [ default: false, values: [false, true], labels: ["off", "on"], + keywords: ["debug bar", "developer tools"], }, { title: "Turn token usage", @@ -229,14 +256,23 @@ const settings: Setting[] = [ default: false, values: [false, true], labels: ["off", "on"], + keywords: ["tokens", "usage", "debug"], }, ] -export function DialogConfig() { +export function settingID(setting: Setting) { + return setting.path.join(".") +} + +export function DialogConfig(props: { current?: string }) { const config = useConfig() const toast = useToast() const themeState = useTheme() - const [selected, setSelected] = createSignal(0) + const current = Math.max( + 0, + settings.findIndex((setting) => settingID(setting) === props.current), + ) + const [selected, setSelected] = createSignal(current) const [saving, setSaving] = createSignal(false) const value = (setting: Setting) => { @@ -261,6 +297,7 @@ export function DialogConfig() { settings.map((setting, index) => ({ title: setting.title, category: setting.category, + searchText: setting.keywords?.join(" "), footer: display(setting), value: index, })), @@ -292,6 +329,8 @@ export function DialogConfig() { setSelected(option.value)} onSelect={(option) => void change(1, option.value)} footerHints={[{ title: "←/→", label: "change" }]} diff --git a/packages/tui/src/ui/dialog-select.tsx b/packages/tui/src/ui/dialog-select.tsx index 88248c92d34..be97539db59 100644 --- a/packages/tui/src/ui/dialog-select.tsx +++ b/packages/tui/src/ui/dialog-select.tsx @@ -22,6 +22,7 @@ export interface DialogSelectProps { noMatchView?: JSX.Element options: DialogSelectOption[] flat?: boolean + filterThreshold?: number ref?: (ref: DialogSelectRef) => void onMove?: (option: DialogSelectOption) => void onFilter?: (query: string) => void @@ -64,6 +65,8 @@ export interface DialogSelectOption { titleView?: JSX.Element value: T description?: string + searchText?: string + searchFooter?: JSX.Element | string details?: string[] detailsColor?: RGBA detailsWrap?: boolean @@ -164,12 +167,13 @@ export function DialogSelect(props: DialogSelectProps) { ) if (!needle) return options - // prioritize title matches (weight: 2) over category matches (weight: 1). + // prioritize title matches (weight: 2) over category and supplemental search text matches (weight: 1). // users typically search by the item name, and not its category. const result = fuzzysort .go(needle, options, { - keys: ["title", "category"], - scoreFn: (r) => r[0].score * 2 + r[1].score, + keys: ["title", "category", "searchText"], + scoreFn: (r) => r[0].score * 2 + r[1].score + r[2].score, + threshold: props.filterThreshold, }) .map((x) => x.obj) @@ -704,7 +708,9 @@ export function DialogSelect(props: DialogSelectProps) {