diff --git a/README.md b/README.md index f350197..c7c21b1 100644 --- a/README.md +++ b/README.md @@ -110,19 +110,27 @@ want to change a few configuration items via method calls, you can `Pause()` the spinner first. After making the changes you can call `Unpause()`, and it will continue rendering like normal with the newly applied configuration. -#### Supporting non-TTY Output Targets -`yacspin` also has native support for non-TTY output targets. This is detected -automatically within the constructor, or can be specified via the `NotTTY` -`Config` struct field, and results in a different mode of operation. +#### Supporting Non-Interactive (TTY) Output Targets +`yacspin` also has native support for non-interactive (TTY) output targets. By +default this is detected in the constructor, or can be overriden via the +`TerminalMode` `Config` struct field. When detecting the application is not +running withn a TTY session, the behavior of the spinner is different. -Specifically, when this is detected the spinner no longer uses colors, disables -the automatic spinner animation, and instead only animates the spinner when updating the -message. In addition, each animation is rendered on a new line instead of -overwriting the current line. +Specifically, when this is automatically detected the spinner no longer uses +colors, disables the automatic spinner animation, and instead only animates the +spinner when updating the message. In addition, each animation is rendered on a +new line instead of overwriting the current line. This should result in human-readable output without any changes needed by consumers, even when the system is writing to a non-TTY destination. +#### Manually Stepping Animation +If you'd like to manually animate the spinner, you can do so by setting the +`TerminalMode` to `ForceNoTTYMode | ForceSmartTerminalMode`. In this mode the +spinner will still use colors and other text stylings, but the animation only +happens when data is updated and on individual lines. You can accomplish this by +calling the `Message()` method with the same used previously. + ## Usage ``` go get github.com/theckman/yacspin diff --git a/spinner.go b/spinner.go index 547ed1f..dd96e5f 100644 --- a/spinner.go +++ b/spinner.go @@ -89,6 +89,48 @@ func setToCharSlice(ss []string) ([]character, int) { return c, maxWidth } +// TerminalMode is a type to represent the bit flag controlling the terminal +// mode of the spinner, accepted as a field on the Config struct. See the +// comments on the exported constants for more info. +type TerminalMode uint32 + +const ( + // AutomaticMode configures the constructor function to try and determine if + // the application using yacspin is being executed within a interactive + // (teletype [TTY]) session. + AutomaticMode TerminalMode = 1 << iota + + // ForceTTYMode configures the spinner to operate as if it's running within + // a TTY session. + ForceTTYMode + + // ForceNoTTYMode configures the spinner to operate as if it's not running + // within a TTY session. This mode causes the spinner to only animate when + // data is being updated. Each animation is rendered on a new line. You can + // trigger an animation by calling the Message() method, including with the + // last value it was called with. + ForceNoTTYMode + + // ForceDumbTerminalMode configures the spinner to operate as if it's + // running within a dumb terminal. This means the spinner will not use ANSI + // escape sequences to print colors or to erase each line. Line erasure to + // animate the spinner is accomplished by overwriting the line with space + // characters. + ForceDumbTerminalMode + + // ForceSmartTerminalMode configures the spinner to operate as if it's + // running within a terminal that supports ANSI escape sequences (VT100). + // This includes printing of stylized text, and more better line erasure to + // animate the spinner. + ForceSmartTerminalMode +) + +func termModeAuto(t TerminalMode) bool { return t&AutomaticMode > 0 } +func termModeForceTTY(t TerminalMode) bool { return t&ForceTTYMode > 0 } +func termModeForceNoTTY(t TerminalMode) bool { return t&ForceNoTTYMode > 0 } +func termModeForceDumb(t TerminalMode) bool { return t&ForceDumbTerminalMode > 0 } +func termModeForceSmart(t TerminalMode) bool { return t&ForceSmartTerminalMode > 0 } + // Config is the configuration structure for the Spinner type, which you provide // to the New() function. Some of the fields can be updated after the *Spinner // is constructed, others can only be set when calling the constructor. Please @@ -96,8 +138,6 @@ func setToCharSlice(ss []string) ([]character, int) { type Config struct { // Frequency specifies how often to animate the spinner. Optimal value // depends on the character set you use. - // - // Note: This is a required value (cannot be 0). Frequency time.Duration // Writer is the place where we are outputting the spinner, and can't be @@ -200,10 +240,38 @@ type Config struct { // respects the ColorAll field. StopFailColors []string + // TerminalMode is a bitflag field to control how the internal TTY / "dumb + // terminal" detection works, to allow consumers to override the internal + // behaviors. To set this value, it's recommended to use the TerminalMode + // constants exported by this package. + // + // If not set, the New() function implicitly sets it to AutomaticMode. The + // New() function also returns an error if you have conflicting flags, such + // as setting ForceTTYMode and ForceNoTTYMode, or if you set AutomaticMode + // and any other flags set. + // + // When in AutomaticMode, the New() function attempts to determine if the + // current application is running within an interactive (teletype [TTY]) + // session. If it does not appear to be within a TTY, it sets this field + // value to ForceNoTTYMode | ForceDumbTerminalMode. + // + // If this does appear to be a TTY, the ForceTTYMode bitflag will bet set. + // Similarly, if it's a TTY and the TERM environment variable isn't set to + // "dumb" the ForceSmartTerminalMode bitflag will also be set. + // + // If the deprecated NoTTY Config struct field is set to true, and this + // field is AutomaticMode, the New() function sets field to the value of + // ForceNoTTYMode | ForceDumbTerminalMode. + TerminalMode TerminalMode + // NotTTY tells the spinner that the Writer should not be treated as a TTY. // This results in the animation being disabled, with the animation only // happening whenever the data is updated. This mode also renders each // update on new line, versus reusing the current line. + // + // Deprecated: use TerminalMode field instead by setting it to: + // ForceNoTTYMode | ForceDumbTerminalMode. This will be removed in a future + // release. NotTTY bool } @@ -224,8 +292,7 @@ type Spinner struct { colorAll bool cursorHidden bool suffixAutoColon bool - isDumbTerm bool - isNotTTY bool + termMode TerminalMode spinnerAtEnd bool status *uint32 @@ -269,20 +336,50 @@ const ( // New creates a new unstarted spinner. If stdout does not appear to be a TTY, // this constructor implicitly sets cfg.NotTTY to true. func New(cfg Config) (*Spinner, error) { - if cfg.Frequency < 1 { - return nil, errors.New("cfg.Frequency must be greater than 0") - } - if cfg.ShowCursor && cfg.HideCursor { return nil, errors.New("cfg.ShowCursor and cfg.HideCursor cannot be true") } + if cfg.TerminalMode == 0 { + cfg.TerminalMode = AutomaticMode + } + + // AutomaticMode flag has been set, but so have others + if termModeAuto(cfg.TerminalMode) && cfg.TerminalMode != AutomaticMode { + return nil, errors.New("cfg.TerminalMode cannot have AutomaticMode flag set if others are set") + } + + if termModeForceTTY(cfg.TerminalMode) && termModeForceNoTTY(cfg.TerminalMode) { + return nil, errors.New("cfg.TerminalMode cannot have both ForceTTYMode and ForceNoTTYMode flags set") + } + + if termModeForceDumb(cfg.TerminalMode) && termModeForceSmart(cfg.TerminalMode) { + return nil, errors.New("cfg.TerminalMode cannot have both ForceDumbTerminalMode and ForceSmartTerminalMode flags set") + } + if cfg.HideCursor { cfg.ShowCursor = false } - if !isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()) { - cfg.NotTTY = true + // cfg.NotTTY compatibility + if cfg.TerminalMode == AutomaticMode && cfg.NotTTY { + cfg.TerminalMode = ForceNoTTYMode | ForceDumbTerminalMode + } + + // is this a dumb terminal / not a TTY? + if cfg.TerminalMode == AutomaticMode && !isatty.IsTerminal(os.Stdout.Fd()) && !isatty.IsCygwinTerminal(os.Stdout.Fd()) { + cfg.TerminalMode = ForceNoTTYMode | ForceDumbTerminalMode + } + + // if cfg.TerminalMode is still equal to AutomaticMode, this is a TTY + if cfg.TerminalMode == AutomaticMode { + cfg.TerminalMode = ForceTTYMode + + if os.Getenv("TERM") == "dumb" { + cfg.TerminalMode |= ForceDumbTerminalMode + } else { + cfg.TerminalMode |= ForceSmartTerminalMode + } } buf := bytes.NewBuffer(make([]byte, 2048)) @@ -300,7 +397,7 @@ func New(cfg Config) (*Spinner, error) { cursorHidden: !cfg.ShowCursor, spinnerAtEnd: cfg.SpinnerAtEnd, suffixAutoColon: cfg.SuffixAutoColon, - isDumbTerm: os.Getenv("TERM") == "dumb", + termMode: cfg.TerminalMode, colorFn: fmt.Sprintf, stopColorFn: fmt.Sprintf, stopFailColorFn: fmt.Sprintf, @@ -325,9 +422,9 @@ func New(cfg Config) (*Spinner, error) { // can only error if the charset is empty, and we prevent that above _ = s.CharSet(cfg.CharSet) - if cfg.NotTTY { - s.isNotTTY = true - s.isDumbTerm = true + if termModeForceNoTTY(s.termMode) { + // hack to prevent the animation from running if not a TTY + s.frequency = time.Duration(math.MaxInt64) } if cfg.Writer == nil { @@ -442,6 +539,10 @@ func (s *Spinner) Start() error { s.mu.Lock() + if s.frequency < 1 && termModeForceTTY(s.termMode) { + return errors.New("spinner Frequency duration must be greater than 0 when used within a TTY") + } + if len(s.chars) == 0 { s.mu.Unlock() @@ -456,11 +557,6 @@ func (s *Spinner) Start() error { s.frequencyUpdateCh = make(chan time.Duration, 4) s.dataUpdateCh, s.cancelCh = make(chan struct{}, 1), make(chan struct{}, 1) - if s.isNotTTY { - // hack to prevent the animation from running if not a TTY - s.frequency = time.Duration(math.MaxInt64) - } - s.mu.Unlock() // because of the atomic swap above, we know it's safe to mutate these @@ -643,7 +739,7 @@ func (s *Spinner) painter(cancel, dataUpdate, pause <-chan struct{}, done chan<- case <-dataUpdate: // if this is not a TTY: animate the spinner on the data update - s.paintUpdate(timer, s.isNotTTY) + s.paintUpdate(timer, termModeForceNoTTY(s.termMode)) case frequency := <-frequencyUpdate: handleFrequencyUpdate(frequency, timer, lastTick) @@ -692,7 +788,7 @@ func (s *Spinner) paintUpdate(timer *time.Timer, animate bool) { defer s.buffer.Reset() - if !s.isDumbTerm { + if termModeForceSmart(s.termMode) { if err := erase(s.buffer); err != nil { panic(fmt.Sprintf("failed to erase line: %v", err)) } @@ -703,7 +799,7 @@ func (s *Spinner) paintUpdate(timer *time.Timer, animate bool) { } } - if _, err := paint(s.buffer, mw, c, p, m, suf, s.suffixAutoColon, s.colorAll, s.spinnerAtEnd, false, s.isNotTTY, cFn); err != nil { + if _, err := paint(s.buffer, mw, c, p, m, suf, s.suffixAutoColon, s.colorAll, s.spinnerAtEnd, false, termModeForceNoTTY(s.termMode), cFn); err != nil { panic(fmt.Sprintf("failed to paint line: %v", err)) } } else { @@ -711,7 +807,7 @@ func (s *Spinner) paintUpdate(timer *time.Timer, animate bool) { panic(fmt.Sprintf("failed to erase line: %v", err)) } - n, err := paint(s.buffer, mw, c, p, m, suf, s.suffixAutoColon, false, s.spinnerAtEnd, false, s.isNotTTY, fmt.Sprintf) + n, err := paint(s.buffer, mw, c, p, m, suf, s.suffixAutoColon, false, s.spinnerAtEnd, false, termModeForceNoTTY(s.termMode), fmt.Sprintf) if err != nil { panic(fmt.Sprintf("failed to paint line: %v", err)) } @@ -755,7 +851,7 @@ func (s *Spinner) paintStop(chanOk bool) { defer s.buffer.Reset() - if !s.isDumbTerm { + if termModeForceSmart(s.termMode) { if err := erase(s.buffer); err != nil { panic(fmt.Sprintf("failed to erase line: %v", err)) } @@ -768,7 +864,7 @@ func (s *Spinner) paintStop(chanOk bool) { if c.Size > 0 || len(m) > 0 { // paint the line with a newline as it's the final line - if _, err := paint(s.buffer, mw, c, p, m, suf, s.suffixAutoColon, s.colorAll, s.spinnerAtEnd, true, s.isNotTTY, cFn); err != nil { + if _, err := paint(s.buffer, mw, c, p, m, suf, s.suffixAutoColon, s.colorAll, s.spinnerAtEnd, true, termModeForceNoTTY(s.termMode), cFn); err != nil { panic(fmt.Sprintf("failed to paint line: %v", err)) } } @@ -778,7 +874,7 @@ func (s *Spinner) paintStop(chanOk bool) { } if c.Size > 0 || len(m) > 0 { - if _, err := paint(s.buffer, mw, c, p, m, suf, s.suffixAutoColon, false, s.spinnerAtEnd, true, s.isNotTTY, fmt.Sprintf); err != nil { + if _, err := paint(s.buffer, mw, c, p, m, suf, s.suffixAutoColon, false, s.spinnerAtEnd, true, termModeForceNoTTY(s.termMode), fmt.Sprintf); err != nil { panic(fmt.Sprintf("failed to paint line: %v", err)) } } @@ -801,7 +897,9 @@ func erase(w io.Writer) error { // eraseDumbTerm clears the line on dumb terminals func (s *Spinner) eraseDumbTerm(w io.Writer) error { - if s.isNotTTY { + if termModeForceNoTTY(s.termMode) { + // non-TTY outputs use \n instead of line erasure, + // so return early return nil } @@ -882,7 +980,9 @@ func (s *Spinner) Frequency(d time.Duration) error { return errors.New("duration must be greater than 0") } - if s.isNotTTY { + if termModeForceNoTTY(s.termMode) { + // when output target is not a TTY, we don't animate spinner + // so there is no need to update the frequency return nil } diff --git a/spinner_test.go b/spinner_test.go index 930fc1e..c6b6a4d 100644 --- a/spinner_test.go +++ b/spinner_test.go @@ -17,6 +17,8 @@ import ( "github.com/mattn/go-runewidth" ) +const termModeTTY = ForceTTYMode | ForceSmartTerminalMode + // testErrCheck looks to see if errContains is a substring of err.Error(). If // not, this calls t.Fatal(). It also calls t.Fatal() if there was an error, but // errContains is empty. Returns true if you should continue running the test, @@ -48,24 +50,21 @@ func testErrCheck(t *testing.T, name string, errContains string, err error) bool func TestNew(t *testing.T) { tests := []struct { - name string - writer io.Writer - maxWidth int - cfg Config - charSet []character - err string + name string + writer io.Writer + maxWidth int + overrideFreq time.Duration + cfg Config + charSet []character + err string }{ - { - name: "empty_config", - writer: os.Stdout, - err: "cfg.Frequency must be greater than 0", - }, { name: "config_with_frequency_and_default_writer", maxWidth: 1, writer: os.Stdout, cfg: Config{ - Frequency: 100 * time.Millisecond, + Frequency: 100 * time.Millisecond, + TerminalMode: termModeTTY, }, }, { @@ -105,6 +104,30 @@ func TestNew(t *testing.T) { }, err: "cfg.ShowCursor and cfg.HideCursor cannot be true", }, + { + name: "config_with_conflicting_TerminalMode_Auto", + cfg: Config{ + Frequency: 100 * time.Millisecond, + TerminalMode: AutomaticMode | ForceTTYMode, + }, + err: "cfg.TerminalMode cannot have AutomaticMode flag set if others are set", + }, + { + name: "config_with_conflicting_TerminalMode_TTY", + cfg: Config{ + Frequency: 100 * time.Millisecond, + TerminalMode: ForceTTYMode | ForceNoTTYMode, + }, + err: "cfg.TerminalMode cannot have both ForceTTYMode and ForceNoTTYMode flags set", + }, + { + name: "config_with_conflicting_TerminalMode_Term", + cfg: Config{ + Frequency: 100 * time.Millisecond, + TerminalMode: ForceDumbTerminalMode | ForceSmartTerminalMode, + }, + err: "cfg.TerminalMode cannot have both ForceDumbTerminalMode and ForceSmartTerminalMode flags set", + }, { name: "full_config_with_deprecated_hidden_cursor", writer: os.Stderr, @@ -126,6 +149,7 @@ func TestNew(t *testing.T) { StopFailCharacter: "✗", StopFailColors: []string{"fgHiRed"}, SpinnerAtEnd: true, + TerminalMode: termModeTTY, }, }, { @@ -149,6 +173,19 @@ func TestNew(t *testing.T) { StopFailCharacter: "✗", StopFailColors: []string{"fgHiRed"}, SpinnerAtEnd: true, + TerminalMode: termModeTTY, + }, + }, + { + name: "not_tty", + writer: os.Stderr, + maxWidth: 3, + overrideFreq: 9223372036854775807, + cfg: Config{ + Frequency: 100 * time.Millisecond, + Writer: os.Stderr, + CharSet: CharSets[59], + NotTTY: true, }, }, } @@ -193,8 +230,14 @@ func TestNew(t *testing.T) { t.Fatal("spinner.frequencyUpdateCh is nil") } - if spinner.frequency != tt.cfg.Frequency { - t.Errorf("spinner.frequency = %s, want %s", spinner.frequency, tt.cfg.Frequency) + if tt.overrideFreq > 0 { + if spinner.frequency != tt.overrideFreq { + t.Errorf("spinner.frequency = %d (%s), want %d (%s)", spinner.frequency, spinner.frequency, tt.overrideFreq, tt.overrideFreq) + } + } else { + if spinner.frequency != tt.cfg.Frequency { + t.Errorf("spinner.frequency = %d (%s), want %d (%s)", spinner.frequency, spinner.frequency, tt.cfg.Frequency, tt.cfg.Frequency) + } } if spinner.writer == nil { @@ -221,6 +264,16 @@ func TestNew(t *testing.T) { t.Errorf("spinner.stopMsg = %q, want %q", spinner.stopMsg, tt.cfg.StopMessage) } + if tt.cfg.NotTTY { + if spinner.termMode != ForceDumbTerminalMode|ForceNoTTYMode { + t.Error("spinner.termMode != ForceDumbTerminalMode | ForceNoTTYMode") + } + + if d := time.Duration(math.MaxInt64); spinner.frequency != d { + t.Errorf("spinner.frequency = %d (%s), want %d (%s)", spinner.frequency, spinner.frequency, d, d) + } + } + sc := character{Value: tt.cfg.StopCharacter, Size: runewidth.StringWidth(tt.cfg.StopCharacter)} if spinner.stopChar != sc { t.Errorf("spinner.stopChar = %#v, want %#v", spinner.stopChar, sc) @@ -342,8 +395,8 @@ func TestNew_dumbTerm(t *testing.T) { spinner, err := New(cfg) testErrCheck(t, "New()", "", err) - if !spinner.isDumbTerm { - t.Fatal("spinner.isDumbTerm = false, want true") + if !termModeForceDumb(spinner.termMode) { + t.Fatal("spinner.termMode does not contain ForceDumbTerminalMode flag") } } @@ -482,7 +535,10 @@ func TestSpinner_Frequency(t *testing.T) { mu: &sync.Mutex{}, frequency: 0, frequencyUpdateCh: tt.ch, - isNotTTY: tt.isNotTTY, + } + + if tt.isNotTTY { + spinner.termMode = ForceDumbTerminalMode | ForceNoTTYMode } tmr := time.NewTimer(2 * time.Second) @@ -784,6 +840,19 @@ func TestSpinner_Start(t *testing.T) { err string }{ + { + name: "invalid_frequency_when_tty", + spinner: &Spinner{ + status: uint32Ptr(statusStopped), + mu: &sync.Mutex{}, + frequency: 0, + colorFn: fmt.Sprintf, + stopColorFn: fmt.Sprintf, + stopFailColorFn: fmt.Sprintf, + termMode: ForceTTYMode, + }, + err: "spinner Frequency duration must be greater than 0 when used within a TTY", + }, { name: "running_spinner", spinner: &Spinner{ @@ -846,13 +915,13 @@ func TestSpinner_Start(t *testing.T) { buffer: &bytes.Buffer{}, status: uint32Ptr(statusStopped), mu: &sync.Mutex{}, - frequency: time.Millisecond, + frequency: 9223372036854775807, colorFn: fmt.Sprintf, stopColorFn: fmt.Sprintf, stopFailColorFn: fmt.Sprintf, stopMsg: "stop msg", stopFailMsg: "stop fail msg", - isNotTTY: true, + termMode: ForceNoTTYMode, maxWidth: 3, chars: []character{ character{ @@ -899,7 +968,7 @@ func TestSpinner_Start(t *testing.T) { t.Fatal("painter did not write data") } - if max := time.Duration(math.MaxInt64); tt.spinner.isNotTTY && tt.spinner.frequency != max { + if max := time.Duration(math.MaxInt64); termModeForceNoTTY(tt.spinner.termMode) && tt.spinner.frequency != max { t.Fatalf("tt.spinner.duration = %s, want %s", tt.spinner.frequency, max) } }) @@ -1188,6 +1257,7 @@ func TestSpinner_paintUpdate(t *testing.T) { colorFn: fmt.Sprintf, chars: []character{{Value: "y", Size: 1}, {Value: "z", Size: 1}}, frequency: 10, + termMode: termModeTTY, }, want: "\r\033[K\ray msg\r\033[K\raz msg\r\033[K\raz msg\r\033[K\ray msg", }, @@ -1204,6 +1274,7 @@ func TestSpinner_paintUpdate(t *testing.T) { chars: []character{{Value: "y", Size: 1}, {Value: "z", Size: 1}}, frequency: 10, spinnerAtEnd: true, + termMode: termModeTTY, }, want: "\r\033[K\rmsg ay \r\033[K\rmsg az \r\033[K\rmsg az \r\033[K\rmsg ay ", }, @@ -1220,6 +1291,7 @@ func TestSpinner_paintUpdate(t *testing.T) { chars: []character{{Value: "y", Size: 1}, {Value: "z", Size: 1}}, frequency: 10, suffixAutoColon: true, + termMode: termModeTTY, }, want: "\r\033[K\ray msg\r\033[K\raz msg\r\033[K\raz msg\r\033[K\ray msg", }, @@ -1236,6 +1308,7 @@ func TestSpinner_paintUpdate(t *testing.T) { chars: []character{{Value: "y", Size: 1}, {Value: "z", Size: 1}}, frequency: 10, suffixAutoColon: true, + termMode: termModeTTY, }, want: "\r\033[K\ray foo: msg\r\033[K\raz foo: msg\r\033[K\raz foo: msg\r\033[K\ray foo: msg", }, @@ -1252,6 +1325,7 @@ func TestSpinner_paintUpdate(t *testing.T) { colorFn: fmt.Sprintf, chars: []character{{Value: "y", Size: 1}, {Value: "z", Size: 1}}, frequency: 10, + termMode: termModeTTY, }, want: "\r\033[K\r\r\033[?25l\ray msg\r\033[K\r\r\033[?25l\raz msg\r\033[K\r\r\033[?25l\raz msg\r\033[K\r\r\033[?25l\ray msg", }, @@ -1268,7 +1342,8 @@ func TestSpinner_paintUpdate(t *testing.T) { colorFn: fmt.Sprintf, chars: []character{{Value: "y", Size: 1}, {Value: "z", Size: 1}}, frequency: 10, - isDumbTerm: true, + // TODO(theckman): verify + termMode: ForceDumbTerminalMode, }, want: "\r\ray msg\r \raz msg\r \raz msg\r \ray msg", }, @@ -1281,6 +1356,7 @@ func TestSpinner_paintUpdate(t *testing.T) { colorFn: fmt.Sprintf, chars: []character{{Value: "", Size: 0}}, frequency: 10, + termMode: termModeTTY, }, want: "\r\033[K\r\r\033[K\r\r\033[K\r\r\033[K\r", }, @@ -1327,6 +1403,7 @@ func TestSpinner_paintStop(t *testing.T) { stopColorFn: fmt.Sprintf, stopChar: character{Value: "x", Size: 1}, stopMsg: "stop", + termMode: termModeTTY, }, want: "\r\033[K\rax stop\n", }, @@ -1343,6 +1420,7 @@ func TestSpinner_paintStop(t *testing.T) { spinnerAtEnd: true, stopChar: character{Value: "x", Size: 1}, stopMsg: "stop", + termMode: termModeTTY, }, want: "\r\033[K\rstop ax \n", }, @@ -1360,6 +1438,7 @@ func TestSpinner_paintStop(t *testing.T) { suffixAutoColon: true, stopChar: character{Value: "x", Size: 1}, stopMsg: "stop", + termMode: termModeTTY, }, want: "\r\033[K\rstop ax \n", }, @@ -1376,6 +1455,7 @@ func TestSpinner_paintStop(t *testing.T) { stopChar: character{Value: "x", Size: 1}, stopMsg: "stop", suffixAutoColon: true, + termMode: termModeTTY, }, want: "\r\033[K\rax stop\n", }, @@ -1392,6 +1472,7 @@ func TestSpinner_paintStop(t *testing.T) { stopChar: character{Value: "x", Size: 1}, stopMsg: "stop", suffixAutoColon: true, + termMode: termModeTTY, }, want: "\r\033[K\rax foo: stop\n", }, @@ -1408,6 +1489,7 @@ func TestSpinner_paintStop(t *testing.T) { stopChar: character{Value: "x", Size: 1}, stopMsg: "", suffixAutoColon: true, + termMode: termModeTTY, }, want: "\r\033[K\rax \n", }, @@ -1424,6 +1506,7 @@ func TestSpinner_paintStop(t *testing.T) { stopColorFn: fmt.Sprintf, stopChar: character{Value: "x", Size: 1}, stopMsg: "stop", + termMode: termModeTTY, }, want: "\r\033[K\r\r\033[?25h\rax stop\n", }, @@ -1440,7 +1523,8 @@ func TestSpinner_paintStop(t *testing.T) { stopColorFn: fmt.Sprintf, stopChar: character{Value: "x", Size: 1}, stopMsg: "stop", - isDumbTerm: true, + // TODO(theckman): verify + termMode: ForceDumbTerminalMode, lastPrintLen: 10, }, want: "\r \rax stop\n", @@ -1456,6 +1540,7 @@ func TestSpinner_paintStop(t *testing.T) { stopFailColorFn: fmt.Sprintf, stopFailChar: character{Value: "y", Size: 1}, stopFailMsg: "stop", + termMode: termModeTTY, }, want: "\r\033[K\ray stop\n", }, @@ -1468,18 +1553,20 @@ func TestSpinner_paintStop(t *testing.T) { suffix: " ", maxWidth: 1, stopFailColorFn: fmt.Sprintf, + termMode: termModeTTY, }, want: "\r\033[K\r", }, { name: "fail_no_char_no_msg_dumb_term", spinner: &Spinner{ - buffer: &bytes.Buffer{}, - mu: &sync.Mutex{}, - prefix: "a", - suffix: " ", - maxWidth: 1, - isDumbTerm: true, + buffer: &bytes.Buffer{}, + mu: &sync.Mutex{}, + prefix: "a", + suffix: " ", + maxWidth: 1, + // TODO(theckman): verify + termMode: ForceDumbTerminalMode, stopFailColorFn: fmt.Sprintf, }, want: "\r\r", @@ -1498,6 +1585,7 @@ func TestSpinner_paintStop(t *testing.T) { stopFailChar: character{Value: "y", Size: 1}, stopFailMsg: "stop", colorAll: true, + termMode: termModeTTY, }, want: "\r\033[K\rfullColor: ay stop\n", }, @@ -1516,6 +1604,7 @@ func TestSpinner_paintStop(t *testing.T) { stopFailMsg: "stop", colorAll: true, spinnerAtEnd: true, + termMode: termModeTTY, }, want: "\r\033[K\rfullColor: stop ay \n", }, @@ -1533,6 +1622,7 @@ func TestSpinner_paintStop(t *testing.T) { stopFailChar: character{Value: "", Size: 0}, stopFailMsg: "stop", colorAll: true, + termMode: termModeTTY, }, want: "\r\033[K\rfullColor: stop\n", }, @@ -1669,6 +1759,7 @@ func TestSpinner_painter(t *testing.T) { doneCh: done, dataUpdateCh: dataUpdate, frequencyUpdateCh: frequencyUpdate, + termMode: termModeTTY, } go spinner.painter(cancel, dataUpdate, pause, done, frequencyUpdate) @@ -1744,8 +1835,7 @@ func TestSpinner_painter(t *testing.T) { doneCh: done, dataUpdateCh: dataUpdate, frequencyUpdateCh: frequencyUpdate, - isNotTTY: true, - isDumbTerm: true, + termMode: ForceDumbTerminalMode | ForceNoTTYMode, } go spinner.painter(cancel, dataUpdate, pause, done, frequencyUpdate)