Skip to content

Commit a3b658c

Browse files
feat(ui): Improve scroll at bottom to support scrolling without focus
1 parent 3fd6e82 commit a3b658c

File tree

3 files changed

+56
-10
lines changed

3 files changed

+56
-10
lines changed

lua/opencode/state.lua

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
---@field last_input_window_position integer[]|nil
1515
---@field last_output_window_position integer[]|nil
1616
---@field last_code_win_before_opencode integer|nil
17+
---@field output_window_at_bottom boolean
1718
---@field current_code_buf number|nil
1819
---@field display_route any|nil
1920
---@field current_mode string
@@ -55,6 +56,7 @@ local _state = {
5556
last_focused_opencode_window = nil,
5657
last_input_window_position = nil,
5758
last_output_window_position = nil,
59+
output_window_at_bottom = true,
5860
last_code_win_before_opencode = nil,
5961
current_code_buf = nil,
6062
display_route = nil,

lua/opencode/ui/output_window.lua

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,32 @@ function M.mounted(windows)
2727
return windows and windows.output_buf and windows.output_win and vim.api.nvim_win_is_valid(windows.output_win)
2828
end
2929

30+
---Check if the output window is currently at the bottom
31+
---@param win? integer Window ID, defaults to state.windows.output_win
32+
---@return boolean true if at bottom, false otherwise
33+
function M.is_at_bottom(win)
34+
win = win or (state.windows and state.windows.output_win)
35+
36+
if not win or not vim.api.nvim_win_is_valid(win) then
37+
return true -- Assume at bottom if window invalid
38+
end
39+
40+
if not state.windows or not state.windows.output_buf then
41+
return true
42+
end
43+
44+
local ok, line_count = pcall(vim.api.nvim_buf_line_count, state.windows.output_buf)
45+
if not ok or not line_count or line_count == 0 then
46+
return true -- Empty buffer, consider at bottom
47+
end
48+
49+
local botline = vim.fn.line('w$', win)
50+
51+
-- Consider at bottom if bottom visible line is at or near the end
52+
-- Use -1 tolerance for wrapped lines
53+
return botline >= line_count - 1
54+
end
55+
3056
function M.setup(windows)
3157
vim.api.nvim_set_option_value('winhighlight', config.ui.window_highlight, { win = windows.output_win })
3258
vim.api.nvim_set_option_value('wrap', true, { win = windows.output_win })
@@ -177,6 +203,16 @@ function M.setup_autocmds(windows, group)
177203
state.subscribe('current_permission', function()
178204
require('opencode.keymap').toggle_permission_keymap(windows.output_buf)
179205
end)
206+
207+
-- Track scroll position when window is scrolled
208+
vim.api.nvim_create_autocmd('WinScrolled', {
209+
group = group,
210+
buffer = windows.output_buf,
211+
callback = function()
212+
-- Update state to track if user is at bottom
213+
state.output_window_at_bottom = M.is_at_bottom(windows.output_win)
214+
end,
215+
})
180216
end
181217

182218
function M.clear()

lua/opencode/ui/renderer.lua

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,10 @@ function M.reset()
5050
require('opencode.api').respond_to_permission('reject')
5151
end
5252
state.current_permission = nil
53+
54+
-- Reset scroll state when session changes
55+
state.output_window_at_bottom = true
56+
5357
trigger_on_data_rendered()
5458
end
5559

@@ -224,26 +228,30 @@ function M.scroll_to_bottom()
224228
return
225229
end
226230

227-
local botline = vim.fn.line('w$', state.windows.output_win)
228-
local cursor = vim.api.nvim_win_get_cursor(state.windows.output_win)
229-
local cursor_row = cursor[1] or 0
230-
local is_focused = vim.api.nvim_get_current_win() == state.windows.output_win
231-
232231
local prev_line_count = M._prev_line_count or 0
233232

234233
---@cast line_count integer
235234
M._prev_line_count = line_count
236235

237-
local was_at_bottom = (botline >= prev_line_count) or prev_line_count == 0
238-
239236
trigger_on_data_rendered()
240237

241-
if is_focused and cursor_row < prev_line_count - 1 then
242-
return
238+
-- Determine if we should scroll to bottom
239+
local should_scroll = false
240+
241+
-- Always scroll on initial render
242+
if prev_line_count == 0 then
243+
should_scroll = true
244+
-- Scroll if user is at bottom (respects manual scroll position)
245+
elseif state.output_window_at_bottom then
246+
should_scroll = true
243247
end
244248

245-
if was_at_bottom or not is_focused then
249+
if should_scroll then
246250
vim.api.nvim_win_set_cursor(state.windows.output_win, { line_count, 0 })
251+
state.output_window_at_bottom = true
252+
else
253+
-- User has scrolled up, don't scroll
254+
state.output_window_at_bottom = false
247255
end
248256
end
249257

0 commit comments

Comments
 (0)