refactor(tui): remove legacy keymap layer (#37206)
Co-authored-by: Kit Langton <kit.langton@gmail.com> Co-authored-by: Dax Raad <d@ironbay.co>
This commit is contained in:
parent
e916b99742
commit
b4a4ef0b3c
19 changed files with 535 additions and 1025 deletions
|
|
@ -1,8 +1,7 @@
|
|||
import { createMemo } from "solid-js"
|
||||
import { DialogSelect, type DialogSelectRef } from "../ui/dialog-select"
|
||||
import { type DialogContext } from "../ui/dialog"
|
||||
import { COMMAND_PALETTE_COMMAND } from "../keymap"
|
||||
import { Keymap, type KeymapCommand } from "../context/keymap"
|
||||
import { COMMAND_PALETTE_COMMAND, Keymap, type KeymapCommand } from "../context/keymap"
|
||||
|
||||
function isSuggestedPaletteCommand(command: KeymapCommand) {
|
||||
const suggested = command.suggested
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import { useTerminalDimensions } from "@opentui/solid"
|
|||
import { Locale } from "../../util/locale"
|
||||
import type { PromptInfo, PromptPartRef } from "../../prompt/history"
|
||||
import { useFrecency } from "../../prompt/frecency"
|
||||
import { useBindings } from "../../keymap"
|
||||
import { Keymap } from "../../context/keymap"
|
||||
import { displayCharAt, mentionTriggerIndex } from "../../prompt/display"
|
||||
import type { FileSystemEntry } from "@opencode-ai/client"
|
||||
|
|
@ -578,48 +577,49 @@ export function Autocomplete(props: {
|
|||
setStore("selected", 0)
|
||||
}
|
||||
|
||||
useBindings(() => ({
|
||||
Keymap.createLayer(() => ({
|
||||
mode: "autocomplete",
|
||||
target: props.input,
|
||||
enabled: () => Boolean(store.visible),
|
||||
commands: [
|
||||
{
|
||||
name: "prompt.autocomplete.prev",
|
||||
id: "prompt.autocomplete.prev",
|
||||
title: "Previous autocomplete item",
|
||||
category: "Autocomplete",
|
||||
group: "Autocomplete",
|
||||
run() {
|
||||
setStore("input", "keyboard")
|
||||
move(-1)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prompt.autocomplete.next",
|
||||
id: "prompt.autocomplete.next",
|
||||
title: "Next autocomplete item",
|
||||
category: "Autocomplete",
|
||||
group: "Autocomplete",
|
||||
run() {
|
||||
setStore("input", "keyboard")
|
||||
move(1)
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prompt.autocomplete.hide",
|
||||
id: "prompt.autocomplete.hide",
|
||||
title: "Hide autocomplete",
|
||||
category: "Autocomplete",
|
||||
group: "Autocomplete",
|
||||
run() {
|
||||
hide()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prompt.autocomplete.select",
|
||||
id: "prompt.autocomplete.select",
|
||||
title: "Select autocomplete item",
|
||||
category: "Autocomplete",
|
||||
group: "Autocomplete",
|
||||
run() {
|
||||
select()
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "prompt.autocomplete.complete",
|
||||
id: "prompt.autocomplete.complete",
|
||||
title: "Complete autocomplete item",
|
||||
category: "Autocomplete",
|
||||
group: "Autocomplete",
|
||||
run() {
|
||||
const selected = options()[store.selected]
|
||||
if (selected?.isDirectory) {
|
||||
|
|
@ -631,13 +631,6 @@ export function Autocomplete(props: {
|
|||
},
|
||||
},
|
||||
],
|
||||
bindings: [
|
||||
"prompt.autocomplete.prev",
|
||||
"prompt.autocomplete.next",
|
||||
"prompt.autocomplete.hide",
|
||||
"prompt.autocomplete.select",
|
||||
"prompt.autocomplete.complete",
|
||||
].flatMap((command) => config.keybinds.get(command)),
|
||||
}))
|
||||
|
||||
function show(mode: "@" | "/") {
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ import {
|
|||
PasteEvent,
|
||||
decodePasteBytes,
|
||||
type KeyEvent,
|
||||
type Renderable,
|
||||
} from "@opentui/core"
|
||||
import type { CommandContext } from "@opentui/keymap"
|
||||
import { createEffect, createMemo, onMount, createSignal, onCleanup, on, Show, Switch, Match } from "solid-js"
|
||||
import { registerOpencodeSpinner } from "../register-spinner"
|
||||
import path from "path"
|
||||
|
|
@ -46,7 +44,6 @@ import { useToast } from "../../ui/toast"
|
|||
import { createFadeIn } from "../../util/signal"
|
||||
import { DialogSkill } from "../dialog-skill"
|
||||
import { useArgs } from "../../context/args"
|
||||
import { OPENCODE_BASE_MODE, useBindings, useCommandShortcut, useLeaderActive, useOpencodeKeymap } from "../../keymap"
|
||||
import { useConfig } from "../../config"
|
||||
import { usePromptMove } from "./move"
|
||||
import { readLocalAttachment } from "./local-attachment"
|
||||
|
|
@ -155,7 +152,7 @@ export function Prompt(props: PromptProps) {
|
|||
let anchor: BoxRenderable
|
||||
const [inputTarget, setInputTarget] = createSignal<TextareaRenderable | undefined>()
|
||||
|
||||
const leader = useLeaderActive()
|
||||
const leader = Keymap.useLeaderActive()
|
||||
const local = useLocal()
|
||||
const args = useArgs()
|
||||
const paths = useTuiPaths()
|
||||
|
|
@ -183,10 +180,10 @@ export function Prompt(props: PromptProps) {
|
|||
)
|
||||
const history = usePromptHistory()
|
||||
const stash = usePromptStash()
|
||||
const keymap = useOpencodeKeymap()
|
||||
const agentShortcut = useCommandShortcut("agent.cycle")
|
||||
const paletteShortcut = useCommandShortcut("command.palette.show")
|
||||
const liveWorkShortcut = useCommandShortcut("session.child.first")
|
||||
const keymap = Keymap.use()
|
||||
const agentShortcut = Keymap.useShortcut("agent.cycle")
|
||||
const paletteShortcut = Keymap.useShortcut("command.palette.show")
|
||||
const liveWorkShortcut = Keymap.useShortcut("session.child.first")
|
||||
const renderer = useRenderer()
|
||||
const exit = useExit()
|
||||
const dimensions = useTerminalDimensions()
|
||||
|
|
@ -387,7 +384,7 @@ export function Prompt(props: PromptProps) {
|
|||
title: "Clear prompt",
|
||||
name: "prompt.clear",
|
||||
category: "Prompt",
|
||||
hidden: true,
|
||||
palette: undefined,
|
||||
run: () => {
|
||||
clearPrompt()
|
||||
dialog.clear()
|
||||
|
|
@ -397,8 +394,10 @@ export function Prompt(props: PromptProps) {
|
|||
title: "Submit prompt",
|
||||
name: "prompt.submit",
|
||||
category: "Prompt",
|
||||
hidden: true,
|
||||
run: async () => {
|
||||
palette: undefined,
|
||||
run: async (_input: string | undefined, event?: KeyEvent) => {
|
||||
event?.preventDefault()
|
||||
event?.stopPropagation()
|
||||
if (!input.focused) return
|
||||
const handled = await submit()
|
||||
if (!handled) return
|
||||
|
|
@ -420,10 +419,10 @@ export function Prompt(props: PromptProps) {
|
|||
title: "Paste",
|
||||
name: "prompt.paste",
|
||||
category: "Prompt",
|
||||
hidden: true,
|
||||
run: async (ctx: CommandContext<Renderable, KeyEvent>) => {
|
||||
ctx.event.preventDefault()
|
||||
ctx.event.stopPropagation()
|
||||
palette: undefined,
|
||||
run: async (_input: string | undefined, event?: KeyEvent) => {
|
||||
event?.preventDefault()
|
||||
event?.stopPropagation()
|
||||
const content = await clipboard.read?.()
|
||||
if (content?.mime.startsWith("image/")) {
|
||||
await pasteAttachment({
|
||||
|
|
@ -441,7 +440,7 @@ export function Prompt(props: PromptProps) {
|
|||
title: "Interrupt session",
|
||||
name: "session.interrupt",
|
||||
category: "Session",
|
||||
hidden: true,
|
||||
palette: undefined,
|
||||
enabled: status() === "running",
|
||||
run: () => {
|
||||
if (auto()?.visible) return
|
||||
|
|
@ -472,7 +471,7 @@ export function Prompt(props: PromptProps) {
|
|||
title: "Background blocking tools",
|
||||
name: "session.background",
|
||||
category: "Session",
|
||||
hidden: true,
|
||||
palette: undefined,
|
||||
enabled: status() === "running",
|
||||
run: () => {
|
||||
if (auto()?.visible) return
|
||||
|
|
@ -564,18 +563,24 @@ export function Prompt(props: PromptProps) {
|
|||
move.open()
|
||||
},
|
||||
},
|
||||
].map((entry) => ({
|
||||
namespace: "palette",
|
||||
...entry,
|
||||
})),
|
||||
].map(
|
||||
({ name, category, ...command }) =>
|
||||
({
|
||||
id: name,
|
||||
group: category,
|
||||
bind: false,
|
||||
palette: true as const,
|
||||
...command,
|
||||
}) satisfies KeymapCommand,
|
||||
),
|
||||
)
|
||||
|
||||
useBindings(() => ({
|
||||
Keymap.createLayer(() => ({
|
||||
mode: "global",
|
||||
commands: promptCommands(),
|
||||
}))
|
||||
|
||||
useBindings(() => ({
|
||||
mode: OPENCODE_BASE_MODE,
|
||||
Keymap.createLayer(() => ({
|
||||
bindings: [
|
||||
"prompt.submit",
|
||||
"prompt.editor",
|
||||
|
|
@ -587,7 +592,7 @@ export function Prompt(props: PromptProps) {
|
|||
"session.interrupt",
|
||||
"session.background",
|
||||
"session.move",
|
||||
].flatMap((command) => config.keybinds.get(command)),
|
||||
],
|
||||
}))
|
||||
|
||||
const ref: PromptRef = {
|
||||
|
|
@ -803,33 +808,40 @@ export function Prompt(props: PromptProps) {
|
|||
))
|
||||
},
|
||||
},
|
||||
].map((entry) => ({
|
||||
namespace: "palette",
|
||||
...entry,
|
||||
})),
|
||||
].map(
|
||||
({ name, category, ...command }) =>
|
||||
({
|
||||
id: name,
|
||||
group: category,
|
||||
bind: false,
|
||||
palette: true as const,
|
||||
...command,
|
||||
}) satisfies KeymapCommand,
|
||||
),
|
||||
)
|
||||
|
||||
useBindings(() => ({
|
||||
Keymap.createLayer(() => ({
|
||||
mode: "global",
|
||||
commands: stashCommands(),
|
||||
}))
|
||||
|
||||
useBindings(() => {
|
||||
Keymap.createLayer(() => {
|
||||
return {
|
||||
target: inputTarget,
|
||||
enabled: inputTarget() !== undefined && !props.disabled,
|
||||
bindings: config.keybinds.get("prompt.paste"),
|
||||
bindings: ["prompt.paste"],
|
||||
}
|
||||
})
|
||||
|
||||
useBindings(() => {
|
||||
Keymap.createLayer(() => {
|
||||
return {
|
||||
target: inputTarget,
|
||||
enabled: inputTarget() !== undefined && !props.disabled && store.prompt.text !== "",
|
||||
bindings: config.keybinds.get("prompt.clear"),
|
||||
bindings: ["prompt.clear"],
|
||||
}
|
||||
})
|
||||
|
||||
useBindings(() => {
|
||||
Keymap.createLayer(() => {
|
||||
return {
|
||||
target: inputTarget,
|
||||
enabled: (() => {
|
||||
|
|
@ -842,12 +854,12 @@ export function Prompt(props: PromptProps) {
|
|||
input?.visualCursor.offset === 0
|
||||
)
|
||||
})(),
|
||||
bindings: [
|
||||
commands: [
|
||||
{
|
||||
key: "!",
|
||||
desc: "Shell mode",
|
||||
bind: "!",
|
||||
title: "Shell mode",
|
||||
group: "Prompt",
|
||||
cmd: () => {
|
||||
run: () => {
|
||||
setStore("placeholder", randomIndex(shell().length))
|
||||
setStore("mode", "shell")
|
||||
},
|
||||
|
|
@ -856,26 +868,28 @@ export function Prompt(props: PromptProps) {
|
|||
}
|
||||
})
|
||||
|
||||
useBindings(() => {
|
||||
Keymap.createLayer(() => {
|
||||
return {
|
||||
target: inputTarget,
|
||||
enabled: inputTarget() !== undefined && store.mode === "shell",
|
||||
bindings: [{ key: "escape", desc: "Exit shell mode", group: "Prompt", cmd: () => setStore("mode", "normal") }],
|
||||
commands: [{ bind: "escape", title: "Exit shell mode", group: "Prompt", run: () => setStore("mode", "normal") }],
|
||||
}
|
||||
})
|
||||
|
||||
useBindings(() => {
|
||||
Keymap.createLayer(() => {
|
||||
return {
|
||||
target: inputTarget,
|
||||
enabled: (() => {
|
||||
cursorVersion()
|
||||
return inputTarget() !== undefined && store.mode === "shell" && input?.visualCursor.offset === 0
|
||||
})(),
|
||||
bindings: [{ key: "backspace", desc: "Exit shell mode", group: "Prompt", cmd: () => setStore("mode", "normal") }],
|
||||
commands: [
|
||||
{ bind: "backspace", title: "Exit shell mode", group: "Prompt", run: () => setStore("mode", "normal") },
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
useBindings(() => {
|
||||
Keymap.createLayer(() => {
|
||||
return {
|
||||
priority: 1,
|
||||
target: inputTarget,
|
||||
|
|
@ -885,9 +899,9 @@ export function Prompt(props: PromptProps) {
|
|||
})(),
|
||||
commands: [
|
||||
{
|
||||
name: "prompt.history.previous",
|
||||
id: "prompt.history.previous",
|
||||
title: "Previous prompt history",
|
||||
category: "Prompt",
|
||||
group: "Prompt",
|
||||
run() {
|
||||
if (input.cursorOffset !== 0) {
|
||||
if (input.scrollY + input.visualCursor.visualRow === 0) {
|
||||
|
|
@ -908,11 +922,10 @@ export function Prompt(props: PromptProps) {
|
|||
},
|
||||
},
|
||||
],
|
||||
bindings: config.keybinds.get("prompt.history.previous"),
|
||||
}
|
||||
})
|
||||
|
||||
useBindings(() => {
|
||||
Keymap.createLayer(() => {
|
||||
return {
|
||||
priority: 1,
|
||||
target: inputTarget,
|
||||
|
|
@ -922,9 +935,9 @@ export function Prompt(props: PromptProps) {
|
|||
})(),
|
||||
commands: [
|
||||
{
|
||||
name: "prompt.history.next",
|
||||
id: "prompt.history.next",
|
||||
title: "Next prompt history",
|
||||
category: "Prompt",
|
||||
group: "Prompt",
|
||||
run() {
|
||||
if (input.cursorOffset !== input.plainText.length) {
|
||||
if (
|
||||
|
|
@ -948,7 +961,6 @@ export function Prompt(props: PromptProps) {
|
|||
},
|
||||
},
|
||||
],
|
||||
bindings: config.keybinds.get("prompt.history.next"),
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -1429,7 +1441,7 @@ export function Prompt(props: PromptProps) {
|
|||
// Windows Terminal <1.25 can surface image-only clipboard as an
|
||||
// empty bracketed paste. Windows Terminal 1.25+ does not.
|
||||
if (!pastedContent) {
|
||||
keymap.dispatchCommand("prompt.paste")
|
||||
keymap.dispatch("prompt.paste")
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1589,10 +1601,7 @@ export function Prompt(props: PromptProps) {
|
|||
</box>
|
||||
</Match>
|
||||
<Match when={true}>
|
||||
<Show
|
||||
when={!props.hint && locationLabel()}
|
||||
fallback={props.hint ?? <text />}
|
||||
>
|
||||
<Show when={!props.hint && locationLabel()} fallback={props.hint ?? <text />}>
|
||||
{(location) => (
|
||||
<text fg={themeV2.text.subdued()} wrapMode="none" truncate flexGrow={1} flexShrink={1}>
|
||||
{location()}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue