-
Notifications
You must be signed in to change notification settings - Fork 19
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(cellbuf): initial scroll optimization
- Loading branch information
1 parent
5c6b21f
commit e9f42af
Showing
3 changed files
with
590 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,278 @@ | ||
package cellbuf | ||
|
||
import ( | ||
"strings" | ||
|
||
"github.com/charmbracelet/x/ansi" | ||
) | ||
|
||
// scrollOptimize optimizes the screen to transform the old buffer into the new | ||
// buffer. | ||
func (s *Screen) scrollOptimize() { | ||
height := s.newbuf.Height() | ||
if s.oldnum == nil || len(s.oldnum) < height { | ||
var needLines int | ||
if len(s.oldnum) < height { | ||
needLines = height | ||
} else { | ||
needLines = len(s.oldnum) | ||
} | ||
|
||
s.oldnum = make([]int, needLines) | ||
} | ||
|
||
// Calculate the indices | ||
s.updateHashmap() | ||
if len(s.hashtab) < height { | ||
return | ||
} | ||
|
||
// Pass 1 - from top to bottom scrolling up | ||
for i := 0; i < height; { | ||
for i < height && (s.oldnum[i] == newIndex || s.oldnum[i] <= i) { | ||
i++ | ||
} | ||
if i >= height { | ||
break | ||
} | ||
|
||
shift := s.oldnum[i] - i // shift > 0 | ||
start := i | ||
|
||
i++ | ||
for i < height && s.oldnum[i] != newIndex && s.oldnum[i]-i == shift { | ||
i++ | ||
} | ||
end := i - 1 + shift | ||
|
||
if !s.scrolln(shift, start, end, height-1) { | ||
continue | ||
} | ||
} | ||
|
||
// Pass 2 - from bottom to top scrolling down | ||
for i := height - 1; i >= 0; { | ||
for i >= 0 && (s.oldnum[i] == newIndex || s.oldnum[i] >= i) { | ||
i-- | ||
} | ||
if i < 0 { | ||
break | ||
} | ||
|
||
shift := s.oldnum[i] - i // shift < 0 | ||
end := i | ||
|
||
i-- | ||
for i >= 0 && s.oldnum[i] != newIndex && s.oldnum[i]-i == shift { | ||
i-- | ||
} | ||
|
||
start := i + 1 - (-shift) | ||
if !s.scrolln(shift, start, end, height-1) { | ||
continue | ||
} | ||
} | ||
} | ||
|
||
// scrolln scrolls the screen up by n lines. | ||
func (s *Screen) scrolln(n, top, bot, maxY int) (v bool) { | ||
blank := s.clearBlank() | ||
if n > 0 { | ||
// Scroll up (forward) | ||
v = s.scrollUp(n, top, bot, 0, maxY, blank) | ||
if !v { | ||
s.buf.WriteString(ansi.SetTopBottomMargins(top+1, bot+1)) | ||
|
||
// XXX: How should we handle this in inline mode when not using alternate screen? | ||
s.cur.X, s.cur.Y = -1, -1 | ||
v = s.scrollUp(n, top, bot, top, bot, blank) | ||
s.buf.WriteString(ansi.SetTopBottomMargins(1, maxY+1)) | ||
s.cur.X, s.cur.Y = -1, -1 | ||
} | ||
|
||
if !v { | ||
v = s.scrollIdl(n, top, bot-n+1, blank) | ||
} | ||
|
||
// Clear newly shifted-in lines. | ||
if v { | ||
if bot == maxY { | ||
s.move(0, bot-n+1) | ||
s.clearToBottom(&BlankCell) | ||
} else { | ||
for i := 0; i < n; i++ { | ||
s.move(0, bot-i) | ||
s.clearToEnd(&BlankCell, false) | ||
} | ||
} | ||
} | ||
} else if n < 0 { | ||
// Scroll down (backward) | ||
v = s.scrollDown(-n, top, bot, 0, maxY, blank) | ||
if !v { | ||
s.buf.WriteString(ansi.SetTopBottomMargins(top+1, bot+1)) | ||
|
||
// XXX: How should we handle this in inline mode when not using alternate screen? | ||
s.cur.X, s.cur.Y = -1, -1 | ||
v = s.scrollDown(-n, top, bot, top, bot, blank) | ||
s.buf.WriteString(ansi.SetTopBottomMargins(1, maxY+1)) | ||
s.cur.X, s.cur.Y = -1, -1 | ||
|
||
if !v { | ||
v = s.scrollIdl(-n, bot+n+1, top, blank) | ||
} | ||
|
||
// Clear newly shifted-in lines. | ||
if v { | ||
for i := 0; i < -n; i++ { | ||
s.move(0, top+i) | ||
s.clearToEnd(&BlankCell, false) | ||
} | ||
} | ||
} | ||
} | ||
|
||
if !v { | ||
return | ||
} | ||
|
||
s.scrollBuffer(s.curbuf, n, top, bot, blank) | ||
|
||
// shift hash values too, they can be reused | ||
s.scrollOldhash(n, top, bot) | ||
|
||
return true | ||
} | ||
|
||
// scrollBuffer scrolls the buffer by n lines. | ||
func (s *Screen) scrollBuffer(b *Buffer, n, top, bot int, blank *Cell) { | ||
if top < 0 || bot < top || bot >= b.Height() { | ||
// Nothing to scroll | ||
return | ||
} | ||
|
||
if n < 0 { | ||
// shift n lines downwards | ||
limit := top - n | ||
for line := bot; line >= limit && n >= 0 && n >= top; line-- { | ||
copy(b.Lines[line], b.Lines[line+n]) | ||
} | ||
for line := top; line < limit && n <= b.Height()-1 && n <= bot; line++ { | ||
b.FillRect(blank, Rect(0, line, b.Width(), 1)) | ||
} | ||
} | ||
|
||
if n > 0 { | ||
// shift n lines upwards | ||
limit := bot - n | ||
for line := top; line <= limit && n <= b.Height()-1 && n <= bot; line++ { | ||
copy(b.Lines[line], b.Lines[line+n]) | ||
} | ||
for line := bot; line > limit && n >= 0 && n >= top; line-- { | ||
b.FillRect(blank, Rect(0, line, b.Width(), 1)) | ||
} | ||
} | ||
|
||
s.touchLine(b.Width(), b.Height(), top, bot-top+1, true) | ||
} | ||
|
||
// touchLine marks the line as touched. | ||
func (s *Screen) touchLine(width, height, y, n int, changed bool) { | ||
if n < 0 || y < 0 || y >= height { | ||
return // Nothing to touch | ||
} | ||
|
||
for i := y; i < y+n && i < height; i++ { | ||
chg, ok := s.touch[i] | ||
if changed { | ||
chg.firstCell = 0 | ||
chg.lastCell = width - 1 | ||
} else { | ||
if ok { | ||
chg.firstCell = newIndex | ||
chg.lastCell = newIndex | ||
} | ||
} | ||
s.touch[i] = chg | ||
} | ||
} | ||
|
||
// scrollUp scrolls the screen up by n lines. | ||
func (s *Screen) scrollUp(n, top, bot, minY, maxY int, blank *Cell) bool { | ||
if n == 1 && top == minY && bot == maxY { | ||
s.move(0, bot) | ||
s.updatePen(blank) | ||
s.buf.WriteByte('\n') | ||
} else if n == 1 && bot == maxY { | ||
s.move(0, top) | ||
s.updatePen(blank) | ||
s.buf.WriteString(ansi.DeleteLine(1)) | ||
} else if top == minY && bot == maxY { | ||
if s.xtermLike { | ||
s.move(0, bot) | ||
} else { | ||
s.move(0, top) | ||
} | ||
s.updatePen(blank) | ||
if s.xtermLike { | ||
s.buf.WriteString(ansi.ScrollUp(n)) | ||
} else { | ||
s.buf.WriteString(strings.Repeat("\n", n)) | ||
} | ||
} else if bot == maxY { | ||
s.move(0, top) | ||
s.updatePen(blank) | ||
s.buf.WriteString(ansi.DeleteLine(n)) | ||
} else { | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
// scrollDown scrolls the screen down by n lines. | ||
func (s *Screen) scrollDown(n, top, bot, minY, maxY int, blank *Cell) bool { | ||
if n == 1 && top == minY && bot == maxY { | ||
s.move(0, top) | ||
s.updatePen(blank) | ||
s.buf.WriteString(ansi.ReverseIndex) | ||
} else if n == 1 && bot == maxY { | ||
s.move(0, top) | ||
s.updatePen(blank) | ||
s.buf.WriteString(ansi.InsertLine(1)) | ||
} else if top == minY && bot == maxY { | ||
s.move(0, top) | ||
s.updatePen(blank) | ||
if s.xtermLike { | ||
s.buf.WriteString(ansi.ScrollDown(n)) | ||
} else { | ||
s.buf.WriteString(strings.Repeat(ansi.ReverseIndex, n)) | ||
} | ||
} else if bot == maxY { | ||
s.move(0, top) | ||
s.updatePen(blank) | ||
s.buf.WriteString(ansi.InsertLine(n)) | ||
} else { | ||
return false | ||
} | ||
return true | ||
} | ||
|
||
// scrollIdl scrolls the screen n lines by using [ansi.DL] at del and using | ||
// [ansi.IL] at ins. | ||
func (s *Screen) scrollIdl(n, del, ins int, blank *Cell) bool { | ||
if n < 0 { | ||
return false | ||
} | ||
|
||
// Delete lines | ||
s.move(0, del) | ||
s.updatePen(blank) | ||
s.buf.WriteString(ansi.DeleteLine(n)) | ||
|
||
// Insert lines | ||
s.move(0, ins) | ||
s.updatePen(blank) | ||
s.buf.WriteString(ansi.InsertLine(n)) | ||
|
||
return true | ||
} |
Oops, something went wrong.