Merge remote-tracking branch 'refs/remotes/origin/main'

This commit is contained in:
Leon Mika 2023-01-28 10:00:48 +11:00
commit 3bf5b6ec93
23 changed files with 265 additions and 29 deletions

View file

@ -13,8 +13,10 @@ import (
"github.com/lmika/audax/internal/common/workspaces" "github.com/lmika/audax/internal/common/workspaces"
"github.com/lmika/audax/internal/dynamo-browse/controllers" "github.com/lmika/audax/internal/dynamo-browse/controllers"
"github.com/lmika/audax/internal/dynamo-browse/providers/dynamo" "github.com/lmika/audax/internal/dynamo-browse/providers/dynamo"
"github.com/lmika/audax/internal/dynamo-browse/providers/inputhistorystore"
"github.com/lmika/audax/internal/dynamo-browse/providers/settingstore" "github.com/lmika/audax/internal/dynamo-browse/providers/settingstore"
"github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore" "github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore"
"github.com/lmika/audax/internal/dynamo-browse/services/inputhistory"
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer" "github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
"github.com/lmika/audax/internal/dynamo-browse/services/jobs" "github.com/lmika/audax/internal/dynamo-browse/services/jobs"
keybindings_service "github.com/lmika/audax/internal/dynamo-browse/services/keybindings" keybindings_service "github.com/lmika/audax/internal/dynamo-browse/services/keybindings"
@ -81,6 +83,7 @@ func main() {
dynamoProvider := dynamo.NewProvider(dynamoClient) dynamoProvider := dynamo.NewProvider(dynamoClient)
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws) resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws)
settingStore := settingstore.New(ws) settingStore := settingstore.New(ws)
inputHistoryStore := inputhistorystore.NewInputHistoryStore(ws)
if *flagRO { if *flagRO {
if err := settingStore.SetReadOnly(*flagRO); err != nil { if err := settingStore.SetReadOnly(*flagRO); err != nil {
@ -98,10 +101,11 @@ func main() {
itemRendererService := itemrenderer.NewService(uiStyles.ItemView.FieldType, uiStyles.ItemView.MetaInfo) itemRendererService := itemrenderer.NewService(uiStyles.ItemView.FieldType, uiStyles.ItemView.MetaInfo)
scriptManagerService := scriptmanager.New() scriptManagerService := scriptmanager.New()
jobsService := jobs.NewService(eventBus) jobsService := jobs.NewService(eventBus)
inputHistoryService := inputhistory.New(inputHistoryStore)
state := controllers.NewState() state := controllers.NewState()
jobsController := controllers.NewJobsController(jobsService, eventBus, false) jobsController := controllers.NewJobsController(jobsService, eventBus, false)
tableReadController := controllers.NewTableReadController(state, tableService, workspaceService, itemRendererService, jobsController, eventBus, *flagTable) tableReadController := controllers.NewTableReadController(state, tableService, workspaceService, itemRendererService, jobsController, inputHistoryService, eventBus, *flagTable)
tableWriteController := controllers.NewTableWriteController(state, tableService, jobsController, tableReadController, settingStore) tableWriteController := controllers.NewTableWriteController(state, tableService, jobsController, tableReadController, settingStore)
columnsController := controllers.NewColumnsController(eventBus) columnsController := controllers.NewColumnsController(eventBus)
exportController := controllers.NewExportController(state, columnsController) exportController := controllers.NewExportController(state, columnsController)
@ -112,7 +116,7 @@ func main() {
keyBindingService := keybindings_service.NewService(keyBindings) keyBindingService := keybindings_service.NewService(keyBindings)
keyBindingController := controllers.NewKeyBindingController(keyBindingService) keyBindingController := controllers.NewKeyBindingController(keyBindingService)
commandController := commandctrl.NewCommandController() commandController := commandctrl.NewCommandController(inputHistoryService)
commandController.AddCommandLookupExtension(scriptController) commandController.AddCommandLookupExtension(scriptController)
model := ui.NewModel( model := ui.NewModel(

View file

@ -32,7 +32,7 @@ func main() {
ctrl := controllers.NewLogFileController(service, flag.Arg(0)) ctrl := controllers.NewLogFileController(service, flag.Arg(0))
cmdController := commandctrl.NewCommandController() cmdController := commandctrl.NewCommandController(nil)
//cmdController.AddCommands(&commandctrl.CommandList{ //cmdController.AddCommands(&commandctrl.CommandList{
// Commands: map[string]commandctrl.Command{ // Commands: map[string]commandctrl.Command{
// "cd": func(args []string) tea.Cmd { // "cd": func(args []string) tea.Cmd {

View file

@ -47,7 +47,7 @@ func main() {
ctrl := controllers.New(service) ctrl := controllers.New(service)
cmdController := commandctrl.NewCommandController() cmdController := commandctrl.NewCommandController(nil)
cmdController.AddCommands(&commandctrl.CommandList{ cmdController.AddCommands(&commandctrl.CommandList{
Commands: map[string]commandctrl.Command{ Commands: map[string]commandctrl.Command{
"cd": func(ec commandctrl.ExecContext, args []string) tea.Msg { "cd": func(ec commandctrl.ExecContext, args []string) tea.Msg {

View file

@ -3,6 +3,7 @@ package commandctrl
import ( import (
"bufio" "bufio"
"bytes" "bytes"
"context"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/pkg/errors" "github.com/pkg/errors"
"log" "log"
@ -14,13 +15,17 @@ import (
"github.com/lmika/shellwords" "github.com/lmika/shellwords"
) )
const commandsCategory = "commands"
type CommandController struct { type CommandController struct {
historyProvider IterProvider
commandList *CommandList commandList *CommandList
lookupExtensions []CommandLookupExtension lookupExtensions []CommandLookupExtension
} }
func NewCommandController() *CommandController { func NewCommandController(historyProvider IterProvider) *CommandController {
return &CommandController{ return &CommandController{
historyProvider: historyProvider,
commandList: nil, commandList: nil,
lookupExtensions: nil, lookupExtensions: nil,
} }
@ -38,6 +43,7 @@ func (c *CommandController) AddCommandLookupExtension(ext CommandLookupExtension
func (c *CommandController) Prompt() tea.Msg { func (c *CommandController) Prompt() tea.Msg {
return events.PromptForInputMsg{ return events.PromptForInputMsg{
Prompt: ":", Prompt: ":",
History: c.historyProvider.Iter(context.Background(), commandsCategory),
OnDone: func(value string) tea.Msg { OnDone: func(value string) tea.Msg {
return c.Execute(value) return c.Execute(value)
}, },

View file

@ -1,7 +1,9 @@
package commandctrl_test package commandctrl_test
import ( import (
"context"
"github.com/lmika/audax/internal/common/ui/events" "github.com/lmika/audax/internal/common/ui/events"
"github.com/lmika/audax/internal/dynamo-browse/services"
"testing" "testing"
"github.com/lmika/audax/internal/common/ui/commandctrl" "github.com/lmika/audax/internal/common/ui/commandctrl"
@ -10,7 +12,7 @@ import (
func TestCommandController_Prompt(t *testing.T) { func TestCommandController_Prompt(t *testing.T) {
t.Run("prompt user for a command", func(t *testing.T) { t.Run("prompt user for a command", func(t *testing.T) {
cmd := commandctrl.NewCommandController() cmd := commandctrl.NewCommandController(mockIterProvider{})
res := cmd.Prompt() res := cmd.Prompt()
@ -19,3 +21,10 @@ func TestCommandController_Prompt(t *testing.T) {
assert.Equal(t, ":", promptForInputMsg.Prompt) assert.Equal(t, ":", promptForInputMsg.Prompt)
}) })
} }
type mockIterProvider struct {
}
func (m mockIterProvider) Iter(ctx context.Context, category string) services.HistoryProvider {
return nil
}

View file

@ -0,0 +1,10 @@
package commandctrl
import (
"context"
"github.com/lmika/audax/internal/dynamo-browse/services"
)
type IterProvider interface {
Iter(ctx context.Context, category string) services.HistoryProvider
}

View file

@ -2,6 +2,7 @@ package events
import ( import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/audax/internal/dynamo-browse/services"
"log" "log"
) )
@ -22,21 +23,22 @@ func SetTeaMessage(event tea.Msg) tea.Cmd {
} }
} }
func PromptForInput(prompt string, onDone func(value string) tea.Msg) tea.Msg { func PromptForInput(prompt string, history services.HistoryProvider, onDone func(value string) tea.Msg) tea.Msg {
return PromptForInputMsg{ return PromptForInputMsg{
Prompt: prompt, Prompt: prompt,
History: history,
OnDone: onDone, OnDone: onDone,
} }
} }
func Confirm(prompt string, onResult func(yes bool) tea.Msg) tea.Msg { func Confirm(prompt string, onResult func(yes bool) tea.Msg) tea.Msg {
return PromptForInput(prompt, func(value string) tea.Msg { return PromptForInput(prompt, nil, func(value string) tea.Msg {
return onResult(value == "y") return onResult(value == "y")
}) })
} }
func ConfirmYes(prompt string, onYes func() tea.Msg) tea.Msg { func ConfirmYes(prompt string, onYes func() tea.Msg) tea.Msg {
return PromptForInput(prompt, func(value string) tea.Msg { return PromptForInput(prompt, nil, func(value string) tea.Msg {
if value == "y" { if value == "y" {
return onYes() return onYes()
} }

View file

@ -2,6 +2,7 @@ package events
import ( import (
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/lmika/audax/internal/dynamo-browse/services"
) )
// Error indicates that an error occurred // Error indicates that an error occurred
@ -21,6 +22,7 @@ type ModeMessage string
// PromptForInput indicates that the context is requesting a line of input // PromptForInput indicates that the context is requesting a line of input
type PromptForInputMsg struct { type PromptForInputMsg struct {
Prompt string Prompt string
History services.HistoryProvider
OnDone func(value string) tea.Msg OnDone func(value string) tea.Msg
OnCancel func() tea.Msg OnCancel func() tea.Msg
} }

View file

@ -67,7 +67,7 @@ func (cc *ColumnsController) onNewResultSet(rs *models.ResultSet, op resultSetUp
} }
func (cc *ColumnsController) AddColumn(afterIndex int) tea.Msg { func (cc *ColumnsController) AddColumn(afterIndex int) tea.Msg {
return events.PromptForInput("column expr: ", func(value string) tea.Msg { return events.PromptForInput("column expr: ", nil, func(value string) tea.Msg {
colExpr, err := queryexpr.Parse(value) colExpr, err := queryexpr.Parse(value)
if err != nil { if err != nil {
return events.Error(err) return events.Error(err)

View file

@ -11,6 +11,7 @@ import (
"github.com/lmika/audax/internal/dynamo-browse/models/attrcodec" "github.com/lmika/audax/internal/dynamo-browse/models/attrcodec"
"github.com/lmika/audax/internal/dynamo-browse/models/queryexpr" "github.com/lmika/audax/internal/dynamo-browse/models/queryexpr"
"github.com/lmika/audax/internal/dynamo-browse/models/serialisable" "github.com/lmika/audax/internal/dynamo-browse/models/serialisable"
"github.com/lmika/audax/internal/dynamo-browse/services/inputhistory"
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer" "github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
"github.com/lmika/audax/internal/dynamo-browse/services/viewsnapshot" "github.com/lmika/audax/internal/dynamo-browse/services/viewsnapshot"
bus "github.com/lmika/events" bus "github.com/lmika/events"
@ -42,11 +43,17 @@ const (
MarkOpToggle MarkOpToggle
) )
const (
queryInputHistoryCategory = "queries"
filterInputHistoryCategory = "filters"
)
type TableReadController struct { type TableReadController struct {
tableService TableReadService tableService TableReadService
workspaceService *viewsnapshot.ViewSnapshotService workspaceService *viewsnapshot.ViewSnapshotService
itemRendererService *itemrenderer.Service itemRendererService *itemrenderer.Service
jobController *JobsController jobController *JobsController
inputHistoryService *inputhistory.Service
eventBus *bus.Bus eventBus *bus.Bus
tableName string tableName string
loadFromLastView bool loadFromLastView bool
@ -63,6 +70,7 @@ func NewTableReadController(
workspaceService *viewsnapshot.ViewSnapshotService, workspaceService *viewsnapshot.ViewSnapshotService,
itemRendererService *itemrenderer.Service, itemRendererService *itemrenderer.Service,
jobController *JobsController, jobController *JobsController,
inputHistoryService *inputhistory.Service,
eventBus *bus.Bus, eventBus *bus.Bus,
tableName string, tableName string,
) *TableReadController { ) *TableReadController {
@ -72,6 +80,7 @@ func NewTableReadController(
workspaceService: workspaceService, workspaceService: workspaceService,
itemRendererService: itemRendererService, itemRendererService: itemRendererService,
jobController: jobController, jobController: jobController,
inputHistoryService: inputHistoryService,
eventBus: eventBus, eventBus: eventBus,
tableName: tableName, tableName: tableName,
mutex: new(sync.Mutex), mutex: new(sync.Mutex),
@ -137,6 +146,7 @@ func (c *TableReadController) ScanTable(name string) tea.Msg {
func (c *TableReadController) PromptForQuery() tea.Msg { func (c *TableReadController) PromptForQuery() tea.Msg {
return events.PromptForInputMsg{ return events.PromptForInputMsg{
Prompt: "query: ", Prompt: "query: ",
History: c.inputHistoryService.Iter(context.Background(), queryInputHistoryCategory),
OnDone: func(value string) tea.Msg { OnDone: func(value string) tea.Msg {
resultSet := c.state.ResultSet() resultSet := c.state.ResultSet()
if resultSet == nil { if resultSet == nil {
@ -289,6 +299,7 @@ func (c *TableReadController) Mark(op MarkOp) tea.Msg {
func (c *TableReadController) Filter() tea.Msg { func (c *TableReadController) Filter() tea.Msg {
return events.PromptForInputMsg{ return events.PromptForInputMsg{
Prompt: "filter: ", Prompt: "filter: ",
History: c.inputHistoryService.Iter(context.Background(), filterInputHistoryCategory),
OnDone: func(value string) tea.Msg { OnDone: func(value string) tea.Msg {
resultSet := c.state.ResultSet() resultSet := c.state.ResultSet()
if resultSet == nil { if resultSet == nil {

View file

@ -8,8 +8,10 @@ import (
"github.com/lmika/audax/internal/dynamo-browse/controllers" "github.com/lmika/audax/internal/dynamo-browse/controllers"
"github.com/lmika/audax/internal/dynamo-browse/models" "github.com/lmika/audax/internal/dynamo-browse/models"
"github.com/lmika/audax/internal/dynamo-browse/providers/dynamo" "github.com/lmika/audax/internal/dynamo-browse/providers/dynamo"
"github.com/lmika/audax/internal/dynamo-browse/providers/inputhistorystore"
"github.com/lmika/audax/internal/dynamo-browse/providers/settingstore" "github.com/lmika/audax/internal/dynamo-browse/providers/settingstore"
"github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore" "github.com/lmika/audax/internal/dynamo-browse/providers/workspacestore"
"github.com/lmika/audax/internal/dynamo-browse/services/inputhistory"
"github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer" "github.com/lmika/audax/internal/dynamo-browse/services/itemrenderer"
"github.com/lmika/audax/internal/dynamo-browse/services/jobs" "github.com/lmika/audax/internal/dynamo-browse/services/jobs"
"github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager" "github.com/lmika/audax/internal/dynamo-browse/services/scriptmanager"
@ -600,9 +602,12 @@ func newService(t *testing.T, cfg serviceConfig) *services {
resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws) resultSetSnapshotStore := workspacestore.NewResultSetSnapshotStore(ws)
settingStore := settingstore.New(ws) settingStore := settingstore.New(ws)
inputHistoryStore := inputhistorystore.NewInputHistoryStore(ws)
workspaceService := viewsnapshot.NewService(resultSetSnapshotStore) workspaceService := viewsnapshot.NewService(resultSetSnapshotStore)
itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer()) itemRendererService := itemrenderer.NewService(itemrenderer.PlainTextRenderer(), itemrenderer.PlainTextRenderer())
scriptService := scriptmanager.New() scriptService := scriptmanager.New()
inputHistoryService := inputhistory.New(inputHistoryStore)
client := testdynamo.SetupTestTable(t, testData) client := testdynamo.SetupTestTable(t, testData)
@ -612,14 +617,14 @@ func newService(t *testing.T, cfg serviceConfig) *services {
state := controllers.NewState() state := controllers.NewState()
jobsController := controllers.NewJobsController(jobs.NewService(eventBus), eventBus, true) jobsController := controllers.NewJobsController(jobs.NewService(eventBus), eventBus, true)
readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, jobsController, eventBus, cfg.tableName) readController := controllers.NewTableReadController(state, service, workspaceService, itemRendererService, jobsController, inputHistoryService, eventBus, cfg.tableName)
writeController := controllers.NewTableWriteController(state, service, jobsController, readController, settingStore) writeController := controllers.NewTableWriteController(state, service, jobsController, readController, settingStore)
settingsController := controllers.NewSettingsController(settingStore, eventBus) settingsController := controllers.NewSettingsController(settingStore, eventBus)
columnsController := controllers.NewColumnsController(eventBus) columnsController := controllers.NewColumnsController(eventBus)
exportController := controllers.NewExportController(state, columnsController) exportController := controllers.NewExportController(state, columnsController)
scriptController := controllers.NewScriptController(scriptService, readController, settingsController, eventBus) scriptController := controllers.NewScriptController(scriptService, readController, settingsController, eventBus)
commandController := commandctrl.NewCommandController() commandController := commandctrl.NewCommandController(inputHistoryService)
commandController.AddCommandLookupExtension(scriptController) commandController.AddCommandLookupExtension(scriptController)
if cfg.isReadOnly { if cfg.isReadOnly {

View file

@ -194,7 +194,7 @@ func (a irFieldBeginsWith) canBeExecutedAsQuery(info *models.TableInfo, qci *que
return false return false
} }
if keyName == info.Keys.SortKey { if keyName == info.Keys.SortKey && qci.hasSeenPrimaryKey(info) {
return qci.addKey(info, a.name.keyName()) return qci.addKey(info, a.name.keyName())
} }

View file

@ -143,6 +143,9 @@ func TestModExpr_Query(t *testing.T) {
scanCase("when request sk equals something", `sk="something"`, `#0 = :0`, scanCase("when request sk equals something", `sk="something"`, `#0 = :0`,
exprNameIsString(0, 0, "sk", "something"), exprNameIsString(0, 0, "sk", "something"),
), ),
scanCase("when request sk starts with something", `sk^="something"`, `begins_with (#0, :0)`,
exprNameIsString(0, 0, "sk", "something"),
),
scanCase("with not equal", `sk != "something"`, `#0 <> :0`, scanCase("with not equal", `sk != "something"`, `#0 <> :0`,
exprNameIsString(0, 0, "sk", "something"), exprNameIsString(0, 0, "sk", "something"),
), ),

View file

@ -69,6 +69,10 @@ func (a *astLiteralValue) String() string {
return *a.StringVal return *a.StringVal
case a.IntVal != nil: case a.IntVal != nil:
return strconv.FormatInt(*a.IntVal, 10) return strconv.FormatInt(*a.IntVal, 10)
case a.TrueBoolValue:
return "true"
case a.FalseBoolValue:
return "false"
} }
return "" return ""
} }

View file

@ -0,0 +1,10 @@
package inputhistorystore
import "time"
type inputHistoryItem struct {
ID int `storm:"id,increment"`
Category string `storm:"index"`
Time time.Time
Item string
}

View file

@ -0,0 +1,49 @@
package inputhistorystore
import (
"context"
"github.com/asdine/storm"
"github.com/lmika/audax/internal/common/sliceutils"
"github.com/lmika/audax/internal/common/workspaces"
"github.com/pkg/errors"
"sort"
"time"
)
const inputHistoryStore = "InputHistoryStore"
type Store struct {
ws storm.Node
}
func NewInputHistoryStore(ws *workspaces.Workspace) *Store {
return &Store{
ws: ws.DB().From(inputHistoryStore),
}
}
// Items returns all items from a category ordered by the time
func (as *Store) Items(ctx context.Context, category string) ([]string, error) {
var items []inputHistoryItem
if err := as.ws.Find("Category", category, &items); err != nil {
if errors.Is(err, storm.ErrNotFound) {
return nil, nil
}
return nil, err
}
sort.Slice(items, func(i, j int) bool {
return items[i].Time.Before(items[j].Time)
})
return sliceutils.Map(items, func(t inputHistoryItem) string {
return t.Item
}), nil
}
func (as *Store) PutItem(ctx context.Context, category string, item string) error {
return as.ws.Save(&inputHistoryItem{
Time: time.Now(),
Category: category,
Item: item,
})
}

View file

@ -0,0 +1,13 @@
package services
type HistoryProvider interface {
// Len returns the number of historical items
Len() int
// Item returns the historical item at index 'idx', where items are chronologically ordered such that the
// item at 0 is the oldest item.
Item(idx int) string
// PutItem adds an item to the history
PutItem(item string)
}

View file

@ -0,0 +1,8 @@
package inputhistory
import "context"
type HistoryItemStore interface {
Items(ctx context.Context, category string) ([]string, error)
PutItem(ctx context.Context, category string, item string) error
}

View file

@ -0,0 +1,49 @@
package inputhistory
import (
"context"
"github.com/lmika/audax/internal/dynamo-browse/services"
"log"
"strings"
)
func (svc *Service) Iter(ctx context.Context, category string) services.HistoryProvider {
items, err := svc.store.Items(ctx, category)
if err != nil {
log.Printf("warn: cannot get iter for '%v': %v", category, err)
return nil
}
return &Iter{svc, items, category}
}
func (svc *Service) PutItem(ctx context.Context, category string, value string) error {
return svc.store.PutItem(ctx, category, value)
}
type Iter struct {
svc *Service
items []string
category string
}
func (i *Iter) Len() int {
return len(i.items)
}
func (i *Iter) Item(idx int) string {
return i.items[idx]
}
func (i *Iter) PutItem(item string) {
if strings.TrimSpace(item) == "" {
return
}
if len(i.items) > 0 && i.items[len(i.items)-1] == item {
return
}
if err := i.svc.PutItem(context.Background(), i.category, item); err != nil {
log.Printf("warn: cannot put input history: category = %v, value = %v, err = %v", i.category, item, err)
}
}

View file

@ -0,0 +1,9 @@
package inputhistory
type Service struct {
store HistoryItemStore
}
func New(store HistoryItemStore) *Service {
return &Service{store: store}
}

View file

@ -20,7 +20,7 @@ type StatusAndPrompt struct {
statusMessage string statusMessage string
spinner spinner.Model spinner spinner.Model
spinnerVisible bool spinnerVisible bool
pendingInput *events.PromptForInputMsg pendingInput *pendingInputState
textInput textinput.Model textInput textinput.Model
width, height int width, height int
lastModeLineHeight int lastModeLineHeight int
@ -83,15 +83,15 @@ func (s *StatusAndPrompt) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
s.textInput.Prompt = msg.Prompt s.textInput.Prompt = msg.Prompt
s.textInput.Focus() s.textInput.Focus()
s.textInput.SetValue("") s.textInput.SetValue("")
s.pendingInput = &msg s.pendingInput = newPendingInputState(msg)
case tea.KeyMsg: case tea.KeyMsg:
if s.pendingInput != nil { if s.pendingInput != nil {
switch msg.Type { switch msg.Type {
case tea.KeyCtrlC, tea.KeyEsc: case tea.KeyCtrlC, tea.KeyEsc:
if s.pendingInput.OnCancel != nil { if s.pendingInput.originalMsg.OnCancel != nil {
pendingInput := s.pendingInput pendingInput := s.pendingInput
cc.Add(func() tea.Msg { cc.Add(func() tea.Msg {
m := pendingInput.OnCancel() m := pendingInput.originalMsg.OnCancel()
return m return m
}) })
} }
@ -100,18 +100,48 @@ func (s *StatusAndPrompt) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
pendingInput := s.pendingInput pendingInput := s.pendingInput
s.pendingInput = nil s.pendingInput = nil
return s, func() tea.Msg { m := pendingInput.originalMsg.OnDone(s.textInput.Value())
m := pendingInput.OnDone(s.textInput.Value())
return m return s, tea.Batch(
events.SetTeaMessage(m),
func() tea.Msg {
if historyProvider := pendingInput.originalMsg.History; historyProvider != nil {
if _, isErrMsg := m.(events.ErrorMsg); !isErrMsg {
historyProvider.PutItem(s.textInput.Value())
}
}
return nil
},
)
case tea.KeyUp:
if historyProvider := s.pendingInput.originalMsg.History; historyProvider != nil && historyProvider.Len() > 0 {
if s.pendingInput.historyIdx < 0 {
s.pendingInput.historyIdx = historyProvider.Len() - 1
} else if s.pendingInput.historyIdx > 0 {
s.pendingInput.historyIdx -= 1
} else {
s.pendingInput.historyIdx = 0
}
s.textInput.SetValue(historyProvider.Item(s.pendingInput.historyIdx))
s.textInput.SetCursor(len(s.textInput.Value()))
}
case tea.KeyDown:
if historyProvider := s.pendingInput.originalMsg.History; historyProvider != nil && historyProvider.Len() > 0 {
if s.pendingInput.historyIdx >= 0 && s.pendingInput.historyIdx < historyProvider.Len()-1 {
s.pendingInput.historyIdx += 1
}
s.textInput.SetValue(historyProvider.Item(s.pendingInput.historyIdx))
s.textInput.SetCursor(len(s.textInput.Value()))
} }
default: default:
if msg.Type == tea.KeyRunes { if msg.Type == tea.KeyRunes {
msg.Runes = sliceutils.Filter(msg.Runes, func(r rune) bool { return r != '\x0d' && r != '\x0a' }) msg.Runes = sliceutils.Filter(msg.Runes, func(r rune) bool { return r != '\x0d' && r != '\x0a' })
} }
}
newTextInput, cmd := s.textInput.Update(msg) newTextInput, cmd := s.textInput.Update(msg)
s.textInput = newTextInput s.textInput = newTextInput
return s, cmd return s, cmd
}
} else { } else {
s.statusMessage = "" s.statusMessage = ""
} }

View file

@ -0,0 +1,12 @@
package statusandprompt
import "github.com/lmika/audax/internal/common/ui/events"
type pendingInputState struct {
originalMsg events.PromptForInputMsg
historyIdx int
}
func newPendingInputState(msg events.PromptForInputMsg) *pendingInputState {
return &pendingInputState{originalMsg: msg, historyIdx: -1}
}

View file

@ -56,7 +56,7 @@ func (c *SSMController) ChangePrefix(newPrefix string) tea.Msg {
} }
func (c *SSMController) Clone(param models.SSMParameter) tea.Msg { func (c *SSMController) Clone(param models.SSMParameter) tea.Msg {
return events.PromptForInput("New key: ", func(value string) tea.Msg { return events.PromptForInput("New key: ", nil, func(value string) tea.Msg {
return func() tea.Msg { return func() tea.Msg {
ctx := context.Background() ctx := context.Background()
if err := c.service.Clone(ctx, param, value); err != nil { if err := c.service.Clone(ctx, param, value); err != nil {