1package ansi
2
3import (
4 "bytes"
5 "strconv"
6)
7
8func findSafeScroll(frame CellSource, damage []Damage) (Damage, bool) {
9 for _, d := range damage {
10 if d.Kind == DamageScrollUp && isSafeScroll(frame, d) {
11 return d, true
12 }
13 }
14 return Damage{}, false
15}
16
17func isSafeScroll(frame CellSource, d Damage) bool {
18 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
19}
20
21func emitScrollUp(out *bytes.Buffer, d Damage) {
22 out.WriteString("\x1b[0m")
23 // \x1b[%d;%dr — set scroll region
24 out.WriteString("\x1b[")
25 var b [16]byte
26 n := strconv.AppendInt(b[:0], int64(d.Y+1), 10)
27 out.Write(n)
28 out.WriteByte(';')
29 n = strconv.AppendInt(b[:0], int64(d.Y+d.Height), 10)
30 out.Write(n)
31 out.WriteByte('r')
32 // Position cursor at bottom of scroll region.
33 writeCursor(out, d.Y+d.Height-1, 0)
34 if d.Count == 1 {
35 out.WriteByte('\n')
36 } else {
37 out.WriteString("\x1b[")
38 n = strconv.AppendInt(b[:0], int64(d.Count), 10)
39 out.Write(n)
40 out.WriteByte('S')
41 }
42 out.WriteString("\x1b[r")
43}
44
45func canApplyScrollAgainst(frame CellSource, scroll Damage, damage []Damage, committed Frame) bool {
46 switch frame := frame.(type) {
47 case Frame:
48 return canApplyDenseScrollAgainst(frame, scroll, damage, committed)
49 case *Frame:
50 return canApplyDenseScrollAgainst(*frame, scroll, damage, committed)
51 }
52 for y := scroll.Y; y < scroll.Y+scroll.Height-scroll.Count; y++ {
53 for x := range scroll.Width {
54 column := scroll.X + x
55 committedCell, frameCell := committed.Cell(column, y+scroll.Count), frame.Cell(column, y)
56 if committedCell == frameCell || committedCell.Equal(frameCell) {
57 continue
58 }
59 if !damageCoversCell(damage, column, y) {
60 return false
61 }
62 }
63 }
64 return true
65}
66
67func canApplyDenseScrollAgainst(frame Frame, scroll Damage, damage []Damage, committed Frame) bool {
68 for y := scroll.Y; y < scroll.Y+scroll.Height-scroll.Count; y++ {
69 for x := range scroll.Width {
70 column := scroll.X + x
71 committedCell, frameCell := committed.At(column, y+scroll.Count), frame.At(column, y)
72 if committedCell == frameCell || committedCell.Equal(frameCell) {
73 continue
74 }
75 if !damageCoversCell(damage, column, y) {
76 return false
77 }
78 }
79 }
80 return true
81}
82
83func damageCoversCell(damage []Damage, x, y int) bool {
84 for _, d := range damage {
85 if (d.Kind == DamageText || d.Kind == DamageClear) && x >= d.X && x < d.X+d.Width && y >= d.Y && y < d.Y+d.Height {
86 return true
87 }
88 }
89 return false
90}