summaryrefslogtreecommitdiff
path: root/internal/hexaiaction/tui_custom.go
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2025-09-14 23:40:26 +0300
committerPaul Buetow <paul@buetow.org>2025-09-14 23:40:26 +0300
commitf4470bbcfbe3b14c99baeef475fe872825a13a39 (patch)
treee12fc6168d21119dfff3a0fef5b6c5b54149f3ab /internal/hexaiaction/tui_custom.go
parent68438c98d23545ff791768e3e219cd21d3814e0c (diff)
release: v0.10.0v0.10.0
Diffstat (limited to 'internal/hexaiaction/tui_custom.go')
-rw-r--r--internal/hexaiaction/tui_custom.go76
1 files changed, 76 insertions, 0 deletions
diff --git a/internal/hexaiaction/tui_custom.go b/internal/hexaiaction/tui_custom.go
new file mode 100644
index 0000000..91d4b81
--- /dev/null
+++ b/internal/hexaiaction/tui_custom.go
@@ -0,0 +1,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.
+// On picking one, it sets selectedCustom and returns ActionCustom.
+func RunTUIWithCustom(customs []appconfig.CustomAction, menuHotkey string) (ActionKind, error) {
+ // When no customs, fall back to default menu
+ if len(customs) == 0 {
+ return RunTUI()
+ }
+ // 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, err
+ }
+ if mm, ok := md.(model); ok {
+ if mm.chosen != ActionCustom {
+ return mm.chosen, nil
+ }
+ }
+ // Custom submenu: list each action; select one maps to ActionCustom and sets global
+ 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, 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]
+ selectedCustom = &c
+ return ActionCustom, nil
+ }
+ }
+ }
+ }
+ return ActionSkip, 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) }