Skip to content

Commit a217357

Browse files
Apply PR #26246: use keymap state for layer visibility
2 parents f934936 + 29b7871 commit a217357

11 files changed

Lines changed: 252 additions & 220 deletions

File tree

packages/opencode/src/cli/cmd/tui/app.tsx

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,16 @@ import { TuiPluginRuntime } from "@/cli/cmd/tui/plugin/runtime"
6464
import { createTuiApi } from "@/cli/cmd/tui/plugin/api"
6565
import type { RouteMap } from "@/cli/cmd/tui/plugin/api"
6666
import { FormatError, FormatUnknownError } from "@/cli/error"
67-
import { CommandPaletteProvider, useCommandPalette } from "./context/command-palette"
68-
import { OpencodeKeymapProvider, registerOpencodeKeymap, useBindings, useOpencodeKeymap } from "./keymap"
67+
import { CommandPaletteDialog } from "./component/command-palette"
68+
import {
69+
COMMAND_PALETTE_COMMAND,
70+
OPENCODE_BASE_MODE,
71+
OpencodeKeymapProvider,
72+
createOpencodeModeStack,
73+
registerOpencodeKeymap,
74+
useBindings,
75+
useOpencodeKeymap,
76+
} from "./keymap"
6977

7078
import type { EventSource } from "./context/sdk"
7179
import { DialogVariant } from "./component/dialog-variant"
@@ -170,6 +178,7 @@ export function tui(input: {
170178

171179
const onBeforeExit = async () => {
172180
offKeymap()
181+
modeStack.dispose()
173182
await TuiPluginRuntime.dispose()
174183
}
175184

@@ -179,6 +188,7 @@ export function tui(input: {
179188
const mode = (await renderer.waitForThemeMode(1000)) ?? "dark"
180189

181190
const keymap = createDefaultOpenTuiKeymap(renderer)
191+
const modeStack = createOpencodeModeStack(keymap)
182192
const offKeymap = registerOpencodeKeymap(keymap, renderer, input.config)
183193

184194
await render(() => {
@@ -218,17 +228,15 @@ export function tui(input: {
218228
<LocalProvider>
219229
<PromptStashProvider>
220230
<DialogProvider>
221-
<CommandPaletteProvider>
222-
<FrecencyProvider>
223-
<PromptHistoryProvider>
224-
<PromptRefProvider>
225-
<EditorContextProvider>
226-
<App onSnapshot={input.onSnapshot} />
227-
</EditorContextProvider>
228-
</PromptRefProvider>
229-
</PromptHistoryProvider>
230-
</FrecencyProvider>
231-
</CommandPaletteProvider>
231+
<FrecencyProvider>
232+
<PromptHistoryProvider>
233+
<PromptRefProvider>
234+
<EditorContextProvider>
235+
<App onSnapshot={input.onSnapshot} />
236+
</EditorContextProvider>
237+
</PromptRefProvider>
238+
</PromptHistoryProvider>
239+
</FrecencyProvider>
232240
</DialogProvider>
233241
</PromptStashProvider>
234242
</LocalProvider>
@@ -258,7 +266,6 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
258266
const dialog = useDialog()
259267
const local = useLocal()
260268
const kv = useKV()
261-
const command = useCommandPalette()
262269
const keymap = useOpencodeKeymap()
263270
const event = useEvent()
264271
const sdk = useSDK()
@@ -431,12 +438,12 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
431438
const appCommands = createMemo(() =>
432439
[
433440
{
434-
name: "command.palette.show",
441+
name: COMMAND_PALETTE_COMMAND,
435442
title: "Show command palette",
436443
category: "System",
437444
hidden: true,
438445
run: () => {
439-
command.show()
446+
dialog.replace(() => <CommandPaletteDialog />)
440447
},
441448
},
442449
{
@@ -793,12 +800,12 @@ function App(props: { onSnapshot?: () => Promise<string[]> }) {
793800
}))
794801

795802
useBindings(() => ({
796-
enabled: command.matcher,
803+
opencodeMode: OPENCODE_BASE_MODE,
797804
bindings: tuiConfig.keybinds.gather("app", appBindingCommands),
798805
}))
799806

800807
event.on(TuiEvent.CommandExecute.type, (evt) => {
801-
command.run(evt.properties.command)
808+
keymap.dispatchCommand(evt.properties.command)
802809
})
803810

804811
event.on(TuiEvent.ToastShow.type, (evt) => {
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import { createMemo } from "solid-js"
2+
import { DialogSelect, type DialogSelectRef } from "@tui/ui/dialog-select"
3+
import { type DialogContext } from "@tui/ui/dialog"
4+
import {
5+
COMMAND_PALETTE_COMMAND,
6+
formatKeyBindings,
7+
type OpenTuiKeymap,
8+
useKeymapSelector,
9+
useOpencodeKeymap,
10+
} from "../keymap"
11+
import { useTuiConfig } from "../context/tui-config"
12+
13+
type PaletteCommandEntry = ReturnType<OpenTuiKeymap["getCommandEntries"]>[number]
14+
15+
function isVisiblePaletteCommand(entry: PaletteCommandEntry) {
16+
return entry.command.hidden !== true && entry.command.name !== COMMAND_PALETTE_COMMAND
17+
}
18+
19+
function isSuggestedPaletteCommand(entry: PaletteCommandEntry) {
20+
const suggested = entry.command.suggested
21+
if (typeof suggested === "boolean") return suggested
22+
if (typeof suggested === "function") return suggested() === true
23+
return false
24+
}
25+
26+
export function CommandPaletteDialog() {
27+
const config = useTuiConfig()
28+
const keymap = useOpencodeKeymap()
29+
const entries = useKeymapSelector((keymap: OpenTuiKeymap) => {
30+
const query = {
31+
namespace: "palette",
32+
}
33+
const reachable = keymap
34+
.getCommandEntries({
35+
...query,
36+
visibility: "reachable",
37+
})
38+
.filter(isVisiblePaletteCommand)
39+
const registeredBindings = keymap.getCommandBindings({
40+
visibility: "registered",
41+
commands: reachable.map((entry) => entry.command.name),
42+
})
43+
44+
return reachable.map((entry) => ({
45+
...entry,
46+
bindings: registeredBindings.get(entry.command.name) ?? entry.bindings,
47+
}))
48+
})
49+
const options = createMemo(() =>
50+
entries().map((entry) => ({
51+
title: typeof entry.command.title === "string" ? entry.command.title : entry.command.name,
52+
description: typeof entry.command.desc === "string" ? entry.command.desc : undefined,
53+
category: typeof entry.command.category === "string" ? entry.command.category : undefined,
54+
footer: formatKeyBindings(entry.bindings, config),
55+
value: entry.command.name,
56+
suggested: isSuggestedPaletteCommand(entry),
57+
onSelect: (dialog: DialogContext) => {
58+
dialog.clear()
59+
keymap.dispatchCommand(entry.command.name)
60+
},
61+
})),
62+
)
63+
64+
let ref: DialogSelectRef<string>
65+
const list = () => {
66+
if (ref?.filter) return options()
67+
return [
68+
...options()
69+
.filter((option) => option.suggested)
70+
.map((option) => ({
71+
...option,
72+
value: `suggested:${option.value}`,
73+
category: "Suggested",
74+
})),
75+
...options(),
76+
]
77+
}
78+
79+
return <DialogSelect ref={(value) => (ref = value)} title="Commands" options={list()} />
80+
}

packages/opencode/src/cli/cmd/tui/component/prompt/autocomplete.tsx

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,11 @@ import { getScrollAcceleration } from "../../util/scroll"
1212
import { useTuiConfig } from "../../context/tui-config"
1313
import { useTheme, selectedForeground } from "@tui/context/theme"
1414
import { SplitBorder } from "@tui/component/border"
15-
import { useCommandPalette } from "../../context/command-palette"
1615
import { useTerminalDimensions } from "@opentui/solid"
1716
import { Locale } from "@/util/locale"
1817
import type { PromptInfo } from "./history"
1918
import { useFrecency } from "./frecency"
20-
import { useBindings } from "../../keymap"
19+
import { useBindings, useCommandSlashes, useOpencodeModeStack } from "../../keymap"
2120

2221
function removeLineRange(input: string) {
2322
const hashIndex = input.lastIndexOf("#")
@@ -82,7 +81,8 @@ export function Autocomplete(props: {
8281
const editor = useEditorContext()
8382
const sdk = useSDK()
8483
const sync = useSync()
85-
const command = useCommandPalette()
84+
const slashes = useCommandSlashes()
85+
const modeStack = useOpencodeModeStack()
8686
const { theme } = useTheme()
8787
const dimensions = useTerminalDimensions()
8888
const frecency = useFrecency()
@@ -96,6 +96,12 @@ export function Autocomplete(props: {
9696

9797
const [positionTick, setPositionTick] = createSignal(0)
9898

99+
createEffect(() => {
100+
if (!store.visible) return
101+
const popMode = modeStack.push("autocomplete")
102+
onCleanup(popMode)
103+
})
104+
99105
createEffect(() => {
100106
if (store.visible) {
101107
let lastPos = { x: 0, y: 0, width: 0 }
@@ -281,7 +287,6 @@ export function Autocomplete(props: {
281287
const { filename, part } = createFilePart(item, lineRange)
282288
const index = store.visible === "@" ? store.index : props.input().cursorOffset
283289

284-
command.suspend(false)
285290
setStore("visible", false)
286291
setStore("index", index)
287292
insertPart(filename, part)
@@ -398,7 +403,7 @@ export function Autocomplete(props: {
398403
})
399404

400405
const commands = createMemo((): AutocompleteOption[] => {
401-
const results: AutocompleteOption[] = [...command.slashes()]
406+
const results: AutocompleteOption[] = [...slashes()]
402407

403408
for (const serverCommand of sync.data.command) {
404409
if (serverCommand.source === "skill") continue
@@ -582,7 +587,6 @@ export function Autocomplete(props: {
582587
}))
583588

584589
function show(mode: "@" | "/") {
585-
command.suspend(true)
586590
setStore({
587591
visible: mode,
588592
index: props.input().cursorOffset,
@@ -599,7 +603,6 @@ export function Autocomplete(props: {
599603
draft.input = props.input().plainText
600604
})
601605
}
602-
command.suspend(false)
603606
setStore("visible", false)
604607
}
605608

packages/opencode/src/cli/cmd/tui/component/prompt/index.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -59,8 +59,13 @@ import { DialogWorkspaceUnavailable } from "../dialog-workspace-unavailable"
5959
import { useArgs } from "@tui/context/args"
6060
import { Flag } from "@opencode-ai/core/flag/flag"
6161
import { type WorkspaceStatus } from "../workspace-label"
62-
import { useCommandPalette } from "../../context/command-palette"
63-
import { useBindings, useCommandShortcut, useLeaderActive, useOpencodeKeymap } from "../../keymap"
62+
import {
63+
OPENCODE_BASE_MODE,
64+
useBindings,
65+
useCommandShortcut,
66+
useLeaderActive,
67+
useOpencodeKeymap,
68+
} from "../../keymap"
6469
import { useTuiConfig } from "../../context/tui-config"
6570

6671
export type PromptProps = {
@@ -152,7 +157,6 @@ export function Prompt(props: PromptProps) {
152157
const status = createMemo(() => sync.data.session_status?.[props.sessionID ?? ""] ?? { type: "idle" })
153158
const history = usePromptHistory()
154159
const stash = usePromptStash()
155-
const command = useCommandPalette()
156160
const keymap = useOpencodeKeymap()
157161
const agentShortcut = useCommandShortcut("agent.cycle")
158162
const paletteShortcut = useCommandShortcut("command.palette.show")
@@ -640,7 +644,7 @@ export function Prompt(props: PromptProps) {
640644
}))
641645

642646
useBindings(() => ({
643-
enabled: command.matcher,
647+
opencodeMode: OPENCODE_BASE_MODE,
644648
bindings: tuiConfig.keybinds.gather("prompt.palette", [
645649
"prompt.submit",
646650
"prompt.editor",

0 commit comments

Comments
 (0)