summaryrefslogtreecommitdiff
path: root/internal/hexaiaction/tui_custom.go
blob: 2e6561b849a54d74fcc8ca6ae89b2f17c13471c9 (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
package hexaiaction

import (
	"unicode/utf8"

	"github.com/charmbracelet/bubbles/list"
	tea "github.com/charmbracelet/bubbletea"

	"codeberg.org/snonux/hexai/internal/appconfig"
)

// RunTUIWithCustom shows the main menu plus a configurable "Custom actions…" item.
// If the user selects that item, it shows a submenu listing user-defined custom actions.
func RunTUIWithCustom(customs []appconfig.CustomAction, menuHotkey string) (ActionKind, *appconfig.CustomAction, error) {
	// When no customs, fall back to default menu
	if len(customs) == 0 {
		kind, err := RunTUI()
		return kind, nil, err
	}
	// Build main menu with an extra entry
	hk := 'a'
	if r, _ := utf8.DecodeRuneInString(menuHotkey); r != utf8.RuneError && r != 0 {
		hk = r
	}
	// Create a model with default items plus Custom actions…
	m := newModel()
	items := m.list.Items()
	items = append(items, item{title: "Custom actions…", desc: "", kind: ActionCustom, hotkey: hk})
	m.list.SetItems(items)
	// Run main menu
	p := teaNewProgram(m)
	md, err := p.Run()
	if err != nil {
		return ActionSkip, nil, err
	}
	if mm, ok := md.(model); ok {
		// If user chose built-in items (including Custom prompt), return immediately.
		if mm.chosen != ActionCustom {
			return mm.chosen, nil, nil
		}
	}
	// Custom submenu: list each action; selection maps to ActionCustom and returns that action.
	sub := newModel()
	subItems := make([]list.Item, 0, len(customs))
	for _, ca := range customs {
		r := rune(0)
		if rr, _ := utf8.DecodeRuneInString(ca.Hotkey); rr != utf8.RuneError && rr != 0 {
			r = rr
		}
		subItems = append(subItems, item{title: ca.Title, desc: "", kind: ActionCustom, hotkey: r})
	}
	sub.list.SetItems(subItems)
	sp := teaNewProgram(sub)
	smd, err := sp.Run()
	if err != nil {
		return ActionSkip, nil, err
	}
	if sm, ok := smd.(model); ok {
		if it, ok := sm.list.SelectedItem().(item); ok {
			// Map by title
			for i := range customs {
				if customs[i].Title == it.title {
					c := customs[i]
					return ActionCustom, &c, nil
				}
			}
		}
	}
	return ActionSkip, nil, nil
}

// teaNewProgram is a tiny seam for tests to stub bubbletea program creation.
var teaNewProgram = func(m model) teaProgram { return tea.NewProgram(m) }

// teaProgram is the subset of bubbletea.Program we need; enables testing seam.
type teaProgram interface{ Run() (tea.Model, error) }