Skip to content

Commit

Permalink
fix: enable OPOST when initializing the terminal
Browse files Browse the repository at this point in the history
Before switching to golang/term for terminal handling, we used
containerd/console. Which by default, uses Termios OPOST when setting
the terminal to raw mode. This is needed because we need to post process
output to render the terminal correctly.

This also switches to using charmbracelet/x/exp/term since golang/term
doesn't allow setting a custom Termios settings.

Fixes: #1000
Fixes: 4624106 (feat: reduce console/term dependencies (#897))
  • Loading branch information
aymanbagabas committed May 8, 2024
1 parent 556cdde commit 359c1a5
Show file tree
Hide file tree
Showing 6 changed files with 43 additions and 22 deletions.
7 changes: 4 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,23 @@ module github.com/charmbracelet/bubbletea
go 1.18

require (
github.com/charmbracelet/x/exp/term v0.0.0-20240507171223-71e9351b56e7
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f
github.com/mattn/go-localereader v0.0.1
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6
github.com/muesli/cancelreader v0.2.2
github.com/muesli/reflow v0.3.0
github.com/muesli/termenv v0.15.2
golang.org/x/sync v0.7.0
golang.org/x/sys v0.19.0
golang.org/x/term v0.19.0
golang.org/x/sys v0.20.0
)

require (
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/mattn/go-isatty v0.0.18 // indirect
github.com/mattn/go-runewidth v0.0.15 // indirect
github.com/rivo/uniseg v0.4.6 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
golang.org/x/text v0.3.8 // indirect
)
15 changes: 9 additions & 6 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/charmbracelet/x/exp/term v0.0.0-20240507171223-71e9351b56e7 h1:ATeHxDzJnkCWPCNhTPZUMxyD7AE94ATJDKHN3wZNRUY=
github.com/charmbracelet/x/exp/term v0.0.0-20240507171223-71e9351b56e7/go.mod h1:qeR6w1zITbkF7vEhcx0CqX5GfnIiQloJWQghN6HfP+c=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
Expand All @@ -21,15 +23,16 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg=
github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
golang.org/x/exp v0.0.0-20220909182711-5c715a9e8561 h1:MDc5xs78ZrZr3HMQugiXOAkSZtfTpbJLDr/lwfgO53E=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.19.0 h1:+ThwsDv+tYfnJFhF4L8jITxu1tdTWRTZpdsWgEgjL6Q=
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.3.8 h1:nAL+RVCQ9uMn3vJZbV+MRnydTJFPf8qqY42YiA6MrqY=
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
6 changes: 3 additions & 3 deletions tea.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ import (
"sync/atomic"
"syscall"

"github.com/charmbracelet/x/exp/term"
"github.com/muesli/cancelreader"
"github.com/muesli/termenv"
"golang.org/x/sync/errgroup"
"golang.org/x/term"
)

// ErrProgramKilled is returned by [Program.Run] when the program got killed.
Expand Down Expand Up @@ -249,7 +249,7 @@ func (p *Program) handleSignals() chan struct{} {
func (p *Program) handleResize() chan struct{} {
ch := make(chan struct{})

if f, ok := p.output.TTY().(*os.File); ok && term.IsTerminal(int(f.Fd())) {
if f, ok := p.output.TTY().(*os.File); ok && term.IsTerminal(f.Fd()) {
// Get the initial terminal size and send it to the program.
go p.checkResize()

Expand Down Expand Up @@ -441,7 +441,7 @@ func (p *Program) Run() (Model, error) {
if !isFile {
break
}
if term.IsTerminal(int(f.Fd())) {
if term.IsTerminal(f.Fd()) {
break
}

Expand Down
8 changes: 4 additions & 4 deletions tty.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ import (
"os"
"time"

"github.com/charmbracelet/x/exp/term"
"github.com/muesli/cancelreader"
"golang.org/x/term"
)

func (p *Program) initTerminal() error {
Expand Down Expand Up @@ -42,7 +42,7 @@ func (p *Program) restoreTerminalState() error {
// restoreInput restores the tty input to its original state.
func (p *Program) restoreInput() error {
if p.tty != nil && p.previousTtyState != nil {
if err := term.Restore(int(p.tty.Fd()), p.previousTtyState); err != nil {
if err := term.Restore(p.tty.Fd(), p.previousTtyState); err != nil {
return fmt.Errorf("error restoring console: %w", err)
}
}
Expand Down Expand Up @@ -90,12 +90,12 @@ func (p *Program) waitForReadLoop() {
// via a WindowSizeMsg.
func (p *Program) checkResize() {
f, ok := p.output.TTY().(*os.File)
if !ok || !term.IsTerminal(int(f.Fd())) {
if !ok || !term.IsTerminal(f.Fd()) {
// can't query window size
return
}

w, h, err := term.GetSize(int(f.Fd()))
w, h, err := term.GetSize(f.Fd())
if err != nil {
select {
case <-p.ctx.Done():
Expand Down
23 changes: 20 additions & 3 deletions tty_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,34 @@ import (
"fmt"
"os"

"golang.org/x/term"
"github.com/charmbracelet/x/exp/term"
"golang.org/x/sys/unix"
)

func (p *Program) initInput() (err error) {
// Check if input is a terminal
if f, ok := p.input.(*os.File); ok && term.IsTerminal(int(f.Fd())) {
if f, ok := p.input.(*os.File); ok && term.IsTerminal(f.Fd()) {
p.tty = f
p.previousTtyState, err = term.MakeRaw(int(p.tty.Fd()))
p.previousTtyState, err = term.GetState(p.tty.Fd())
if err != nil {
return fmt.Errorf("error entering raw mode: %w", err)
}

state := &term.State{}
state.Termios = p.previousTtyState.Termios

// XXX: We set the terminal to raw mode + OPOST (output processing) here.
state.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON
state.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN
state.Cflag &^= unix.CSIZE | unix.PARENB
state.Oflag |= unix.OPOST
state.Cflag |= unix.CS8
state.Cc[unix.VMIN] = 1
state.Cc[unix.VTIME] = 0

if err := term.SetState(p.tty.Fd(), state); err != nil {
return fmt.Errorf("error setting terminal state: %w", err)
}
}

return nil
Expand Down
6 changes: 3 additions & 3 deletions tty_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,17 @@ import (
"fmt"
"os"

"github.com/charmbracelet/x/exp/term"
"golang.org/x/sys/windows"
"golang.org/x/term"
)

func (p *Program) initInput() (err error) {
// Save stdin state and enable VT input
// We enable VT processing using Termenv, but we also need to enable VT
// input here.
if f, ok := p.input.(*os.File); ok && term.IsTerminal(int(f.Fd())) {
if f, ok := p.input.(*os.File); ok && term.IsTerminal(f.Fd()) {
p.tty = f
p.previousTtyState, err = term.MakeRaw(int(p.tty.Fd()))
p.previousTtyState, err = term.MakeRaw(p.tty.Fd())
if err != nil {
return err
}
Expand Down

0 comments on commit 359c1a5

Please sign in to comment.