func handleUserInput()

in internal/engine/interactive/interactive.go [108:259]


func handleUserInput(
	model InteractiveModeModel,
	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 {
			logging.GlobalLogger.Info("Command is already executing, ignoring execute command")
			break
		}

		// Prevent the user from executing a command if the previous command has
		// not been executed successfully or executed at all.
		previousCodeBlock := model.currentCodeBlock - 1
		if previousCodeBlock >= 0 {
			previousCodeBlockState := model.codeBlockState[previousCodeBlock]
			if !previousCodeBlockState.Success {
				logging.GlobalLogger.Info(
					"Previous command has not been executed successfully, ignoring execute command",
				)
				break
			}
		}

		// Prevent the user from executing a command if the current command has
		// already been executed successfully.
		codeBlockState := model.codeBlockState[model.currentCodeBlock]
		if codeBlockState.Success {
			logging.GlobalLogger.Info(
				"Command has already been executed successfully, ignoring execute command",
			)
			break
		}

		codeBlock := codeBlockState.CodeBlock

		model.executingCommand = true

		// If we're on the last step and the command is an SSH command, we need
		// to report the status before executing the command. This is needed for
		// one click deployments and does not affect the normal execution flow.
		if model.currentCodeBlock == len(model.codeBlockState)-1 &&
			patterns.SshCommand.MatchString(codeBlock.Content) {
			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,
			)

			commands = append(commands, tea.Sequence(
				common.UpdateAzureStatus(model.azureStatus, model.environment),
				func() tea.Msg {
					return common.ExecuteCodeBlockSync(codeBlock, lib.CopyMap(model.env))
				}))

		} else {
			commands = append(commands, common.ExecuteCodeBlockAsync(
				codeBlock,
				lib.CopyMap(model.env),
			))
		}

	case key.Matches(message, model.commands.previous):
		if model.executingCommand {
			logging.GlobalLogger.Info("Command is already executing, ignoring execute command")
			break
		}
		if model.currentCodeBlock > 0 {
			model.currentCodeBlock--
		}
	case key.Matches(message, model.commands.next):
		if model.executingCommand {
			logging.GlobalLogger.Info("Command is already executing, ignoring execute command")
			break
		}
		if model.currentCodeBlock < len(model.codeBlockState)-1 {
			model.currentCodeBlock++
		}

	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
}