fix: use /dev/tty for width autodetect; default config width: 0

This commit is contained in:
Heiko Schlittermann (HS12-RIPE) 2026-05-30 15:06:21 +02:00
commit ec374d7d06
3 changed files with 69 additions and 6 deletions

View file

@ -19,8 +19,8 @@ style: "auto"
mouse: false
# use pager to display markdown
pager: false
# word-wrap at width
width: 80
# word-wrap at width (0 = autodetect terminal width)
width: 0
# show all files, including hidden and ignored.
all: false
`

View file

@ -1,6 +1,7 @@
package main
import (
"os"
"testing"
)
@ -39,3 +40,39 @@ func TestGlowFlags(t *testing.T) {
}
}
}
// TestTerminalWidthFromFdRejectsPipe ensures that a pipe (the canonical
// non-tty fd type) is reported as "not a terminal" rather than e.g.
// returning a stale or zero width as success. This is the regression we
// care about for `glow x.md | less`: the pager's pipe must not be
// mistaken for a terminal.
func TestTerminalWidthFromFdRejectsPipe(t *testing.T) {
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("os.Pipe: %v", err)
}
t.Cleanup(func() {
_ = r.Close()
_ = w.Close()
})
for name, fd := range map[string]uintptr{"read end": r.Fd(), "write end": w.Fd()} {
if gotW, ok := terminalWidthFromFd(fd); ok {
t.Errorf("%s: expected (_, false), got (%d, true)", name, gotW)
}
}
}
// TestTerminalWidthFromFdRejectsRegularFile guards the other common
// non-tty case: stdout redirected to a file (`glow x.md > out`).
func TestTerminalWidthFromFdRejectsRegularFile(t *testing.T) {
f, err := os.CreateTemp(t.TempDir(), "glow-width-*")
if err != nil {
t.Fatalf("CreateTemp: %v", err)
}
t.Cleanup(func() { _ = f.Close() })
if gotW, ok := terminalWidthFromFd(f.Fd()); ok {
t.Errorf("regular file: expected (_, false), got (%d, true)", gotW)
}
}

34
main.go
View file

@ -191,12 +191,17 @@ func validateOptions(cmd *cobra.Command) error {
style = "notty"
}
// Detect terminal width
// Detect terminal width. Prefer /dev/tty so that `glow <x.md` and
// `glow x.md | less` still get the real terminal size; fall back to
// stdout (the original behaviour) when /dev/tty isn't available.
if !cmd.Flags().Changed("width") { //nolint:nestif
if isTerminal && width == 0 {
w, _, err := term.GetSize(int(os.Stdout.Fd()))
if err == nil {
if width == 0 {
if w, ok := terminalWidthFromTTY(); ok {
width = uint(w) //nolint:gosec
} else if isTerminal {
if w, ok := terminalWidthFromFd(os.Stdout.Fd()); ok {
width = uint(w) //nolint:gosec
}
}
if width > 120 {
@ -210,6 +215,27 @@ func validateOptions(cmd *cobra.Command) error {
return nil
}
// terminalWidthFromTTY reads the controlling terminal's width via
// /dev/tty, so pipes and stdin redirection don't fool the autodetector.
func terminalWidthFromTTY() (int, bool) {
tty, err := os.Open("/dev/tty")
if err != nil {
return 0, false
}
defer tty.Close() //nolint:errcheck
return terminalWidthFromFd(tty.Fd())
}
// terminalWidthFromFd returns the terminal width for the given fd, or
// (0, false) when fd is not a terminal or the ioctl fails.
func terminalWidthFromFd(fd uintptr) (int, bool) {
w, _, err := term.GetSize(int(fd)) //nolint:gosec
if err != nil || w <= 0 {
return 0, false
}
return w, true
}
func stdinIsPipe() (bool, error) {
stat, err := os.Stdin.Stat()
if err != nil {