mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-11 23:40:28 -10:00
* feat(tui): Ctrl+X leader chords and emacs menu navigation Add Ctrl+X leader shortcuts for common slash commands (with Ctrl+X ? chord map), and Ctrl+P/N previous/next selection in modals and pickers. * fix(tui): drop Ctrl+X e /edit leader chord /edit replaces the composer and would discard an in-progress draft; leave it unbound so leader shortcuts stay draft-preserving. * fix(tui): consume idle Ctrl+N as a no-op Reserve Ctrl+N for emacs menu navigation so it never falls through to remapped global bindings when no selection surface is open. * docs: drop manual CHANGELOG entries for TUI shortcuts
63 lines
1.9 KiB
Go
63 lines
1.9 KiB
Go
package tui
|
||
|
||
import tea "charm.land/bubbletea/v2"
|
||
|
||
// moveModalSelection moves the highlight on an open selection surface by delta
|
||
// (−1 previous / +1 next), matching arrow-key behavior for that surface.
|
||
// handled is false when no list/selector modal owns the keys (caller may fall
|
||
// through to plan toggle, composer, etc.).
|
||
//
|
||
// Surfaces: permission options, ask_user option lists, provider/MCP wizards and
|
||
// managers, command pickers, slash/file autocomplete. Does not scroll the
|
||
// transcript or drive composer history — those stay arrow-only.
|
||
func (m model) moveModalSelection(delta int) (model, tea.Cmd, bool) {
|
||
if delta == 0 {
|
||
return m, nil, false
|
||
}
|
||
if m.pendingPermission != nil {
|
||
return m.movePermissionCursor(delta), nil, true
|
||
}
|
||
if m.pendingAskUser != nil {
|
||
// moveAskUserCursor is a no-op in free-text mode (parity with arrows).
|
||
return m.moveAskUserCursor(delta), nil, true
|
||
}
|
||
if m.providerWizard != nil {
|
||
m.burstCount = 0
|
||
next, cmd := m.handleProviderWizardKey(syntheticArrowKey(delta))
|
||
return next, cmd, true
|
||
}
|
||
if m.mcpAddWizard != nil {
|
||
m.burstCount = 0
|
||
next, cmd := m.handleMCPAddWizardKey(syntheticArrowKey(delta))
|
||
return next, cmd, true
|
||
}
|
||
if m.mcpManager != nil {
|
||
m.burstCount = 0
|
||
next, cmd := m.handleMCPManagerKey(syntheticArrowKey(delta))
|
||
return next, cmd, true
|
||
}
|
||
if m.picker != nil {
|
||
if m.modelPickerIsLoading() {
|
||
return m, nil, true
|
||
}
|
||
m.pickerMoved(delta)
|
||
return m, nil, true
|
||
}
|
||
if m.suggestionsActive() {
|
||
m.moveSuggestion(delta)
|
||
return m, nil, true
|
||
}
|
||
return m, nil, false
|
||
}
|
||
|
||
// syntheticArrowKey builds a KeyUp (delta < 0) or KeyDown (delta > 0) message so
|
||
// wizard/manager handlers that only match arrow keys can share one code path
|
||
// with Ctrl+P / Ctrl+N.
|
||
func syntheticArrowKey(delta int) tea.KeyPressMsg {
|
||
code := tea.KeyDown
|
||
if delta < 0 {
|
||
code = tea.KeyUp
|
||
}
|
||
return tea.KeyPressMsg(tea.Key{Code: code})
|
||
}
|