summaryrefslogtreecommitdiff
path: root/internal/hexaiaction/tui.go
blob: 549a6ab1270f96516517ceb22abf61b6ee85e808 (plain)
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")
}