feat(tui): completions: add select and insert keybinds

This adds keybinds to select next/previous completion item using ctrl+p
and ctrl+n similar to Vim's completion behavior.
This commit is contained in:
Ayman Bagabas
2025-07-23 13:20:03 -04:00
parent da127ca626
commit cc5ef2912d
4 changed files with 54 additions and 23 deletions

View File

@@ -187,9 +187,11 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
value = value[:m.completionsStartIndex]
value += item.Path
m.textarea.SetValue(value)
m.isCompletionsOpen = false
m.currentQuery = ""
m.completionsStartIndex = 0
if !msg.Insert {
m.isCompletionsOpen = false
m.currentQuery = ""
m.completionsStartIndex = 0
}
return m, nil
}
case openEditorMsg:

View File

@@ -36,7 +36,8 @@ type CompletionsOpenedMsg struct{}
type CloseCompletionsMsg struct{}
type SelectCompletionMsg struct {
Value any // The value of the selected completion item
Value any // The value of the selected completion item
Insert bool
}
type Completions interface {
@@ -115,6 +116,30 @@ func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
d, cmd := c.list.Update(msg)
c.list = d.(list.ListModel)
return c, cmd
case key.Matches(msg, c.keyMap.UpInsert):
selectedItemInx := c.list.SelectedIndex() - 1
items := c.list.Items()
if selectedItemInx == list.NoSelection || selectedItemInx < 0 {
return c, nil // No item selected, do nothing
}
selectedItem := items[selectedItemInx].(CompletionItem).Value()
c.list.SetSelected(selectedItemInx)
return c, util.CmdHandler(SelectCompletionMsg{
Value: selectedItem,
Insert: true,
})
case key.Matches(msg, c.keyMap.DownInsert):
selectedItemInx := c.list.SelectedIndex() + 1
items := c.list.Items()
if selectedItemInx == list.NoSelection || selectedItemInx >= len(items) {
return c, nil // No item selected, do nothing
}
selectedItem := items[selectedItemInx].(CompletionItem).Value()
c.list.SetSelected(selectedItemInx)
return c, util.CmdHandler(SelectCompletionMsg{
Value: selectedItem,
Insert: true,
})
case key.Matches(msg, c.keyMap.Select):
selectedItemInx := c.list.SelectedIndex()
if selectedItemInx == list.NoSelection {

View File

@@ -9,6 +9,8 @@ type KeyMap struct {
Up,
Select,
Cancel key.Binding
DownInsert,
UpInsert key.Binding
}
func DefaultKeyMap() KeyMap {
@@ -29,6 +31,14 @@ func DefaultKeyMap() KeyMap {
key.WithKeys("esc"),
key.WithHelp("esc", "cancel"),
),
DownInsert: key.NewBinding(
key.WithKeys("ctrl+n"),
key.WithHelp("ctrl+n", "insert next"),
),
UpInsert: key.NewBinding(
key.WithKeys("ctrl+p"),
key.WithHelp("ctrl+p", "insert previous"),
),
}
}

View File

@@ -319,26 +319,20 @@ func (a *appModel) handleWindowResize(width, height int) tea.Cmd {
// handleKeyPressMsg processes keyboard input and routes to appropriate handlers.
func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
if a.completions.Open() {
// completions
keyMap := a.completions.KeyMap()
switch {
case key.Matches(msg, keyMap.Up), key.Matches(msg, keyMap.Down),
key.Matches(msg, keyMap.Select), key.Matches(msg, keyMap.Cancel),
key.Matches(msg, keyMap.UpInsert), key.Matches(msg, keyMap.DownInsert):
u, cmd := a.completions.Update(msg)
a.completions = u.(completions.Completions)
return cmd
}
}
switch {
// completions
case a.completions.Open() && key.Matches(msg, a.completions.KeyMap().Up):
u, cmd := a.completions.Update(msg)
a.completions = u.(completions.Completions)
return cmd
case a.completions.Open() && key.Matches(msg, a.completions.KeyMap().Down):
u, cmd := a.completions.Update(msg)
a.completions = u.(completions.Completions)
return cmd
case a.completions.Open() && key.Matches(msg, a.completions.KeyMap().Select):
u, cmd := a.completions.Update(msg)
a.completions = u.(completions.Completions)
return cmd
case a.completions.Open() && key.Matches(msg, a.completions.KeyMap().Cancel):
u, cmd := a.completions.Update(msg)
a.completions = u.(completions.Completions)
return cmd
// help
// help
case key.Matches(msg, a.keyMap.Help):
a.status.ToggleFullHelp()
a.showingFullHelp = !a.showingFullHelp