1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
|
package hexaiaction
import (
"fmt"
"strings"
"github.com/charmbracelet/bubbles/list"
tea "github.com/charmbracelet/bubbletea"
)
// item implements list.Item
type item struct {
title, desc string
kind ActionKind
hotkey rune
}
func (i item) Title() string { return i.title }
func (i item) Description() string { return i.desc }
func (i item) FilterValue() string { return i.title }
type model struct {
list list.Model
chosen ActionKind
done bool
}
func newModel() model {
items := []list.Item{
item{title: "Rewrite selection", desc: "", kind: ActionRewrite, hotkey: 'r'},
item{title: "Simplify and improve", desc: "", kind: ActionSimplify, hotkey: 'i'},
item{title: "Document code", desc: "", kind: ActionDocument, hotkey: 'c'},
item{title: "Generate Go unit test(s)", desc: "", kind: ActionGoTest, hotkey: 't'},
item{title: "Custom prompt", desc: "", kind: ActionCustomPrompt, hotkey: 'p'},
item{title: "Skip", desc: "", kind: ActionSkip, hotkey: 's'},
}
l := list.New(items, oneLineDelegate{}, 0, 0)
l.SetShowTitle(false)
l.SetShowHelp(false)
l.SetShowStatusBar(false)
l.SetFilteringEnabled(false)
return model{list: l}
}
func (m model) Init() tea.Cmd { return nil }
func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
switch msg := msg.(type) {
case tea.KeyMsg:
return handleKey(m, msg)
case tea.WindowSizeMsg:
m.list.SetSize(msg.Width, msg.Height)
}
var cmd tea.Cmd
m.list, cmd = m.list.Update(msg)
return m, cmd
}
func handleKey(m model, msg tea.KeyMsg) (tea.Model, tea.Cmd) {
raw := msg.String()
low := strings.ToLower(raw)
switch low {
case "esc", "q":
// Treat ESC and q as Skip/quit
m.chosen = ActionSkip
m.done = true
return m, tea.Quit
case "enter":
if it, ok := m.list.SelectedItem().(item); ok {
m.chosen = it.kind
m.done = true
return m, tea.Quit
}
case "j", "down":
m.list.CursorDown()
case "k", "up":
m.list.CursorUp()
case "g", "home":
m.list.Select(0)
case "end":
if n := len(m.list.Items()); n > 0 {
m.list.Select(n - 1)
}
case "s", "r", "c", "t", "i", "p":
items := m.list.Items()
for i := 0; i < len(items); i++ {
if it, ok := items[i].(item); ok && strings.ToLower(string(it.hotkey)) == low {
m.list.Select(i)
m.chosen = it.kind
m.done = true
return m, tea.Quit
}
}
}
if raw == "G" { // Shift+G jumps to end
if n := len(m.list.Items()); n > 0 {
m.list.Select(n - 1)
}
}
return m, nil
}
func (m model) View() string {
if m.done {
return ""
}
return m.list.View()
}
// RunTUI returns the chosen ActionKind.
func RunTUI() (ActionKind, error) {
p := tea.NewProgram(newModel())
md, err := p.Run()
if err != nil {
return ActionSkip, err
}
if m, ok := md.(model); ok {
if m.chosen == "" {
return ActionSkip, nil
}
return m.chosen, nil
}
return ActionSkip, fmt.Errorf("unexpected model type")
}
|