func askOnePrompt()

in cli/azd/pkg/input/asker.go [84:247]


func askOnePrompt(p survey.Prompt, response interface{}, isTerminal bool, stdout io.Writer, stdin io.Reader) error {
	// Like (*bufio.Reader).ReadString(byte) except that it does not buffer input from the input stream.
	// Instead, it reads a byte at a time until a delimiter is found or EOF is encountered,
	// returning bytes read with no extra characters consumed.
	readStringNoBuffer := func(r io.Reader, delim byte) (string, error) {
		strBuf := bytes.Buffer{}
		readBuf := make([]byte, 1)
		for {
			bytesRead, err := r.Read(readBuf)
			if bytesRead > 0 {
				// discard err, per documentation, WriteByte always succeeds.
				_ = strBuf.WriteByte(readBuf[0])
			}

			if err != nil {
				return strBuf.String(), err
			}

			if readBuf[0] == delim {
				return strBuf.String(), nil
			}
		}
	}

	if isTerminal {
		opts := []survey.AskOpt{}

		// When asking a question which requires a text response, show the cursor, it helps
		// users understand we need some input.
		if _, ok := p.(*survey.Input); ok {
			opts = append(opts, withShowCursor)
		}

		opts = append(opts, survey.WithIcons(func(icons *survey.IconSet) {
			// use bright, bold, blue question mark for all questions
			icons.Question.Format = "blue+hb"
			icons.SelectFocus.Format = "blue+hb"

			icons.Help.Format = "black+h"
			icons.Help.Text = "Hint:"

			icons.MarkedOption.Text = "[" + color.GreenString("✓") + "]"
			icons.MarkedOption.Format = ""
		}))

		return survey.AskOne(p, response, opts...)
	}

	switch v := p.(type) {
	case *survey.Input:
		var pResponse = response.(*string)
		fmt.Fprintf(stdout, "%s", v.Message[0:len(v.Message)-1])
		if v.Default != "" {
			fmt.Fprintf(stdout, " (or hit enter to use the default %s)", v.Default)
		}
		fmt.Fprintf(stdout, "%s ", v.Message[len(v.Message)-1:])
		result, err := readStringNoBuffer(stdin, '\n')
		if err != nil && !errors.Is(err, io.EOF) {
			return fmt.Errorf("reading response: %w", err)
		}
		result = strings.TrimSpace(result)
		if result == "" && v.Default != "" {
			result = v.Default
		}
		*pResponse = result
		return nil
	case *survey.Password:
		var pResponse = response.(*string)
		fmt.Fprintf(stdout, "%s", v.Message)
		result, err := readStringNoBuffer(stdin, '\n')
		if err != nil && !errors.Is(err, io.EOF) {
			return fmt.Errorf("reading response: %w", err)
		}
		result = strings.TrimSpace(result)
		*pResponse = result
		return nil
	case *survey.MultiSelect:
		// For multi-selection, azd will do a Select for each item, using the default to control the Y or N
		defValue, err := v.Default.([]string)
		if !err {
			return fmt.Errorf("default response type is not a string list '%s'", v.Message)
		}
		fmt.Fprintf(stdout, "%s:", v.Message)
		selection := make([]string, 0, len(v.Options))
		for _, item := range v.Options {
			response := slices.Contains(defValue, item)
			err := askOnePrompt(&survey.Confirm{
				Message: fmt.Sprintf("\n  select %s?", item),
			}, &response, isTerminal, stdout, stdin)
			if err != nil {
				return err
			}
			confirmation := "N"
			if response {
				confirmation = "Y"
				selection = append(selection, item)
			}
			fmt.Fprintf(stdout, "  %s", confirmation)
		}
		// assign the selection to the response
		*(response.(*[]string)) = selection

		return nil
	case *survey.Select:
		fmt.Fprintf(stdout, "%s", v.Message[0:len(v.Message)-1])
		if v.Default != nil {
			fmt.Fprintf(stdout, " (or hit enter to use the default %v)", v.Default)
		}
		fmt.Fprintf(stdout, "%s ", v.Message[len(v.Message)-1:])
		result, err := readStringNoBuffer(stdin, '\n')
		if err != nil && !errors.Is(err, io.EOF) {
			return fmt.Errorf("reading response: %w", err)
		}
		result = strings.TrimSpace(result)
		if result == "" && v.Default != nil {
			result = v.Default.(string)
		}
		for idx, val := range v.Options {
			if val == result {
				switch ptr := response.(type) {
				case *string:
					*ptr = val
				case *int:
					*ptr = idx
				default:
					return fmt.Errorf("bad type %T for result, should be (*int or *string)", response)
				}

				return nil
			}
		}
		return fmt.Errorf(
			"'%s' is not an allowed choice. allowed choices: %v",
			result,
			strings.Join(v.Options, ","))
	case *survey.Confirm:
		var pResponse = response.(*bool)

		for {
			fmt.Fprint(stdout, v.Message)
			if *pResponse {
				fmt.Fprint(stdout, " (Y/n)")
			} else {
				fmt.Fprintf(stdout, " (y/N)")
			}
			result, err := readStringNoBuffer(stdin, '\n')
			if err != nil && !errors.Is(err, io.EOF) {
				return fmt.Errorf("reading response: %w", err)
			}
			switch strings.TrimSpace(result) {
			case "Y", "y":
				*pResponse = true
				return nil
			case "N", "n":
				*pResponse = false
				return nil
			case "":
				return nil
			}
		}
	default:
		panic(fmt.Sprintf("don't know how to prompt for type %T", p))
	}
}