From e9b95b2e9199bfd94d6ad2f67a12ef5ae60f4f21 Mon Sep 17 00:00:00 2001
From: Adam <2363879+adamdotdevin@users.noreply.github.com>
Date: Mon, 15 Dec 2025 04:09:57 -0600
Subject: wip(desktop): progress

---
 packages/desktop/src/context/command.tsx | 255 +++++++++++++++++++++++++++++++
 1 file changed, 255 insertions(+)
 create mode 100644 packages/desktop/src/context/command.tsx

(limited to 'packages/desktop/src/context/command.tsx')

diff --git a/packages/desktop/src/context/command.tsx b/packages/desktop/src/context/command.tsx
new file mode 100644
index 000000000..b17a98270
--- /dev/null
+++ b/packages/desktop/src/context/command.tsx
@@ -0,0 +1,255 @@
+import { createMemo, createSignal, onCleanup, onMount, Show, type Accessor } from "solid-js"
+import { createSimpleContext } from "@opencode-ai/ui/context"
+import { useDialog } from "@opencode-ai/ui/context/dialog"
+import { Dialog } from "@opencode-ai/ui/dialog"
+import { List } from "@opencode-ai/ui/list"
+
+const IS_MAC = typeof navigator === "object" && /(Mac|iPod|iPhone|iPad)/.test(navigator.platform)
+
+/**
+ * Keybind configuration type.
+ * Format: "mod+key" where mod can be ctrl, alt, shift, meta (or cmd on mac)
+ * Multiple keybinds can be separated by comma: "mod+p,ctrl+shift+p"
+ * Use "mod" for platform-appropriate modifier (cmd on mac, ctrl elsewhere)
+ */
+export type KeybindConfig = string
+
+export interface Keybind {
+  key: string
+  ctrl: boolean
+  meta: boolean
+  shift: boolean
+  alt: boolean
+}
+
+export interface CommandOption {
+  /** Unique identifier for the command */
+  id: string
+  /** Display title in the command palette */
+  title: string
+  /** Optional description */
+  description?: string
+  /** Category for grouping in the palette */
+  category?: string
+  /** Keybind string (e.g., "mod+p", "ctrl+shift+t") */
+  keybind?: KeybindConfig
+  /** Slash command trigger (e.g., "models" for /models) */
+  slash?: string
+  /** Whether to show in the "Suggested" section */
+  suggested?: boolean
+  /** Whether the command is disabled */
+  disabled?: boolean
+  /** Handler when command is selected */
+  onSelect?: (source?: "palette" | "keybind" | "slash") => void
+}
+
+export function parseKeybind(config: string): Keybind[] {
+  if (!config || config === "none") return []
+
+  return config.split(",").map((combo) => {
+    const parts = combo.trim().toLowerCase().split("+")
+    const keybind: Keybind = {
+      key: "",
+      ctrl: false,
+      meta: false,
+      shift: false,
+      alt: false,
+    }
+
+    for (const part of parts) {
+      switch (part) {
+        case "ctrl":
+        case "control":
+          keybind.ctrl = true
+          break
+        case "meta":
+        case "cmd":
+        case "command":
+          keybind.meta = true
+          break
+        case "mod":
+          if (IS_MAC) keybind.meta = true
+          else keybind.ctrl = true
+          break
+        case "alt":
+        case "option":
+          keybind.alt = true
+          break
+        case "shift":
+          keybind.shift = true
+          break
+        default:
+          keybind.key = part
+          break
+      }
+    }
+
+    return keybind
+  })
+}
+
+export function matchKeybind(keybinds: Keybind[], event: KeyboardEvent): boolean {
+  const eventKey = event.key.toLowerCase()
+
+  for (const kb of keybinds) {
+    const keyMatch = kb.key === eventKey
+    const ctrlMatch = kb.ctrl === (event.ctrlKey || false)
+    const metaMatch = kb.meta === (event.metaKey || false)
+    const shiftMatch = kb.shift === (event.shiftKey || false)
+    const altMatch = kb.alt === (event.altKey || false)
+
+    if (keyMatch && ctrlMatch && metaMatch && shiftMatch && altMatch) {
+      return true
+    }
+  }
+
+  return false
+}
+
+export function formatKeybind(config: string): string {
+  if (!config || config === "none") return ""
+
+  const keybinds = parseKeybind(config)
+  if (keybinds.length === 0) return ""
+
+  const kb = keybinds[0]
+  const parts: string[] = []
+
+  if (kb.ctrl) parts.push(IS_MAC ? "⌃" : "Ctrl")
+  if (kb.alt) parts.push(IS_MAC ? "⌥" : "Alt")
+  if (kb.shift) parts.push(IS_MAC ? "⇧" : "Shift")
+  if (kb.meta) parts.push(IS_MAC ? "⌘" : "Meta")
+
+  if (kb.key) {
+    const displayKey = kb.key.length === 1 ? kb.key.toUpperCase() : kb.key.charAt(0).toUpperCase() + kb.key.slice(1)
+    parts.push(displayKey)
+  }
+
+  return IS_MAC ? parts.join("") : parts.join("+")
+}
+
+function DialogCommand(props: { options: CommandOption[] }) {
+  const dialog = useDialog()
+
+  return (
+    <Dialog title="Commands">
+      <List
+        class="px-2.5"
+        search={{ placeholder: "Search commands", autofocus: true }}
+        emptyMessage="No commands found"
+        items={() => props.options.filter((x) => !x.id.startsWith("suggested.") || !x.disabled)}
+        key={(x) => x.id}
+        groupBy={(x) => x.category ?? ""}
+        onSelect={(option) => {
+          if (option) {
+            dialog.clear()
+            option.onSelect?.("palette")
+          }
+        }}
+      >
+        {(option) => (
+          <div class="w-full flex items-center justify-between gap-4">
+            <div class="flex items-center gap-2 min-w-0">
+              <span class="text-14-regular text-text-strong whitespace-nowrap">{option.title}</span>
+              <Show when={option.description}>
+                <span class="text-14-regular text-text-weak truncate">{option.description}</span>
+              </Show>
+            </div>
+            <Show when={option.keybind}>
+              <span class="text-12-regular text-text-subtle shrink-0">{formatKeybind(option.keybind!)}</span>
+            </Show>
+          </div>
+        )}
+      </List>
+    </Dialog>
+  )
+}
+
+export const { use: useCommand, provider: CommandProvider } = createSimpleContext({
+  name: "Command",
+  init: () => {
+    const [registrations, setRegistrations] = createSignal<Accessor<CommandOption[]>[]>([])
+    const [suspendCount, setSuspendCount] = createSignal(0)
+    const dialog = useDialog()
+
+    const options = createMemo(() => {
+      const all = registrations().flatMap((x) => x())
+      const suggested = all.filter((x) => x.suggested && !x.disabled)
+      return [
+        ...suggested.map((x) => ({
+          ...x,
+          id: "suggested." + x.id,
+          category: "Suggested",
+        })),
+        ...all,
+      ]
+    })
+
+    const suspended = () => suspendCount() > 0
+
+    const showPalette = () => {
+      if (dialog.stack.length === 0) {
+        dialog.replace(() => <DialogCommand options={options().filter((x) => !x.disabled)} />)
+      }
+    }
+
+    const handleKeyDown = (event: KeyboardEvent) => {
+      if (suspended()) return
+
+      // Check for command palette keybind (mod+shift+p)
+      const paletteKeybinds = parseKeybind("mod+shift+p")
+      if (matchKeybind(paletteKeybinds, event)) {
+        event.preventDefault()
+        showPalette()
+        return
+      }
+
+      // Check registered command keybinds
+      for (const option of options()) {
+        if (option.disabled) continue
+        if (!option.keybind) continue
+
+        const keybinds = parseKeybind(option.keybind)
+        if (matchKeybind(keybinds, event)) {
+          event.preventDefault()
+          option.onSelect?.("keybind")
+          return
+        }
+      }
+    }
+
+    onMount(() => {
+      document.addEventListener("keydown", handleKeyDown)
+    })
+
+    onCleanup(() => {
+      document.removeEventListener("keydown", handleKeyDown)
+    })
+
+    return {
+      register(cb: () => CommandOption[]) {
+        const results = createMemo(cb)
+        setRegistrations((arr) => [results, ...arr])
+        onCleanup(() => {
+          setRegistrations((arr) => arr.filter((x) => x !== results))
+        })
+      },
+      trigger(id: string, source?: "palette" | "keybind" | "slash") {
+        for (const option of options()) {
+          if (option.id === id || option.id === "suggested." + id) {
+            option.onSelect?.(source)
+            return
+          }
+        }
+      },
+      show: showPalette,
+      keybinds(enabled: boolean) {
+        setSuspendCount((count) => count + (enabled ? -1 : 1))
+      },
+      suspended,
+      get options() {
+        return options()
+      },
+    }
+  },
+})
-- 
cgit v1.2.3

