func()

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...)
}