terminal: use exact window size for pager (#3249)

Instead of using a fixed 100x30 window size query the operating system
for the exact size, also fixes a bug where the last line before calling
the pager is repeated twice.
This commit is contained in:
Alessandro Arzilli
2023-01-16 18:20:43 +01:00
committed by GitHub
parent 58fc3931e8
commit 2be9cf1fab
3 changed files with 79 additions and 11 deletions

View File

@ -100,6 +100,8 @@ type pagingWriter struct {
pager string
lastnl bool
cancel func()
lines, columns int
}
type pagingWriterMode uint8
@ -108,9 +110,6 @@ const (
pagingWriterNormal pagingWriterMode = iota
pagingWriterMaybe
pagingWriterPaging
pagingWriterMaxLines = 30
pagingWriterColsPerLine = 100
)
func (w *pagingWriter) Write(p []byte) (nn int, err error) {
@ -141,7 +140,7 @@ func (w *pagingWriter) Write(p []byte) (nn int, err error) {
w.cmdStdin.Write(w.buf)
w.buf = nil
w.mode = pagingWriterPaging
return w.cmdStdin.Write(p)
return len(p), nil
} else {
if len(p) > 0 {
w.lastnl = p[len(p)-1] == '\n'
@ -202,17 +201,17 @@ func (w *pagingWriter) PageMaybe(cancel func()) {
}
w.lastnl = true
w.cancel = cancel
w.getWindowSize()
}
func (w *pagingWriter) largeOutput() bool {
if len(w.buf) > pagingWriterMaxLines*pagingWriterColsPerLine {
return true
}
nl := 0
lines := 0
lineStart := 0
for i := range w.buf {
if w.buf[i] == '\n' {
nl++
if nl > pagingWriterMaxLines {
if i-lineStart > w.columns || w.buf[i] == '\n' {
lineStart = i
lines++
if lines > w.lines {
return true
}
}

25
pkg/terminal/out_unix.go Normal file
View File

@ -0,0 +1,25 @@
//go:build linux || darwin || freebsd
// +build linux darwin freebsd
package terminal
import (
"syscall"
"unsafe"
)
type winSize struct {
row, col uint16
xpixel, ypixel uint16
}
func (w *pagingWriter) getWindowSize() {
var ws winSize
ok, _, _ := syscall.Syscall(syscall.SYS_IOCTL, uintptr(syscall.Stdout), syscall.TIOCGWINSZ, uintptr(unsafe.Pointer(&ws)))
if int(ok) < 0 {
w.mode = pagingWriterNormal
return
}
w.lines = int(ws.row)
w.columns = int(ws.col)
}

View File

@ -0,0 +1,44 @@
package terminal
import (
"syscall"
"unsafe"
)
var (
kernel32 = syscall.NewLazyDLL("kernel32.dll")
procGetStdHandle = kernel32.NewProc("GetStdHandle")
procGetConsoleScreenBufferInfo = kernel32.NewProc("GetConsoleScreenBufferInfo")
)
type coord struct {
x, y int16
}
type smallRect struct {
left, top, right, bottom int16
}
type consoleScreenBufferInfo struct {
dwSize coord
dwCursorPosition coord
wAttributes int16
srWindow smallRect
dwMaximumWindowSize coord
}
func (w *pagingWriter) getWindowSize() {
hout, _, err := procGetStdHandle.Call(uintptr(uint32(-12 & 0xFFFFFFFF))) // stdout handle
if err != syscall.Errno(0) {
w.mode = pagingWriterNormal
return
}
var sbi consoleScreenBufferInfo
_, _, err = procGetConsoleScreenBufferInfo.Call(uintptr(hout), uintptr(unsafe.Pointer(&sbi)))
if err != syscall.Errno(0) {
w.mode = pagingWriterNormal
return
}
w.columns = int(sbi.srWindow.right - sbi.srWindow.left + 1)
w.lines = int(sbi.srWindow.bottom - sbi.srWindow.top + 1)
}