@@ -8,6 +8,7 @@ M.debug_namespace = vim.api.nvim_create_namespace('opencode_output_debug')
88M .markdown_namespace = vim .api .nvim_create_namespace (' opencode_output_markdown' )
99M ._last_visible_bottom_by_win = {}
1010M ._was_at_bottom_by_win = {}
11+ M ._prev_line_count_by_win = {}
1112
1213local _update_depth = 0
1314local _update_buf = nil
@@ -81,19 +82,9 @@ function M.buffer_valid(windows)
8182 return windows and windows .output_buf and vim .api .nvim_buf_is_valid (windows .output_buf )
8283end
8384
84- --- Check if the output window viewport is scrolled to the bottom of the buffer.
85- --- Returns true if the output window should continue auto-scrolling to follow
86- --- new content. Uses the viewport position (visible bottom line) rather than
87- --- the cursor, so that mouse-wheel scrolling—which moves the viewport but not
88- --- the cursor—correctly stops the tail-follow behavior.
89- ---
90- --- The `_was_at_bottom_by_win` flag is the persistent signal: it is set to
91- --- `true` by `scroll_win_to_bottom` and cleared to `false` by
92- --- `sync_cursor_with_viewport` whenever the viewport is scrolled away from the
93- --- buffer's last line. Reading a sticky flag (rather than the live viewport
94- --- position) lets callers like `renderer.scroll_to_bottom()` that run *after*
95- --- a buffer write still return the correct answer even though the viewport has
96- --- not yet caught up to the newly appended lines.
85+ --- Check if the cursor in the output window is at (or was at) the bottom of
86+ --- the buffer, using the same logic as the original implementation.
87+ --- Returns true if the window should continue auto-scrolling.
9788--- @param win ? integer Window ID , defaults to state.windows.output_win
9889--- @return boolean
9990function M .is_at_bottom (win )
@@ -116,18 +107,13 @@ function M.is_at_bottom(win)
116107 return true
117108 end
118109
119- -- Prefer the sticky flag when it has been set by scroll/WinScrolled events.
120- -- Fall back to a live viewport check on the very first call (flag is nil).
121- if M ._was_at_bottom_by_win [win ] ~= nil then
122- return M ._was_at_bottom_by_win [win ] == true
123- end
124-
125- local visible_bottom = M .get_visible_bottom_line (win )
126- if not visible_bottom then
110+ local ok2 , cursor = pcall (vim .api .nvim_win_get_cursor , win )
111+ if not ok2 then
127112 return true
128113 end
129114
130- return visible_bottom >= line_count
115+ local prev_line_count = M ._prev_line_count_by_win [win ] or line_count
116+ return cursor [1 ] >= prev_line_count or cursor [1 ] >= line_count
131117end
132118
133119--- @param win ? integer
@@ -146,11 +132,13 @@ function M.reset_scroll_tracking(win)
146132 if win then
147133 M ._last_visible_bottom_by_win [win ] = nil
148134 M ._was_at_bottom_by_win [win ] = nil
135+ M ._prev_line_count_by_win [win ] = nil
149136 return
150137 end
151138
152139 M ._last_visible_bottom_by_win = {}
153140 M ._was_at_bottom_by_win = {}
141+ M ._prev_line_count_by_win = {}
154142end
155143
156144--- @param win ? integer
@@ -174,12 +162,6 @@ function M.sync_cursor_with_viewport(win)
174162 end
175163
176164 M ._last_visible_bottom_by_win [win ] = visible_bottom
177-
178- -- Update the sticky at-bottom flag based on whether the viewport now shows
179- -- the last line. This is the key mechanism: when the user scrolls up (mouse
180- -- or keyboard), WinScrolled fires here and clears the flag so that the next
181- -- `is_at_bottom()` call returns false and streaming stops following the tail.
182- M ._was_at_bottom_by_win [win ] = visible_bottom >= line_count
183165end
184166
185167--- @param windows OpencodeWindowState
0 commit comments