forked from Mirrors/bubbletea
feat: use Termenv.Output to write to tty
This commit is contained in:
parent
5c4218e5f6
commit
6c449e55bf
|
@ -11,7 +11,7 @@ require (
|
|||
github.com/lucasb-eyer/go-colorful v1.2.0
|
||||
github.com/mattn/go-isatty v0.0.16
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/muesli/termenv v0.12.1-0.20220615005108-4e9068de9898
|
||||
github.com/muesli/termenv v0.13.0
|
||||
)
|
||||
|
||||
replace github.com/charmbracelet/bubbletea => ../
|
||||
|
|
|
@ -39,8 +39,9 @@ github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+Ei
|
|||
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/microcosm-cc/bluemonday v1.0.17 h1:Z1a//hgsQ4yjC+8zEkV8IWySkXnsxmdSY642CTFQb5Y=
|
||||
github.com/microcosm-cc/bluemonday v1.0.17/go.mod h1:Z0r70sCuXHig8YpBzCc5eGHAap2K7e/u082ZUpDRRqM=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||
|
@ -53,8 +54,8 @@ github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKt
|
|||
github.com/muesli/termenv v0.9.0/go.mod h1:R/LzAKf+suGs4IsO95y7+7DpFHO0KABgnZqtlyx2mBw=
|
||||
github.com/muesli/termenv v0.11.1-0.20220204035834-5ac8409525e0/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
github.com/muesli/termenv v0.12.1-0.20220615005108-4e9068de9898 h1:0j+cbZdhLgpNxjg0nWCasHUA82fgWOXxxGgWNVOLS1I=
|
||||
github.com/muesli/termenv v0.12.1-0.20220615005108-4e9068de9898/go.mod h1:bN6sPNtkiahdhHv2Xm6RGU16LSCxfbIZvMfqjOCfrR4=
|
||||
github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0=
|
||||
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
|
||||
github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec=
|
||||
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
|
@ -81,7 +82,6 @@ golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
|
@ -91,7 +91,6 @@ golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
|
3
go.mod
3
go.mod
|
@ -9,8 +9,7 @@ require (
|
|||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b
|
||||
github.com/muesli/cancelreader v0.2.2
|
||||
github.com/muesli/reflow v0.3.0
|
||||
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab
|
||||
github.com/muesli/termenv v0.13.0
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211
|
||||
golang.org/x/text v0.3.7 // indirect
|
||||
)
|
||||
|
|
11
go.sum
11
go.sum
|
@ -1,29 +1,30 @@
|
|||
github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg=
|
||||
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
|
||||
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI=
|
||||
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0=
|
||||
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
24
options.go
24
options.go
|
@ -1,6 +1,10 @@
|
|||
package tea
|
||||
|
||||
import "io"
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
// ProgramOption is used to set options when initializing a Program. Program can
|
||||
// accept a variable number of options.
|
||||
|
@ -13,17 +17,17 @@ type ProgramOption func(*Program)
|
|||
// WithOutput sets the output which, by default, is stdout. In most cases you
|
||||
// won't need to use this.
|
||||
func WithOutput(output io.Writer) ProgramOption {
|
||||
return func(m *Program) {
|
||||
m.output = output
|
||||
return func(p *Program) {
|
||||
p.output = termenv.NewOutput(output, termenv.WithColorCache(true))
|
||||
}
|
||||
}
|
||||
|
||||
// WithInput sets the input which, by default, is stdin. In most cases you
|
||||
// won't need to use this.
|
||||
func WithInput(input io.Reader) ProgramOption {
|
||||
return func(m *Program) {
|
||||
m.input = input
|
||||
m.startupOptions |= withCustomInput
|
||||
return func(p *Program) {
|
||||
p.input = input
|
||||
p.startupOptions |= withCustomInput
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -39,8 +43,8 @@ func WithInputTTY() ProgramOption {
|
|||
// unusable state after a panic because Bubble Tea will not perform its usual
|
||||
// cleanup on exit.
|
||||
func WithoutCatchPanics() ProgramOption {
|
||||
return func(m *Program) {
|
||||
m.CatchPanics = false
|
||||
return func(p *Program) {
|
||||
p.CatchPanics = false
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -114,8 +118,8 @@ func WithMouseAllMotion() ProgramOption {
|
|||
// programs. For example, your program could behave like a daemon if output is
|
||||
// not a TTY.
|
||||
func WithoutRenderer() ProgramOption {
|
||||
return func(m *Program) {
|
||||
m.renderer = &nilRenderer{}
|
||||
return func(p *Program) {
|
||||
p.renderer = &nilRenderer{}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,8 +9,8 @@ func TestOptions(t *testing.T) {
|
|||
t.Run("output", func(t *testing.T) {
|
||||
var b bytes.Buffer
|
||||
p := NewProgram(nil, WithOutput(&b))
|
||||
if p.output != &b {
|
||||
t.Errorf("expected output to custom, got %v", p.output)
|
||||
if p.output.TTY() != nil {
|
||||
t.Errorf("expected output to custom, got %v", p.output.TTY().Fd())
|
||||
}
|
||||
})
|
||||
|
||||
|
|
53
screen.go
53
screen.go
|
@ -1,53 +0,0 @@
|
|||
package tea
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
te "github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
func hideCursor(w io.Writer) {
|
||||
fmt.Fprintf(w, te.CSI+te.HideCursorSeq)
|
||||
}
|
||||
|
||||
func showCursor(w io.Writer) {
|
||||
fmt.Fprintf(w, te.CSI+te.ShowCursorSeq)
|
||||
}
|
||||
|
||||
func clearLine(w io.Writer) {
|
||||
fmt.Fprintf(w, te.CSI+te.EraseLineSeq, 2)
|
||||
}
|
||||
|
||||
func cursorUp(w io.Writer) {
|
||||
fmt.Fprintf(w, te.CSI+te.CursorUpSeq, 1)
|
||||
}
|
||||
|
||||
func cursorDown(w io.Writer) {
|
||||
fmt.Fprintf(w, te.CSI+te.CursorDownSeq, 1)
|
||||
}
|
||||
|
||||
func insertLine(w io.Writer, numLines int) {
|
||||
fmt.Fprintf(w, te.CSI+"%dL", numLines)
|
||||
}
|
||||
|
||||
func moveCursor(w io.Writer, row, col int) {
|
||||
fmt.Fprintf(w, te.CSI+te.CursorPositionSeq, row, col)
|
||||
}
|
||||
|
||||
func changeScrollingRegion(w io.Writer, top, bottom int) {
|
||||
fmt.Fprintf(w, te.CSI+te.ChangeScrollingRegionSeq, top, bottom)
|
||||
}
|
||||
|
||||
func cursorBack(w io.Writer, n int) {
|
||||
fmt.Fprintf(w, te.CSI+te.CursorBackSeq, n)
|
||||
}
|
||||
|
||||
func enterAltScreen(w io.Writer) {
|
||||
fmt.Fprintf(w, te.CSI+te.AltScreenSeq)
|
||||
moveCursor(w, 0, 0)
|
||||
}
|
||||
|
||||
func exitAltScreen(w io.Writer) {
|
||||
fmt.Fprintf(w, te.CSI+te.ExitAltScreenSeq)
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
package tea
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"io"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestScreen(t *testing.T) {
|
||||
exercise := func(t *testing.T, fn func(io.Writer), expect []byte) {
|
||||
var w bytes.Buffer
|
||||
fn(&w)
|
||||
if !bytes.Equal(w.Bytes(), expect) {
|
||||
t.Errorf("expected %q, got %q", expect, w.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
t.Run("change scrolling region", func(t *testing.T) {
|
||||
exercise(t, func(w io.Writer) {
|
||||
changeScrollingRegion(w, 16, 22)
|
||||
}, []byte("\x1b[16;22r"))
|
||||
})
|
||||
|
||||
t.Run("line", func(t *testing.T) {
|
||||
t.Run("clear", func(t *testing.T) {
|
||||
exercise(t, clearLine, []byte("\x1b[2K"))
|
||||
})
|
||||
|
||||
t.Run("insert", func(t *testing.T) {
|
||||
exercise(t, func(w io.Writer) {
|
||||
insertLine(w, 12)
|
||||
}, []byte("\x1b[12L"))
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("cursor", func(t *testing.T) {
|
||||
t.Run("hide", func(t *testing.T) {
|
||||
exercise(t, hideCursor, []byte("\x1b[?25l"))
|
||||
})
|
||||
|
||||
t.Run("show", func(t *testing.T) {
|
||||
exercise(t, showCursor, []byte("\x1b[?25h"))
|
||||
})
|
||||
|
||||
t.Run("up", func(t *testing.T) {
|
||||
exercise(t, cursorUp, []byte("\x1b[1A"))
|
||||
})
|
||||
|
||||
t.Run("down", func(t *testing.T) {
|
||||
exercise(t, cursorDown, []byte("\x1b[1B"))
|
||||
})
|
||||
|
||||
t.Run("move", func(t *testing.T) {
|
||||
exercise(t, func(w io.Writer) {
|
||||
moveCursor(w, 10, 20)
|
||||
}, []byte("\x1b[10;20H"))
|
||||
})
|
||||
|
||||
t.Run("back", func(t *testing.T) {
|
||||
exercise(t, func(w io.Writer) {
|
||||
cursorBack(w, 15)
|
||||
}, []byte("\x1b[15D"))
|
||||
})
|
||||
})
|
||||
}
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/muesli/ansi/compressor"
|
||||
"github.com/muesli/reflow/truncate"
|
||||
"github.com/muesli/termenv"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -24,7 +25,7 @@ const (
|
|||
// In cases where very high performance is needed the renderer can be told
|
||||
// to exclude ranges of lines, allowing them to be written to directly.
|
||||
type standardRenderer struct {
|
||||
out io.Writer
|
||||
out *termenv.Output
|
||||
buf bytes.Buffer
|
||||
queuedMessageLines []string
|
||||
framerate time.Duration
|
||||
|
@ -49,7 +50,7 @@ type standardRenderer struct {
|
|||
|
||||
// newRenderer creates a new renderer. Normally you'll want to initialize it
|
||||
// with os.Stdout as the first argument.
|
||||
func newRenderer(out io.Writer, mtx *sync.Mutex, useANSICompressor bool) renderer {
|
||||
func newRenderer(out *termenv.Output, mtx *sync.Mutex, useANSICompressor bool) renderer {
|
||||
r := &standardRenderer{
|
||||
out: out,
|
||||
mtx: mtx,
|
||||
|
@ -58,7 +59,7 @@ func newRenderer(out io.Writer, mtx *sync.Mutex, useANSICompressor bool) rendere
|
|||
queuedMessageLines: []string{},
|
||||
}
|
||||
if r.useANSICompressor {
|
||||
r.out = &compressor.Writer{Forward: out}
|
||||
r.out = termenv.NewOutput(&compressor.Writer{Forward: out})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
@ -75,13 +76,13 @@ func (r *standardRenderer) start() {
|
|||
// stop permanently halts the renderer, rendering the final frame.
|
||||
func (r *standardRenderer) stop() {
|
||||
r.flush()
|
||||
clearLine(r.out)
|
||||
r.out.ClearLine()
|
||||
r.once.Do(func() {
|
||||
close(r.done)
|
||||
})
|
||||
|
||||
if r.useANSICompressor {
|
||||
if w, ok := r.out.(io.WriteCloser); ok {
|
||||
if w, ok := r.out.TTY().(io.WriteCloser); ok {
|
||||
_ = w.Close()
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +90,7 @@ func (r *standardRenderer) stop() {
|
|||
|
||||
// kill halts the renderer. The final frame will not be rendered.
|
||||
func (r *standardRenderer) kill() {
|
||||
clearLine(r.out)
|
||||
r.out.ClearLine()
|
||||
r.once.Do(func() {
|
||||
close(r.done)
|
||||
})
|
||||
|
@ -122,7 +123,8 @@ func (r *standardRenderer) flush() {
|
|||
}
|
||||
|
||||
// Output buffer
|
||||
out := new(bytes.Buffer)
|
||||
buf := &bytes.Buffer{}
|
||||
out := termenv.NewOutput(buf)
|
||||
|
||||
newLines := strings.Split(r.buf.String(), "\n")
|
||||
numLinesThisFlush := len(newLines)
|
||||
|
@ -145,10 +147,10 @@ func (r *standardRenderer) flush() {
|
|||
if (len(newLines) <= len(oldLines)) && (len(newLines) > i && len(oldLines) > i) && (newLines[i] == oldLines[i]) {
|
||||
skipLines[i] = struct{}{}
|
||||
} else if _, exists := r.ignoreLines[i]; !exists {
|
||||
clearLine(out)
|
||||
out.ClearLine()
|
||||
}
|
||||
|
||||
cursorUp(out)
|
||||
out.CursorUp(1)
|
||||
}
|
||||
|
||||
if _, exists := r.ignoreLines[0]; !exists {
|
||||
|
@ -161,8 +163,8 @@ func (r *standardRenderer) flush() {
|
|||
// standard (whereas others are proprietary to, say, VT100/VT52).
|
||||
// If cursor previous line (ESC[ + <n> + F) were better supported
|
||||
// we could use that above to eliminate this step.
|
||||
cursorBack(out, r.width)
|
||||
clearLine(out)
|
||||
out.CursorBack(r.width)
|
||||
out.ClearLine()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -179,7 +181,7 @@ func (r *standardRenderer) flush() {
|
|||
if _, skip := skipLines[i]; skip {
|
||||
// Unless this is the last line, move the cursor down.
|
||||
if i < len(newLines)-1 {
|
||||
cursorDown(out)
|
||||
out.CursorDown(1)
|
||||
}
|
||||
} else {
|
||||
line := newLines[i]
|
||||
|
@ -195,10 +197,10 @@ func (r *standardRenderer) flush() {
|
|||
line = truncate.String(line, uint(r.width))
|
||||
}
|
||||
|
||||
_, _ = io.WriteString(out, line)
|
||||
_, _ = out.WriteString(line)
|
||||
|
||||
if i < len(newLines)-1 {
|
||||
_, _ = io.WriteString(out, "\r\n")
|
||||
_, _ = out.WriteString("\r\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -210,12 +212,12 @@ func (r *standardRenderer) flush() {
|
|||
// This case fixes a bug in macOS terminal. In other terminals the
|
||||
// other case seems to do the job regardless of whether or not we're
|
||||
// using the full terminal window.
|
||||
moveCursor(out, r.linesRendered, 0)
|
||||
out.MoveCursor(r.linesRendered, 0)
|
||||
} else {
|
||||
cursorBack(out, r.width)
|
||||
out.CursorBack(r.width)
|
||||
}
|
||||
|
||||
_, _ = r.out.Write(out.Bytes())
|
||||
_, _ = r.out.Write(buf.Bytes())
|
||||
r.lastRender = r.buf.String()
|
||||
r.buf.Reset()
|
||||
}
|
||||
|
@ -270,15 +272,17 @@ func (r *standardRenderer) setIgnoredLines(from int, to int) {
|
|||
|
||||
// Erase ignored lines
|
||||
if r.linesRendered > 0 {
|
||||
out := new(bytes.Buffer)
|
||||
buf := &bytes.Buffer{}
|
||||
out := termenv.NewOutput(buf)
|
||||
|
||||
for i := r.linesRendered - 1; i >= 0; i-- {
|
||||
if _, exists := r.ignoreLines[i]; exists {
|
||||
clearLine(out)
|
||||
out.ClearLine()
|
||||
}
|
||||
cursorUp(out)
|
||||
out.CursorUp(1)
|
||||
}
|
||||
moveCursor(out, r.linesRendered, 0) // put cursor back
|
||||
_, _ = r.out.Write(out.Bytes())
|
||||
out.MoveCursor(r.linesRendered, 0) // put cursor back
|
||||
_, _ = r.out.Write(buf.Bytes())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -311,18 +315,19 @@ func (r *standardRenderer) insertTop(lines []string, topBoundary, bottomBoundary
|
|||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
buf := &bytes.Buffer{}
|
||||
out := termenv.NewOutput(buf)
|
||||
|
||||
changeScrollingRegion(b, topBoundary, bottomBoundary)
|
||||
moveCursor(b, topBoundary, 0)
|
||||
insertLine(b, len(lines))
|
||||
_, _ = io.WriteString(b, strings.Join(lines, "\r\n"))
|
||||
changeScrollingRegion(b, 0, r.height)
|
||||
out.ChangeScrollingRegion(topBoundary, bottomBoundary)
|
||||
out.MoveCursor(topBoundary, 0)
|
||||
out.InsertLines(len(lines))
|
||||
_, _ = out.WriteString(strings.Join(lines, "\r\n"))
|
||||
out.ChangeScrollingRegion(0, r.height)
|
||||
|
||||
// Move cursor back to where the main rendering routine expects it to be
|
||||
moveCursor(b, r.linesRendered, 0)
|
||||
out.MoveCursor(r.linesRendered, 0)
|
||||
|
||||
_, _ = r.out.Write(b.Bytes())
|
||||
_, _ = r.out.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
// insertBottom effectively scrolls down. It inserts lines at the bottom of
|
||||
|
@ -338,17 +343,18 @@ func (r *standardRenderer) insertBottom(lines []string, topBoundary, bottomBound
|
|||
r.mtx.Lock()
|
||||
defer r.mtx.Unlock()
|
||||
|
||||
b := new(bytes.Buffer)
|
||||
buf := &bytes.Buffer{}
|
||||
out := termenv.NewOutput(buf)
|
||||
|
||||
changeScrollingRegion(b, topBoundary, bottomBoundary)
|
||||
moveCursor(b, bottomBoundary, 0)
|
||||
_, _ = io.WriteString(b, "\r\n"+strings.Join(lines, "\r\n"))
|
||||
changeScrollingRegion(b, 0, r.height)
|
||||
out.ChangeScrollingRegion(topBoundary, bottomBoundary)
|
||||
out.MoveCursor(bottomBoundary, 0)
|
||||
_, _ = out.WriteString("\r\n" + strings.Join(lines, "\r\n"))
|
||||
out.ChangeScrollingRegion(0, r.height)
|
||||
|
||||
// Move cursor back to where the main rendering routine expects it to be
|
||||
moveCursor(b, r.linesRendered, 0)
|
||||
out.MoveCursor(r.linesRendered, 0)
|
||||
|
||||
_, _ = r.out.Write(b.Bytes())
|
||||
_, _ = r.out.Write(buf.Bytes())
|
||||
}
|
||||
|
||||
// handleMessages handles internal messages for the renderer.
|
||||
|
|
45
tea.go
45
tea.go
|
@ -23,7 +23,7 @@ import (
|
|||
"github.com/containerd/console"
|
||||
isatty "github.com/mattn/go-isatty"
|
||||
"github.com/muesli/cancelreader"
|
||||
te "github.com/muesli/termenv"
|
||||
"github.com/muesli/termenv"
|
||||
"golang.org/x/term"
|
||||
)
|
||||
|
||||
|
@ -89,9 +89,10 @@ type Program struct {
|
|||
errs chan error
|
||||
readLoopDone chan struct{}
|
||||
|
||||
output io.Writer // where to send output. this will usually be os.Stdout.
|
||||
input io.Reader // this will usually be os.Stdin.
|
||||
cancelReader cancelreader.CancelReader
|
||||
output *termenv.Output // where to send output. this will usually be os.Stdout.
|
||||
restoreOutput func() error
|
||||
input io.Reader // this will usually be os.Stdin.
|
||||
cancelReader cancelreader.CancelReader
|
||||
|
||||
renderer renderer
|
||||
altScreenActive bool
|
||||
|
@ -253,7 +254,6 @@ func NewProgram(model Model, opts ...ProgramOption) *Program {
|
|||
p := &Program{
|
||||
mtx: &sync.Mutex{},
|
||||
initialModel: model,
|
||||
output: os.Stdout,
|
||||
input: os.Stdin,
|
||||
msgs: make(chan Msg),
|
||||
CatchPanics: true,
|
||||
|
@ -265,6 +265,16 @@ func NewProgram(model Model, opts ...ProgramOption) *Program {
|
|||
opt(p)
|
||||
}
|
||||
|
||||
// if no output was set, set it to stdout
|
||||
if p.output == nil {
|
||||
p.output = termenv.DefaultOutput()
|
||||
|
||||
// cache detected color values
|
||||
termenv.WithColorCache(true)(p.output)
|
||||
}
|
||||
|
||||
p.restoreOutput, _ = termenv.EnableVirtualTerminalProcessing(p.output)
|
||||
|
||||
return p
|
||||
}
|
||||
|
||||
|
@ -429,7 +439,7 @@ func (p *Program) StartReturningModel() (Model, error) {
|
|||
}
|
||||
defer p.cancelReader.Close() // nolint:errcheck
|
||||
|
||||
if f, ok := p.output.(*os.File); ok && isatty.IsTerminal(f.Fd()) {
|
||||
if f, ok := p.output.TTY().(*os.File); ok && isatty.IsTerminal(f.Fd()) {
|
||||
// Get the initial terminal size and send it to the program.
|
||||
go func() {
|
||||
w, h, err := term.GetSize(int(f.Fd()))
|
||||
|
@ -527,7 +537,7 @@ func (p *Program) StartReturningModel() (Model, error) {
|
|||
p.DisableMouseAllMotion()
|
||||
|
||||
case hideCursorMsg:
|
||||
hideCursor(p.output)
|
||||
p.output.HideCursor()
|
||||
|
||||
case execMsg:
|
||||
// NB: this blocks.
|
||||
|
@ -606,6 +616,10 @@ func (p *Program) shutdown(kill bool) {
|
|||
p.DisableMouseCellMotion()
|
||||
p.DisableMouseAllMotion()
|
||||
_ = p.restoreTerminalState()
|
||||
|
||||
if p.restoreOutput != nil {
|
||||
_ = p.restoreOutput()
|
||||
}
|
||||
}
|
||||
|
||||
// EnterAltScreen enters the alternate screen buffer, which consumes the entire
|
||||
|
@ -620,7 +634,8 @@ func (p *Program) EnterAltScreen() {
|
|||
return
|
||||
}
|
||||
|
||||
enterAltScreen(p.output)
|
||||
p.output.AltScreen()
|
||||
p.output.MoveCursor(0, 0)
|
||||
|
||||
p.altScreenActive = true
|
||||
if p.renderer != nil {
|
||||
|
@ -639,7 +654,7 @@ func (p *Program) ExitAltScreen() {
|
|||
return
|
||||
}
|
||||
|
||||
exitAltScreen(p.output)
|
||||
p.output.ExitAltScreen()
|
||||
|
||||
p.altScreenActive = false
|
||||
if p.renderer != nil {
|
||||
|
@ -654,7 +669,8 @@ func (p *Program) ExitAltScreen() {
|
|||
func (p *Program) EnableMouseCellMotion() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
fmt.Fprintf(p.output, te.CSI+te.EnableMouseCellMotionSeq)
|
||||
|
||||
p.output.EnableMouseCellMotion()
|
||||
}
|
||||
|
||||
// DisableMouseCellMotion disables Mouse Cell Motion tracking. This will be
|
||||
|
@ -664,7 +680,8 @@ func (p *Program) EnableMouseCellMotion() {
|
|||
func (p *Program) DisableMouseCellMotion() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
fmt.Fprintf(p.output, te.CSI+te.DisableMouseCellMotionSeq)
|
||||
|
||||
p.output.DisableMouseCellMotion()
|
||||
}
|
||||
|
||||
// EnableMouseAllMotion enables mouse click, release, wheel and motion events,
|
||||
|
@ -675,7 +692,8 @@ func (p *Program) DisableMouseCellMotion() {
|
|||
func (p *Program) EnableMouseAllMotion() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
fmt.Fprintf(p.output, te.CSI+te.EnableMouseAllMotionSeq)
|
||||
|
||||
p.output.EnableMouseAllMotion()
|
||||
}
|
||||
|
||||
// DisableMouseAllMotion disables All Motion mouse tracking. This will be
|
||||
|
@ -685,7 +703,8 @@ func (p *Program) EnableMouseAllMotion() {
|
|||
func (p *Program) DisableMouseAllMotion() {
|
||||
p.mtx.Lock()
|
||||
defer p.mtx.Unlock()
|
||||
fmt.Fprintf(p.output, te.CSI+te.DisableMouseAllMotionSeq)
|
||||
|
||||
p.output.DisableMouseAllMotion()
|
||||
}
|
||||
|
||||
// ReleaseTerminal restores the original terminal state and cancels the input
|
||||
|
|
4
tty.go
4
tty.go
|
@ -20,14 +20,14 @@ func (p *Program) initTerminal() error {
|
|||
}
|
||||
}
|
||||
|
||||
hideCursor(p.output)
|
||||
p.output.HideCursor()
|
||||
return nil
|
||||
}
|
||||
|
||||
// restoreTerminalState restores the terminal to the state prior to running the
|
||||
// Bubble Tea program.
|
||||
func (p Program) restoreTerminalState() error {
|
||||
showCursor(p.output)
|
||||
p.output.ShowCursor()
|
||||
|
||||
if p.console != nil {
|
||||
err := p.console.Reset()
|
||||
|
|
|
@ -4,11 +4,9 @@
|
|||
package tea
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
|
||||
"github.com/containerd/console"
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func (p *Program) initInput() error {
|
||||
|
@ -26,8 +24,6 @@ func (p *Program) initInput() error {
|
|||
p.console = c
|
||||
}
|
||||
|
||||
enableAnsiColors(p.output)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -49,18 +45,3 @@ func openInputTTY() (*os.File, error) {
|
|||
}
|
||||
return f, nil
|
||||
}
|
||||
|
||||
// enableAnsiColors enables support for ANSI color sequences in Windows
|
||||
// default console. Note that this only works with Windows 10.
|
||||
func enableAnsiColors(w io.Writer) {
|
||||
f, ok := w.(*os.File)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
stdout := windows.Handle(f.Fd())
|
||||
var originalMode uint32
|
||||
|
||||
_ = windows.GetConsoleMode(stdout, &originalMode)
|
||||
_ = windows.SetConsoleMode(stdout, originalMode|windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING)
|
||||
}
|
||||
|
|
|
@ -1,29 +1,30 @@
|
|||
github.com/aymanbagabas/go-osc52 v1.0.3 h1:DTwqENW7X9arYimJrPeGZcV0ln14sGMt3pHZspWD+Mg=
|
||||
github.com/aymanbagabas/go-osc52 v1.0.3/go.mod h1:zT8H+Rk4VSabYN90pWyugflM3ZhpTZNC7cASDfUCdT4=
|
||||
github.com/containerd/console v1.0.3 h1:lIr7SlA5PxZyMV30bDW0MGbiOPXwc63yRuCP0ARubLw=
|
||||
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||
github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
|
||||
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
||||
github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
||||
github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU=
|
||||
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/mattn/go-runewidth v0.0.14 h1:+xnbZSEeDbOIg5/mE6JF0w6n9duR1l3/WmbinWVwUuU=
|
||||
github.com/mattn/go-runewidth v0.0.14/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b h1:1XF24mVaiu7u+CFywTdcDo2ie1pzzhwjt6RHqzpMU34=
|
||||
github.com/muesli/ansi v0.0.0-20211018074035-2e021307bc4b/go.mod h1:fQuZ0gauxyBcmsdE3ZT4NasjaRdxmbCS0jRHsrWu3Ho=
|
||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
||||
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739 h1:QANkGiGr39l1EESqrE0gZw0/AJNYzIvoGLhIoVYtluI=
|
||||
github.com/muesli/termenv v0.11.1-0.20220212125758-44cd13922739/go.mod h1:Bd5NYQ7pd+SrtBSrSNoBBmXlcY8+Xj4BMJgh8qcZrvs=
|
||||
github.com/muesli/termenv v0.13.0 h1:wK20DRpJdDX8b7Ek2QfhvqhRQFZ237RGRO0RQ/Iqdy0=
|
||||
github.com/muesli/termenv v0.13.0/go.mod h1:sP1+uffeLaEYpyOTb8pLCUctGcGLnoFjSn4YJK5e2bc=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
|
||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab h1:2QkjZIsXupsJbJIdSjjUOgWK3aEtzyuh2mPt3l/CkeU=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
|
|
Loading…
Reference in New Issue