package ansi import ( "bytes" "strconv" ) func findSafeScroll(frame CellSource, damage []Damage) (Damage, bool) { for _, d := range damage { if d.Kind == DamageScrollUp && isSafeScroll(frame, d) { return d, true } } return Damage{}, false } func isSafeScroll(frame CellSource, d Damage) bool { return d.X == 0 && d.Width == frame.Columns() && d.Y >= 0 && d.Y <= frame.Rows() && d.Height > 0 && d.Height <= frame.Rows()-d.Y && d.Count > 0 && d.Count < d.Height } func emitScrollUp(out *bytes.Buffer, d Damage) { out.WriteString("\x1b[0m") // \x1b[%d;%dr — set scroll region out.WriteString("\x1b[") var b [16]byte n := strconv.AppendInt(b[:0], int64(d.Y+1), 10) out.Write(n) out.WriteByte(';') n = strconv.AppendInt(b[:0], int64(d.Y+d.Height), 10) out.Write(n) out.WriteByte('r') // Position cursor at bottom of scroll region. writeCursor(out, d.Y+d.Height-1, 0) if d.Count == 1 { out.WriteByte('\n') } else { out.WriteString("\x1b[") n = strconv.AppendInt(b[:0], int64(d.Count), 10) out.Write(n) out.WriteByte('S') } out.WriteString("\x1b[r") } func canApplyScrollAgainst(frame CellSource, scroll Damage, damage []Damage, committed Frame) bool { switch frame := frame.(type) { case Frame: return canApplyDenseScrollAgainst(frame, scroll, damage, committed) case *Frame: return canApplyDenseScrollAgainst(*frame, scroll, damage, committed) } for y := scroll.Y; y < scroll.Y+scroll.Height-scroll.Count; y++ { for x := range scroll.Width { column := scroll.X + x committedCell, frameCell := committed.Cell(column, y+scroll.Count), frame.Cell(column, y) if committedCell == frameCell || committedCell.Equal(frameCell) { continue } if !damageCoversCell(damage, column, y) { return false } } } return true } func canApplyDenseScrollAgainst(frame Frame, scroll Damage, damage []Damage, committed Frame) bool { for y := scroll.Y; y < scroll.Y+scroll.Height-scroll.Count; y++ { for x := range scroll.Width { column := scroll.X + x committedCell, frameCell := committed.At(column, y+scroll.Count), frame.At(column, y) if committedCell == frameCell || committedCell.Equal(frameCell) { continue } if !damageCoversCell(damage, column, y) { return false } } } return true } func damageCoversCell(damage []Damage, x, y int) bool { for _, d := range damage { if (d.Kind == DamageText || d.Kind == DamageClear) && x >= d.X && x < d.X+d.Width && y >= d.Y && y < d.Y+d.Height { return true } } return false }