okami.mom/ultraviolet
git clone https://git.nkp.pet/okami.mom/ultraviolet
13b333e
upstream
1package uv 2 3import ( 4"strings" 5 6"github.com/charmbracelet/x/ansi" 7) 8 9// scrollOptimize optimizes the screen to transform the old buffer into the new 10// buffer. 11func (s * TerminalRenderer )scrollOptimize (newbuf * RenderBuffer ) { 12height := newbuf .Height () 13if s .oldnum == nil || len (s .oldnum )< height { 14s .oldnum = append (s .oldnum ,make ([]int ,height - len (s .oldnum ))... ) 15 } 16 17// Calculate the indices 18s .updateHashmap (newbuf ) 19if len (s .hashtab )< height { 20return 21 } 22 23// Pass 1 - from top to bottom scrolling up 24for i := 0 ;i < height ; { 25for i < height && (s .oldnum [i ]== newIndex || s .oldnum [i ]<= i ) { 26i ++ 27 } 28if i >= height { 29break 30 } 31 32shift := s .oldnum [i ]- i // shift > 0 33start := i 34 35i ++ 36for i < height && s .oldnum [i ]!= newIndex && s .oldnum [i ]- i == shift { 37i ++ 38 } 39end := i - 1 + shift 40 41if ! s .scrolln (newbuf ,shift ,start ,end ,height - 1 ) { 42continue 43 } 44 } 45 46// Pass 2 - from bottom to top scrolling down 47for i := height - 1 ;i >= 0 ; { 48for i >= 0 && (s .oldnum [i ]== newIndex || s .oldnum [i ]>= i ) { 49i -- 50 } 51if i < 0 { 52break 53 } 54 55shift := s .oldnum [i ]- i // shift < 0 56end := i 57 58i -- 59for i >= 0 && s .oldnum [i ]!= newIndex && s .oldnum [i ]- i == shift { 60i -- 61 } 62 63start := i + 1 - (- shift ) 64if ! s .scrolln (newbuf ,shift ,start ,end ,height - 1 ) { 65continue 66 } 67 } 68} 69 70// scrolln scrolls the screen up by n lines. 71func (s * TerminalRenderer )scrolln (newbuf * RenderBuffer ,n ,top ,bot ,maxY int ) (v bool ) {//nolint:unparam 72blank := s .clearBlank () 73if n > 0 {//nolint:nestif 74// Scroll up (forward) 75v = s .scrollUp (newbuf ,n ,top ,bot ,0 ,maxY ,blank ) 76if ! v { 77s .buf .WriteString (ansi .SetTopBottomMargins (top + 1 ,bot + 1 )) 78 79// XXX: How should we handle this in inline mode when not using alternate screen? 80s .cur .X ,s .cur .Y = - 1 ,- 1 81v = s .scrollUp (newbuf ,n ,top ,bot ,top ,bot ,blank ) 82s .buf .WriteString (ansi .SetTopBottomMargins (1 ,maxY + 1 )) 83s .cur .X ,s .cur .Y = - 1 ,- 1 84 } 85 86if ! v { 87v = s .scrollIdl (newbuf ,n ,top ,bot - n + 1 ,blank ) 88 } 89 }else if n < 0 { 90// Scroll down (backward) 91v = s .scrollDown (newbuf ,- n ,top ,bot ,0 ,maxY ,blank ) 92if ! v { 93s .buf .WriteString (ansi .SetTopBottomMargins (top + 1 ,bot + 1 )) 94 95// XXX: How should we handle this in inline mode when not using alternate screen? 96s .cur .X ,s .cur .Y = - 1 ,- 1 97v = s .scrollDown (newbuf ,- n ,top ,bot ,top ,bot ,blank ) 98s .buf .WriteString (ansi .SetTopBottomMargins (1 ,maxY + 1 )) 99s .cur .X ,s .cur .Y = - 1 ,- 1 100 101if ! v { 102v = s .scrollIdl (newbuf ,- n ,bot + n + 1 ,top ,blank ) 103 } 104 } 105 } 106 107if ! v { 108return false 109 } 110 111s .scrollBuffer (s .curbuf ,n ,top ,bot ,blank ) 112 113// Every row in the range moved, whether or not the application drew into 114// it. A row the scroll already put in the right place costs a comparison 115// and no output, which is the whole point of scrolling in hardware. 116s .damage (top ,bot - top + 1 ) 117 118// shift hash values too, they can be reused 119s .scrollOldhash (n ,top ,bot ) 120 121return true 122} 123 124// scrollBuffer scrolls the buffer by n lines. 125func (s * TerminalRenderer )scrollBuffer (b * RenderBuffer ,n ,top ,bot int ,blank * Cell ) { 126if top < 0 || bot < top || bot >= b .Height () { 127// Nothing to scroll 128return 129 } 130 131if n < 0 { 132// shift n lines downwards 133limit := top - n 134for line := bot ;line >= limit && line >= 0 && line >= top ;line -- { 135copy (b .Lines [line ],b .Lines [line + n ]) 136 } 137for line := top ;line < limit && line <= b .Height ()- 1 && line <= bot ;line ++ { 138b .FillArea (blank ,Rect (0 ,line ,b .Width (),1 )) 139 } 140 } 141 142if n > 0 { 143// shift n lines upwards 144limit := bot - n 145for line := top ;line <= limit && line <= b .Height ()- 1 && line <= bot ;line ++ { 146copy (b .Lines [line ],b .Lines [line + n ]) 147 } 148for line := bot ;line > limit && line >= 0 && line >= top ;line -- { 149b .FillArea (blank ,Rect (0 ,line ,b .Width (),1 )) 150 } 151 } 152} 153 154// scrollUp scrolls the screen up by n lines. 155func (s * TerminalRenderer )scrollUp (newbuf * RenderBuffer ,n ,top ,bot ,minY ,maxY int ,blank * Cell )bool { 156if n == 1 && top == minY && bot == maxY {//nolint:nestif 157s .move (newbuf ,0 ,bot ) 158s .updatePen (blank ) 159s .buf .WriteByte ('\n' ) 160 }else if n == 1 && bot == maxY { 161s .move (newbuf ,0 ,top ) 162s .updatePen (blank ) 163s .buf .WriteString (ansi .DeleteLine (1 )) 164 }else if top == minY && bot == maxY { 165supportsSU := s .caps .Contains (capSU ) 166s .move (newbuf ,0 ,bot ) 167s .updatePen (blank ) 168if supportsSU { 169s .buf .WriteString (ansi .ScrollUp (n )) 170 }else { 171s .buf .WriteString (strings .Repeat ("\n" ,n )) 172 } 173 }else if bot == maxY { 174s .move (newbuf ,0 ,top ) 175s .updatePen (blank ) 176s .buf .WriteString (ansi .DeleteLine (n )) 177 }else { 178return false 179 } 180return true 181} 182 183// scrollDown scrolls the screen down by n lines. 184func (s * TerminalRenderer )scrollDown (newbuf * RenderBuffer ,n ,top ,bot ,minY ,maxY int ,blank * Cell )bool { 185if n == 1 && top == minY && bot == maxY {//nolint:nestif 186s .move (newbuf ,0 ,top ) 187s .updatePen (blank ) 188s .buf .WriteString (ansi .ReverseIndex ) 189 }else if n == 1 && bot == maxY { 190s .move (newbuf ,0 ,top ) 191s .updatePen (blank ) 192s .buf .WriteString (ansi .InsertLine (1 )) 193 }else if top == minY && bot == maxY { 194s .move (newbuf ,0 ,top ) 195s .updatePen (blank ) 196if s .caps .Contains (capSD ) { 197s .buf .WriteString (ansi .ScrollDown (n )) 198 }else { 199s .buf .WriteString (strings .Repeat (ansi .ReverseIndex ,n )) 200 } 201 }else if bot == maxY { 202s .move (newbuf ,0 ,top ) 203s .updatePen (blank ) 204s .buf .WriteString (ansi .InsertLine (n )) 205 }else { 206return false 207 } 208return true 209} 210 211// scrollIdl scrolls the screen n lines by using [ansi.DL] at del and using 212// [ansi.IL] at ins. 213func (s * TerminalRenderer )scrollIdl (newbuf * RenderBuffer ,n ,del ,ins int ,blank * Cell )bool { 214if n < 0 { 215return false 216 } 217 218// Delete lines 219s .move (newbuf ,0 ,del ) 220s .updatePen (blank ) 221s .buf .WriteString (ansi .DeleteLine (n )) 222 223// Insert lines 224s .move (newbuf ,0 ,ins ) 225s .updatePen (blank ) 226s .buf .WriteString (ansi .InsertLine (n )) 227 228return true 229}