on main help sign in
Merge pull request #290 from charmbracelet/charm-432

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

commitae9da39b7dbd961de756ed039bcb6e64e17b2500 authorKujtim Hoxha <kujtimii.h@gmail.com> dateThu 24 Jul 2025, 09:20 (1 year ago) parent5c9f168cc5ef29
4 files changed+54−23
internal/tui/components/chat/editor/editor.go+5−3 internal/tui/components/completions/completions.go+26−1 internal/tui/components/completions/keys.go+10 internal/tui/tui.go+13−19
internal/tui/components/chat/editor/editor.go+5−3
@@ -187,9 +187,11 @@ func (m *editorCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
187187 value = value[:m.completionsStartIndex]
188188 value += item.Path
189189 m.textarea.SetValue(value)
190− m.isCompletionsOpen = false
191− m.currentQuery = ""
192− m.completionsStartIndex = 0
190+ if !msg.Insert {
191+ m.isCompletionsOpen = false
192+ m.currentQuery = ""
193+ m.completionsStartIndex = 0
194+ }
193195 return m, nil
194196 }
195197 case openEditorMsg:
internal/tui/components/completions/completions.go+26−1
@@ -36,7 +36,8 @@ type CompletionsOpenedMsg struct{}
3636type CloseCompletionsMsg struct{}
3737
3838type SelectCompletionMsg struct {
39− Value any // The value of the selected completion item
39+ Value any // The value of the selected completion item
40+ Insert bool
4041}
4142
4243type Completions interface {
@@ -115,6 +116,30 @@ func (c *completionsCmp) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
115116 d, cmd := c.list.Update(msg)
116117 c.list = d.(list.ListModel)
117118 return c, cmd
119+ case key.Matches(msg, c.keyMap.UpInsert):
120+ selectedItemInx := c.list.SelectedIndex() - 1
121+ items := c.list.Items()
122+ if selectedItemInx == list.NoSelection || selectedItemInx < 0 {
123+ return c, nil // No item selected, do nothing
124+ }
125+ selectedItem := items[selectedItemInx].(CompletionItem).Value()
126+ c.list.SetSelected(selectedItemInx)
127+ return c, util.CmdHandler(SelectCompletionMsg{
128+ Value: selectedItem,
129+ Insert: true,
130+ })
131+ case key.Matches(msg, c.keyMap.DownInsert):
132+ selectedItemInx := c.list.SelectedIndex() + 1
133+ items := c.list.Items()
134+ if selectedItemInx == list.NoSelection || selectedItemInx >= len(items) {
135+ return c, nil // No item selected, do nothing
136+ }
137+ selectedItem := items[selectedItemInx].(CompletionItem).Value()
138+ c.list.SetSelected(selectedItemInx)
139+ return c, util.CmdHandler(SelectCompletionMsg{
140+ Value: selectedItem,
141+ Insert: true,
142+ })
118143 case key.Matches(msg, c.keyMap.Select):
119144 selectedItemInx := c.list.SelectedIndex()
120145 if selectedItemInx == list.NoSelection {
internal/tui/components/completions/keys.go+10
@@ -9,6 +9,8 @@ type KeyMap struct {
99 Up,
1010 Select,
1111 Cancel key.Binding
12+ DownInsert,
13+ UpInsert key.Binding
1214}
1315
1416func DefaultKeyMap() KeyMap {
@@ -29,6 +31,14 @@ func DefaultKeyMap() KeyMap {
2931 key.WithKeys("esc"),
3032 key.WithHelp("esc", "cancel"),
3133 ),
34+ DownInsert: key.NewBinding(
35+ key.WithKeys("ctrl+n"),
36+ key.WithHelp("ctrl+n", "insert next"),
37+ ),
38+ UpInsert: key.NewBinding(
39+ key.WithKeys("ctrl+p"),
40+ key.WithHelp("ctrl+p", "insert previous"),
41+ ),
3242 }
3343}
3444
internal/tui/tui.go+13−19
@@ -319,26 +319,20 @@ func (a *appModel) handleWindowResize(width, height int) tea.Cmd {
319319
320320// handleKeyPressMsg processes keyboard input and routes to appropriate handlers.
321321func (a *appModel) handleKeyPressMsg(msg tea.KeyPressMsg) tea.Cmd {
322+ if a.completions.Open() {
323+ // completions
324+ keyMap := a.completions.KeyMap()
325+ switch {
326+ case key.Matches(msg, keyMap.Up), key.Matches(msg, keyMap.Down),
327+ key.Matches(msg, keyMap.Select), key.Matches(msg, keyMap.Cancel),
328+ key.Matches(msg, keyMap.UpInsert), key.Matches(msg, keyMap.DownInsert):
329+ u, cmd := a.completions.Update(msg)
330+ a.completions = u.(completions.Completions)
331+ return cmd
332+ }
333+ }
322334 switch {
323− // completions
324− case a.completions.Open() && key.Matches(msg, a.completions.KeyMap().Up):
325− u, cmd := a.completions.Update(msg)
326− a.completions = u.(completions.Completions)
327− return cmd
328−
329− case a.completions.Open() && key.Matches(msg, a.completions.KeyMap().Down):
330− u, cmd := a.completions.Update(msg)
331− a.completions = u.(completions.Completions)
332− return cmd
333− case a.completions.Open() && key.Matches(msg, a.completions.KeyMap().Select):
334− u, cmd := a.completions.Update(msg)
335− a.completions = u.(completions.Completions)
336− return cmd
337− case a.completions.Open() && key.Matches(msg, a.completions.KeyMap().Cancel):
338− u, cmd := a.completions.Update(msg)
339− a.completions = u.(completions.Completions)
340− return cmd
341− // help
335+ // help
342336 case key.Matches(msg, a.keyMap.Help):
343337 a.status.ToggleFullHelp()
344338 a.showingFullHelp = !a.showingFullHelp