mirror of
https://github.com/patriceckhart/zot.git
synced 2026-06-26 21:36:31 +02:00
interactive: keep viewport anchored when streaming below
scrollOffset is measured from the bottom of the chat buffer, so when the agent appends new lines while the user has scrolled up to read history, the visible window slides down through the buffer and the content the user was reading drifts off the top. Track the previous chat line count and column width across redraws. While the user is in free-scroll (scrollOffset > 0) and the terminal hasn't been resized, bump scrollOffset by the chat-length delta so the visible content stays pinned. Compensation is skipped on resize (line counts aren't comparable across reflows) and when following the tail (scrollOffset == 0), where new content should keep pushing the viewport as before.
This commit is contained in:
parent
5905729776
commit
ec79818512
1 changed files with 35 additions and 0 deletions
|
|
@ -192,6 +192,17 @@ type Interactive struct {
|
|||
scrollOffset int // rows from the bottom; 0 = pinned to latest
|
||||
prevScrollOffset int // last value redraw snapped against; tracks intent
|
||||
|
||||
// prevChatLen and prevChatCols track the chat buffer's size at the
|
||||
// last redraw so that when content grows below the user's viewport
|
||||
// while they're scrolled up reading history, we can bump
|
||||
// scrollOffset by exactly the growth and keep the visible content
|
||||
// pinned. Without this, every streamed line shifts the visible
|
||||
// window down through the buffer (because scrollOffset is measured
|
||||
// from the bottom) and the user's reading position drifts upward
|
||||
// and off the top.
|
||||
prevChatLen int
|
||||
prevChatCols int
|
||||
|
||||
// Messages typed while a turn is in flight. Each is delivered as
|
||||
// its own follow-up turn once the current one finishes. Rendered
|
||||
// above the status bar as "sliding in: ..." chips.
|
||||
|
|
@ -815,6 +826,30 @@ func (i *Interactive) redraw() {
|
|||
chatRows = 1
|
||||
}
|
||||
|
||||
// Auto-follow guard: when the user has scrolled up (scrollOffset
|
||||
// > 0) and the agent appends new content below the viewport while
|
||||
// they're reading, compensate so the visible content stays
|
||||
// anchored. scrollOffset is measured from the bottom of `chat`,
|
||||
// so without compensation a growing buffer pushes the window
|
||||
// downward through the content and the lines the user was
|
||||
// reading scroll off the top.
|
||||
//
|
||||
// Skip compensation when the terminal width changed (a resize
|
||||
// reflows the whole buffer and the line-count delta no longer
|
||||
// corresponds to appended content) and when scrollOffset is 0
|
||||
// (the user is following the tail and wants new content to push
|
||||
// the view down as usual).
|
||||
if i.scrollOffset > 0 && i.prevChatCols == cols && i.prevChatLen > 0 {
|
||||
if delta := len(chat) - i.prevChatLen; delta != 0 {
|
||||
i.scrollOffset += delta
|
||||
if i.scrollOffset < 0 {
|
||||
i.scrollOffset = 0
|
||||
}
|
||||
}
|
||||
}
|
||||
i.prevChatLen = len(chat)
|
||||
i.prevChatCols = cols
|
||||
|
||||
// Apply scroll offset to the chat slice.
|
||||
maxOffset := len(chat) - chatRows
|
||||
if maxOffset < 0 {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue