dynamo-browse/internal/common/ui/commandctrl/cmdpacks/modui.go
Leon Mika 32ae488066
All checks were successful
ci / build (push) Successful in 3m17s
Moved package to lmika.dev/cmd/dynamo-browse
2025-05-26 22:04:23 +10:00

211 lines
4.6 KiB
Go

package cmdpacks
import (
"context"
tea "github.com/charmbracelet/bubbletea"
"lmika.dev/cmd/dynamo-browse/internal/common/ui/commandctrl"
"lmika.dev/cmd/dynamo-browse/internal/common/ui/events"
"lmika.dev/cmd/dynamo-browse/internal/dynamo-browse/controllers"
"lmika.dev/cmd/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
}