mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-10 07:08:28 +00:00
fix(app): prioritize shortcuts in terminal (#35668)
This commit is contained in:
parent
c5fe32fbb1
commit
65fd2e5c91
3 changed files with 50 additions and 6 deletions
|
|
@ -1,5 +1,5 @@
|
|||
import { describe, expect, test } from "bun:test"
|
||||
import { upsertCommandRegistration } from "./command"
|
||||
import { resolveKeybindOption, upsertCommandRegistration } from "./command"
|
||||
|
||||
describe("upsertCommandRegistration", () => {
|
||||
test("replaces keyed registrations", () => {
|
||||
|
|
@ -23,3 +23,19 @@ describe("upsertCommandRegistration", () => {
|
|||
expect(next[1]?.options).toBe(one)
|
||||
})
|
||||
})
|
||||
|
||||
describe("resolveKeybindOption", () => {
|
||||
test("prefers a matching contextual command over the global fallback", () => {
|
||||
const fallback = { id: "tab.close", title: "Close tab" }
|
||||
const contextual = { id: "terminal.close", title: "Close terminal", when: () => true }
|
||||
|
||||
expect(resolveKeybindOption([fallback, contextual], new KeyboardEvent("keydown"))).toBe(contextual)
|
||||
})
|
||||
|
||||
test("uses the global fallback outside the command context", () => {
|
||||
const fallback = { id: "tab.close", title: "Close tab" }
|
||||
const contextual = { id: "terminal.close", title: "Close terminal", when: () => false }
|
||||
|
||||
expect(resolveKeybindOption([fallback, contextual], new KeyboardEvent("keydown"))).toBe(fallback)
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -82,10 +82,15 @@ export interface CommandOption {
|
|||
suggested?: boolean
|
||||
disabled?: boolean
|
||||
hidden?: boolean
|
||||
when?: (event: KeyboardEvent) => boolean
|
||||
onSelect?: (source?: "palette" | "keybind" | "slash") => void
|
||||
onHighlight?: () => (() => void) | void
|
||||
}
|
||||
|
||||
export function resolveKeybindOption(candidates: CommandOption[] | undefined, event: KeyboardEvent) {
|
||||
return candidates?.find((option) => option.when?.(event)) ?? candidates?.find((option) => !option.when)
|
||||
}
|
||||
|
||||
type CommandSource = "palette" | "keybind" | "slash"
|
||||
|
||||
export type CommandCatalogItem = {
|
||||
|
|
@ -334,7 +339,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||
})
|
||||
|
||||
const keymap = createMemo(() => {
|
||||
const map = new Map<string, CommandOption>()
|
||||
const map = new Map<string, CommandOption[]>()
|
||||
for (const option of options()) {
|
||||
if (option.id.startsWith(SUGGESTED_PREFIX)) continue
|
||||
if (option.disabled) continue
|
||||
|
|
@ -344,8 +349,12 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||
for (const kb of keybinds) {
|
||||
if (!kb.key) continue
|
||||
const sig = signature(kb.key, kb.ctrl, kb.meta, kb.shift, kb.alt)
|
||||
if (map.has(sig)) continue
|
||||
map.set(sig, option)
|
||||
const existing = map.get(sig)
|
||||
if (existing) {
|
||||
existing.push(option)
|
||||
continue
|
||||
}
|
||||
map.set(sig, [option])
|
||||
}
|
||||
}
|
||||
return map
|
||||
|
|
@ -374,7 +383,7 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||
|
||||
const sig = signatureFromEvent(event)
|
||||
const isPalette = palette().has(sig)
|
||||
const option = keymap().get(sig)
|
||||
const option = resolveKeybindOption(keymap().get(sig), event)
|
||||
const modified = event.ctrlKey || event.metaKey || event.altKey
|
||||
const isTab = event.key === "Tab"
|
||||
|
||||
|
|
@ -383,17 +392,19 @@ export const { use: useCommand, provider: CommandProvider } = createSimpleContex
|
|||
|
||||
if (isPalette) {
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
showPalette()
|
||||
return
|
||||
}
|
||||
|
||||
if (!option) return
|
||||
event.preventDefault()
|
||||
event.stopPropagation()
|
||||
option.onSelect?.("keybind")
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
makeEventListener(document, "keydown", handleKeyDown)
|
||||
makeEventListener(document, "keydown", handleKeyDown, { capture: true })
|
||||
})
|
||||
|
||||
function register(cb: () => CommandOption[]): void
|
||||
|
|
|
|||
|
|
@ -269,6 +269,14 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||
view().terminal.open()
|
||||
}
|
||||
|
||||
const closeTerminal = () => {
|
||||
const id = terminal.active()
|
||||
if (!id) return
|
||||
const last = terminal.all().length === 1
|
||||
void terminal.close(id)
|
||||
if (last) view().terminal.close()
|
||||
}
|
||||
|
||||
const chooseMcp = () => {
|
||||
void openDialog(
|
||||
() => import("@/components/dialog-select-mcp"),
|
||||
|
|
@ -518,6 +526,15 @@ export const useSessionCommands = (actions: SessionCommandContext) => {
|
|||
]
|
||||
|
||||
const terminalCmds = () => [
|
||||
terminalCommand({
|
||||
id: "terminal.close",
|
||||
title: language.t("terminal.close"),
|
||||
keybind: "mod+w",
|
||||
hidden: true,
|
||||
when: (event) =>
|
||||
event.target instanceof Element && !!event.target.closest('[data-component="terminal"]'),
|
||||
onSelect: closeTerminal,
|
||||
}),
|
||||
terminalCommand({
|
||||
id: "terminal.new",
|
||||
title: language.t("command.terminal.new"),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue