Repository: cli/cli
File: pkg/iostreams/iostreams.go (Go)

Code before:
		}
		return
	}

	spinnerDisabledValue, spinnerDisabledIsSet := os.LookupEnv("GH_SPINNER_DISABLED")
	falseyValues := []string{"false", "0", "no", ""}

	var spinnerStyle []string
	if spinnerDisabledIsSet && !slices.Contains(falseyValues, spinnerDisabledValue) {
		// This progress indicator must be readable by screen readers
		spinnerStyle = []string{"Working..."}
	} else {
		// https://github.com/briandowns/spinner#available-character-sets
		// ⣾ ⣷ ⣽ ⣻ ⡿
		spinnerStyle = spinner.CharSets[11]
	}

	sp := spinner.New(spinnerStyle, 120*time.Millisecond, spinner.WithWriter(s.ErrOut), spinner.WithColor("fgCyan"))
	if label != "" {
		sp.Prefix = label + " "
	}

	sp.Start()
	s.progressIndicator = sp
}

func (s *IOStreams) StopProgressIndicator() {
	s.progressIndicatorMu.Lock()
	defer s.progressIndicatorMu.Unlock()
	if s.progressIndicator == nil {
		return
	}
	s.progressIndicator.Stop()
	s.progressIndicator = nil
}

func (s *IOStreams) RunWithProgress(label string, run func() error) error {
	s.StartProgressIndicatorWithLabel(label)
	defer s.StopProgressIndicator()

	return run()
}

func (s *IOStreams) StartAlternateScreenBuffer() {
	if s.alternateScreenBufferEnabled {
		s.alternateScreenBufferMu.Lock()
		defer s.alternateScreenBufferMu.Unlock()

		if _, err := fmt.Fprint(s.Out, "\x1b[?1049h"); err == nil {
			s.alternateScreenBufferActive = true


Review comment (security):
**thought:** Even with these changes, I think `briandowns/spinner` is still erasing content and reprinting it as the underlying `erase()` logic doesn't make assumptions of what is on the screen and erases part of it.

Source: https://github.com/briandowns/spinner/blob/55430861f77b20de3f456775b00c706519464f36/spinner.go#L451C1-L481C1

```go
// erase deletes written characters on the current line.
// Caller must already hold s.lock.
func (s *Spinner) erase() {
	n := utf8.RuneCountInString(s.lastOutputPlain)
	if runtime.GOOS == "windows" && !isWindowsTerminalOnWindows {
		clearString := "\r" + strings.Repeat(" ", n) + "\r"
		fmt.Fprint(s.Writer, clearString)
		s.lastOutputPlain = ""
		return
	}

	numberOfLinesToErase := computeNumberOfLinesNeededToPrintString(s.lastOutputPlain)

	// Taken from https://en.wikipedia.org/wiki/ANSI_escape_code:
	// \r     - Carriage return - Moves the cursor to column zero
	// \033[K - Erases part of the line. If n is 0 (or missing), clear from
	// cursor to the end of the line. If n is 1, clear from cursor to beginning
	// of the line. If n is 2, clear entire line. Cursor position does not
	// change.
	// \033[F - Go to the beginning of previous line
	eraseCodeString := strings.Builder{}
	// current position is at the end of the last printed line. Start by erasing current line
	eraseCodeString.WriteString("\r\033[K") // start by erasing current line
	for i := 1; i < numberOfLinesToErase; i++ {
		// For each additional lines, go up one line and erase it.
		eraseCodeString.WriteString("\033[F\033[K")
	}
	fmt.Fprint(s.Writer, eraseCodeString.String())
	s.lastOutputPlain = ""
}
```

Code after:
	if s.spinnerDisabled {
		// If the spinner is disabled, simply print a
		// textual progress indicator and return.
		// This means that s.ProgressIndicator will be nil.
		// See also: the comment on StopProgressIndicator()
		s.startTextualProgressIndicator(label)
		return
	}

	s.progressIndicatorMu.Lock()
	defer s.progressIndicatorMu.Unlock()

	if s.progressIndicator != nil {
		if label == "" {
			s.progressIndicator.Prefix = ""
		} else {
			s.progressIndicator.Prefix = label + " "
		}
		return
	}

	// https://github.com/briandowns/spinner#available-character-sets
	// ⣾ ⣷ ⣽ ⣻ ⡿
	spinnerStyle := spinner.CharSets[11]

	sp := spinner.New(spinnerStyle, 120*time.Millisecond, spinner.WithWriter(s.ErrOut), spinner.WithColor("fgCyan"))
	if label != "" {
		sp.Prefix = label + " "
	}

	sp.Start()
	s.progressIndicator = sp
}

func (s *IOStreams) startTextualProgressIndicator(label string) {
	s.progressIndicatorMu.Lock()
	defer s.progressIndicatorMu.Unlock()

	// Default label when spinner disabled is "Working..."
	if label == "" {
		label = "Working..."
	}

	// Add an ellipsis to the label if it doesn't already have one.
	ellipsis := "..."
	if !strings.HasSuffix(label, ellipsis) {
		label = label + ellipsis
	}

	fmt.Fprintf(s.ErrOut, "%s%s", s.ColorScheme().Cyan(label), "\n")
}