Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pkg/cmd/factory/default.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"net/http"
"os"
"regexp"
"slices"
"time"

"github.com/cli/cli/v2/api"
Expand Down Expand Up @@ -283,6 +284,12 @@ func ioStreams(f *cmdutil.Factory) *iostreams.IOStreams {
io.SetNeverPrompt(true)
}

ghSpinnerDisabledValue, ghSpinnerDisabledIsSet := os.LookupEnv("GH_SPINNER_DISABLED")
falseyValues := []string{"false", "0", "no", ""}
if ghSpinnerDisabledIsSet && !slices.Contains(falseyValues, ghSpinnerDisabledValue) {
io.SetSpinnerDisabled(true)
}
Comment thread
BagToad marked this conversation as resolved.

// Pager precedence
// 1. GH_PAGER
// 2. pager from config
Expand Down
48 changes: 48 additions & 0 deletions pkg/cmd/factory/default_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,54 @@ func Test_ioStreams_prompt(t *testing.T) {
}
}

func Test_ioStreams_spinnerDisabled(t *testing.T) {
tests := []struct {
name string
spinnerDisabled bool
env map[string]string
}{
{
name: "default config",
spinnerDisabled: false,
},
{
name: "spinner disabled via GH_SPINNER_DISABLED env var = 0",
env: map[string]string{"GH_SPINNER_DISABLED": "0"},
spinnerDisabled: false,
},
{
name: "spinner disabled via GH_SPINNER_DISABLED env var = false",
env: map[string]string{"GH_SPINNER_DISABLED": "false"},
spinnerDisabled: false,
},
{
name: "spinner disabled via GH_SPINNER_DISABLED env var = no",
env: map[string]string{"GH_SPINNER_DISABLED": "no"},
spinnerDisabled: false,
},
{
name: "spinner enabled via GH_SPINNER_DISABLED env var = 1",
env: map[string]string{"GH_SPINNER_DISABLED": "1"},
spinnerDisabled: true,
},
{
name: "spinner enabled via GH_SPINNER_DISABLED env var = true",
env: map[string]string{"GH_SPINNER_DISABLED": "true"},
spinnerDisabled: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
for k, v := range tt.env {
t.Setenv(k, v)
}
f := New("1")
Comment thread
BagToad marked this conversation as resolved.
io := ioStreams(f)
assert.Equal(t, tt.spinnerDisabled, io.GetSpinnerDisabled())
})
}
}

func Test_ioStreams_colorLabels(t *testing.T) {
tests := []struct {
name string
Expand Down
3 changes: 3 additions & 0 deletions pkg/cmd/root/help_topic.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,9 @@ var HelpTopics = []helpTopic{

%[1]sGH_ACCESSIBLE_PROMPTER%[1]s (preview): set to a truthy value to enable prompts that are
more compatible with speech synthesis and braille screen readers.

%[1]sGH_SPINNER_DISABLED%[1]s: set to a truthy value to replace the spinner animation with
a textual progress indicator.
`, "`"),
},
{
Expand Down
47 changes: 44 additions & 3 deletions pkg/iostreams/iostreams.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ type IOStreams struct {
pagerCommand string
pagerProcess *os.Process

neverPrompt bool
neverPrompt bool
spinnerDisabled bool

TempFileOverride *os.File
}
Expand Down Expand Up @@ -273,6 +274,14 @@ func (s *IOStreams) SetNeverPrompt(v bool) {
s.neverPrompt = v
}

func (s *IOStreams) GetSpinnerDisabled() bool {
return s.spinnerDisabled
}

func (s *IOStreams) SetSpinnerDisabled(v bool) {
s.spinnerDisabled = v
}

func (s *IOStreams) StartProgressIndicator() {
s.StartProgressIndicatorWithLabel("")
}
Expand All @@ -282,6 +291,15 @@ func (s *IOStreams) StartProgressIndicatorWithLabel(label string) {
return
}

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()
Comment thread
BagToad marked this conversation as resolved.
s.startTextualProgressIndicator(label)
return
}

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

Expand All @@ -295,8 +313,10 @@ func (s *IOStreams) StartProgressIndicatorWithLabel(label string) {
}

// https://github.com/briandowns/spinner#available-character-sets
dotStyle := spinner.CharSets[11]
sp := spinner.New(dotStyle, 120*time.Millisecond, spinner.WithWriter(s.ErrOut), spinner.WithColor("fgCyan"))
// ⣾ ⣷ ⣽ ⣻ ⡿
spinnerStyle := spinner.CharSets[11]

sp := spinner.New(spinnerStyle, 120*time.Millisecond, spinner.WithWriter(s.ErrOut), spinner.WithColor("fgCyan"))
Comment thread
BagToad marked this conversation as resolved.
if label != "" {
sp.Prefix = label + " "
}
Expand All @@ -305,6 +325,27 @@ func (s *IOStreams) StartProgressIndicatorWithLabel(label string) {
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
}
Comment thread
BagToad marked this conversation as resolved.

fmt.Fprintf(s.ErrOut, "%s%s", s.ColorScheme().Cyan(label), "\n")
}
Comment thread
BagToad marked this conversation as resolved.

// StopProgressIndicator stops the progress indicator if it is running.
// Note that a textual progess indicator does not create a progress indicator,
// so this method is a no-op in that case.
func (s *IOStreams) StopProgressIndicator() {
s.progressIndicatorMu.Lock()
defer s.progressIndicatorMu.Unlock()
Expand Down
Loading