From 0b0ae5bf0c6b965c7233342704c90639ac2c7773 Mon Sep 17 00:00:00 2001 From: vmarcella Date: Mon, 26 Aug 2024 13:44:34 -0700 Subject: [PATCH 1/2] [add] new bindings for interactive mode in the portal. --- internal/engine/interactive/interactive.go | 107 ++++++++++++++++++++- internal/lib/strings.go | 11 +++ internal/lib/strings_test.go | 1 + 3 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 internal/lib/strings.go create mode 100644 internal/lib/strings_test.go diff --git a/internal/engine/interactive/interactive.go b/internal/engine/interactive/interactive.go index a7669571..c786b677 100644 --- a/internal/engine/interactive/interactive.go +++ b/internal/engine/interactive/interactive.go @@ -2,6 +2,7 @@ package interactive import ( "fmt" + "strconv" "strings" "time" @@ -21,11 +22,15 @@ import ( "github.com/charmbracelet/lipgloss" ) +// All interactive mode inputs. type InteractiveModeCommands struct { - execute key.Binding - quit key.Binding - previous key.Binding - next key.Binding + execute key.Binding + executeAll key.Binding + executeMany key.Binding + next key.Binding + pause key.Binding + previous key.Binding + quit key.Binding } type interactiveModeComponents struct { @@ -43,6 +48,9 @@ type InteractiveModeModel struct { env map[string]string environment string executingCommand bool + stepsToBeExecuted int + recordingInput bool + recordedInput string height int help help.Model resourceGroupName string @@ -101,6 +109,41 @@ func handleUserInput( message tea.KeyMsg, ) (InteractiveModeModel, []tea.Cmd) { var commands []tea.Cmd + + // If we're recording input for a multi-char command, + if model.recordingInput { + isNumber := lib.IsNumber(message.String()) + + // If the input is a number, append it to the recorded input. + if message.Type == tea.KeyRunes && isNumber { + model.recordedInput += message.String() + return model, commands + } + + // If the input is not a number, we'll stop recording input and reset + // the commands remaining to the recorded input. + if message.Type == tea.KeyEnter || !isNumber { + commandsRemaining, _ := strconv.Atoi(model.recordedInput) + + if commandsRemaining > len(model.codeBlockState)-model.currentCodeBlock { + commandsRemaining = len(model.codeBlockState) - model.currentCodeBlock + } + + logging.GlobalLogger.Debugf("Will execute the next %d steps", commandsRemaining) + model.stepsToBeExecuted = commandsRemaining + commands = append(commands, func() tea.Msg { + return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}} + }) + + model.recordingInput = false + model.recordedInput = "" + logging.GlobalLogger.Debugf( + "Recording input stopped and previously recorded input cleared.", + ) + return model, commands + } + } + switch { case key.Matches(message, model.commands.execute): if model.executingCommand { @@ -179,6 +222,22 @@ func handleUserInput( case key.Matches(message, model.commands.quit): commands = append(commands, tea.Quit) + + case key.Matches(message, model.commands.executeAll): + model.stepsToBeExecuted = len(model.codeBlockState) - model.currentCodeBlock + commands = append( + commands, + func() tea.Msg { + return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}} + }, + ) + case key.Matches(message, model.commands.executeMany): + model.recordingInput = true + case key.Matches(message, model.commands.pause): + if !model.executingCommand { + logging.GlobalLogger.Info("No command is currently executing, ignoring pause command") + } + model.stepsToBeExecuted = 0 } return model, commands @@ -273,6 +332,8 @@ func (model InteractiveModeModel) Update(message tea.Msg) (tea.Model, tea.Cmd) { logging.GlobalLogger.Debugf("Step name has not changed, not incrementing step for Azure") } + model.stepsToBeExecuted-- + // If the scenario has been completed, we need to update the azure // status and quit the program. if model.currentCodeBlock == len(model.codeBlockState) { @@ -292,7 +353,19 @@ func (model InteractiveModeModel) Update(message tea.Msg) (tea.Model, tea.Cmd) { ), ) } else { - commands = append(commands, common.UpdateAzureStatus(model.azureStatus, model.environment)) + commands = append( + commands, + tea.Sequence( + common.UpdateAzureStatus(model.azureStatus, model.environment), + // Send a key event to trigger + func() tea.Msg { + if model.stepsToBeExecuted <= 0 { + return nil + } + return tea.KeyMsg{Type: tea.KeyRunes, Runes: []rune{'e'}} + }, + ), + ) } case common.FailedCommandMessage: @@ -540,6 +613,24 @@ func NewInteractiveModeModel( ui.CommandPrompt(language) + codeBlockState[0].CodeBlock.Content, } + // Configure extra keybinds used for executing the many/all commands. + executeAllKeybind := key.NewBinding( + key.WithKeys("a"), + ) + + executeManyKeybind := key.NewBinding( + key.WithKeys("m"), + ) + pauseKeybind := key.NewBinding( + key.WithKeys("p"), + ) + + if environment != "azure" { + executeAllKeybind.SetEnabled(false) + executeManyKeybind.SetEnabled(false) + pauseKeybind.SetEnabled(false) + } + return InteractiveModeModel{ scenarioTitle: title, commands: InteractiveModeCommands{ @@ -559,7 +650,13 @@ func NewInteractiveModeModel( key.WithKeys("right"), key.WithHelp("→", "Go to the next command."), ), + // Only enabled when in the azure environment. + executeAll: executeAllKeybind, + executeMany: executeManyKeybind, + pause: pauseKeybind, }, + stepsToBeExecuted: 0, + env: env, subscription: subscription, resourceGroupName: "", diff --git a/internal/lib/strings.go b/internal/lib/strings.go new file mode 100644 index 00000000..5b001dea --- /dev/null +++ b/internal/lib/strings.go @@ -0,0 +1,11 @@ +package lib + +// Checks if a given string is a number. +func IsNumber(str string) bool { + for _, r := range str { + if r < '0' || r > '9' { + return false + } + } + return true +} diff --git a/internal/lib/strings_test.go b/internal/lib/strings_test.go new file mode 100644 index 00000000..55c21f80 --- /dev/null +++ b/internal/lib/strings_test.go @@ -0,0 +1 @@ +package lib From d345d8317b7f626da576416c0cda28175394799c Mon Sep 17 00:00:00 2001 From: vmarcella Date: Mon, 26 Aug 2024 14:53:32 -0700 Subject: [PATCH 2/2] [add] commands for all environments. --- internal/engine/interactive/interactive.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/internal/engine/interactive/interactive.go b/internal/engine/interactive/interactive.go index c786b677..83d67e70 100644 --- a/internal/engine/interactive/interactive.go +++ b/internal/engine/interactive/interactive.go @@ -497,6 +497,8 @@ func (model InteractiveModeModel) helpView() string { // Command related bindings { model.commands.execute, + model.commands.executeAll, + model.commands.executeMany, model.commands.previous, model.commands.next, }, @@ -616,21 +618,17 @@ func NewInteractiveModeModel( // Configure extra keybinds used for executing the many/all commands. executeAllKeybind := key.NewBinding( key.WithKeys("a"), + key.WithHelp("a", "Execute all remaining commands."), ) executeManyKeybind := key.NewBinding( key.WithKeys("m"), + key.WithHelp("m", "Execute the next commands."), ) pauseKeybind := key.NewBinding( - key.WithKeys("p"), + key.WithKeys("p", "Pause execution of commands."), ) - if environment != "azure" { - executeAllKeybind.SetEnabled(false) - executeManyKeybind.SetEnabled(false) - pauseKeybind.SetEnabled(false) - } - return InteractiveModeModel{ scenarioTitle: title, commands: InteractiveModeCommands{