terminal: Do not write unchanged status lines

Check if each line of status is changed, and write
the line to the terminal only if it has changed
This commit is contained in:
Donggyu Kim
2026-05-14 10:42:13 +02:00
committed by Michael Eischer
parent ff575a978d
commit e33bcede2f
5 changed files with 83 additions and 5 deletions
+31 -5
View File
@@ -40,6 +40,7 @@ type Terminal struct {
clearCurrentLine func(io.Writer, uintptr) error
moveCursorUp func(io.Writer, uintptr, int) error
moveCursorDown func(io.Writer, uintptr, int) error
}
type message struct {
@@ -123,6 +124,7 @@ func New(rd io.ReadCloser, wr io.Writer, errWriter io.Writer, disableStatus bool
t.fd = d.Fd()
t.clearCurrentLine = terminal.ClearCurrentLine(t.fd)
t.moveCursorUp = terminal.MoveCursorUp(t.fd)
t.moveCursorDown = terminal.MoveCursorDown(t.fd)
}
if terminal.OutputIsTerminal(d.Fd()) {
t.outputIsTerminal = true
@@ -202,6 +204,18 @@ func (t *Terminal) Run(ctx context.Context) {
t.runWithoutStatus(ctx)
}
func findUnchangedLines(curr, last []string) []bool {
unchanged := make([]bool, len(curr))
for i := range min(len(curr), len(last)) {
if curr[i] == last[i] {
unchanged[i] = true
}
}
return unchanged
}
// run listens on the channels and updates the terminal screen.
func (t *Terminal) run(ctx context.Context) {
var status []string
@@ -210,7 +224,7 @@ func (t *Terminal) run(ctx context.Context) {
select {
case <-ctx.Done():
if !terminal.IsProcessBackground(t.fd) {
t.writeStatus([]string{})
t.writeStatus([]string{}, nil)
}
return
@@ -241,7 +255,7 @@ func (t *Terminal) run(ctx context.Context) {
continue
}
t.writeStatus(status)
t.writeStatus(status, nil)
lastWrittenStatus = append([]string{}, status...)
case stat := <-t.status:
status = append(status[:0], stat.lines...)
@@ -252,7 +266,8 @@ func (t *Terminal) run(ctx context.Context) {
}
if !slices.Equal(status, lastWrittenStatus) {
t.writeStatus(status)
unchangedLines := findUnchangedLines(status, lastWrittenStatus)
t.writeStatus(status, unchangedLines)
// Copy the status slice to avoid aliasing
lastWrittenStatus = append([]string{}, status...)
}
@@ -260,7 +275,7 @@ func (t *Terminal) run(ctx context.Context) {
}
}
func (t *Terminal) writeStatus(status []string) {
func (t *Terminal) writeStatus(status []string, unchanged []bool) {
statusLen := len(status)
status = append([]string{}, status...)
for i := len(status); i < t.lastStatusLen; i++ {
@@ -273,7 +288,18 @@ func (t *Terminal) writeStatus(status []string) {
}
t.lastStatusLen = statusLen
for _, line := range status {
for i, line := range status {
if unchanged != nil && i < len(unchanged) && unchanged[i] {
// don't write unchanged lines every frame
if i < len(status)-1 {
// just move the cursor down to the next line
if err := t.moveCursorDown(t.wr, t.fd, 1); err != nil {
_, _ = fmt.Fprintf(t.errWriter, "write failed: %v\n", err)
}
}
continue
}
if err := t.clearCurrentLine(t.wr, t.fd); err != nil {
_, _ = fmt.Fprintf(t.errWriter, "write failed: %v\n", err)
}