Merge pull request #6421 from cli/go-gh-term
Dogfood term package from go-gh
This commit is contained in:
commit
9ec2107cc6
13 changed files with 96 additions and 393 deletions
|
|
@ -64,10 +64,6 @@ func mainRun() exitCode {
|
|||
|
||||
cmdFactory := factory.New(buildVersion)
|
||||
stderr := cmdFactory.IOStreams.ErrOut
|
||||
|
||||
if spec := os.Getenv("GH_FORCE_TTY"); spec != "" {
|
||||
cmdFactory.IOStreams.ForceTerminal(spec)
|
||||
}
|
||||
if !cmdFactory.IOStreams.ColorEnabled() {
|
||||
surveyCore.DisableColor = true
|
||||
ansi.DisableColors(true)
|
||||
|
|
|
|||
6
go.mod
6
go.mod
|
|
@ -9,7 +9,7 @@ require (
|
|||
github.com/cenkalti/backoff/v4 v4.1.3
|
||||
github.com/charmbracelet/glamour v0.5.1-0.20220727184942-e70ff2d969da
|
||||
github.com/charmbracelet/lipgloss v0.5.0
|
||||
github.com/cli/go-gh v0.1.2
|
||||
github.com/cli/go-gh v0.1.3-0.20221102170023-e3ec45fb1d1b
|
||||
github.com/cli/oauth v0.9.0
|
||||
github.com/cli/safeexec v1.0.0
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.2
|
||||
|
|
@ -26,7 +26,6 @@ require (
|
|||
github.com/mattn/go-colorable v0.1.13
|
||||
github.com/mattn/go-isatty v0.0.16
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d
|
||||
github.com/muesli/termenv v0.12.0
|
||||
github.com/muhammadmuzzammil1998/jsonc v0.0.0-20201229145248-615b0916ca38
|
||||
github.com/opentracing/opentracing-go v1.1.0
|
||||
github.com/shurcooL/githubv4 v0.0.0-20220520033151-0b4e3294ff00
|
||||
|
|
@ -36,7 +35,6 @@ require (
|
|||
github.com/stretchr/testify v1.7.5
|
||||
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5
|
||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/text v0.3.8
|
||||
google.golang.org/grpc v1.49.0
|
||||
|
|
@ -62,6 +60,7 @@ require (
|
|||
github.com/mattn/go-runewidth v0.0.13 // indirect
|
||||
github.com/microcosm-cc/bluemonday v1.0.20 // indirect
|
||||
github.com/muesli/reflow v0.3.0 // indirect
|
||||
github.com/muesli/termenv v0.12.0 // indirect
|
||||
github.com/olekukonko/tablewriter v0.0.5 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/rivo/uniseg v0.2.0 // indirect
|
||||
|
|
@ -73,6 +72,7 @@ require (
|
|||
github.com/yuin/goldmark-emoji v1.0.1 // indirect
|
||||
golang.org/x/net v0.0.0-20220923203811-8be639271d50 // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a // indirect
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab // indirect
|
||||
google.golang.org/genproto v0.0.0-20200825200019-8632dd797987 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
)
|
||||
|
|
|
|||
4
go.sum
4
go.sum
|
|
@ -60,8 +60,8 @@ github.com/cli/browser v1.1.0 h1:xOZBfkfY9L9vMBgqb1YwRirGu6QFaQ5dP/vXt5ENSOY=
|
|||
github.com/cli/browser v1.1.0/go.mod h1:HKMQAt9t12kov91Mn7RfZxyJQQgWgyS/3SZswlZ5iTI=
|
||||
github.com/cli/crypto v0.0.0-20210929142629-6be313f59b03 h1:3f4uHLfWx4/WlnMPXGai03eoWAI+oGHJwr+5OXfxCr8=
|
||||
github.com/cli/crypto v0.0.0-20210929142629-6be313f59b03/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
github.com/cli/go-gh v0.1.2 h1:DoiHIo7uuK51Tw5dmawHfIMcBq9CsNNZ2uQTPkP4pLU=
|
||||
github.com/cli/go-gh v0.1.2/go.mod h1:bqxLdCoTZ73BuiPEJx4olcO/XKhVZaFDchFagYRBweE=
|
||||
github.com/cli/go-gh v0.1.3-0.20221102170023-e3ec45fb1d1b h1:W17Cf1UmOvLPbrHcFs9InoY3VPxC0TJWx3QwnjnD4TY=
|
||||
github.com/cli/go-gh v0.1.3-0.20221102170023-e3ec45fb1d1b/go.mod h1:bqxLdCoTZ73BuiPEJx4olcO/XKhVZaFDchFagYRBweE=
|
||||
github.com/cli/oauth v0.9.0 h1:nxBC0Df4tUzMkqffAB+uZvisOwT3/N9FpkfdTDtafxc=
|
||||
github.com/cli/oauth v0.9.0/go.mod h1:qd/FX8ZBD6n1sVNQO3aIdRxeu5LGw9WhKnYhIIoC2A4=
|
||||
github.com/cli/safeexec v1.0.0 h1:0VngyaIyqACHdcMNWfo6+KdUYnqEr2Sg+bSP1pdF+dI=
|
||||
|
|
|
|||
|
|
@ -180,7 +180,7 @@ func isGitMissing(err error) bool {
|
|||
}
|
||||
|
||||
func shellQuote(s string) string {
|
||||
if strings.ContainsAny(s, " $") {
|
||||
if strings.ContainsAny(s, " $\\") {
|
||||
return "'" + s + "'"
|
||||
}
|
||||
return s
|
||||
|
|
|
|||
|
|
@ -2,7 +2,6 @@ package iostreams
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
|
|
@ -25,30 +24,6 @@ var (
|
|||
}
|
||||
)
|
||||
|
||||
func EnvColorDisabled() bool {
|
||||
return os.Getenv("NO_COLOR") != "" || os.Getenv("CLICOLOR") == "0"
|
||||
}
|
||||
|
||||
func EnvColorForced() bool {
|
||||
return os.Getenv("CLICOLOR_FORCE") != "" && os.Getenv("CLICOLOR_FORCE") != "0"
|
||||
}
|
||||
|
||||
func Is256ColorSupported() bool {
|
||||
return IsTrueColorSupported() ||
|
||||
strings.Contains(os.Getenv("TERM"), "256") ||
|
||||
strings.Contains(os.Getenv("COLORTERM"), "256")
|
||||
}
|
||||
|
||||
func IsTrueColorSupported() bool {
|
||||
term := os.Getenv("TERM")
|
||||
colorterm := os.Getenv("COLORTERM")
|
||||
|
||||
return strings.Contains(term, "24bit") ||
|
||||
strings.Contains(term, "truecolor") ||
|
||||
strings.Contains(colorterm, "24bit") ||
|
||||
strings.Contains(colorterm, "truecolor")
|
||||
}
|
||||
|
||||
func NewColorScheme(enabled, is256enabled bool, trueColor bool) *ColorScheme {
|
||||
return &ColorScheme{
|
||||
enabled: enabled,
|
||||
|
|
|
|||
|
|
@ -6,127 +6,6 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEnvColorDisabled(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
NO_COLOR string
|
||||
CLICOLOR string
|
||||
CLICOLOR_FORCE string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "pristine env",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "NO_COLOR enabled",
|
||||
NO_COLOR: "1",
|
||||
CLICOLOR: "",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "CLICOLOR disabled",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "0",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "CLICOLOR enabled",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "1",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "CLICOLOR_FORCE has no effect",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "",
|
||||
CLICOLOR_FORCE: "1",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv("NO_COLOR", tt.NO_COLOR)
|
||||
t.Setenv("CLICOLOR", tt.CLICOLOR)
|
||||
t.Setenv("CLICOLOR_FORCE", tt.CLICOLOR_FORCE)
|
||||
|
||||
if got := EnvColorDisabled(); got != tt.want {
|
||||
t.Errorf("EnvColorDisabled(): want %v, got %v", tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestEnvColorForced(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
NO_COLOR string
|
||||
CLICOLOR string
|
||||
CLICOLOR_FORCE string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "pristine env",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "NO_COLOR enabled",
|
||||
NO_COLOR: "1",
|
||||
CLICOLOR: "",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "CLICOLOR disabled",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "0",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "CLICOLOR enabled",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "1",
|
||||
CLICOLOR_FORCE: "",
|
||||
want: false,
|
||||
},
|
||||
{
|
||||
name: "CLICOLOR_FORCE enabled",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "",
|
||||
CLICOLOR_FORCE: "1",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "CLICOLOR_FORCE disabled",
|
||||
NO_COLOR: "",
|
||||
CLICOLOR: "",
|
||||
CLICOLOR_FORCE: "0",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Setenv("NO_COLOR", tt.NO_COLOR)
|
||||
t.Setenv("CLICOLOR", tt.CLICOLOR)
|
||||
t.Setenv("CLICOLOR_FORCE", tt.CLICOLOR_FORCE)
|
||||
|
||||
if got := EnvColorForced(); got != tt.want {
|
||||
t.Errorf("EnvColorForced(): want %v, got %v", tt.want, got)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestColorFromRGB(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
|
|||
|
|
@ -3,8 +3,9 @@
|
|||
|
||||
package iostreams
|
||||
|
||||
import "errors"
|
||||
import "os"
|
||||
|
||||
func enableVirtualTerminalProcessing(fd uintptr) error {
|
||||
return errors.New("not implemented")
|
||||
func hasAlternateScreenBuffer(hasTrueColor bool) bool {
|
||||
// on non-Windows, we just assume that alternate screen buffer is supported in most cases
|
||||
return os.Getenv("TERM") != "dumb"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,14 +3,8 @@
|
|||
|
||||
package iostreams
|
||||
|
||||
import (
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func enableVirtualTerminalProcessing(fd uintptr) error {
|
||||
stdout := windows.Handle(fd)
|
||||
|
||||
var originalMode uint32
|
||||
windows.GetConsoleMode(stdout, &originalMode)
|
||||
return windows.SetConsoleMode(stdout, originalMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
func hasAlternateScreenBuffer(hasTrueColor bool) bool {
|
||||
// on Windows we just assume that alternate screen buffer is supported if we
|
||||
// enabled virtual terminal processing, which in turn enables truecolor
|
||||
return hasTrueColor
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,18 +8,16 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/briandowns/spinner"
|
||||
ghTerm "github.com/cli/go-gh/pkg/term"
|
||||
"github.com/cli/safeexec"
|
||||
"github.com/google/shlex"
|
||||
"github.com/mattn/go-colorable"
|
||||
"github.com/mattn/go-isatty"
|
||||
"github.com/muesli/termenv"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
const DefaultWidth = 80
|
||||
|
|
@ -39,14 +37,22 @@ type fileReader interface {
|
|||
Fd() uintptr
|
||||
}
|
||||
|
||||
type term interface {
|
||||
IsTerminalOutput() bool
|
||||
IsColorEnabled() bool
|
||||
Is256ColorSupported() bool
|
||||
IsTrueColorSupported() bool
|
||||
Theme() string
|
||||
Size() (int, int, error)
|
||||
}
|
||||
|
||||
type IOStreams struct {
|
||||
term term
|
||||
|
||||
In fileReader
|
||||
Out fileWriter
|
||||
ErrOut io.Writer
|
||||
|
||||
colorEnabled bool
|
||||
is256enabled bool
|
||||
hasTrueColor bool
|
||||
terminalTheme string
|
||||
|
||||
progressIndicatorEnabled bool
|
||||
|
|
@ -63,8 +69,9 @@ type IOStreams struct {
|
|||
stdoutIsTTY bool
|
||||
stderrTTYOverride bool
|
||||
stderrIsTTY bool
|
||||
termWidthOverride int
|
||||
ttySize func() (int, int, error)
|
||||
|
||||
colorOverride bool
|
||||
colorEnabled bool
|
||||
|
||||
pagerCommand string
|
||||
pagerProcess *os.Process
|
||||
|
|
@ -75,42 +82,42 @@ type IOStreams struct {
|
|||
}
|
||||
|
||||
func (s *IOStreams) ColorEnabled() bool {
|
||||
return s.colorEnabled
|
||||
if s.colorOverride {
|
||||
return s.colorEnabled
|
||||
}
|
||||
return s.term.IsColorEnabled()
|
||||
}
|
||||
|
||||
func (s *IOStreams) ColorSupport256() bool {
|
||||
return s.is256enabled
|
||||
if s.colorOverride {
|
||||
return s.colorEnabled
|
||||
}
|
||||
return s.term.Is256ColorSupported()
|
||||
}
|
||||
|
||||
func (s *IOStreams) HasTrueColor() bool {
|
||||
return s.hasTrueColor
|
||||
if s.colorOverride {
|
||||
return s.colorEnabled
|
||||
}
|
||||
return s.term.IsTrueColorSupported()
|
||||
}
|
||||
|
||||
// DetectTerminalTheme is a utility to call before starting the output pager so that the terminal background
|
||||
// can be reliably detected.
|
||||
func (s *IOStreams) DetectTerminalTheme() {
|
||||
if !s.ColorEnabled() {
|
||||
s.terminalTheme = "none"
|
||||
return
|
||||
}
|
||||
|
||||
if s.pagerProcess != nil {
|
||||
if !s.ColorEnabled() || s.pagerProcess != nil {
|
||||
s.terminalTheme = "none"
|
||||
return
|
||||
}
|
||||
|
||||
style := os.Getenv("GLAMOUR_STYLE")
|
||||
if style != "" && style != "auto" {
|
||||
// ensure GLAMOUR_STYLE takes precedence over "light" and "dark" themes
|
||||
s.terminalTheme = "none"
|
||||
return
|
||||
}
|
||||
|
||||
if termenv.HasDarkBackground() {
|
||||
s.terminalTheme = "dark"
|
||||
return
|
||||
}
|
||||
|
||||
s.terminalTheme = "light"
|
||||
s.terminalTheme = s.term.Theme()
|
||||
}
|
||||
|
||||
// TerminalTheme returns "light", "dark", or "none" depending on the background color of the terminal.
|
||||
|
|
@ -123,6 +130,7 @@ func (s *IOStreams) TerminalTheme() string {
|
|||
}
|
||||
|
||||
func (s *IOStreams) SetColorEnabled(colorEnabled bool) {
|
||||
s.colorOverride = true
|
||||
s.colorEnabled = colorEnabled
|
||||
}
|
||||
|
||||
|
|
@ -150,10 +158,12 @@ func (s *IOStreams) IsStdoutTTY() bool {
|
|||
if s.stdoutTTYOverride {
|
||||
return s.stdoutIsTTY
|
||||
}
|
||||
if stdout, ok := s.Out.(*os.File); ok {
|
||||
return isTerminal(stdout)
|
||||
// support GH_FORCE_TTY
|
||||
if s.term.IsTerminalOutput() {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
stdout, ok := s.Out.(*os.File)
|
||||
return ok && isCygwinTerminal(stdout.Fd())
|
||||
}
|
||||
|
||||
func (s *IOStreams) SetStderrTTY(isTTY bool) {
|
||||
|
|
@ -339,65 +349,13 @@ func (s *IOStreams) RefreshScreen() {
|
|||
}
|
||||
}
|
||||
|
||||
// TerminalWidth returns the width of the terminal that stdout is attached to.
|
||||
// TODO: investigate whether ProcessTerminalWidth could replace all this.
|
||||
// TerminalWidth returns the width of the terminal that controls the process
|
||||
func (s *IOStreams) TerminalWidth() int {
|
||||
if s.termWidthOverride > 0 {
|
||||
return s.termWidthOverride
|
||||
}
|
||||
|
||||
defaultWidth := DefaultWidth
|
||||
|
||||
if w, _, err := terminalSize(s.Out.Fd()); err == nil {
|
||||
w, _, err := s.term.Size()
|
||||
if err == nil && w > 0 {
|
||||
return w
|
||||
}
|
||||
|
||||
if isCygwinTerminal(s.Out.Fd()) {
|
||||
tputExe, err := safeexec.LookPath("tput")
|
||||
if err != nil {
|
||||
return defaultWidth
|
||||
}
|
||||
tputCmd := exec.Command(tputExe, "cols")
|
||||
tputCmd.Stdin = os.Stdin
|
||||
if out, err := tputCmd.Output(); err == nil {
|
||||
if w, err := strconv.Atoi(strings.TrimSpace(string(out))); err == nil {
|
||||
return w
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return defaultWidth
|
||||
}
|
||||
|
||||
// ProcessTerminalWidth returns the width of the terminal that the process is attached to.
|
||||
func (s *IOStreams) ProcessTerminalWidth() int {
|
||||
w, _, err := s.ttySize()
|
||||
if err != nil {
|
||||
return DefaultWidth
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
func (s *IOStreams) ForceTerminal(spec string) {
|
||||
s.colorEnabled = !EnvColorDisabled()
|
||||
s.SetStdoutTTY(true)
|
||||
|
||||
if w, err := strconv.Atoi(spec); err == nil {
|
||||
s.termWidthOverride = w
|
||||
return
|
||||
}
|
||||
|
||||
ttyWidth, _, err := s.ttySize()
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
s.termWidthOverride = ttyWidth
|
||||
|
||||
if strings.HasSuffix(spec, "%") {
|
||||
if p, err := strconv.Atoi(spec[:len(spec)-1]); err == nil {
|
||||
s.termWidthOverride = int(float64(s.termWidthOverride) * (float64(p) / 100))
|
||||
}
|
||||
}
|
||||
return DefaultWidth
|
||||
}
|
||||
|
||||
func (s *IOStreams) ColorScheme() *ColorScheme {
|
||||
|
|
@ -427,25 +385,16 @@ func (s *IOStreams) TempFile(dir, pattern string) (*os.File, error) {
|
|||
}
|
||||
|
||||
func System() *IOStreams {
|
||||
stdoutIsTTY := isTerminal(os.Stdout)
|
||||
stderrIsTTY := isTerminal(os.Stderr)
|
||||
terminal := ghTerm.FromEnv()
|
||||
|
||||
var stdout fileWriter = os.Stdout
|
||||
isVirtualTerminal := false
|
||||
if stdoutIsTTY {
|
||||
// enables ANSI escape sequences on modern Windows
|
||||
if err := enableVirtualTerminalProcessing(os.Stdout.Fd()); err == nil {
|
||||
isVirtualTerminal = true
|
||||
} else {
|
||||
// as a fallback, translate ANSI escape sequences to Windows console syscalls
|
||||
colorableStdout := colorable.NewColorable(os.Stdout)
|
||||
if colorableStdout != os.Stdout {
|
||||
// ensure that the file descriptor of the original stdout is preserved
|
||||
stdout = &fdWriter{
|
||||
fd: os.Stdout.Fd(),
|
||||
Writer: colorableStdout,
|
||||
}
|
||||
}
|
||||
// On Windows with no virtual terminal processing support, translate ANSI escape
|
||||
// sequences to console syscalls
|
||||
if colorableStdout := colorable.NewColorable(os.Stdout); colorableStdout != os.Stdout {
|
||||
// ensure that the file descriptor of the original stdout is preserved
|
||||
stdout = &fdWriter{
|
||||
fd: os.Stdout.Fd(),
|
||||
Writer: colorableStdout,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -453,27 +402,49 @@ func System() *IOStreams {
|
|||
In: os.Stdin,
|
||||
Out: stdout,
|
||||
ErrOut: colorable.NewColorable(os.Stderr),
|
||||
colorEnabled: EnvColorForced() || (!EnvColorDisabled() && stdoutIsTTY),
|
||||
is256enabled: isVirtualTerminal || Is256ColorSupported(),
|
||||
hasTrueColor: isVirtualTerminal || IsTrueColorSupported(),
|
||||
pagerCommand: os.Getenv("PAGER"),
|
||||
ttySize: ttySize,
|
||||
term: &terminal,
|
||||
}
|
||||
|
||||
if stdoutIsTTY && stderrIsTTY {
|
||||
stdoutIsTTY := io.IsStdoutTTY()
|
||||
|
||||
if stdoutIsTTY && io.IsStderrTTY() {
|
||||
io.progressIndicatorEnabled = true
|
||||
}
|
||||
|
||||
if stdoutIsTTY && isVirtualTerminal {
|
||||
if stdoutIsTTY && hasAlternateScreenBuffer(terminal.IsTrueColorSupported()) {
|
||||
io.alternateScreenBufferEnabled = true
|
||||
}
|
||||
|
||||
// prevent duplicate isTerminal queries now that we know the answer
|
||||
io.SetStdoutTTY(stdoutIsTTY)
|
||||
io.SetStderrTTY(stderrIsTTY)
|
||||
return io
|
||||
}
|
||||
|
||||
type fakeTerm struct{}
|
||||
|
||||
func (t fakeTerm) IsTerminalOutput() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t fakeTerm) IsColorEnabled() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t fakeTerm) Is256ColorSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t fakeTerm) IsTrueColorSupported() bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func (t fakeTerm) Theme() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func (t fakeTerm) Size() (int, int, error) {
|
||||
return 80, -1, nil
|
||||
}
|
||||
|
||||
func Test() (*IOStreams, *bytes.Buffer, *bytes.Buffer, *bytes.Buffer) {
|
||||
in := &bytes.Buffer{}
|
||||
out := &bytes.Buffer{}
|
||||
|
|
@ -485,9 +456,7 @@ func Test() (*IOStreams, *bytes.Buffer, *bytes.Buffer, *bytes.Buffer) {
|
|||
},
|
||||
Out: &fdWriter{fd: 1, Writer: out},
|
||||
ErrOut: errOut,
|
||||
ttySize: func() (int, int, error) {
|
||||
return -1, -1, errors.New("ttySize not implemented in tests")
|
||||
},
|
||||
term: &fakeTerm{},
|
||||
}
|
||||
io.SetStdinTTY(false)
|
||||
io.SetStdoutTTY(false)
|
||||
|
|
@ -496,18 +465,13 @@ func Test() (*IOStreams, *bytes.Buffer, *bytes.Buffer, *bytes.Buffer) {
|
|||
}
|
||||
|
||||
func isTerminal(f *os.File) bool {
|
||||
return isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd())
|
||||
return ghTerm.IsTerminal(f) || isCygwinTerminal(f.Fd())
|
||||
}
|
||||
|
||||
func isCygwinTerminal(fd uintptr) bool {
|
||||
return isatty.IsCygwinTerminal(fd)
|
||||
}
|
||||
|
||||
// terminalSize measures the viewport of the terminal that the output stream is connected to
|
||||
func terminalSize(fd uintptr) (int, int, error) {
|
||||
return term.GetSize(int(fd))
|
||||
}
|
||||
|
||||
// pagerWriter implements a WriteCloser that wraps all EPIPE errors in an ErrClosedPagerPipe type.
|
||||
type pagerWriter struct {
|
||||
io.WriteCloser
|
||||
|
|
|
|||
|
|
@ -2,79 +2,11 @@ package iostreams
|
|||
|
||||
import (
|
||||
"bufio"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIOStreams_ForceTerminal(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
iostreams *IOStreams
|
||||
arg string
|
||||
wantTTY bool
|
||||
wantWidth int
|
||||
}{
|
||||
{
|
||||
name: "explicit width",
|
||||
iostreams: &IOStreams{},
|
||||
arg: "72",
|
||||
wantTTY: true,
|
||||
wantWidth: 72,
|
||||
},
|
||||
{
|
||||
name: "measure width",
|
||||
iostreams: &IOStreams{
|
||||
ttySize: func() (int, int, error) {
|
||||
return 72, 0, nil
|
||||
},
|
||||
},
|
||||
arg: "true",
|
||||
wantTTY: true,
|
||||
wantWidth: 72,
|
||||
},
|
||||
{
|
||||
name: "measure width fails",
|
||||
iostreams: &IOStreams{
|
||||
Out: &fdWriter{
|
||||
Writer: io.Discard,
|
||||
fd: 1,
|
||||
},
|
||||
ttySize: func() (int, int, error) {
|
||||
return -1, -1, errors.New("ttySize sabotage!")
|
||||
},
|
||||
},
|
||||
arg: "true",
|
||||
wantTTY: true,
|
||||
wantWidth: 80,
|
||||
},
|
||||
{
|
||||
name: "apply percentage",
|
||||
iostreams: &IOStreams{
|
||||
ttySize: func() (int, int, error) {
|
||||
return 72, 0, nil
|
||||
},
|
||||
},
|
||||
arg: "50%",
|
||||
wantTTY: true,
|
||||
wantWidth: 36,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
tt.iostreams.ForceTerminal(tt.arg)
|
||||
if isTTY := tt.iostreams.IsStdoutTTY(); isTTY != tt.wantTTY {
|
||||
t.Errorf("IOStreams.IsStdoutTTY() = %v, want %v", isTTY, tt.wantTTY)
|
||||
}
|
||||
if tw := tt.iostreams.TerminalWidth(); tw != tt.wantWidth {
|
||||
t.Errorf("IOStreams.TerminalWidth() = %v, want %v", tw, tt.wantWidth)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestStopAlternateScreenBuffer(t *testing.T) {
|
||||
ios, _, stdout, _ := Test()
|
||||
ios.SetAlternateScreenBufferEnabled(true)
|
||||
|
|
|
|||
|
|
@ -1,20 +0,0 @@
|
|||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
package iostreams
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
// ttySize measures the size of the controlling terminal for the current process
|
||||
func ttySize() (int, int, error) {
|
||||
f, err := os.Open("/dev/tty")
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
defer f.Close()
|
||||
return term.GetSize(int(f.Fd()))
|
||||
}
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
package iostreams
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
func ttySize() (int, int, error) {
|
||||
f, err := os.Open("CONOUT$")
|
||||
if err != nil {
|
||||
return -1, -1, err
|
||||
}
|
||||
defer f.Close()
|
||||
return term.GetSize(int(f.Fd()))
|
||||
}
|
||||
|
|
@ -40,10 +40,8 @@ func NewTablePrinterWithOptions(ios *iostreams.IOStreams, opts TablePrinterOptio
|
|||
if opts.IsTTY {
|
||||
if opts.MaxWidth > 0 {
|
||||
maxWidth = opts.MaxWidth
|
||||
} else if ios.IsStdoutTTY() {
|
||||
maxWidth = ios.TerminalWidth()
|
||||
} else {
|
||||
maxWidth = ios.ProcessTerminalWidth()
|
||||
maxWidth = ios.TerminalWidth()
|
||||
}
|
||||
}
|
||||
tp := tableprinter.New(out, opts.IsTTY, maxWidth)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue