fix super modifier parsing

This commit is contained in:
Sebastian Herrlinger
2025-12-11 19:18:27 +01:00
parent 639320b3e1
commit 80e04be84f
7 changed files with 146 additions and 27 deletions

View File

@@ -199,7 +199,7 @@ export function DialogModel(props: { providerID?: string }) {
<DialogSelect
keybind={[
{
keybind: { ctrl: true, name: "a", meta: false, shift: false, leader: false },
keybind: Keybind.parse("ctrl+a")[0],
title: connected() ? "Connect provider" : "View all providers",
onTrigger() {
dialog.replace(() => <DialogProvider />)

View File

@@ -10,6 +10,7 @@ import { useSync } from "@tui/context/sync"
import { Identifier } from "@/id/id"
import { createStore, produce } from "solid-js/store"
import { useKeybind } from "@tui/context/keybind"
import { Keybind } from "@/util/keybind"
import { usePromptHistory, type PromptInfo } from "./history"
import { type AutocompleteRef, Autocomplete } from "./autocomplete"
import { useCommandDialog } from "../dialog-command"
@@ -85,7 +86,7 @@ const TEXTAREA_ACTIONS = [
] as const
function mapTextareaKeybindings(
keybinds: Record<string, { ctrl: boolean; meta: boolean; shift: boolean; leader: boolean; name: string }[]>,
keybinds: Record<string, Keybind.Info[]>,
action: (typeof TEXTAREA_ACTIONS)[number],
): KeyBinding[] {
const configKey = `input_${action.replace(/-/g, "_")}`
@@ -96,6 +97,7 @@ function mapTextareaKeybindings(
ctrl: binding.ctrl || undefined,
meta: binding.meta || undefined,
shift: binding.shift || undefined,
super: binding.super || undefined,
action,
}))
}

View File

@@ -73,21 +73,11 @@ export const { use: useKeybind, provider: KeybindProvider } = createSimpleContex
return store.leader
},
parse(evt: ParsedKey): Keybind.Info {
if (evt.name === "\x1F")
return {
ctrl: true,
name: "_",
shift: false,
leader: false,
meta: false,
}
return {
ctrl: evt.ctrl,
name: evt.name,
shift: evt.shift,
leader: store.leader,
meta: evt.meta,
// Handle special case for Ctrl+Underscore (represented as \x1F)
if (evt.name === "\x1F") {
return Keybind.fromParsedKey({ ...evt, name: "_", ctrl: true }, store.leader)
}
return Keybind.fromParsedKey(evt, store.leader)
},
match(key: keyof KeybindsConfig, evt: ParsedKey) {
const keybind = keybinds()[key]

View File

@@ -512,8 +512,8 @@ export namespace Config {
input_delete_to_line_start: z.string().optional().default("ctrl+u").describe("Delete to start of line in input"),
input_backspace: z.string().optional().default("backspace,shift+backspace").describe("Backspace in input"),
input_delete: z.string().optional().default("ctrl+d,delete,shift+delete").describe("Delete character in input"),
input_undo: z.string().optional().default("ctrl+-").describe("Undo in input"),
input_redo: z.string().optional().default("ctrl+.").describe("Redo in input"),
input_undo: z.string().optional().default("ctrl+-,super+z").describe("Undo in input"),
input_redo: z.string().optional().default("ctrl+.,super+shift+z").describe("Redo in input"),
input_word_forward: z
.string()
.optional()

View File

@@ -1,16 +1,35 @@
import { isDeepEqual } from "remeda"
import type { ParsedKey } from "@opentui/core"
export namespace Keybind {
export type Info = {
ctrl: boolean
meta: boolean
shift: boolean
leader: boolean
name: string
/**
* Keybind info derived from OpenTUI's ParsedKey with our custom `leader` field.
* This ensures type compatibility and catches missing fields at compile time.
*/
export type Info = Pick<ParsedKey, "name" | "ctrl" | "meta" | "shift" | "super"> & {
leader: boolean // our custom field
}
export function match(a: Info, b: Info): boolean {
return isDeepEqual(a, b)
// Normalize super field (undefined and false are equivalent)
const normalizedA = { ...a, super: a.super ?? false }
const normalizedB = { ...b, super: b.super ?? false }
return isDeepEqual(normalizedA, normalizedB)
}
/**
* Convert OpenTUI's ParsedKey to our Keybind.Info format.
* This helper ensures all required fields are present and avoids manual object creation.
*/
export function fromParsedKey(key: ParsedKey, leader = false): Info {
return {
name: key.name,
ctrl: key.ctrl,
meta: key.meta,
shift: key.shift,
super: key.super ?? false,
leader,
}
}
export function toString(info: Info): string {
@@ -18,6 +37,7 @@ export namespace Keybind {
if (info.ctrl) parts.push("ctrl")
if (info.meta) parts.push("alt")
if (info.super) parts.push("super")
if (info.shift) parts.push("shift")
if (info.name) {
if (info.name === "delete") parts.push("del")
@@ -58,6 +78,9 @@ export namespace Keybind {
case "option":
info.meta = true
break
case "super":
info.super = true
break
case "shift":
info.shift = true
break