Skip to content

Commit

Permalink
fix(output): export output writer
Browse files Browse the repository at this point in the history
Termenv output can be a buffer, ssh session, a file, or anything that
implements the io.Writer interface.

In the case of an ssh session, the std ssh library returns a
io.ReadWriter for ssh sessions and the current Termenv implementation
makes it impossible to read terminal status reports since it expects a
file in return.

In addition, the TTY() function is confusing since it implies that the
returned value is a TTY. Which is not always true until we check that
using the isTTY() function.

Deprecate TTY() in favor of Writer() which simply returns the underlying
writer. The caller then can infer the type of the writer and decide what
to do with it.

This also deprecate parts of commit 669c9ab 669c9ab
  • Loading branch information
aymanbagabas authored and muesli committed Aug 25, 2023
1 parent 3466887 commit 3b3da4b
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 58 deletions.
26 changes: 18 additions & 8 deletions output.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ var (
)

// File represents a file descriptor.
//
// Deprecated: Use *os.File instead.
type File interface {
io.ReadWriter
Fd() uintptr
Expand All @@ -23,7 +25,7 @@ type OutputOption = func(*Output)
// Output is a terminal output.
type Output struct {
Profile
tty io.Writer
w io.Writer
environ Environ

assumeTTY bool
Expand Down Expand Up @@ -61,10 +63,10 @@ func SetDefaultOutput(o *Output) {
output = o
}

// NewOutput returns a new Output for the given file descriptor.
func NewOutput(tty io.Writer, opts ...OutputOption) *Output {
// NewOutput returns a new Output for the given writer.
func NewOutput(w io.Writer, opts ...OutputOption) *Output {
o := &Output{
tty: tty,
w: w,
environ: &osEnviron{},
Profile: -1,
fgSync: &sync.Once{},
Expand All @@ -73,8 +75,8 @@ func NewOutput(tty io.Writer, opts ...OutputOption) *Output {
bgColor: NoColor{},
}

if o.tty == nil {
o.tty = os.Stdout
if o.w == nil {
o.w = os.Stdout
}
for _, opt := range opts {
opt(o)
Expand Down Expand Up @@ -180,15 +182,23 @@ func (o *Output) HasDarkBackground() bool {

// TTY returns the terminal's file descriptor. This may be nil if the output is
// not a terminal.
//
// Deprecated: Use Writer() instead.
func (o Output) TTY() File {
if f, ok := o.tty.(File); ok {
if f, ok := o.w.(File); ok {
return f
}
return nil
}

// Writer returns the underlying writer. This may be of type io.Writer,
// io.ReadWriter, or *os.File.
func (o Output) Writer() io.Writer {
return o.w
}

func (o Output) Write(p []byte) (int, error) {
return o.tty.Write(p)
return o.w.Write(p)
}

// WriteString writes the given string to the output.
Expand Down
88 changes: 44 additions & 44 deletions screen.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,234 +71,234 @@ const (

// Reset the terminal to its default style, removing any active styles.
func (o Output) Reset() {
fmt.Fprint(o.tty, CSI+ResetSeq+"m")
fmt.Fprint(o.w, CSI+ResetSeq+"m")
}

// SetForegroundColor sets the default foreground color.
func (o Output) SetForegroundColor(color Color) {
fmt.Fprintf(o.tty, OSC+SetForegroundColorSeq, color)
fmt.Fprintf(o.w, OSC+SetForegroundColorSeq, color)
}

// SetBackgroundColor sets the default background color.
func (o Output) SetBackgroundColor(color Color) {
fmt.Fprintf(o.tty, OSC+SetBackgroundColorSeq, color)
fmt.Fprintf(o.w, OSC+SetBackgroundColorSeq, color)
}

// SetCursorColor sets the cursor color.
func (o Output) SetCursorColor(color Color) {
fmt.Fprintf(o.tty, OSC+SetCursorColorSeq, color)
fmt.Fprintf(o.w, OSC+SetCursorColorSeq, color)
}

// RestoreScreen restores a previously saved screen state.
func (o Output) RestoreScreen() {
fmt.Fprint(o.tty, CSI+RestoreScreenSeq)
fmt.Fprint(o.w, CSI+RestoreScreenSeq)
}

// SaveScreen saves the screen state.
func (o Output) SaveScreen() {
fmt.Fprint(o.tty, CSI+SaveScreenSeq)
fmt.Fprint(o.w, CSI+SaveScreenSeq)
}

// AltScreen switches to the alternate screen buffer. The former view can be
// restored with ExitAltScreen().
func (o Output) AltScreen() {
fmt.Fprint(o.tty, CSI+AltScreenSeq)
fmt.Fprint(o.w, CSI+AltScreenSeq)
}

// ExitAltScreen exits the alternate screen buffer and returns to the former
// terminal view.
func (o Output) ExitAltScreen() {
fmt.Fprint(o.tty, CSI+ExitAltScreenSeq)
fmt.Fprint(o.w, CSI+ExitAltScreenSeq)
}

// ClearScreen clears the visible portion of the terminal.
func (o Output) ClearScreen() {
fmt.Fprintf(o.tty, CSI+EraseDisplaySeq, 2)
fmt.Fprintf(o.w, CSI+EraseDisplaySeq, 2)
o.MoveCursor(1, 1)
}

// MoveCursor moves the cursor to a given position.
func (o Output) MoveCursor(row int, column int) {
fmt.Fprintf(o.tty, CSI+CursorPositionSeq, row, column)
fmt.Fprintf(o.w, CSI+CursorPositionSeq, row, column)
}

// HideCursor hides the cursor.
func (o Output) HideCursor() {
fmt.Fprint(o.tty, CSI+HideCursorSeq)
fmt.Fprint(o.w, CSI+HideCursorSeq)
}

// ShowCursor shows the cursor.
func (o Output) ShowCursor() {
fmt.Fprint(o.tty, CSI+ShowCursorSeq)
fmt.Fprint(o.w, CSI+ShowCursorSeq)
}

// SaveCursorPosition saves the cursor position.
func (o Output) SaveCursorPosition() {
fmt.Fprint(o.tty, CSI+SaveCursorPositionSeq)
fmt.Fprint(o.w, CSI+SaveCursorPositionSeq)
}

// RestoreCursorPosition restores a saved cursor position.
func (o Output) RestoreCursorPosition() {
fmt.Fprint(o.tty, CSI+RestoreCursorPositionSeq)
fmt.Fprint(o.w, CSI+RestoreCursorPositionSeq)
}

// CursorUp moves the cursor up a given number of lines.
func (o Output) CursorUp(n int) {
fmt.Fprintf(o.tty, CSI+CursorUpSeq, n)
fmt.Fprintf(o.w, CSI+CursorUpSeq, n)
}

// CursorDown moves the cursor down a given number of lines.
func (o Output) CursorDown(n int) {
fmt.Fprintf(o.tty, CSI+CursorDownSeq, n)
fmt.Fprintf(o.w, CSI+CursorDownSeq, n)
}

// CursorForward moves the cursor up a given number of lines.
func (o Output) CursorForward(n int) {
fmt.Fprintf(o.tty, CSI+CursorForwardSeq, n)
fmt.Fprintf(o.w, CSI+CursorForwardSeq, n)
}

// CursorBack moves the cursor backwards a given number of cells.
func (o Output) CursorBack(n int) {
fmt.Fprintf(o.tty, CSI+CursorBackSeq, n)
fmt.Fprintf(o.w, CSI+CursorBackSeq, n)
}

// CursorNextLine moves the cursor down a given number of lines and places it at
// the beginning of the line.
func (o Output) CursorNextLine(n int) {
fmt.Fprintf(o.tty, CSI+CursorNextLineSeq, n)
fmt.Fprintf(o.w, CSI+CursorNextLineSeq, n)
}

// CursorPrevLine moves the cursor up a given number of lines and places it at
// the beginning of the line.
func (o Output) CursorPrevLine(n int) {
fmt.Fprintf(o.tty, CSI+CursorPreviousLineSeq, n)
fmt.Fprintf(o.w, CSI+CursorPreviousLineSeq, n)
}

// ClearLine clears the current line.
func (o Output) ClearLine() {
fmt.Fprint(o.tty, CSI+EraseEntireLineSeq)
fmt.Fprint(o.w, CSI+EraseEntireLineSeq)
}

// ClearLineLeft clears the line to the left of the cursor.
func (o Output) ClearLineLeft() {
fmt.Fprint(o.tty, CSI+EraseLineLeftSeq)
fmt.Fprint(o.w, CSI+EraseLineLeftSeq)
}

// ClearLineRight clears the line to the right of the cursor.
func (o Output) ClearLineRight() {
fmt.Fprint(o.tty, CSI+EraseLineRightSeq)
fmt.Fprint(o.w, CSI+EraseLineRightSeq)
}

// ClearLines clears a given number of lines.
func (o Output) ClearLines(n int) {
clearLine := fmt.Sprintf(CSI+EraseLineSeq, 2)
cursorUp := fmt.Sprintf(CSI+CursorUpSeq, 1)
fmt.Fprint(o.tty, clearLine+strings.Repeat(cursorUp+clearLine, n))
fmt.Fprint(o.w, clearLine+strings.Repeat(cursorUp+clearLine, n))
}

// ChangeScrollingRegion sets the scrolling region of the terminal.
func (o Output) ChangeScrollingRegion(top, bottom int) {
fmt.Fprintf(o.tty, CSI+ChangeScrollingRegionSeq, top, bottom)
fmt.Fprintf(o.w, CSI+ChangeScrollingRegionSeq, top, bottom)
}

// InsertLines inserts the given number of lines at the top of the scrollable
// region, pushing lines below down.
func (o Output) InsertLines(n int) {
fmt.Fprintf(o.tty, CSI+InsertLineSeq, n)
fmt.Fprintf(o.w, CSI+InsertLineSeq, n)
}

// DeleteLines deletes the given number of lines, pulling any lines in
// the scrollable region below up.
func (o Output) DeleteLines(n int) {
fmt.Fprintf(o.tty, CSI+DeleteLineSeq, n)
fmt.Fprintf(o.w, CSI+DeleteLineSeq, n)
}

// EnableMousePress enables X10 mouse mode. Button press events are sent only.
func (o Output) EnableMousePress() {
fmt.Fprint(o.tty, CSI+EnableMousePressSeq)
fmt.Fprint(o.w, CSI+EnableMousePressSeq)
}

// DisableMousePress disables X10 mouse mode.
func (o Output) DisableMousePress() {
fmt.Fprint(o.tty, CSI+DisableMousePressSeq)
fmt.Fprint(o.w, CSI+DisableMousePressSeq)
}

// EnableMouse enables Mouse Tracking mode.
func (o Output) EnableMouse() {
fmt.Fprint(o.tty, CSI+EnableMouseSeq)
fmt.Fprint(o.w, CSI+EnableMouseSeq)
}

// DisableMouse disables Mouse Tracking mode.
func (o Output) DisableMouse() {
fmt.Fprint(o.tty, CSI+DisableMouseSeq)
fmt.Fprint(o.w, CSI+DisableMouseSeq)
}

// EnableMouseHilite enables Hilite Mouse Tracking mode.
func (o Output) EnableMouseHilite() {
fmt.Fprint(o.tty, CSI+EnableMouseHiliteSeq)
fmt.Fprint(o.w, CSI+EnableMouseHiliteSeq)
}

// DisableMouseHilite disables Hilite Mouse Tracking mode.
func (o Output) DisableMouseHilite() {
fmt.Fprint(o.tty, CSI+DisableMouseHiliteSeq)
fmt.Fprint(o.w, CSI+DisableMouseHiliteSeq)
}

// EnableMouseCellMotion enables Cell Motion Mouse Tracking mode.
func (o Output) EnableMouseCellMotion() {
fmt.Fprint(o.tty, CSI+EnableMouseCellMotionSeq)
fmt.Fprint(o.w, CSI+EnableMouseCellMotionSeq)
}

// DisableMouseCellMotion disables Cell Motion Mouse Tracking mode.
func (o Output) DisableMouseCellMotion() {
fmt.Fprint(o.tty, CSI+DisableMouseCellMotionSeq)
fmt.Fprint(o.w, CSI+DisableMouseCellMotionSeq)
}

// EnableMouseAllMotion enables All Motion Mouse mode.
func (o Output) EnableMouseAllMotion() {
fmt.Fprint(o.tty, CSI+EnableMouseAllMotionSeq)
fmt.Fprint(o.w, CSI+EnableMouseAllMotionSeq)
}

// DisableMouseAllMotion disables All Motion Mouse mode.
func (o Output) DisableMouseAllMotion() {
fmt.Fprint(o.tty, CSI+DisableMouseAllMotionSeq)
fmt.Fprint(o.w, CSI+DisableMouseAllMotionSeq)
}

// EnableMouseExtendedMotion enables Extended Mouse mode (SGR). This should be
// enabled in conjunction with EnableMouseCellMotion, and EnableMouseAllMotion.
func (o Output) EnableMouseExtendedMode() {
fmt.Fprint(o.tty, CSI+EnableMouseExtendedModeSeq)
fmt.Fprint(o.w, CSI+EnableMouseExtendedModeSeq)
}

// DisableMouseExtendedMotion disables Extended Mouse mode (SGR).
func (o Output) DisableMouseExtendedMode() {
fmt.Fprint(o.tty, CSI+DisableMouseExtendedModeSeq)
fmt.Fprint(o.w, CSI+DisableMouseExtendedModeSeq)
}

// EnableMousePixelsMotion enables Pixel Motion Mouse mode (SGR-Pixels). This
// should be enabled in conjunction with EnableMouseCellMotion, and
// EnableMouseAllMotion.
func (o Output) EnableMousePixelsMode() {
fmt.Fprint(o.tty, CSI+EnableMousePixelsModeSeq)
fmt.Fprint(o.w, CSI+EnableMousePixelsModeSeq)
}

// DisableMousePixelsMotion disables Pixel Motion Mouse mode (SGR-Pixels).
func (o Output) DisableMousePixelsMode() {
fmt.Fprint(o.tty, CSI+DisableMousePixelsModeSeq)
fmt.Fprint(o.w, CSI+DisableMousePixelsModeSeq)
}

// SetWindowTitle sets the terminal window title.
func (o Output) SetWindowTitle(title string) {
fmt.Fprintf(o.tty, OSC+SetWindowTitleSeq, title)
fmt.Fprintf(o.w, OSC+SetWindowTitleSeq, title)
}

// EnableBracketedPaste enables bracketed paste.
func (o Output) EnableBracketedPaste() {
fmt.Fprintf(o.tty, CSI+EnableBracketedPasteSeq)
fmt.Fprintf(o.w, CSI+EnableBracketedPasteSeq)
}

// DisableBracketedPaste disables bracketed paste.
func (o Output) DisableBracketedPaste() {
fmt.Fprintf(o.tty, CSI+DisableBracketedPasteSeq)
fmt.Fprintf(o.w, CSI+DisableBracketedPasteSeq)
}

// Legacy functions.
Expand Down
2 changes: 1 addition & 1 deletion screen_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func tempOutput(t *testing.T) *Output {

func verify(t *testing.T, o *Output, exp string) {
t.Helper()
tty := o.tty.(*os.File)
tty := o.w.(*os.File)

if _, err := tty.Seek(0, 0); err != nil {
t.Fatal(err)
Expand Down
7 changes: 4 additions & 3 deletions termenv.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package termenv

import (
"errors"
"os"

"github.com/mattn/go-isatty"
)
Expand Down Expand Up @@ -31,11 +32,11 @@ func (o *Output) isTTY() bool {
if len(o.environ.Getenv("CI")) > 0 {
return false
}
if o.TTY() == nil {
return false
if f, ok := o.Writer().(*os.File); ok {
return isatty.IsTerminal(f.Fd())
}

return isatty.IsTerminal(o.TTY().Fd())
return false
}

// ColorProfile returns the supported color profile:
Expand Down
5 changes: 3 additions & 2 deletions termenv_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ package termenv

import (
"fmt"
"os"
"strconv"

"golang.org/x/sys/windows"
Expand Down Expand Up @@ -103,8 +104,8 @@ func EnableVirtualTerminalProcessing(o *Output) (restoreFunc func() error, err e
}

// If o is not a tty, then there is nothing to do.
tty := o.TTY()
if tty == nil {
tty, ok := o.Writer().(*os.File)
if tty == nil || !ok {
return
}

Expand Down

0 comments on commit 3b3da4b

Please sign in to comment.