|
|
@@ -1,607 +0,0 @@
|
|
|
-/** @jsxImportSource @opentui/solid */
|
|
|
-import { RGBA, TextAttributes, type KeyEvent, type Renderable } from "@opentui/core"
|
|
|
-import { useTerminalDimensions } from "@opentui/solid"
|
|
|
-import { createEffect, createMemo, createSignal, For, Show } from "solid-js"
|
|
|
-import { Keymap } from "../../context/keymap"
|
|
|
-import type { ActiveKey } from "@opentui/keymap"
|
|
|
-import type { TuiPlugin, TuiPluginApi } from "@opencode-ai/plugin/v1/tui"
|
|
|
-import type { BuiltinTuiPlugin } from "../builtins"
|
|
|
-
|
|
|
-const command = {
|
|
|
- toggle: "which-key.toggle",
|
|
|
- toggleLayout: "which-key.layout.toggle",
|
|
|
- togglePending: "which-key.pending.toggle",
|
|
|
- groupPrevious: "which-key.group.previous",
|
|
|
- groupNext: "which-key.group.next",
|
|
|
- scrollUp: "which-key.scroll.up",
|
|
|
- scrollDown: "which-key.scroll.down",
|
|
|
- pageUp: "which-key.page.up",
|
|
|
- pageDown: "which-key.page.down",
|
|
|
- home: "which-key.home",
|
|
|
- end: "which-key.end",
|
|
|
-} as const
|
|
|
-
|
|
|
-const LAYER_PRIORITY = 900
|
|
|
-const toggleCommands = [command.toggle, command.toggleLayout, command.togglePending] as const
|
|
|
-const scrollCommands = [
|
|
|
- command.scrollUp,
|
|
|
- command.scrollDown,
|
|
|
- command.pageUp,
|
|
|
- command.pageDown,
|
|
|
- command.home,
|
|
|
- command.end,
|
|
|
-] as const
|
|
|
-const panelCommands = [command.groupPrevious, command.groupNext, ...scrollCommands] as const
|
|
|
-const COLUMN_GAP = 4
|
|
|
-const TAB_GAP = 3
|
|
|
-const MIN_TAB_GAP = 1
|
|
|
-const TAB_CONTENT_GAP = 1
|
|
|
-const MIN_COLUMN_WIDTH = 28
|
|
|
-const MAX_COLUMN_WIDTH = 44
|
|
|
-const PANEL_HEIGHT_RATIO = 0.3
|
|
|
-const MIN_PANEL_HEIGHT = 8
|
|
|
-const MAX_PANEL_HEIGHT = 16
|
|
|
-const PANEL_TOP_PADDING = 1
|
|
|
-const FOOTER_HEIGHT = 1
|
|
|
-const FOOTER_MARGIN = 1
|
|
|
-const UNKNOWN = "Unknown"
|
|
|
-
|
|
|
-type Layout = "dock" | "overlay"
|
|
|
-
|
|
|
-type Color = RGBA | string
|
|
|
-
|
|
|
-type Skin = {
|
|
|
- panel: Color
|
|
|
- text: Color
|
|
|
- muted: Color
|
|
|
- subtle: Color
|
|
|
- key: Color
|
|
|
- accent: Color
|
|
|
- tab: Color
|
|
|
- tabText: Color
|
|
|
-}
|
|
|
-
|
|
|
-type Entry = {
|
|
|
- type: "entry"
|
|
|
- key: string
|
|
|
- label: string
|
|
|
- group: string
|
|
|
- continues: boolean
|
|
|
-}
|
|
|
-
|
|
|
-type Group = {
|
|
|
- label: string
|
|
|
- entries: Entry[]
|
|
|
-}
|
|
|
-
|
|
|
-type HeaderItem = { type: "tab"; group: Group } | { type: "scroll" }
|
|
|
-
|
|
|
-type GroupHeader = {
|
|
|
- type: "group"
|
|
|
- label: string
|
|
|
-}
|
|
|
-
|
|
|
-type Item = Entry | GroupHeader
|
|
|
-
|
|
|
-function text(value: unknown) {
|
|
|
- if (typeof value !== "string") return undefined
|
|
|
- const trimmed = value.trim()
|
|
|
- return trimmed || undefined
|
|
|
-}
|
|
|
-
|
|
|
-function ink(api: TuiPluginApi, name: string, fallback: string): Color {
|
|
|
- const value = Reflect.get(api.theme.current, name)
|
|
|
- if (typeof value === "string") return value
|
|
|
- if (value instanceof RGBA) return value
|
|
|
- return fallback
|
|
|
-}
|
|
|
-
|
|
|
-function skin(api: TuiPluginApi): Skin {
|
|
|
- return {
|
|
|
- panel: ink(api, "backgroundMenu", "#1c1c1c"),
|
|
|
- text: ink(api, "text", "#f0f0f0"),
|
|
|
- muted: ink(api, "textMuted", "#a5a5a5"),
|
|
|
- subtle: ink(api, "borderSubtle", "#6f6f6f"),
|
|
|
- key: ink(api, "warning", "#ffd75f"),
|
|
|
- accent: ink(api, "primary", "#5f87ff"),
|
|
|
- tab: ink(api, "primary", "#5f87ff"),
|
|
|
- tabText: ink(api, "selectedListItemText", "#ffffff"),
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function activeKeyLabel(active: ActiveKey<Renderable, KeyEvent>) {
|
|
|
- if (active.continues) return text(active.tokenName) ?? text(active.display) ?? UNKNOWN
|
|
|
- return (
|
|
|
- text(active.commandAttrs?.title) ?? text(active.bindingAttrs?.desc) ?? text(active.commandAttrs?.desc) ?? UNKNOWN
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-function activeKeyGroup(active: ActiveKey<Renderable, KeyEvent>) {
|
|
|
- if (active.continues) return "System"
|
|
|
- return text(active.commandAttrs?.category) ?? text(active.bindingAttrs?.group) ?? UNKNOWN
|
|
|
-}
|
|
|
-
|
|
|
-function activeKeyEntry(api: TuiPluginApi, active: ActiveKey<Renderable, KeyEvent>): Entry {
|
|
|
- const key = api.keys.formatSequence([
|
|
|
- {
|
|
|
- stroke: active.stroke,
|
|
|
- display: active.display,
|
|
|
- tokenName: active.tokenName,
|
|
|
- },
|
|
|
- ])
|
|
|
- const label = activeKeyLabel(active)
|
|
|
- return {
|
|
|
- type: "entry",
|
|
|
- key,
|
|
|
- label: active.continues ? `+${label}` : label,
|
|
|
- group: activeKeyGroup(active),
|
|
|
- continues: active.continues,
|
|
|
- }
|
|
|
-}
|
|
|
-
|
|
|
-function grouped(entries: Entry[]): Group[] {
|
|
|
- const map = new Map<string, Entry[]>()
|
|
|
- for (const entry of entries) map.set(entry.group, [...(map.get(entry.group) ?? []), entry])
|
|
|
- return [...map]
|
|
|
- .map(([label, entries]) => ({
|
|
|
- label,
|
|
|
- entries: entries.toSorted(
|
|
|
- (a, b) =>
|
|
|
- Number(b.continues) - Number(a.continues) || a.label.localeCompare(b.label) || a.key.localeCompare(b.key),
|
|
|
- ),
|
|
|
- }))
|
|
|
- .toSorted((a, b) => a.label.localeCompare(b.label))
|
|
|
-}
|
|
|
-
|
|
|
-function commandShortcut(_api: TuiPluginApi, name: string) {
|
|
|
- const shortcuts = Keymap.useShortcuts()
|
|
|
- return () => shortcuts.get(name) ?? ""
|
|
|
-}
|
|
|
-
|
|
|
-function layout(value: unknown): Layout {
|
|
|
- if (value === "overlay") return "overlay"
|
|
|
- return "dock"
|
|
|
-}
|
|
|
-
|
|
|
-function HomeHint(props: { api: TuiPluginApi }) {
|
|
|
- const trigger = commandShortcut(props.api, command.toggle)
|
|
|
- const look = createMemo(() => skin(props.api))
|
|
|
-
|
|
|
- return (
|
|
|
- <box width="100%" maxWidth={75} alignItems="center" paddingTop={1} flexShrink={0}>
|
|
|
- <text fg={look().muted} wrapMode="none">
|
|
|
- Show keyboard shortcuts with <span style={{ fg: look().subtle }}>{trigger() || command.toggle}</span>
|
|
|
- </text>
|
|
|
- </box>
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-function WhichKeyPanel(props: {
|
|
|
- api: TuiPluginApi
|
|
|
- layout: Layout
|
|
|
- mode: () => Layout
|
|
|
- pendingPreview: () => boolean
|
|
|
- pinned: () => boolean
|
|
|
-}) {
|
|
|
- const dimensions = useTerminalDimensions()
|
|
|
- const [offset, setOffset] = createSignal(0)
|
|
|
- const [activeGroup, setActiveGroup] = createSignal<string | undefined>()
|
|
|
- const pending = Keymap.usePendingSequence()
|
|
|
- const active = Keymap.useActiveKeys()
|
|
|
- const pendingActive = createMemo(() => pending().length > 0 && active().length > 0)
|
|
|
- const pendingAutoVisible = createMemo(() => props.mode() === "overlay" && props.pendingPreview() && pendingActive())
|
|
|
- const visible = createMemo(() => props.pinned() || pendingAutoVisible())
|
|
|
- const pendingMode = createMemo(() => visible() && pendingActive())
|
|
|
- const left = 0
|
|
|
- const width = createMemo(() => Math.max(1, dimensions().width))
|
|
|
- const panelHeight = createMemo(() =>
|
|
|
- Math.max(MIN_PANEL_HEIGHT, Math.min(MAX_PANEL_HEIGHT, Math.floor(dimensions().height * PANEL_HEIGHT_RATIO))),
|
|
|
- )
|
|
|
- const contentWidth = createMemo(() => Math.max(1, width() - 2))
|
|
|
- const columns = createMemo(() =>
|
|
|
- Math.max(1, Math.min(3, Math.floor((contentWidth() + COLUMN_GAP) / (MAX_COLUMN_WIDTH + COLUMN_GAP)) || 1)),
|
|
|
- )
|
|
|
- const entries = createMemo(() => active().map((item) => activeKeyEntry(props.api, item)))
|
|
|
- const groups = createMemo(() => grouped(entries()))
|
|
|
- const tabsVisible = createMemo(() => !pendingMode() && groups().length > 0)
|
|
|
- const headerVisible = createMemo(() => tabsVisible() || pendingMode())
|
|
|
- const footerVisible = createMemo(() => !pendingMode())
|
|
|
- const rows = createMemo(() =>
|
|
|
- Math.max(
|
|
|
- 1,
|
|
|
- panelHeight() -
|
|
|
- PANEL_TOP_PADDING -
|
|
|
- (headerVisible() ? 1 : 0) -
|
|
|
- (tabsVisible() ? TAB_CONTENT_GAP : 0) -
|
|
|
- (footerVisible() ? FOOTER_MARGIN + FOOTER_HEIGHT : 0),
|
|
|
- ),
|
|
|
- )
|
|
|
- const pageSize = createMemo(() => rows() * columns())
|
|
|
- const currentGroup = createMemo(() => {
|
|
|
- const group = activeGroup()
|
|
|
- return groups().find((item) => item.label === group) ?? groups()[0]
|
|
|
- })
|
|
|
- const activeEntries = createMemo(() => currentGroup()?.entries ?? [])
|
|
|
- const items = createMemo<Item[]>(() => {
|
|
|
- if (!pendingMode()) return activeEntries()
|
|
|
- return groups().flatMap((group) => [{ type: "group", label: group.label } satisfies GroupHeader, ...group.entries])
|
|
|
- })
|
|
|
- const maxOffset = createMemo(() => Math.max(0, items().length - pageSize()))
|
|
|
- const shown = createMemo(() => {
|
|
|
- const columnsItems: Item[][] = []
|
|
|
- let index = offset()
|
|
|
- for (let column = 0; column < columns() && index < items().length; column++) {
|
|
|
- const list: Item[] = []
|
|
|
- while (list.length < rows() && index < items().length) {
|
|
|
- list.push(items()[index]!)
|
|
|
- index += 1
|
|
|
- }
|
|
|
- columnsItems.push(list)
|
|
|
- }
|
|
|
- return columnsItems
|
|
|
- })
|
|
|
- const rowIndexes = createMemo(() => Array.from({ length: rows() }, (_, index) => index))
|
|
|
- const trigger = commandShortcut(props.api, command.toggle)
|
|
|
- const modeTrigger = commandShortcut(props.api, command.toggleLayout)
|
|
|
- const upActive = createMemo(() => offset() > 0)
|
|
|
- const downActive = createMemo(() => offset() < maxOffset())
|
|
|
- const scrollable = createMemo(() => maxOffset() > 0)
|
|
|
- const headerItems = createMemo<HeaderItem[]>(() => [
|
|
|
- ...(tabsVisible() ? groups().map((group) => ({ type: "tab" as const, group })) : []),
|
|
|
- ...(scrollable() ? [{ type: "scroll" as const }] : []),
|
|
|
- ])
|
|
|
- const tabGap = createMemo(() => {
|
|
|
- const itemCount = headerItems().length
|
|
|
- if (itemCount <= 1) return 0
|
|
|
- const itemWidth = headerItems().reduce(
|
|
|
- (sum, item) => sum + (item.type === "tab" ? item.group.label.length + 2 : 3),
|
|
|
- 0,
|
|
|
- )
|
|
|
- return Math.max(MIN_TAB_GAP, Math.min(TAB_GAP, Math.floor((contentWidth() - itemWidth) / (itemCount - 1))))
|
|
|
- })
|
|
|
- const nextMode = createMemo(() => (props.mode() === "dock" ? "overlay" : "dock"))
|
|
|
- const look = createMemo(() => skin(props.api))
|
|
|
- const columnWidth = createMemo(() =>
|
|
|
- Math.max(1, Math.min(MAX_COLUMN_WIDTH, Math.floor((contentWidth() - (columns() - 1) * COLUMN_GAP) / columns()))),
|
|
|
- )
|
|
|
- const clamp = (value: number) => Math.max(0, Math.min(maxOffset(), value))
|
|
|
- const scroll = (delta: number) => setOffset((value) => clamp(value + delta))
|
|
|
- const moveGroup = (delta: number) => {
|
|
|
- if (pendingMode()) return
|
|
|
- const list = groups()
|
|
|
- if (!list.length) return
|
|
|
- const index = Math.max(
|
|
|
- 0,
|
|
|
- list.findIndex((item) => item.label === currentGroup()?.label),
|
|
|
- )
|
|
|
- setActiveGroup(list[(index + delta + list.length) % list.length]!.label)
|
|
|
- setOffset(0)
|
|
|
- }
|
|
|
-
|
|
|
- Keymap.createLayer(() => ({
|
|
|
- priority: 1000,
|
|
|
- enabled: visible(),
|
|
|
- commands: [
|
|
|
- {
|
|
|
- id: command.groupPrevious,
|
|
|
- bind: false,
|
|
|
- title: "Previous key binding group",
|
|
|
- description: "Show the previous which-key group",
|
|
|
- group: "System",
|
|
|
- run() {
|
|
|
- moveGroup(-1)
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- id: command.groupNext,
|
|
|
- bind: false,
|
|
|
- title: "Next key binding group",
|
|
|
- description: "Show the next which-key group",
|
|
|
- group: "System",
|
|
|
- run() {
|
|
|
- moveGroup(1)
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- id: command.scrollUp,
|
|
|
- bind: false,
|
|
|
- title: "Scroll key bindings up",
|
|
|
- description: "Scroll the which-key panel up",
|
|
|
- group: "System",
|
|
|
- run() {
|
|
|
- scroll(-columns())
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- id: command.scrollDown,
|
|
|
- bind: false,
|
|
|
- title: "Scroll key bindings down",
|
|
|
- description: "Scroll the which-key panel down",
|
|
|
- group: "System",
|
|
|
- run() {
|
|
|
- scroll(columns())
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- id: command.pageUp,
|
|
|
- bind: false,
|
|
|
- title: "Page key bindings up",
|
|
|
- description: "Page the which-key panel up",
|
|
|
- group: "System",
|
|
|
- run() {
|
|
|
- scroll(-pageSize())
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- id: command.pageDown,
|
|
|
- bind: false,
|
|
|
- title: "Page key bindings down",
|
|
|
- description: "Page the which-key panel down",
|
|
|
- group: "System",
|
|
|
- run() {
|
|
|
- scroll(pageSize())
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- id: command.home,
|
|
|
- bind: false,
|
|
|
- title: "First key binding",
|
|
|
- description: "Jump to the first which-key binding",
|
|
|
- group: "System",
|
|
|
- run() {
|
|
|
- setOffset(0)
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- id: command.end,
|
|
|
- bind: false,
|
|
|
- title: "Last key binding",
|
|
|
- description: "Jump to the last which-key binding",
|
|
|
- group: "System",
|
|
|
- run() {
|
|
|
- setOffset(maxOffset())
|
|
|
- },
|
|
|
- },
|
|
|
- ],
|
|
|
- bindings: pendingMode() ? scrollCommands : panelCommands,
|
|
|
- }))
|
|
|
-
|
|
|
- createEffect(() => {
|
|
|
- if (pendingMode()) return
|
|
|
- const group = currentGroup()
|
|
|
- if (group?.label === activeGroup()) return
|
|
|
- setActiveGroup(group?.label)
|
|
|
- })
|
|
|
-
|
|
|
- createEffect(() => {
|
|
|
- if (pendingMode()) return
|
|
|
- activeGroup()
|
|
|
- setOffset(0)
|
|
|
- })
|
|
|
-
|
|
|
- createEffect(() => {
|
|
|
- if (!visible()) setOffset(0)
|
|
|
- })
|
|
|
-
|
|
|
- createEffect(() => {
|
|
|
- pending()
|
|
|
- setOffset(0)
|
|
|
- })
|
|
|
-
|
|
|
- createEffect(() => {
|
|
|
- setOffset((value) => clamp(value))
|
|
|
- })
|
|
|
-
|
|
|
- return (
|
|
|
- <Show when={visible()}>
|
|
|
- <box
|
|
|
- position={props.layout === "overlay" ? "absolute" : "relative"}
|
|
|
- zIndex={3500}
|
|
|
- left={left}
|
|
|
- bottom={props.layout === "overlay" ? 0 : undefined}
|
|
|
- width={dimensions().width}
|
|
|
- height={panelHeight()}
|
|
|
- backgroundColor={look().panel}
|
|
|
- paddingLeft={1}
|
|
|
- paddingRight={1}
|
|
|
- paddingTop={1}
|
|
|
- flexShrink={0}
|
|
|
- flexDirection="column"
|
|
|
- >
|
|
|
- <Show when={headerVisible()}>
|
|
|
- <box width="100%" flexDirection="row" justifyContent="center" gap={tabGap()} flexShrink={0}>
|
|
|
- <For each={headerItems()}>
|
|
|
- {(item) => (
|
|
|
- <Show
|
|
|
- when={item.type === "tab" ? item.group : undefined}
|
|
|
- fallback={
|
|
|
- <box flexShrink={0}>
|
|
|
- <text wrapMode="none">
|
|
|
- <span style={{ fg: upActive() ? look().text : look().muted }}>↑</span>
|
|
|
- <span style={{ fg: look().muted }}> </span>
|
|
|
- <span style={{ fg: downActive() ? look().text : look().muted }}>↓</span>
|
|
|
- </text>
|
|
|
- </box>
|
|
|
- }
|
|
|
- >
|
|
|
- {(group) => {
|
|
|
- const selected = createMemo(() => currentGroup()?.label === group().label)
|
|
|
- return (
|
|
|
- <box
|
|
|
- paddingLeft={1}
|
|
|
- paddingRight={1}
|
|
|
- flexShrink={0}
|
|
|
- backgroundColor={selected() ? look().tab : undefined}
|
|
|
- onMouseDown={() => {
|
|
|
- setActiveGroup(group().label)
|
|
|
- setOffset(0)
|
|
|
- }}
|
|
|
- >
|
|
|
- <text
|
|
|
- fg={selected() ? look().tabText : look().muted}
|
|
|
- attributes={selected() ? TextAttributes.BOLD : undefined}
|
|
|
- wrapMode="none"
|
|
|
- >
|
|
|
- {group().label}
|
|
|
- </text>
|
|
|
- </box>
|
|
|
- )
|
|
|
- }}
|
|
|
- </Show>
|
|
|
- )}
|
|
|
- </For>
|
|
|
- </box>
|
|
|
- </Show>
|
|
|
- <Show when={tabsVisible()}>
|
|
|
- <box height={TAB_CONTENT_GAP} flexShrink={0} />
|
|
|
- </Show>
|
|
|
- <box height={rows()} flexShrink={0} flexDirection="column">
|
|
|
- <Show when={shown().length > 0} fallback={<text fg={look().muted}>No reachable bindings</text>}>
|
|
|
- <For each={rowIndexes()}>
|
|
|
- {(row) => (
|
|
|
- <box width="100%" flexDirection="row" justifyContent="center" gap={COLUMN_GAP}>
|
|
|
- <For each={shown()}>
|
|
|
- {(column) => {
|
|
|
- const item = createMemo(() => column[row])
|
|
|
- const entry = createMemo(() => {
|
|
|
- const value = item()
|
|
|
- if (value?.type !== "entry") return undefined
|
|
|
- return value
|
|
|
- })
|
|
|
- return (
|
|
|
- <box width={columnWidth()} flexDirection="row" gap={1} justifyContent="space-between">
|
|
|
- <Show when={item()}>
|
|
|
- {(value) => (
|
|
|
- <Show
|
|
|
- when={entry()}
|
|
|
- fallback={
|
|
|
- <text fg={look().accent} attributes={TextAttributes.BOLD} wrapMode="none" truncate>
|
|
|
- {value().label}
|
|
|
- </text>
|
|
|
- }
|
|
|
- >
|
|
|
- {(binding) => (
|
|
|
- <>
|
|
|
- <box flexGrow={1} minWidth={0}>
|
|
|
- <text
|
|
|
- fg={binding().continues ? look().accent : look().muted}
|
|
|
- wrapMode="none"
|
|
|
- truncate
|
|
|
- >
|
|
|
- {binding().label}
|
|
|
- </text>
|
|
|
- </box>
|
|
|
- <box flexShrink={0}>
|
|
|
- <text fg={look().text} attributes={TextAttributes.BOLD} wrapMode="none" truncate>
|
|
|
- {binding().key}
|
|
|
- </text>
|
|
|
- </box>
|
|
|
- </>
|
|
|
- )}
|
|
|
- </Show>
|
|
|
- )}
|
|
|
- </Show>
|
|
|
- </box>
|
|
|
- )
|
|
|
- }}
|
|
|
- </For>
|
|
|
- </box>
|
|
|
- )}
|
|
|
- </For>
|
|
|
- </Show>
|
|
|
- </box>
|
|
|
- <Show when={footerVisible()}>
|
|
|
- <box height={FOOTER_MARGIN} flexShrink={0} />
|
|
|
- <box width="100%" flexDirection="row" justifyContent="space-between" flexShrink={0}>
|
|
|
- <box>
|
|
|
- <text fg={look().text} wrapMode="none">
|
|
|
- toggle <span style={{ fg: look().subtle }}>{trigger() || command.toggle}</span>
|
|
|
- </text>
|
|
|
- </box>
|
|
|
- <box>
|
|
|
- <text fg={look().text} wrapMode="none">
|
|
|
- {nextMode()} <span style={{ fg: look().subtle }}>{modeTrigger() || command.toggleLayout}</span>
|
|
|
- </text>
|
|
|
- </box>
|
|
|
- </box>
|
|
|
- </Show>
|
|
|
- </box>
|
|
|
- </Show>
|
|
|
- )
|
|
|
-}
|
|
|
-
|
|
|
-const tui: TuiPlugin = async (api) => {
|
|
|
- const [pinned, setPinned] = createSignal(false)
|
|
|
- const [mode, setMode] = createSignal(layout("dock"))
|
|
|
- const [pendingPreview, setPendingPreview] = createSignal(false)
|
|
|
-
|
|
|
- api.keymap.registerLayer({
|
|
|
- priority: LAYER_PRIORITY,
|
|
|
- commands: [
|
|
|
- {
|
|
|
- name: command.toggle,
|
|
|
- title: "Show key bindings",
|
|
|
- desc: "Toggle which-key overlay",
|
|
|
- category: "System",
|
|
|
- run() {
|
|
|
- setPinned((value) => !value)
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- name: command.toggleLayout,
|
|
|
- title: "Toggle key bindings layout",
|
|
|
- desc: "Switch which-key between dock and overlay mode",
|
|
|
- category: "System",
|
|
|
- run() {
|
|
|
- setMode((value) => {
|
|
|
- const next = value === "dock" ? "overlay" : "dock"
|
|
|
- return next
|
|
|
- })
|
|
|
- },
|
|
|
- },
|
|
|
- {
|
|
|
- name: command.togglePending,
|
|
|
- title: "Toggle pending key preview",
|
|
|
- desc: "Automatically show which-key for pending key sequences in overlay mode",
|
|
|
- category: "System",
|
|
|
- run() {
|
|
|
- setPendingPreview((value) => {
|
|
|
- return !value
|
|
|
- })
|
|
|
- },
|
|
|
- },
|
|
|
- ],
|
|
|
- bindings: toggleCommands.flatMap((command) => api.tuiConfig.keybinds.get(command)),
|
|
|
- })
|
|
|
-
|
|
|
- api.slots.register({
|
|
|
- order: 200,
|
|
|
- slots: {
|
|
|
- home_bottom() {
|
|
|
- return <HomeHint api={api} />
|
|
|
- },
|
|
|
- app() {
|
|
|
- return (
|
|
|
- <Show when={mode() === "overlay"}>
|
|
|
- <WhichKeyPanel api={api} layout="overlay" mode={mode} pendingPreview={pendingPreview} pinned={pinned} />
|
|
|
- </Show>
|
|
|
- )
|
|
|
- },
|
|
|
- app_bottom() {
|
|
|
- return (
|
|
|
- <Show when={mode() === "dock"}>
|
|
|
- <WhichKeyPanel api={api} layout="dock" mode={mode} pendingPreview={pendingPreview} pinned={pinned} />
|
|
|
- </Show>
|
|
|
- )
|
|
|
- },
|
|
|
- },
|
|
|
- })
|
|
|
-}
|
|
|
-
|
|
|
-const plugin: BuiltinTuiPlugin = {
|
|
|
- id: "which-key",
|
|
|
- enabled: false,
|
|
|
- tui,
|
|
|
-}
|
|
|
-
|
|
|
-export default plugin
|