Skip to content

Commit 63fbff5

Browse files
committed
tui: remove deprecated and unused keybinds to simplify configuration
Users no longer see confusing deprecated keybind options in config that were never implemented (switch_mode, file operations, etc). Keybinds now use consistent Zod schema defaults instead of hardcoded fallbacks, making configuration more predictable. Session interruption (esc) now works consistently through keybind system.
1 parent 8b2ce54 commit 63fbff5

File tree

7 files changed

+346
-163
lines changed

7 files changed

+346
-163
lines changed

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

Lines changed: 30 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -316,7 +316,9 @@ export function Prompt(props: PromptProps) {
316316

317317
// Expand pasted text inline before submitting
318318
const allExtmarks = input.extmarks.getAllForTypeId(promptPartTypeId)
319-
const sortedExtmarks = allExtmarks.sort((a: { start: number }, b: { start: number }) => b.start - a.start)
319+
const sortedExtmarks = allExtmarks.sort(
320+
(a: { start: number }, b: { start: number }) => b.start - a.start,
321+
)
320322

321323
for (const extmark of sortedExtmarks) {
322324
const partIndex = store.extmarkToPartIndex.get(extmark.id)
@@ -472,15 +474,28 @@ export function Prompt(props: PromptProps) {
472474
<box
473475
flexDirection="row"
474476
{...SplitBorder}
475-
borderColor={keybind.leader ? Theme.accent : store.mode === "shell" ? Theme.secondary : undefined}
477+
borderColor={
478+
keybind.leader ? Theme.accent : store.mode === "shell" ? Theme.secondary : undefined
479+
}
476480
justifyContent="space-evenly"
477481
>
478-
<box backgroundColor={Theme.backgroundElement} width={3} height="100%" alignItems="center" paddingTop={1}>
482+
<box
483+
backgroundColor={Theme.backgroundElement}
484+
width={3}
485+
height="100%"
486+
alignItems="center"
487+
paddingTop={1}
488+
>
479489
<text attributes={TextAttributes.BOLD} fg={Theme.primary}>
480490
{store.mode === "normal" ? ">" : "!"}
481491
</text>
482492
</box>
483-
<box paddingTop={1} paddingBottom={1} backgroundColor={Theme.backgroundElement} flexGrow={1}>
493+
<box
494+
paddingTop={1}
495+
paddingBottom={1}
496+
backgroundColor={Theme.backgroundElement}
497+
flexGrow={1}
498+
>
484499
<textarea
485500
placeholder={
486501
props.showPlaceholder
@@ -523,7 +538,10 @@ export function Prompt(props: PromptProps) {
523538
return
524539
}
525540
if (store.mode === "shell") {
526-
if ((e.name === "backspace" && input.visualCursor.offset === 0) || e.name === "escape") {
541+
if (
542+
(e.name === "backspace" && input.visualCursor.offset === 0) ||
543+
e.name === "escape"
544+
) {
527545
setStore("mode", "normal")
528546
e.preventDefault()
529547
return
@@ -554,7 +572,7 @@ export function Prompt(props: PromptProps) {
554572
input.cursorOffset = input.plainText.length
555573
}
556574
if (!autocomplete.visible) {
557-
if (e.name === "escape" && props.sessionID) {
575+
if (keybind.match("session_interrupt", e) && props.sessionID) {
558576
sdk.client.session.abort({
559577
path: {
560578
id: props.sessionID,
@@ -644,7 +662,12 @@ export function Prompt(props: PromptProps) {
644662
syntaxStyle={syntaxTheme}
645663
/>
646664
</box>
647-
<box backgroundColor={Theme.backgroundElement} width={1} justifyContent="center" alignItems="center"></box>
665+
<box
666+
backgroundColor={Theme.backgroundElement}
667+
width={1}
668+
justifyContent="center"
669+
alignItems="center"
670+
></box>
648671
</box>
649672
<box flexDirection="row" justifyContent="space-between">
650673
<text flexShrink={0} wrapMode="none">

packages/opencode/src/cli/cmd/tui/context/keybind.tsx

Lines changed: 1 addition & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,7 @@ export const { use: useKeybind, provider: KeybindProvider } = createSimpleContex
1414
const sync = useSync()
1515
const keybinds = createMemo(() => {
1616
return pipe(
17-
DEFAULT_KEYBINDS,
18-
(val) => Object.assign(val, sync.data.config.keybinds),
17+
sync.data.config.keybinds ?? {},
1918
mapValues((value) => Keybind.parse(value)),
2019
)
2120
})
@@ -102,46 +101,3 @@ export const { use: useKeybind, provider: KeybindProvider } = createSimpleContex
102101
return result
103102
},
104103
})
105-
106-
const DEFAULT_KEYBINDS: KeybindsConfig = {
107-
leader: "ctrl+x",
108-
app_help: "<leader>h",
109-
app_exit: "ctrl+c,<leader>q",
110-
status_view: "<leader>s",
111-
editor_open: "<leader>e",
112-
theme_list: "<leader>t",
113-
project_init: "<leader>i",
114-
tool_details: "<leader>d",
115-
thinking_blocks: "<leader>b",
116-
sidebar_toggle: "<leader>b",
117-
session_export: "<leader>x",
118-
session_new: "<leader>n",
119-
session_list: "<leader>l",
120-
session_share: "none",
121-
session_unshare: "none",
122-
session_interrupt: "esc",
123-
session_compact: "<leader>c",
124-
session_child_cycle: "ctrl+right",
125-
session_child_cycle_reverse: "ctrl+left",
126-
session_timeline: "<leader>t",
127-
messages_page_up: "pageup",
128-
messages_page_down: "pagedown",
129-
messages_half_page_up: "ctrl+alt+u",
130-
messages_half_page_down: "ctrl+alt+d",
131-
messages_first: "home",
132-
messages_last: "end",
133-
messages_copy: "<leader>y",
134-
messages_undo: "<leader>u",
135-
messages_redo: "<leader>r",
136-
model_list: "<leader>m",
137-
command_list: "ctrl+p",
138-
model_cycle_recent: "f2",
139-
model_cycle_recent_reverse: "shift+f2",
140-
agent_list: "<leader>a",
141-
agent_cycle: "tab",
142-
agent_cycle_reverse: "shift+tab",
143-
input_clear: "ctrl+c",
144-
input_paste: "ctrl+v",
145-
input_submit: "enter",
146-
input_newline: "ctrl+j,shift+enter",
147-
}

packages/opencode/src/cli/cmd/tui/routes/session/index.tsx

Lines changed: 96 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,14 @@ import { SplitBorder } from "@tui/component/border"
1818
import { syntaxTheme, Theme } from "@tui/context/theme"
1919
import { BoxRenderable, ScrollBoxRenderable, addDefaultParsers } from "@opentui/core"
2020
import { Prompt, type PromptRef } from "@tui/component/prompt"
21-
import type { AssistantMessage, Part, ToolPart, UserMessage, TextPart, ReasoningPart } from "@opencode-ai/sdk"
21+
import type {
22+
AssistantMessage,
23+
Part,
24+
ToolPart,
25+
UserMessage,
26+
TextPart,
27+
ReasoningPart,
28+
} from "@opencode-ai/sdk"
2229
import { useLocal } from "@tui/context/local"
2330
import { Locale } from "@/util/locale"
2431
import type { Tool } from "@/tool/tool"
@@ -177,19 +184,18 @@ export function Session() {
177184
disabled: !!session()?.share?.url,
178185
category: "Session",
179186
onSelect: async (dialog) => {
180-
await sdk.client.session.share({
181-
path: {
182-
id: route.sessionID,
183-
},
184-
})
187+
await sdk.client.session
188+
.share({
189+
path: {
190+
id: route.sessionID,
191+
},
192+
})
185193
.then((res) =>
186194
Clipboard.copy(res.data!.share!.url).catch(() =>
187-
toast.show({ message: "Failed to copy URL to clipboard", type: "error" })
188-
)
189-
)
190-
.then(() =>
191-
toast.show({ message: "Share URL copied to clipboard!", type: "success" })
195+
toast.show({ message: "Failed to copy URL to clipboard", type: "error" }),
196+
),
192197
)
198+
.then(() => toast.show({ message: "Share URL copied to clipboard!", type: "success" }))
193199
.catch(() => toast.show({ message: "Failed to share session", type: "error" }))
194200
dialog.clear()
195201
},
@@ -399,7 +405,14 @@ export function Session() {
399405
},
400406
}}
401407
>
402-
<box flexDirection="row" paddingBottom={1} paddingTop={1} paddingLeft={2} paddingRight={2} gap={2}>
408+
<box
409+
flexDirection="row"
410+
paddingBottom={1}
411+
paddingTop={1}
412+
paddingLeft={2}
413+
paddingRight={2}
414+
gap={2}
415+
>
403416
<box flexGrow={1} gap={1}>
404417
<Show when={session()}>
405418
<Show when={!sidebarVisible()}>
@@ -447,12 +460,18 @@ export function Session() {
447460
paddingTop={1}
448461
paddingBottom={1}
449462
paddingLeft={2}
450-
backgroundColor={hover() ? Theme.backgroundElement : Theme.backgroundPanel}
463+
backgroundColor={
464+
hover() ? Theme.backgroundElement : Theme.backgroundPanel
465+
}
451466
>
452-
<text fg={Theme.textMuted}>{revert()!.reverted.length} message reverted</text>
453467
<text fg={Theme.textMuted}>
454-
<span style={{ fg: Theme.text }}>{keybind.print("messages_redo")}</span> or /redo to
455-
restore
468+
{revert()!.reverted.length} message reverted
469+
</text>
470+
<text fg={Theme.textMuted}>
471+
<span style={{ fg: Theme.text }}>
472+
{keybind.print("messages_redo")}
473+
</span>{" "}
474+
or /redo to restore
456475
</text>
457476
<Show when={revert()!.diffFiles?.length}>
458477
<box marginTop={1}>
@@ -461,10 +480,16 @@ export function Session() {
461480
<text>
462481
{file.filename}
463482
<Show when={file.additions > 0}>
464-
<span style={{ fg: Theme.diffAdded }}> +{file.additions}</span>
483+
<span style={{ fg: Theme.diffAdded }}>
484+
{" "}
485+
+{file.additions}
486+
</span>
465487
</Show>
466488
<Show when={file.deletions > 0}>
467-
<span style={{ fg: Theme.diffRemoved }}> -{file.deletions}</span>
489+
<span style={{ fg: Theme.diffRemoved }}>
490+
{" "}
491+
-{file.deletions}
492+
</span>
468493
</Show>
469494
</text>
470495
)}
@@ -483,7 +508,9 @@ export function Session() {
483508
<UserMessage
484509
index={index()}
485510
onMouseUp={() =>
486-
dialog.replace(() => <DialogMessage messageID={message.id} sessionID={route.sessionID} />)
511+
dialog.replace(() => (
512+
<DialogMessage messageID={message.id} sessionID={route.sessionID} />
513+
))
487514
}
488515
message={message as UserMessage}
489516
parts={sync.data.part[message.id] ?? []}
@@ -539,7 +566,9 @@ function UserMessage(props: {
539566
index: number
540567
pending?: string
541568
}) {
542-
const text = createMemo(() => props.parts.flatMap((x) => (x.type === "text" && !x.synthetic ? [x] : []))[0])
569+
const text = createMemo(
570+
() => props.parts.flatMap((x) => (x.type === "text" && !x.synthetic ? [x] : []))[0],
571+
)
543572
const files = createMemo(() => props.parts.flatMap((x) => (x.type === "file" ? [x] : [])))
544573
const sync = useSync()
545574
const [hover, setHover] = createSignal(false)
@@ -579,8 +608,14 @@ function UserMessage(props: {
579608
})
580609
return (
581610
<text>
582-
<span style={{ bg: bg(), fg: Theme.background }}> {MIME_BADGE[file.mime] ?? file.mime} </span>
583-
<span style={{ bg: Theme.backgroundElement, fg: Theme.textMuted }}> {file.filename} </span>
611+
<span style={{ bg: bg(), fg: Theme.background }}>
612+
{" "}
613+
{MIME_BADGE[file.mime] ?? file.mime}{" "}
614+
</span>
615+
<span style={{ bg: Theme.backgroundElement, fg: Theme.textMuted }}>
616+
{" "}
617+
{file.filename}{" "}
618+
</span>
584619
</text>
585620
)
586621
}}
@@ -591,9 +626,16 @@ function UserMessage(props: {
591626
{sync.data.config.username ?? "You"}{" "}
592627
<Show
593628
when={queued()}
594-
fallback={<span style={{ fg: Theme.textMuted }}>({Locale.time(props.message.time.created)})</span>}
629+
fallback={
630+
<span style={{ fg: Theme.textMuted }}>
631+
({Locale.time(props.message.time.created)})
632+
</span>
633+
}
595634
>
596-
<span style={{ bg: Theme.accent, fg: Theme.backgroundPanel, bold: true }}> QUEUED </span>
635+
<span style={{ bg: Theme.accent, fg: Theme.backgroundPanel, bold: true }}>
636+
{" "}
637+
QUEUED{" "}
638+
</span>
597639
</Show>
598640
</text>
599641
</box>
@@ -632,7 +674,8 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
632674
<Show
633675
when={
634676
!props.message.time.completed ||
635-
(props.last && props.parts.some((item) => item.type === "step-finish" && item.reason === "tool-calls"))
677+
(props.last &&
678+
props.parts.some((item) => item.type === "step-finish" && item.reason === "tool-calls"))
636679
}
637680
>
638681
<box
@@ -644,7 +687,9 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
644687
customBorderChars={SplitBorder.customBorderChars}
645688
borderColor={Theme.backgroundElement}
646689
>
647-
<text fg={local.agent.color(props.message.mode)}>{Locale.titlecase(props.message.mode)}</text>
690+
<text fg={local.agent.color(props.message.mode)}>
691+
{Locale.titlecase(props.message.mode)}
692+
</text>
648693
<Shimmer text={`${props.message.modelID}`} color={Theme.text} />
649694
</box>
650695
</Show>
@@ -656,7 +701,9 @@ function AssistantMessage(props: { message: AssistantMessage; parts: Part[]; las
656701
>
657702
<box paddingLeft={3}>
658703
<text marginTop={1}>
659-
<span style={{ fg: local.agent.color(props.message.mode) }}>{Locale.titlecase(props.message.mode)}</span>{" "}
704+
<span style={{ fg: local.agent.color(props.message.mode) }}>
705+
{Locale.titlecase(props.message.mode)}
706+
</span>{" "}
660707
<span style={{ fg: Theme.textMuted }}>{props.message.modelID}</span>
661708
</text>
662709
</box>
@@ -682,7 +729,12 @@ function ReasoningPart(props: { part: ReasoningPart; message: AssistantMessage }
682729
customBorderChars={SplitBorder.customBorderChars}
683730
borderColor={Theme.backgroundPanel}
684731
>
685-
<box paddingTop={1} paddingBottom={1} paddingLeft={2} backgroundColor={Theme.backgroundPanel}>
732+
<box
733+
paddingTop={1}
734+
paddingBottom={1}
735+
paddingLeft={2}
736+
backgroundColor={Theme.backgroundPanel}
737+
>
686738
<text>{props.part.text.trim()}</text>
687739
</box>
688740
</box>
@@ -814,7 +866,10 @@ function GenericTool(props: ToolProps<any>) {
814866
}
815867

816868
const ToolRegistry = (() => {
817-
const state: Record<string, { name: string; container: "inline" | "block"; render?: Component<ToolProps<any>> }> = {}
869+
const state: Record<
870+
string,
871+
{ name: string; container: "inline" | "block"; render?: Component<ToolProps<any>> }
872+
> = {}
818873
function register<T extends Tool.Info>(input: {
819874
name: string
820875
container: "inline" | "block"
@@ -908,10 +963,16 @@ ToolRegistry.register<typeof WriteTool>({
908963
</ToolTitle>
909964
<box flexDirection="row">
910965
<box flexShrink={0}>
911-
<For each={numbers()}>{(value) => <text style={{ fg: Theme.textMuted }}>{value}</text>}</For>
966+
<For each={numbers()}>
967+
{(value) => <text style={{ fg: Theme.textMuted }}>{value}</text>}
968+
</For>
912969
</box>
913970
<box paddingLeft={1} flexGrow={1}>
914-
<code filetype={filetype(props.input.filePath!)} syntaxStyle={syntaxTheme} content={code()} />
971+
<code
972+
filetype={filetype(props.input.filePath!)}
973+
syntaxStyle={syntaxTheme}
974+
content={code()}
975+
/>
915976
</box>
916977
</box>
917978
</>
@@ -926,7 +987,8 @@ ToolRegistry.register<typeof GlobTool>({
926987
return (
927988
<>
928989
<ToolTitle icon="✱" fallback="Finding files..." when={props.input.pattern}>
929-
Glob "{props.input.pattern}" <Show when={props.input.path}>in {normalizePath(props.input.path)} </Show>
990+
Glob "{props.input.pattern}"{" "}
991+
<Show when={props.input.path}>in {normalizePath(props.input.path)} </Show>
930992
<Show when={props.metadata.count}>({props.metadata.count} matches)</Show>
931993
</ToolTitle>
932994
</>
@@ -940,7 +1002,8 @@ ToolRegistry.register<typeof GrepTool>({
9401002
render(props) {
9411003
return (
9421004
<ToolTitle icon="✱" fallback="Searching content..." when={props.input.pattern}>
943-
Grep "{props.input.pattern}" <Show when={props.input.path}>in {normalizePath(props.input.path)} </Show>
1005+
Grep "{props.input.pattern}"{" "}
1006+
<Show when={props.input.path}>in {normalizePath(props.input.path)} </Show>
9441007
<Show when={props.metadata.matches}>({props.metadata.matches} matches)</Show>
9451008
</ToolTitle>
9461009
)

0 commit comments

Comments
 (0)