Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 37 additions & 3 deletions lua/opencode/ui/renderer/scroll.lua
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,31 @@ local output_window = require('opencode.ui.output_window')

local M = {}

local function with_window_event_autocmds_ignored(fn)
local previous = vim.o.eventignore
local ignored = {
WinEnter = true,
WinLeave = true,
BufEnter = true,
}

for event in previous:gmatch('[^,]+') do
if event ~= '' then
ignored[event] = true
end
end

local events = vim.tbl_keys(ignored)
table.sort(events)
vim.o.eventignore = table.concat(events, ',')

local ok, err = pcall(fn)
vim.o.eventignore = previous
if not ok then
error(err)
end
end

---@param win integer
---@return boolean
local function window_wraps(win)
Expand Down Expand Up @@ -98,9 +123,18 @@ function M.scroll_win_to_bottom(win, buf)
end

if needs_bottom_align then
vim.api.nvim_win_call(win, function()
vim.cmd('normal! zb')
end)
local windows = state.windows
if windows and vim.api.nvim_get_current_win() == windows.input_win then
with_window_event_autocmds_ignored(function()
vim.api.nvim_win_call(win, function()
vim.cmd('normal! zb')
end)
end)
else
vim.api.nvim_win_call(win, function()
vim.cmd('normal! zb')
end)
end
end

output_window._prev_line_count_by_win[win] = line_count
Expand Down
41 changes: 40 additions & 1 deletion tests/unit/cursor_tracking_spec.lua
Original file line number Diff line number Diff line change
Expand Up @@ -372,7 +372,7 @@ describe('renderer.scroll_to_bottom', function()
local ctx = require('opencode.ui.renderer.ctx')
local output_window = require('opencode.ui.output_window')
local stub = require('luassert.stub')
local buf, win
local buf, win, input_buf, input_win

before_each(function()
config.setup({})
Expand All @@ -396,6 +396,8 @@ describe('renderer.scroll_to_bottom', function()
end)

after_each(function()
pcall(vim.api.nvim_win_close, input_win, true)
pcall(vim.api.nvim_buf_delete, input_buf, { force = true })
pcall(vim.api.nvim_win_close, win, true)
pcall(vim.api.nvim_buf_delete, buf, { force = true })
state.ui.set_windows(nil)
Expand Down Expand Up @@ -529,6 +531,43 @@ describe('renderer.scroll_to_bottom', function()
assert.stub(cmd_stub).was_called_with('normal! zb')
cmd_stub:revert()
end)

it('does not leave the focused input window while following output at bottom', function()
input_buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(input_buf, 0, -1, false, { '中文输入' })
input_win = vim.api.nvim_open_win(input_buf, true, {
relative = 'editor',
width = 40,
height = 3,
row = 12,
col = 0,
})
state.ui.set_windows({ output_win = win, output_buf = buf, input_win = input_win, input_buf = input_buf })
vim.api.nvim_set_current_win(input_win)

local winleave_count = 0
local group = vim.api.nvim_create_augroup('OpencodeScrollImeRegression', { clear = true })
vim.api.nvim_create_autocmd('WinLeave', {
group = group,
buffer = input_buf,
callback = function()
winleave_count = winleave_count + 1
end,
})

vim.api.nvim_win_set_height(win, 5)
vim.api.nvim_win_set_cursor(win, { 1, 0 })
config.values.ui.output.always_scroll_to_bottom = true

renderer.scroll_to_bottom()

assert.equals(input_win, vim.api.nvim_get_current_win())
assert.equals(0, winleave_count)
assert.equals(50, vim.api.nvim_win_get_cursor(win)[1])

config.values.ui.output.always_scroll_to_bottom = false
pcall(vim.api.nvim_del_augroup_by_id, group)
end)
end)

describe('ui.focus_input', function()
Expand Down
Loading