summaryrefslogtreecommitdiff
path: root/internal/hexaiaction/tui.go
blob: 317a991aec8bb5daeda9b5c89a681a29ce012ef1 (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
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: ActionCustom, 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")
}