opentui(v6): arrows + enter navigate every completion menu (paths, args)

This commit is contained in:
alt-glitch 2026-06-10 22:14:11 +05:30
parent 72118b049f
commit e1edbb0e89
3 changed files with 18 additions and 7 deletions

View file

@ -47,12 +47,17 @@ function clampSelected(ctx: MenuKeyContext): number {
* Route one key press against the open menu. `modified` is Ctrl/Alt/Option
* modified arrows/Enter never belong to the menu (Tab/Esc keep their
* pre-existing modifier-blind accept/dismiss semantics).
*
* ANY open menu owns plain arrows/Enter (glitch, 2026-06-10): @-path and
* arg menus navigate exactly like the slash menu standard editor-
* autocomplete behavior; Esc dismisses to hand the cursor keys back.
* (`ctx.slashMenu` still feeds the hint text + suggestion rows.)
*/
export function routeMenuKey(name: string, modified: boolean, ctx: MenuKeyContext): MenuKeyAction {
if (ctx.count <= 0) return PASS
if (name === 'tab') return { index: clampSelected(ctx), kind: 'accept' }
if (name === 'escape') return { kind: 'dismiss' }
if (modified || !ctx.slashMenu) return PASS
if (modified) return PASS
const sel = clampSelected(ctx)
if (name === 'up') return { kind: 'move', selected: (sel - 1 + ctx.count) % ctx.count }
if (name === 'down') return { kind: 'move', selected: (sel + 1) % ctx.count }

View file

@ -47,9 +47,17 @@ describe('routeMenuKey — key-routing precedence table', () => {
['Esc dismisses', 'escape', false, ctx({ selected: 2 }), { kind: 'dismiss' }],
// NOT the slash menu (path/@-mention dropdown): arrows + Enter keep their
// existing meanings (history / cursor / textarea submit) …
['Down on a path menu passes', 'down', false, ctx({ slashMenu: false }), { kind: 'pass' }],
['Up on a path menu passes', 'up', false, ctx({ slashMenu: false }), { kind: 'pass' }],
['Enter on a path menu passes (submits as today)', 'return', false, ctx({ slashMenu: false }), { kind: 'pass' }],
// glitch 2026-06-10: ANY open menu owns plain arrows/Enter (path/arg menus
// navigate like the slash menu; Esc hands the cursor keys back).
['Down on a path menu moves', 'down', false, ctx({ slashMenu: false }), { kind: 'move', selected: 1 }],
['Up on a path menu moves (wraps)', 'up', false, ctx({ slashMenu: false }), { kind: 'move', selected: 2 }],
[
'Enter on a path menu accepts the highlighted item',
'return',
false,
ctx({ slashMenu: false }),
{ index: 0, kind: 'accept' }
],
// … but Tab/Esc keep working on ANY menu (pre-Epic-8 semantics)
['Tab on a path menu still accepts', 'tab', false, ctx({ slashMenu: false }), { index: 0, kind: 'accept' }],
['Esc on a path menu still dismisses', 'escape', false, ctx({ slashMenu: false }), { kind: 'dismiss' }],

View file

@ -234,7 +234,6 @@ export function Composer(props: {
// Whether the composer text starts with `/` (slash menu vs path menu) — a
// signal so the dropdown hint stays reactive; the key handler re-checks
// `ta.plainText` directly.
const [slashText, setSlashText] = createSignal(false)
/** Replace the textarea content and park the cursor at the end (history recall). */
const setBuffer = (text: string) => {
@ -416,7 +415,7 @@ export function Composer(props: {
)}
</For>
<text selectable={false} fg={theme().color.muted}>
{slashText() ? '↑/↓ select · Enter/Tab accept · Esc dismiss' : 'Tab complete · Esc dismiss'}
{'↑/↓ select · Enter/Tab accept · Esc dismiss'}
</text>
</box>
</Show>
@ -460,7 +459,6 @@ export function Composer(props: {
}}
onContentChange={() => {
const text = ta?.plainText ?? ''
setSlashText(text.startsWith('/'))
setBufText(text) // drives the token analysis (highlight + suggestion)
props.onType?.(text)
}}