diff --git a/cmd/dynamo-browse/main.go b/cmd/dynamo-browse/main.go index 129702e..9bad96f 100644 --- a/cmd/dynamo-browse/main.go +++ b/cmd/dynamo-browse/main.go @@ -22,7 +22,6 @@ import ( "github.com/lmika/awstools/internal/dynamo-browse/ui" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/modal" - "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/statusandprompt" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/tableselect" "github.com/lmika/gopkgs/cli" ) @@ -157,7 +156,8 @@ func newTestModel(descr string) tea.Model { return nil }, tableselect.ShowTableSelect(func(n string) tea.Cmd { - return statusandprompt.SetStatus("New table = " + n) + // return statusandprompt.SetStatus("New table = " + n) + return nil }), ), ) diff --git a/internal/common/ui/commandctrl/commandctrl.go b/internal/common/ui/commandctrl/commandctrl.go index 0d827a9..298e88c 100644 --- a/internal/common/ui/commandctrl/commandctrl.go +++ b/internal/common/ui/commandctrl/commandctrl.go @@ -23,9 +23,9 @@ func NewCommandController(commands map[string]uimodels.Operation) *CommandContro func (c *CommandController) Prompt() uimodels.Operation { return uimodels.OperationFn(func(ctx context.Context) error { uiCtx := uimodels.Ctx(ctx) - uiCtx.Send(events.PromptForInput{ + uiCtx.Send(events.PromptForInputMsg{ Prompt: ":", - OnDone: c.Execute(), + // OnDone: c.Execute(), }) return nil }) diff --git a/internal/common/ui/dispatcher/context.go b/internal/common/ui/dispatcher/context.go index 46e2fc2..84594d1 100644 --- a/internal/common/ui/dispatcher/context.go +++ b/internal/common/ui/dispatcher/context.go @@ -1,10 +1,7 @@ package dispatcher import ( - "fmt" - tea "github.com/charmbracelet/bubbletea" - "github.com/lmika/awstools/internal/common/ui/events" "github.com/lmika/awstools/internal/common/ui/uimodels" ) @@ -13,20 +10,20 @@ type DispatcherContext struct { } func (dc DispatcherContext) Messagef(format string, args ...interface{}) { - dc.Publisher.Send(events.Message(fmt.Sprintf(format, args...))) + // dc.Publisher.Send(events.Message(fmt.Sprintf(format, args...))) } func (dc DispatcherContext) Send(teaMessage tea.Msg) { - dc.Publisher.Send(teaMessage) + // dc.Publisher.Send(teaMessage) } func (dc DispatcherContext) Message(msg string) { - dc.Publisher.Send(events.Message(msg)) + // dc.Publisher.Send(events.Message(msg)) } func (dc DispatcherContext) Input(prompt string, onDone uimodels.Operation) { - dc.Publisher.Send(events.PromptForInput{ - Prompt: prompt, - OnDone: onDone, - }) + // dc.Publisher.Send(events.PromptForInput{ + // Prompt: prompt, + // OnDone: onDone, + // }) } diff --git a/internal/common/ui/events/commands.go b/internal/common/ui/events/commands.go new file mode 100644 index 0000000..b7e6344 --- /dev/null +++ b/internal/common/ui/events/commands.go @@ -0,0 +1,26 @@ +package events + +import tea "github.com/charmbracelet/bubbletea" + +func Error(err error) tea.Msg { + return ErrorMsg(err) +} + +func SetStatus(msg string) tea.Cmd { + return func() tea.Msg { + return StatusMsg(msg) + } +} + +func PromptForInput(prompt string, onDone func(value string) tea.Cmd) tea.Cmd { + return func() tea.Msg { + return PromptForInputMsg{ + Prompt: prompt, + OnDone: onDone, + } + } +} + +type MessageWithStatus interface { + StatusMessage() string +} diff --git a/internal/common/ui/events/errors.go b/internal/common/ui/events/errors.go index 0c031b6..9688142 100644 --- a/internal/common/ui/events/errors.go +++ b/internal/common/ui/events/errors.go @@ -1,17 +1,17 @@ package events import ( - "github.com/lmika/awstools/internal/common/ui/uimodels" + tea "github.com/charmbracelet/bubbletea" ) // Error indicates that an error occurred -type Error error +type ErrorMsg error // Message indicates that a message should be shown to the user -type Message string +type StatusMsg string // PromptForInput indicates that the context is requesting a line of input -type PromptForInput struct { +type PromptForInputMsg struct { Prompt string - OnDone uimodels.Operation + OnDone func(value string) tea.Cmd } diff --git a/internal/dynamo-browse/controllers/events.go b/internal/dynamo-browse/controllers/events.go index 1289b94..0d05b3a 100644 --- a/internal/dynamo-browse/controllers/events.go +++ b/internal/dynamo-browse/controllers/events.go @@ -1,11 +1,25 @@ package controllers -import "github.com/lmika/awstools/internal/dynamo-browse/models" +import ( + "fmt" + + tea "github.com/charmbracelet/bubbletea" + "github.com/lmika/awstools/internal/dynamo-browse/models" +) type NewResultSet struct { ResultSet *models.ResultSet } +func (rs NewResultSet) StatusMessage() string { + return fmt.Sprintf("%d items returned", len(rs.ResultSet.Items)) +} + type SetReadWrite struct { NewValue bool } + +type PromptForTableMsg struct { + Tables []string + OnSelected func(tableName string) tea.Cmd +} diff --git a/internal/dynamo-browse/controllers/tableread.go b/internal/dynamo-browse/controllers/tableread.go index 85f4737..670c4f1 100644 --- a/internal/dynamo-browse/controllers/tableread.go +++ b/internal/dynamo-browse/controllers/tableread.go @@ -5,6 +5,7 @@ import ( "log" tea "github.com/charmbracelet/bubbletea" + "github.com/lmika/awstools/internal/common/ui/events" "github.com/lmika/awstools/internal/dynamo-browse/models" "github.com/lmika/awstools/internal/dynamo-browse/services/tables" "github.com/pkg/errors" @@ -22,22 +23,62 @@ func NewTableReadController(tableService *tables.Service, tableName string) *Tab } } -func (c *TableReadController) Scan() tea.Cmd { +// Init does an initial scan of the table. If no table is specified, it prompts for a table, then does a scan. +func (c *TableReadController) Init() tea.Cmd { + if c.tableName == "" { + return c.listTables() + } else { + return c.scanTable(c.tableName) + } +} + +func (c *TableReadController) listTables() tea.Cmd { + return func() tea.Msg { + tables, err := c.tableService.ListTables(context.Background()) + if err != nil { + return events.Error(err) + } + + return PromptForTableMsg{ + Tables: tables, + OnSelected: func(tableName string) tea.Cmd { + return c.scanTable(tableName) + }, + } + } +} + +func (c *TableReadController) scanTable(name string) tea.Cmd { return func() tea.Msg { ctx := context.Background() log.Println("Fetching table info") - tableInfo, err := c.tableInfo(ctx) + tableInfo, err := c.tableService.Describe(ctx, name) if err != nil { - log.Println("error: ", err) - return err + return events.Error(errors.Wrapf(err, "cannot describe %v", c.tableName)) } log.Println("Scanning") resultSet, err := c.tableService.Scan(ctx, tableInfo) if err != nil { log.Println("error: ", err) - return err + return events.Error(err) + } + + log.Println("Scan done") + return NewResultSet{resultSet} + } +} + +func (c *TableReadController) Rescan(resultSet *models.ResultSet) tea.Cmd { + return func() tea.Msg { + ctx := context.Background() + + log.Println("Scanning") + resultSet, err := c.tableService.Scan(ctx, resultSet.TableInfo) + if err != nil { + log.Println("error: ", err) + return events.Error(err) } log.Println("Scan done") @@ -79,16 +120,16 @@ func (c *TableReadController) doScan(ctx context.Context, quiet bool) (err error // tableInfo returns the table info from the state if a result set exists. If not, it fetches the // table information from the service. -func (c *TableReadController) tableInfo(ctx context.Context) (*models.TableInfo, error) { - /* - if existingResultSet := CurrentState(ctx).ResultSet; existingResultSet != nil { - return existingResultSet.TableInfo, nil - } - */ +// func (c *TableReadController) tableInfo(ctx context.Context) (*models.TableInfo, error) { +// /* +// if existingResultSet := CurrentState(ctx).ResultSet; existingResultSet != nil { +// return existingResultSet.TableInfo, nil +// } +// */ - tableInfo, err := c.tableService.Describe(ctx, c.tableName) - if err != nil { - return nil, errors.Wrapf(err, "cannot describe %v", c.tableName) - } - return tableInfo, nil -} +// tableInfo, err := c.tableService.Describe(ctx, c.tableName) +// if err != nil { +// return nil, errors.Wrapf(err, "cannot describe %v", c.tableName) +// } +// return tableInfo, nil +// } diff --git a/internal/dynamo-browse/controllers/tablewrite.go b/internal/dynamo-browse/controllers/tablewrite.go index 938fd47..33d6c10 100644 --- a/internal/dynamo-browse/controllers/tablewrite.go +++ b/internal/dynamo-browse/controllers/tablewrite.go @@ -4,7 +4,6 @@ import ( "context" "github.com/lmika/awstools/internal/common/ui/uimodels" - "github.com/lmika/awstools/internal/dynamo-browse/models/modexpr" "github.com/lmika/awstools/internal/dynamo-browse/services/tables" "github.com/pkg/errors" ) @@ -41,56 +40,59 @@ func (c *TableWriteController) ToggleReadWrite() uimodels.Operation { } func (c *TableWriteController) Duplicate() uimodels.Operation { - return uimodels.OperationFn(func(ctx context.Context) error { - uiCtx := uimodels.Ctx(ctx) - state := CurrentState(ctx) - - if state.SelectedItem == nil { - return errors.New("no selected item") - } else if !state.InReadWriteMode { - return errors.New("not in read/write mode") - } - - uiCtx.Input("Dup: ", uimodels.OperationFn(func(ctx context.Context) error { - modExpr, err := modexpr.Parse(uimodels.PromptValue(ctx)) - if err != nil { - return err - } - - newItem, err := modExpr.Patch(state.SelectedItem) - if err != nil { - return err - } - - // TODO: preview new item - + return nil + /* + return uimodels.OperationFn(func(ctx context.Context) error { uiCtx := uimodels.Ctx(ctx) - uiCtx.Input("Put item? ", uimodels.OperationFn(func(ctx context.Context) error { - if uimodels.PromptValue(ctx) != "y" { - return errors.New("operation aborted") - } + state := CurrentState(ctx) - tableInfo, err := c.tableReadControllers.tableInfo(ctx) + if state.SelectedItem == nil { + return errors.New("no selected item") + } else if !state.InReadWriteMode { + return errors.New("not in read/write mode") + } + + uiCtx.Input("Dup: ", uimodels.OperationFn(func(ctx context.Context) error { + modExpr, err := modexpr.Parse(uimodels.PromptValue(ctx)) if err != nil { return err } - // Delete the item - if err := c.tableService.Put(ctx, tableInfo, newItem); err != nil { + newItem, err := modExpr.Patch(state.SelectedItem) + if err != nil { return err } - // Rescan to get updated items - // if err := c.tableReadControllers.doScan(ctx, true); err != nil { - // return err - // } + // TODO: preview new item + uiCtx := uimodels.Ctx(ctx) + uiCtx.Input("Put item? ", uimodels.OperationFn(func(ctx context.Context) error { + if uimodels.PromptValue(ctx) != "y" { + return errors.New("operation aborted") + } + + tableInfo, err := c.tableReadControllers.tableInfo(ctx) + if err != nil { + return err + } + + // Delete the item + if err := c.tableService.Put(ctx, tableInfo, newItem); err != nil { + return err + } + + // Rescan to get updated items + // if err := c.tableReadControllers.doScan(ctx, true); err != nil { + // return err + // } + + return nil + })) return nil })) return nil - })) - return nil - }) + }) + */ } func (c *TableWriteController) Delete() uimodels.Operation { @@ -111,15 +113,17 @@ func (c *TableWriteController) Delete() uimodels.Operation { return errors.New("operation aborted") } - tableInfo, err := c.tableReadControllers.tableInfo(ctx) - if err != nil { - return err - } + /* + tableInfo, err := c.tableReadControllers.tableInfo(ctx) + if err != nil { + return err + } - // Delete the item - if err := c.tableService.Delete(ctx, tableInfo, state.SelectedItem); err != nil { - return err - } + // Delete the item + if err := c.tableService.Delete(ctx, tableInfo, state.SelectedItem); err != nil { + return err + } + */ // Rescan to get updated items // if err := c.tableReadControllers.doScan(ctx, true); err != nil { diff --git a/internal/dynamo-browse/providers/dynamo/provider.go b/internal/dynamo-browse/providers/dynamo/provider.go index 7636845..4c74d74 100644 --- a/internal/dynamo-browse/providers/dynamo/provider.go +++ b/internal/dynamo-browse/providers/dynamo/provider.go @@ -14,6 +14,15 @@ type Provider struct { client *dynamodb.Client } +func (p *Provider) ListTables(ctx context.Context) ([]string, error) { + out, err := p.client.ListTables(ctx, &dynamodb.ListTablesInput{}) + if err != nil { + return nil, errors.Wrapf(err, "cannot list tables") + } + + return out.TableNames, nil +} + func (p *Provider) DescribeTable(ctx context.Context, tableName string) (*models.TableInfo, error) { out, err := p.client.DescribeTable(ctx, &dynamodb.DescribeTableInput{ TableName: aws.String(tableName), diff --git a/internal/dynamo-browse/services/tables/iface.go b/internal/dynamo-browse/services/tables/iface.go index aedae2e..2dddc6c 100644 --- a/internal/dynamo-browse/services/tables/iface.go +++ b/internal/dynamo-browse/services/tables/iface.go @@ -8,6 +8,7 @@ import ( ) type TableProvider interface { + ListTables(ctx context.Context) ([]string, error) DescribeTable(ctx context.Context, tableName string) (*models.TableInfo, error) ScanItems(ctx context.Context, tableName string) ([]models.Item, error) DeleteItem(ctx context.Context, tableName string, key map[string]types.AttributeValue) error diff --git a/internal/dynamo-browse/services/tables/service.go b/internal/dynamo-browse/services/tables/service.go index 22aab20..b052fe1 100644 --- a/internal/dynamo-browse/services/tables/service.go +++ b/internal/dynamo-browse/services/tables/service.go @@ -18,6 +18,10 @@ func NewService(provider TableProvider) *Service { } } +func (s *Service) ListTables(ctx context.Context) ([]string, error) { + return s.provider.ListTables(ctx) +} + func (s *Service) Describe(ctx context.Context, table string) (*models.TableInfo, error) { return s.provider.DescribeTable(ctx, table) } diff --git a/internal/dynamo-browse/ui/model.go b/internal/dynamo-browse/ui/model.go index d6c9722..ba9d3be 100644 --- a/internal/dynamo-browse/ui/model.go +++ b/internal/dynamo-browse/ui/model.go @@ -33,7 +33,7 @@ func NewModel(rc *controllers.TableReadController) Model { } func (m Model) Init() tea.Cmd { - return m.tableReadController.Scan() + return m.tableReadController.Init() } func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { diff --git a/internal/dynamo-browse/ui/modelold.go b/internal/dynamo-browse/ui/modelold.go index 7f62318..f24fedc 100644 --- a/internal/dynamo-browse/ui/modelold.go +++ b/internal/dynamo-browse/ui/modelold.go @@ -11,7 +11,6 @@ import ( "github.com/charmbracelet/lipgloss" "github.com/lmika/awstools/internal/common/ui/commandctrl" "github.com/lmika/awstools/internal/common/ui/dispatcher" - "github.com/lmika/awstools/internal/common/ui/events" "github.com/lmika/awstools/internal/common/ui/uimodels" "github.com/lmika/awstools/internal/dynamo-browse/controllers" ) @@ -40,8 +39,8 @@ type uiModel struct { state controllers.State message string - pendingInput *events.PromptForInput - textInput textinput.Model + // pendingInput *events.PromptForInput + textInput textinput.Model dispatcher *dispatcher.Dispatcher commandController *commandctrl.CommandController @@ -152,15 +151,15 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { m.state.InReadWriteMode = msg.NewValue // Shared events - case events.Error: - m.message = "Error: " + msg.Error() - case events.Message: - m.message = string(msg) - case events.PromptForInput: - m.textInput.Prompt = msg.Prompt - m.textInput.Focus() - m.textInput.SetValue("") - m.pendingInput = &msg + // case events.Error: + // m.message = "Error: " + msg.Error() + // case events.Message: + // m.message = string(msg) + // case events.PromptForInput: + // m.textInput.Prompt = msg.Prompt + // m.textInput.Focus() + // m.textInput.SetValue("") + // m.pendingInput = &msg // Tea events case tea.WindowSizeMsg: @@ -186,18 +185,18 @@ func (m uiModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { case tea.KeyMsg: // If text input in focus, allow that to accept input messages - if m.pendingInput != nil { - switch msg.String() { - case "ctrl+c", "esc": - m.pendingInput = nil - case "enter": - m.invokeOperation(uimodels.WithPromptValue(context.Background(), m.textInput.Value()), m.pendingInput.OnDone) - m.pendingInput = nil - default: - m.textInput, textInputCommands = m.textInput.Update(msg) - } - break - } + // if m.pendingInput != nil { + // switch msg.String() { + // case "ctrl+c", "esc": + // m.pendingInput = nil + // case "enter": + // m.invokeOperation(uimodels.WithPromptValue(context.Background(), m.textInput.Value()), m.pendingInput.OnDone) + // m.pendingInput = nil + // default: + // m.textInput, textInputCommands = m.textInput.Update(msg) + // } + // break + // } switch msg.String() { case "ctrl+c", "q": diff --git a/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go b/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go index b2db4f0..bcc6c13 100644 --- a/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go +++ b/internal/dynamo-browse/ui/teamodels/dynamotableview/model.go @@ -57,7 +57,7 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // TEMP case "s": - return m, m.tableReadControllers.Scan() + return m, m.tableReadControllers.Rescan(m.resultSet) case "ctrl+c", "esc": return m, tea.Quit } diff --git a/internal/dynamo-browse/ui/teamodels/statusandprompt/events.go b/internal/dynamo-browse/ui/teamodels/statusandprompt/events.go deleted file mode 100644 index b1739a2..0000000 --- a/internal/dynamo-browse/ui/teamodels/statusandprompt/events.go +++ /dev/null @@ -1,25 +0,0 @@ -package statusandprompt - -import tea "github.com/charmbracelet/bubbletea" - -type setStatusMsg string - -type startPromptMsg struct { - prompt string - onDone func(val string) tea.Cmd -} - -func SetStatus(newStatus string) tea.Cmd { - return func() tea.Msg { - return setStatusMsg(newStatus) - } -} - -func Prompt(prompt string, onDone func(val string) tea.Cmd) tea.Cmd { - return func() tea.Msg { - return startPromptMsg{ - prompt: prompt, - onDone: onDone, - } - } -} diff --git a/internal/dynamo-browse/ui/teamodels/statusandprompt/model.go b/internal/dynamo-browse/ui/teamodels/statusandprompt/model.go index df3e5a7..2862a36 100644 --- a/internal/dynamo-browse/ui/teamodels/statusandprompt/model.go +++ b/internal/dynamo-browse/ui/teamodels/statusandprompt/model.go @@ -4,6 +4,7 @@ import ( "github.com/charmbracelet/bubbles/textinput" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" + "github.com/lmika/awstools/internal/common/ui/events" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/utils" ) @@ -13,7 +14,7 @@ import ( type StatusAndPrompt struct { model layout.ResizingModel statusMessage string - pendingInput *startPromptMsg + pendingInput *events.PromptForInputMsg textInput textinput.Model width int } @@ -29,15 +30,19 @@ func (s StatusAndPrompt) Init() tea.Cmd { func (s StatusAndPrompt) Update(msg tea.Msg) (tea.Model, tea.Cmd) { switch msg := msg.(type) { - case setStatusMsg: - s.statusMessage = string(msg) - case startPromptMsg: + case events.ErrorMsg: + s.statusMessage = "Error: " + msg.Error() + case events.StatusMsg: + s.statusMessage = string(s.statusMessage) + case events.MessageWithStatus: + s.statusMessage = msg.StatusMessage() + case events.PromptForInputMsg: if s.pendingInput != nil { // ignore, already in an input return s, nil } - s.textInput.Prompt = msg.prompt + s.textInput.Prompt = msg.Prompt s.textInput.Focus() s.textInput.SetValue("") s.pendingInput = &msg @@ -51,7 +56,7 @@ func (s StatusAndPrompt) Update(msg tea.Msg) (tea.Model, tea.Cmd) { pendingInput := s.pendingInput s.pendingInput = nil - return s, pendingInput.onDone(s.textInput.Value()) + return s, pendingInput.OnDone(s.textInput.Value()) } } } diff --git a/internal/dynamo-browse/ui/teamodels/tableselect/items.go b/internal/dynamo-browse/ui/teamodels/tableselect/items.go index 36d76de..65e4308 100644 --- a/internal/dynamo-browse/ui/teamodels/tableselect/items.go +++ b/internal/dynamo-browse/ui/teamodels/tableselect/items.go @@ -15,13 +15,13 @@ func (ti tableItem) Title() string { } func (ti tableItem) Description() string { - return "abc" + return "" } -func toListItems[T list.Item](xs []T) []list.Item { +func toListItems(xs []string) []list.Item { ls := make([]list.Item, len(xs)) for i, x := range xs { - ls[i] = x + ls[i] = tableItem{name: x} } return ls } diff --git a/internal/dynamo-browse/ui/teamodels/tableselect/list.go b/internal/dynamo-browse/ui/teamodels/tableselect/list.go index 167ee1e..84365ab 100644 --- a/internal/dynamo-browse/ui/teamodels/tableselect/list.go +++ b/internal/dynamo-browse/ui/teamodels/tableselect/list.go @@ -20,14 +20,8 @@ type listController struct { list list.Model } -func newListController(w, h int) listController { - tableItems := []tableItem{ - {name: "alpha"}, - {name: "beta"}, - {name: "gamma"}, - } - - items := toListItems(tableItems) +func newListController(tableNames []string, w, h int) listController { + items := toListItems(tableNames) delegate := list.NewDefaultDelegate() delegate.ShowDescription = false diff --git a/internal/dynamo-browse/ui/teamodels/tableselect/model.go b/internal/dynamo-browse/ui/teamodels/tableselect/model.go index 331198e..b3894c9 100644 --- a/internal/dynamo-browse/ui/teamodels/tableselect/model.go +++ b/internal/dynamo-browse/ui/teamodels/tableselect/model.go @@ -2,13 +2,14 @@ package tableselect import ( tea "github.com/charmbracelet/bubbletea" + "github.com/lmika/awstools/internal/dynamo-browse/controllers" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/layout" "github.com/lmika/awstools/internal/dynamo-browse/ui/teamodels/utils" ) type Model struct { submodel tea.Model - pendingSelection *showTableSelectMsg + pendingSelection *controllers.PromptForTableMsg listController listController isLoading bool w, h int @@ -25,10 +26,10 @@ func (m Model) Init() tea.Cmd { func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { var cc utils.CmdCollector switch msg := msg.(type) { - case showTableSelectMsg: + case controllers.PromptForTableMsg: m.isLoading = false m.pendingSelection = &msg - m.listController = newListController(m.w, m.h) + m.listController = newListController(msg.Tables, m.w, m.h) return m, nil case indicateLoadingTablesMsg: m.isLoading = true @@ -37,10 +38,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { if m.pendingSelection != nil { switch msg.String() { case "enter": - var sel showTableSelectMsg + var sel controllers.PromptForTableMsg sel, m.pendingSelection = *m.pendingSelection, nil - return m, sel.onSelected(m.listController.list.SelectedItem().(tableItem).name) + return m, sel.OnSelected(m.listController.list.SelectedItem().(tableItem).name) default: m.listController = cc.Collect(m.listController.Update(msg)).(listController) return m, cc.Cmd()