package cmdpacks import ( "context" tea "github.com/charmbracelet/bubbletea" "github.com/lmika/dynamo-browse/internal/common/ui/commandctrl" "github.com/lmika/dynamo-browse/internal/common/ui/events" "github.com/lmika/dynamo-browse/internal/dynamo-browse/controllers" "github.com/lmika/dynamo-browse/internal/dynamo-browse/services/tables" "ucl.lmika.dev/ucl" ) type uiModule struct { tableService *tables.Service state *controllers.State ckb *customKeyBinding readController *controllers.TableReadController } func (m *uiModule) uiCommand(ctx context.Context, args ucl.CallArgs) (any, error) { var ( name string cmd ucl.Invokable ) if err := args.Bind(&name, &cmd); err != nil { return nil, err } invoker := commandctrl.GetInvoker(ctx) invoker.Inst().SetBuiltinInvokable(name, cmd) return nil, nil } func (m *uiModule) uiPrompt(ctx context.Context, args ucl.CallArgs) (any, error) { var prompt string if err := args.Bind(&prompt); err != nil { return nil, err } resChan := make(chan string) go func() { commandctrl.PostMsg(ctx, events.PromptForInput(prompt, nil, func(value string) tea.Msg { resChan <- value return nil })) }() select { case value := <-resChan: return value, nil case <-ctx.Done(): return nil, ctx.Err() } } func (m *uiModule) uiConfirm(ctx context.Context, args ucl.CallArgs) (any, error) { var prompt string if err := args.Bind(&prompt); err != nil { return nil, err } resChan := make(chan bool) go func() { commandctrl.PostMsg(ctx, events.Confirm(prompt, func(value bool) tea.Msg { resChan <- value return nil })) }() select { case value := <-resChan: return value, nil case <-ctx.Done(): return nil, ctx.Err() } } func (m *uiModule) uiPromptTable(ctx context.Context, args ucl.CallArgs) (any, error) { tables, err := m.tableService.ListTables(context.Background()) if err != nil { return nil, err } resChan := make(chan string) go func() { commandctrl.PostMsg(ctx, controllers.PromptForTableMsg{ Tables: tables, OnSelected: func(tableName string) tea.Msg { resChan <- tableName return nil }, }) }() select { case value := <-resChan: return value, nil case <-ctx.Done(): return nil, ctx.Err() } } func (m *uiModule) uiBind(ctx context.Context, args ucl.CallArgs) (any, error) { var ( bindName string key string inv ucl.Invokable ) if args.NArgs() == 2 { if err := args.Bind(&key, &inv); err != nil { return nil, err } bindName = "custom." + key } else { if err := args.Bind(&bindName, &key, &inv); err != nil { return nil, err } } invoker := commandctrl.GetInvoker(ctx) m.ckb.bindings[bindName] = func() tea.Msg { return invoker.Invoke(inv, nil) } m.ckb.keyBindings[key] = bindName return nil, nil } func (m *uiModule) uiQuery(ctx context.Context, args ucl.CallArgs) (any, error) { q, tableInfo, err := parseQuery(ctx, args, m.state.ResultSet(), m.tableService) if err != nil { return nil, err } commandctrl.PostMsg(ctx, m.readController.RunQuery(q, tableInfo)) return nil, nil } func (m *uiModule) uiFilter(ctx context.Context, args ucl.CallArgs) (any, error) { var filter string if err := args.Bind(&filter); err != nil { return nil, err } commandctrl.PostMsg(ctx, m.readController.Filter(filter)) return nil, nil } func moduleUI( tableService *tables.Service, state *controllers.State, readController *controllers.TableReadController, ) (ucl.Module, controllers.CustomKeyBindingSource) { m := &uiModule{ tableService: tableService, state: state, readController: readController, ckb: &customKeyBinding{ bindings: map[string]tea.Cmd{}, keyBindings: map[string]string{}, }, } return ucl.Module{ Name: "ui", Builtins: map[string]ucl.BuiltinHandler{ "command": m.uiCommand, "prompt": m.uiPrompt, "prompt-table": m.uiPromptTable, "confirm": m.uiConfirm, "query": m.uiQuery, "filter": m.uiFilter, "bind": m.uiBind, }, }, m.ckb } type customKeyBinding struct { bindings map[string]tea.Cmd keyBindings map[string]string } func (c *customKeyBinding) LookupBinding(theKey string) string { return c.keyBindings[theKey] } func (c *customKeyBinding) CustomKeyCommand(key string) tea.Cmd { bindingName, ok := c.keyBindings[key] if !ok { return nil } binding, ok := c.bindings[bindingName] if !ok { return nil } return binding } func (c *customKeyBinding) UnbindKey(key string) { delete(c.keyBindings, key) } func (c *customKeyBinding) Rebind(bindingName string, newKey string) error { c.keyBindings[newKey] = bindingName return nil }