Merge pull request #5681 from heaths/issue5679
Use alternate screen buffer for watching runs
This commit is contained in:
commit
954689ea9f
7 changed files with 164 additions and 123 deletions
|
|
@ -2,11 +2,10 @@ package checks
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/pr/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -131,33 +130,39 @@ func checksRun(opts *ChecksOptions) error {
|
|||
var checks []check
|
||||
var counts checkCounts
|
||||
|
||||
if opts.Watch {
|
||||
opts.IO.StartAlternateScreenBuffer()
|
||||
}
|
||||
|
||||
// Do not return err until we can StopAlternateScreenBuffer()
|
||||
var err error
|
||||
for {
|
||||
findOptions := shared.FindOptions{
|
||||
Selector: opts.SelectorArg,
|
||||
Fields: []string{"number", "headRefName", "statusCheckRollup"},
|
||||
}
|
||||
pr, _, err := opts.Finder.Find(findOptions)
|
||||
|
||||
var pr *api.PullRequest
|
||||
pr, _, err = opts.Finder.Find(findOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
break
|
||||
}
|
||||
|
||||
checks, counts, err = aggregateChecks(pr)
|
||||
if err != nil {
|
||||
return err
|
||||
break
|
||||
}
|
||||
|
||||
if opts.Watch {
|
||||
refreshScreen(opts.IO.Out)
|
||||
if counts.Pending != 0 {
|
||||
cs := opts.IO.ColorScheme()
|
||||
fmt.Fprintln(opts.IO.Out, cs.Boldf("Refreshing checks status every %v seconds. Press Ctrl+C to quit.\n", opts.Interval.Seconds()))
|
||||
}
|
||||
if counts.Pending != 0 && opts.Watch {
|
||||
opts.IO.RefreshScreen()
|
||||
cs := opts.IO.ColorScheme()
|
||||
fmt.Fprintln(opts.IO.Out, cs.Boldf("Refreshing checks status every %v seconds. Press Ctrl+C to quit.\n", opts.Interval.Seconds()))
|
||||
}
|
||||
|
||||
printSummary(opts.IO, counts)
|
||||
err = printTable(opts.IO, checks)
|
||||
if err != nil {
|
||||
return err
|
||||
break
|
||||
}
|
||||
|
||||
if counts.Pending == 0 || !opts.Watch {
|
||||
|
|
@ -167,21 +172,23 @@ func checksRun(opts *ChecksOptions) error {
|
|||
time.Sleep(opts.Interval)
|
||||
}
|
||||
|
||||
opts.IO.StopAlternateScreenBuffer()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if opts.Watch {
|
||||
// Print final summary to original screen buffer
|
||||
printSummary(opts.IO, counts)
|
||||
err = printTable(opts.IO, checks)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if counts.Failed+counts.Pending > 0 {
|
||||
return cmdutil.SilentError
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func refreshScreen(w io.Writer) {
|
||||
if runtime.GOOS == "windows" {
|
||||
// Just clear whole screen; I wasn't able to get the nicer cursor movement thing working
|
||||
fmt.Fprintf(w, "\x1b[2J")
|
||||
} else {
|
||||
// Move cursor to 0,0
|
||||
fmt.Fprint(w, "\x1b[0;0H")
|
||||
// Clear from cursor to bottom of screen
|
||||
fmt.Fprint(w, "\x1b[J")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -103,77 +103,87 @@ func Test_checksRun(t *testing.T) {
|
|||
name string
|
||||
fixture string
|
||||
prJSON string
|
||||
nontty bool
|
||||
tty bool
|
||||
watch bool
|
||||
wantOut string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "no commits",
|
||||
prJSON: `{ "number": 123 }`,
|
||||
tty: true,
|
||||
wantOut: "",
|
||||
wantErr: "no commit found on the pull request",
|
||||
},
|
||||
{
|
||||
name: "no checks",
|
||||
prJSON: `{ "number": 123, "statusCheckRollup": { "nodes": [{"commit": {"oid": "abc"}}]}, "headRefName": "master" }`,
|
||||
tty: true,
|
||||
wantOut: "",
|
||||
wantErr: "no checks reported on the 'master' branch",
|
||||
},
|
||||
{
|
||||
name: "some failing",
|
||||
fixture: "./fixtures/someFailing.json",
|
||||
tty: true,
|
||||
wantOut: "Some checks were not successful\n1 failing, 1 successful, 0 skipped, and 1 pending checks\n\nX sad tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n* slow tests 1m26s sweet link\n",
|
||||
wantErr: "SilentError",
|
||||
},
|
||||
{
|
||||
name: "some pending",
|
||||
fixture: "./fixtures/somePending.json",
|
||||
tty: true,
|
||||
wantOut: "Some checks are still pending\n0 failing, 2 successful, 0 skipped, and 1 pending checks\n\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n* slow tests 1m26s sweet link\n",
|
||||
wantErr: "SilentError",
|
||||
},
|
||||
{
|
||||
name: "all passing",
|
||||
fixture: "./fixtures/allPassing.json",
|
||||
tty: true,
|
||||
wantOut: "All checks were successful\n0 failing, 3 successful, 0 skipped, and 0 pending checks\n\n✓ awesome tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n",
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "watch all passing",
|
||||
fixture: "./fixtures/allPassing.json",
|
||||
tty: true,
|
||||
watch: true,
|
||||
wantOut: "\x1b[?1049hAll checks were successful\n0 failing, 3 successful, 0 skipped, and 0 pending checks\n\n✓ awesome tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n\x1b[?1049lAll checks were successful\n0 failing, 3 successful, 0 skipped, and 0 pending checks\n\n✓ awesome tests 1m26s sweet link\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n",
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "with statuses",
|
||||
fixture: "./fixtures/withStatuses.json",
|
||||
tty: true,
|
||||
wantOut: "Some checks were not successful\n1 failing, 2 successful, 0 skipped, and 0 pending checks\n\nX a status sweet link\n✓ cool tests 1m26s sweet link\n✓ rad tests 1m26s sweet link\n",
|
||||
wantErr: "SilentError",
|
||||
},
|
||||
{
|
||||
name: "no checks",
|
||||
nontty: true,
|
||||
prJSON: `{ "number": 123, "statusCheckRollup": { "nodes": [{"commit": {"oid": "abc"}}]}, "headRefName": "master" }`,
|
||||
wantOut: "",
|
||||
wantErr: "no checks reported on the 'master' branch",
|
||||
},
|
||||
{
|
||||
name: "some failing",
|
||||
nontty: true,
|
||||
fixture: "./fixtures/someFailing.json",
|
||||
wantOut: "sad tests\tfail\t1m26s\tsweet link\ncool tests\tpass\t1m26s\tsweet link\nslow tests\tpending\t1m26s\tsweet link\n",
|
||||
wantErr: "SilentError",
|
||||
},
|
||||
{
|
||||
name: "some pending",
|
||||
nontty: true,
|
||||
fixture: "./fixtures/somePending.json",
|
||||
wantOut: "cool tests\tpass\t1m26s\tsweet link\nrad tests\tpass\t1m26s\tsweet link\nslow tests\tpending\t1m26s\tsweet link\n",
|
||||
wantErr: "SilentError",
|
||||
},
|
||||
{
|
||||
name: "all passing",
|
||||
nontty: true,
|
||||
fixture: "./fixtures/allPassing.json",
|
||||
wantOut: "awesome tests\tpass\t1m26s\tsweet link\ncool tests\tpass\t1m26s\tsweet link\nrad tests\tpass\t1m26s\tsweet link\n",
|
||||
wantErr: "",
|
||||
},
|
||||
{
|
||||
name: "with statuses",
|
||||
nontty: true,
|
||||
fixture: "./fixtures/withStatuses.json",
|
||||
wantOut: "a status\tfail\t0\tsweet link\ncool tests\tpass\t1m26s\tsweet link\nrad tests\tpass\t1m26s\tsweet link\n",
|
||||
wantErr: "SilentError",
|
||||
|
|
@ -181,6 +191,7 @@ func Test_checksRun(t *testing.T) {
|
|||
{
|
||||
name: "some skipped",
|
||||
fixture: "./fixtures/someSkipping.json",
|
||||
tty: true,
|
||||
wantOut: "All checks were successful\n0 failing, 1 successful, 2 skipped, and 0 pending checks\n\n✓ cool tests 1m26s sweet link\n- rad tests 1m26s sweet link\n- skip tests 1m26s sweet link\n",
|
||||
wantErr: "",
|
||||
},
|
||||
|
|
@ -189,7 +200,8 @@ func Test_checksRun(t *testing.T) {
|
|||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ios, _, stdout, _ := iostreams.Test()
|
||||
ios.SetStdoutTTY(!tt.nontty)
|
||||
ios.SetStdoutTTY(tt.tty)
|
||||
ios.SetAlternateScreenBufferEnabled(tt.tty)
|
||||
|
||||
var response *api.PullRequest
|
||||
var jsonReader io.Reader
|
||||
|
|
@ -208,6 +220,7 @@ func Test_checksRun(t *testing.T) {
|
|||
IO: ios,
|
||||
SelectorArg: "123",
|
||||
Finder: shared.NewMockFinder("123", response, ghrepo.New("OWNER", "REPO")),
|
||||
Watch: tt.watch,
|
||||
}
|
||||
|
||||
err := checksRun(opts)
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
package watch
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"time"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
|
|
@ -86,7 +87,6 @@ func watchRun(opts *WatchOptions) error {
|
|||
return fmt.Errorf("failed to determine base repo: %w", err)
|
||||
}
|
||||
|
||||
out := opts.IO.Out
|
||||
cs := opts.IO.ColorScheme()
|
||||
|
||||
runID := opts.RunID
|
||||
|
|
@ -121,7 +121,7 @@ func watchRun(opts *WatchOptions) error {
|
|||
}
|
||||
|
||||
if run.Status == shared.Completed {
|
||||
fmt.Fprintf(out, "Run %s (%s) has already completed with '%s'\n", cs.Bold(run.Name), cs.Cyanf("%d", run.ID), run.Conclusion)
|
||||
fmt.Fprintf(opts.IO.Out, "Run %s (%s) has already completed with '%s'\n", cs.Bold(run.Name), cs.Cyanf("%d", run.ID), run.Conclusion)
|
||||
if opts.ExitStatus && run.Conclusion != shared.Success {
|
||||
return cmdutil.SilentError
|
||||
}
|
||||
|
|
@ -134,11 +134,6 @@ func watchRun(opts *WatchOptions) error {
|
|||
prNumber = fmt.Sprintf(" #%d", number)
|
||||
}
|
||||
|
||||
if err := opts.IO.EnableVirtualTerminalProcessing(); err == nil {
|
||||
// clear entire screen
|
||||
fmt.Fprintf(out, "\x1b[2J")
|
||||
}
|
||||
|
||||
annotationCache := map[int64][]shared.Annotation{}
|
||||
|
||||
duration, err := time.ParseDuration(fmt.Sprintf("%ds", opts.Interval))
|
||||
|
|
@ -146,20 +141,52 @@ func watchRun(opts *WatchOptions) error {
|
|||
return fmt.Errorf("could not parse interval: %w", err)
|
||||
}
|
||||
|
||||
out := &bytes.Buffer{}
|
||||
opts.IO.StartAlternateScreenBuffer()
|
||||
for run.Status != shared.Completed {
|
||||
run, err = renderRun(*opts, client, repo, run, prNumber, annotationCache)
|
||||
// Write to a temporary buffer to reduce total number of fetches
|
||||
run, err = renderRun(out, *opts, client, repo, run, prNumber, annotationCache)
|
||||
if err != nil {
|
||||
return err
|
||||
break
|
||||
}
|
||||
|
||||
if run.Status == shared.Completed {
|
||||
break
|
||||
}
|
||||
|
||||
// If not completed, refresh the screen buffer and write the temporary buffer to stdout
|
||||
opts.IO.RefreshScreen()
|
||||
|
||||
fmt.Fprintln(opts.IO.Out, cs.Boldf("Refreshing run status every %d seconds. Press Ctrl+C to quit.", opts.Interval))
|
||||
fmt.Fprintln(opts.IO.Out)
|
||||
|
||||
_, err = io.Copy(opts.IO.Out, out)
|
||||
out.Reset()
|
||||
|
||||
if err != nil {
|
||||
break
|
||||
}
|
||||
|
||||
time.Sleep(duration)
|
||||
}
|
||||
opts.IO.StopAlternateScreenBuffer()
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Write the last temporary buffer one last time
|
||||
_, err = io.Copy(opts.IO.Out, out)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
symbol, symbolColor := shared.Symbol(cs, run.Status, run.Conclusion)
|
||||
id := cs.Cyanf("%d", run.ID)
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintln(out)
|
||||
fmt.Fprintf(out, "%s Run %s (%s) completed with '%s'\n", symbolColor(symbol), cs.Bold(run.Name), id, run.Conclusion)
|
||||
fmt.Fprintln(opts.IO.Out)
|
||||
fmt.Fprintf(opts.IO.Out, "%s Run %s (%s) completed with '%s'\n", symbolColor(symbol), cs.Bold(run.Name), id, run.Conclusion)
|
||||
}
|
||||
|
||||
if opts.ExitStatus && run.Conclusion != shared.Success {
|
||||
|
|
@ -169,8 +196,7 @@ func watchRun(opts *WatchOptions) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func renderRun(opts WatchOptions, client *api.Client, repo ghrepo.Interface, run *shared.Run, prNumber string, annotationCache map[int64][]shared.Annotation) (*shared.Run, error) {
|
||||
out := opts.IO.Out
|
||||
func renderRun(out io.Writer, opts WatchOptions, client *api.Client, repo ghrepo.Interface, run *shared.Run, prNumber string, annotationCache map[int64][]shared.Annotation) (*shared.Run, error) {
|
||||
cs := opts.IO.ColorScheme()
|
||||
|
||||
var err error
|
||||
|
|
@ -212,18 +238,6 @@ func renderRun(opts WatchOptions, client *api.Client, repo ghrepo.Interface, run
|
|||
return nil, fmt.Errorf("failed to get annotations: %w", annotationErr)
|
||||
}
|
||||
|
||||
if runtime.GOOS == "windows" {
|
||||
// Just clear whole screen; I wasn't able to get the nicer cursor movement thing working
|
||||
fmt.Fprintf(opts.IO.Out, "\x1b[2J")
|
||||
} else {
|
||||
// Move cursor to 0,0
|
||||
fmt.Fprint(opts.IO.Out, "\x1b[0;0H")
|
||||
// Clear from cursor to bottom of screen
|
||||
fmt.Fprint(opts.IO.Out, "\x1b[J")
|
||||
}
|
||||
|
||||
fmt.Fprintln(out, cs.Boldf("Refreshing run status every %d seconds. Press Ctrl+C to quit.", opts.Interval))
|
||||
fmt.Fprintln(out)
|
||||
fmt.Fprintln(out, shared.RenderRunHeader(cs, *run, utils.FuzzyAgo(ago), prNumber))
|
||||
fmt.Fprintln(out)
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"bytes"
|
||||
"io"
|
||||
"net/http"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
|
|
@ -167,16 +166,14 @@ func TestWatchRun(t *testing.T) {
|
|||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
httpStubs func(*httpmock.Registry)
|
||||
askStubs func(*prompt.AskStubber)
|
||||
opts *WatchOptions
|
||||
tty bool
|
||||
wantErr bool
|
||||
errMsg string
|
||||
wantOut string
|
||||
onlyWindows bool
|
||||
skipWindows bool
|
||||
name string
|
||||
httpStubs func(*httpmock.Registry)
|
||||
askStubs func(*prompt.AskStubber)
|
||||
opts *WatchOptions
|
||||
tty bool
|
||||
wantErr bool
|
||||
errMsg string
|
||||
wantOut string
|
||||
}{
|
||||
{
|
||||
name: "run ID provided run already completed",
|
||||
|
|
@ -225,9 +222,8 @@ func TestWatchRun(t *testing.T) {
|
|||
},
|
||||
},
|
||||
{
|
||||
name: "interval respected",
|
||||
skipWindows: true,
|
||||
tty: true,
|
||||
name: "interval respected",
|
||||
tty: true,
|
||||
opts: &WatchOptions{
|
||||
Interval: 0,
|
||||
Prompt: true,
|
||||
|
|
@ -238,27 +234,11 @@ func TestWatchRun(t *testing.T) {
|
|||
AssertOptions([]string{"* cool commit, run (trunk) Feb 23, 2021", "* cool commit, more runs (trunk) Feb 23, 2021"}).
|
||||
AnswerWith("* cool commit, more runs (trunk) Feb 23, 2021")
|
||||
},
|
||||
wantOut: "\x1b[2J\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n✓ trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\n✓ Run more runs (2) completed with 'success'\n",
|
||||
wantOut: "\x1b[?1049h\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\x1b[?1049l✓ trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\n✓ Run more runs (2) completed with 'success'\n",
|
||||
},
|
||||
{
|
||||
name: "interval respected, windows",
|
||||
onlyWindows: true,
|
||||
opts: &WatchOptions{
|
||||
Interval: 0,
|
||||
Prompt: true,
|
||||
},
|
||||
httpStubs: successfulRunStubs,
|
||||
askStubs: func(as *prompt.AskStubber) {
|
||||
as.StubPrompt("Select a workflow run").
|
||||
AssertOptions([]string{"* cool commit, run (trunk) Feb 23, 2021", "* cool commit, more runs (trunk) Feb 23, 2021"}).
|
||||
AnswerWith("* cool commit, more runs (trunk) Feb 23, 2021")
|
||||
},
|
||||
wantOut: "\x1b[2J\x1b[2JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n\x1b[2JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n✓ trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\n✓ cool job in 4m34s (ID 10)\n ✓ fob the barz\n ✓ barz the fob\n",
|
||||
},
|
||||
{
|
||||
name: "exit status respected",
|
||||
tty: true,
|
||||
skipWindows: true,
|
||||
name: "exit status respected",
|
||||
tty: true,
|
||||
opts: &WatchOptions{
|
||||
Interval: 0,
|
||||
Prompt: true,
|
||||
|
|
@ -270,39 +250,13 @@ func TestWatchRun(t *testing.T) {
|
|||
AssertOptions([]string{"* cool commit, run (trunk) Feb 23, 2021", "* cool commit, more runs (trunk) Feb 23, 2021"}).
|
||||
AnswerWith("* cool commit, more runs (trunk) Feb 23, 2021")
|
||||
},
|
||||
wantOut: "\x1b[2J\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk more runs · 2\nTriggered via push about 59 minutes ago\n\n\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\nX trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n\nX Run more runs (2) completed with 'failure'\n",
|
||||
wantErr: true,
|
||||
errMsg: "SilentError",
|
||||
},
|
||||
{
|
||||
name: "exit status respected, windows",
|
||||
onlyWindows: true,
|
||||
opts: &WatchOptions{
|
||||
Interval: 0,
|
||||
Prompt: true,
|
||||
ExitStatus: true,
|
||||
},
|
||||
httpStubs: failedRunStubs,
|
||||
askStubs: func(as *prompt.AskStubber) {
|
||||
as.StubPrompt("Select a workflow run").
|
||||
AssertOptions([]string{"* cool commit, run (trunk) Feb 23, 2021", "* cool commit, more runs (trunk) Feb 23, 2021"}).
|
||||
AnswerWith("* cool commit, more runs (trunk) Feb 23, 2021")
|
||||
},
|
||||
wantOut: "\x1b[2J\x1b[2JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk more runs · 2\nTriggered via push about 59 minutes ago\n\n\x1b[2JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\nX trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n",
|
||||
wantOut: "\x1b[?1049h\x1b[0;0H\x1b[JRefreshing run status every 0 seconds. Press Ctrl+C to quit.\n\n* trunk more runs · 2\nTriggered via push about 59 minutes ago\n\n\x1b[?1049lX trunk more runs · 2\nTriggered via push about 59 minutes ago\n\nJOBS\nX sad job in 4m34s (ID 20)\n ✓ barf the quux\n X quux the barf\n\nANNOTATIONS\nX the job is sad\nsad job: blaze.py#420\n\n\nX Run more runs (2) completed with 'failure'\n",
|
||||
wantErr: true,
|
||||
errMsg: "SilentError",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
if runtime.GOOS == "windows" {
|
||||
if tt.skipWindows {
|
||||
continue
|
||||
}
|
||||
} else if tt.onlyWindows {
|
||||
continue
|
||||
}
|
||||
|
||||
reg := &httpmock.Registry{}
|
||||
tt.httpStubs(reg)
|
||||
tt.opts.HttpClient = func() (*http.Client, error) {
|
||||
|
|
@ -316,6 +270,7 @@ func TestWatchRun(t *testing.T) {
|
|||
|
||||
ios, _, stdout, _ := iostreams.Test()
|
||||
ios.SetStdoutTTY(tt.tty)
|
||||
ios.SetAlternateScreenBufferEnabled(tt.tty)
|
||||
tt.opts.IO = ios
|
||||
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
|
||||
return ghrepo.FromFullName("OWNER/REPO")
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
package iostreams
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
)
|
||||
|
||||
|
|
@ -13,5 +12,5 @@ func (s *IOStreams) EnableVirtualTerminalProcessing() error {
|
|||
}
|
||||
|
||||
func enableVirtualTerminalProcessing(f *os.File) error {
|
||||
return errors.New("not implemented")
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,6 +44,9 @@ type IOStreams struct {
|
|||
progressIndicator *spinner.Spinner
|
||||
progressIndicatorMu sync.Mutex
|
||||
|
||||
alternateScreenBufferEnabled bool
|
||||
alternateScreenBufferActive bool
|
||||
|
||||
stdinTTYOverride bool
|
||||
stdinIsTTY bool
|
||||
stdoutTTYOverride bool
|
||||
|
|
@ -278,6 +281,34 @@ func (s *IOStreams) StopProgressIndicator() {
|
|||
s.progressIndicator = nil
|
||||
}
|
||||
|
||||
func (s *IOStreams) StartAlternateScreenBuffer() {
|
||||
if s.alternateScreenBufferEnabled {
|
||||
if _, err := fmt.Fprint(s.Out, "\x1b[?1049h"); err == nil {
|
||||
s.alternateScreenBufferActive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IOStreams) StopAlternateScreenBuffer() {
|
||||
if s.alternateScreenBufferActive {
|
||||
fmt.Fprint(s.Out, "\x1b[?1049l")
|
||||
s.alternateScreenBufferActive = false
|
||||
}
|
||||
}
|
||||
|
||||
func (s *IOStreams) SetAlternateScreenBufferEnabled(enabled bool) {
|
||||
s.alternateScreenBufferEnabled = enabled
|
||||
}
|
||||
|
||||
func (s *IOStreams) RefreshScreen() {
|
||||
if s.stdoutIsTTY {
|
||||
// Move cursor to 0,0
|
||||
fmt.Fprint(s.Out, "\x1b[0;0H")
|
||||
// Clear from cursor to bottom of screen
|
||||
fmt.Fprint(s.Out, "\x1b[J")
|
||||
}
|
||||
}
|
||||
|
||||
// TerminalWidth returns the width of the terminal that stdout is attached to.
|
||||
// TODO: investigate whether ProcessTerminalWidth could replace all this.
|
||||
func (s *IOStreams) TerminalWidth() int {
|
||||
|
|
@ -373,10 +404,10 @@ func System() *IOStreams {
|
|||
stdoutIsTTY := isTerminal(os.Stdout)
|
||||
stderrIsTTY := isTerminal(os.Stderr)
|
||||
|
||||
assumeTrueColor := false
|
||||
isVirtualTerminal := false
|
||||
if stdoutIsTTY {
|
||||
if err := enableVirtualTerminalProcessing(os.Stdout); err == nil {
|
||||
assumeTrueColor = true
|
||||
isVirtualTerminal = true
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -386,8 +417,8 @@ func System() *IOStreams {
|
|||
Out: colorable.NewColorable(os.Stdout),
|
||||
ErrOut: colorable.NewColorable(os.Stderr),
|
||||
colorEnabled: EnvColorForced() || (!EnvColorDisabled() && stdoutIsTTY),
|
||||
is256enabled: assumeTrueColor || Is256ColorSupported(),
|
||||
hasTrueColor: assumeTrueColor || IsTrueColorSupported(),
|
||||
is256enabled: isVirtualTerminal || Is256ColorSupported(),
|
||||
hasTrueColor: isVirtualTerminal || IsTrueColorSupported(),
|
||||
pagerCommand: os.Getenv("PAGER"),
|
||||
ttySize: ttySize,
|
||||
}
|
||||
|
|
@ -396,6 +427,10 @@ func System() *IOStreams {
|
|||
io.progressIndicatorEnabled = true
|
||||
}
|
||||
|
||||
if stdoutIsTTY && isVirtualTerminal {
|
||||
io.alternateScreenBufferEnabled = true
|
||||
}
|
||||
|
||||
// prevent duplicate isTerminal queries now that we know the answer
|
||||
io.SetStdoutTTY(stdoutIsTTY)
|
||||
io.SetStderrTTY(stderrIsTTY)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package iostreams
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
@ -66,3 +67,20 @@ func TestIOStreams_ForceTerminal(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopAlternateScreenBuffer(t *testing.T) {
|
||||
ios, _, stdout, _ := Test()
|
||||
ios.SetAlternateScreenBufferEnabled(true)
|
||||
|
||||
ios.StartAlternateScreenBuffer()
|
||||
fmt.Fprint(ios.Out, "test")
|
||||
ios.StopAlternateScreenBuffer()
|
||||
|
||||
// Stopping a subsequent time should no-op.
|
||||
ios.StopAlternateScreenBuffer()
|
||||
|
||||
const want = "\x1b[?1049htest\x1b[?1049l"
|
||||
if got := stdout.String(); got != want {
|
||||
t.Errorf("after IOStreams.StopAlternateScreenBuffer() got %q, want %q", got, want)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue