in internal/engine/interactive/interactive.go [281:515]
func (model InteractiveModeModel) Update(message tea.Msg) (tea.Model, tea.Cmd) {
var commands []tea.Cmd
switch message := message.(type) {
case tea.WindowSizeMsg:
model.width = message.Width
model.height = message.Height
logging.GlobalLogger.Debugf("Window size changed to: %d x %d", message.Width, message.Height)
if !model.ready {
model.components = initializeComponents(model, message.Width, message.Height)
model.ready = true
} else {
model.components.stepViewport.Width = message.Width
model.components.outputViewport.Width = message.Width
model.components.azureCLIViewport.Width = message.Width
model.components.updateViewportHeight(message.Height)
}
case tea.KeyMsg:
model, commands = handleUserInput(model, message)
case common.SuccessfulCommandMessage:
// Handle successful command executions
model.executingCommand = false
step := model.currentCodeBlock
// Update the state of the codeblock which finished executing.
codeBlockState := model.codeBlockState[step]
codeBlockState.StdOut = message.StdOut
codeBlockState.StdErr = message.StdErr
codeBlockState.Success = true
model.codeBlockState[step] = codeBlockState
logging.GlobalLogger.Infof("Finished executing:\n %s", codeBlockState.CodeBlock.Content)
// Extract the resource group name from the command output if
// it's not already set.
if model.resourceGroupName == "" && patterns.AzCommand.MatchString(codeBlockState.CodeBlock.Content) {
logging.GlobalLogger.Debugf("Attempting to extract resource group name from command output")
tmpResourceGroup := az.FindResourceGroupName(codeBlockState.StdOut)
if tmpResourceGroup != "" {
logging.GlobalLogger.Infof("Found resource group named: %s", tmpResourceGroup)
model.resourceGroupName = tmpResourceGroup
model.azureStatus.AddResourceURI(az.BuildResourceGroupId(model.subscription, model.resourceGroupName))
}
}
model.CommandLines = append(model.CommandLines, codeBlockState.StdOut)
// Increment the codeblock and update the viewport content.
model.currentCodeBlock++
if model.currentCodeBlock < len(model.codeBlockState) {
nextCommand := model.codeBlockState[model.currentCodeBlock].CodeBlock.Content
nextLanguage := model.codeBlockState[model.currentCodeBlock].CodeBlock.Language
model.CommandLines = append(model.CommandLines, ui.CommandPrompt(nextLanguage)+nextCommand)
}
// Only increment the step for azure if the step name has changed.
nextCodeBlockState := model.codeBlockState[model.currentCodeBlock]
if codeBlockState.StepName != nextCodeBlockState.StepName {
logging.GlobalLogger.Debugf("Step name has changed, incrementing step for Azure")
model.azureStatus.CurrentStep++
} else {
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) {
model.scenarioCompleted = true
model.azureStatus.Status = "Succeeded"
environments.AttachResourceURIsToAzureStatus(
&model.azureStatus,
model.resourceGroupName,
model.environment,
)
environmentVariables, err := lib.LoadEnvironmentStateFile(lib.DefaultEnvironmentStateFile)
if err != nil {
logging.GlobalLogger.Errorf("Failed to load environment state file: %s", err)
model.azureStatus.SetError(err)
}
model.azureStatus.ConfigureMarkdownForDownload(
model.markdownSource,
environmentVariables,
model.environment,
)
model.azureStatus.SetOutput(strings.Join(model.CommandLines, "\n"))
commands = append(
commands,
tea.Sequence(
common.UpdateAzureStatus(model.azureStatus, model.environment),
tea.Quit,
),
)
} else {
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:
// Handle failed command executions
// Update the state of the codeblock which finished executing.
step := model.currentCodeBlock
codeBlockState := model.codeBlockState[step]
codeBlockState.StdOut = message.StdOut
codeBlockState.StdErr = message.StdErr
codeBlockState.Success = false
model.codeBlockState[step] = codeBlockState
model.CommandLines = append(model.CommandLines, codeBlockState.StdErr)
// Report the error
model.executingCommand = false
model.azureStatus.SetError(message.Error)
environments.AttachResourceURIsToAzureStatus(
&model.azureStatus,
model.resourceGroupName,
model.environment,
)
model.azureStatus.SetOutput(strings.Join(model.CommandLines, "\n"))
commands = append(
commands,
tea.Sequence(
common.UpdateAzureStatus(model.azureStatus, model.environment),
tea.Quit,
),
)
case common.AzureStatusUpdatedMessage:
// After the status has been updated, we force a window resize to
// render over the status update. For some reason, clearing the screen
// manually seems to cause the text produced by View() to not render
// properly if we don't trigger a window size event.
commands = append(commands,
tea.Sequence(
tea.ClearScreen,
func() tea.Msg {
return tea.WindowSizeMsg{
Width: model.width,
Height: model.height,
}
},
),
)
}
// Update viewport content
block := model.codeBlockState[model.currentCodeBlock]
renderedStepSection := fmt.Sprintf(
"%s\n\n%s",
block.CodeBlock.Description,
block.CodeBlock.Content,
)
// TODO(vmarcella): We shoulkd figure out a way to not have to recreate
// the renderer every time we update the view.
renderer, err := glamour.NewTermRenderer(
glamour.WithAutoStyle(),
glamour.WithWordWrap(model.width-4),
)
if err == nil {
var glamourizedSection string
glamourizedSection, err = renderer.Render(
fmt.Sprintf(
"%s\n```%s\n%s```",
block.CodeBlock.Description,
block.CodeBlock.Language,
block.CodeBlock.Content,
),
)
if err != nil {
logging.GlobalLogger.Errorf(
"Error rendering codeblock: %s, using non rendered codeblock instead",
err,
)
} else {
renderedStepSection = glamourizedSection
}
} else {
logging.GlobalLogger.Errorf(
"Error creating glamour renderer: %s, using non rendered codeblock instead",
err,
)
}
model.components.stepViewport.SetContent(
renderedStepSection,
)
if block.Success {
model.components.outputViewport.SetContent(block.StdOut)
} else {
model.components.outputViewport.SetContent(block.StdErr)
}
model.components.azureCLIViewport.SetContent(strings.Join(model.CommandLines, "\n"))
// Update all the viewports and append resulting commands.
var command tea.Cmd
model.components.paginator.Page = model.currentCodeBlock
model.components.stepViewport, command = model.components.stepViewport.Update(message)
commands = append(commands, command)
model.components.outputViewport, command = model.components.outputViewport.Update(message)
commands = append(commands, command)
model.components.azureCLIViewport, command = model.components.azureCLIViewport.Update(message)
commands = append(commands, command)
return model, tea.Batch(commands...)
}