Skip to content

Commit 6df3fd2

Browse files
authored
fix: keep input focused during output auto-scroll (#427)
1 parent 7a770eb commit 6df3fd2

2 files changed

Lines changed: 77 additions & 4 deletions

File tree

lua/opencode/ui/renderer/scroll.lua

Lines changed: 37 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,31 @@ local output_window = require('opencode.ui.output_window')
33

44
local M = {}
55

6+
local function with_window_event_autocmds_ignored(fn)
7+
local previous = vim.o.eventignore
8+
local ignored = {
9+
WinEnter = true,
10+
WinLeave = true,
11+
BufEnter = true,
12+
}
13+
14+
for event in previous:gmatch('[^,]+') do
15+
if event ~= '' then
16+
ignored[event] = true
17+
end
18+
end
19+
20+
local events = vim.tbl_keys(ignored)
21+
table.sort(events)
22+
vim.o.eventignore = table.concat(events, ',')
23+
24+
local ok, err = pcall(fn)
25+
vim.o.eventignore = previous
26+
if not ok then
27+
error(err)
28+
end
29+
end
30+
631
---@param win integer
732
---@return boolean
833
local function window_wraps(win)
@@ -98,9 +123,18 @@ function M.scroll_win_to_bottom(win, buf)
98123
end
99124

100125
if needs_bottom_align then
101-
vim.api.nvim_win_call(win, function()
102-
vim.cmd('normal! zb')
103-
end)
126+
local windows = state.windows
127+
if windows and vim.api.nvim_get_current_win() == windows.input_win then
128+
with_window_event_autocmds_ignored(function()
129+
vim.api.nvim_win_call(win, function()
130+
vim.cmd('normal! zb')
131+
end)
132+
end)
133+
else
134+
vim.api.nvim_win_call(win, function()
135+
vim.cmd('normal! zb')
136+
end)
137+
end
104138
end
105139

106140
output_window._prev_line_count_by_win[win] = line_count

tests/unit/cursor_tracking_spec.lua

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -372,7 +372,7 @@ describe('renderer.scroll_to_bottom', function()
372372
local ctx = require('opencode.ui.renderer.ctx')
373373
local output_window = require('opencode.ui.output_window')
374374
local stub = require('luassert.stub')
375-
local buf, win
375+
local buf, win, input_buf, input_win
376376

377377
before_each(function()
378378
config.setup({})
@@ -396,6 +396,8 @@ describe('renderer.scroll_to_bottom', function()
396396
end)
397397

398398
after_each(function()
399+
pcall(vim.api.nvim_win_close, input_win, true)
400+
pcall(vim.api.nvim_buf_delete, input_buf, { force = true })
399401
pcall(vim.api.nvim_win_close, win, true)
400402
pcall(vim.api.nvim_buf_delete, buf, { force = true })
401403
state.ui.set_windows(nil)
@@ -529,6 +531,43 @@ describe('renderer.scroll_to_bottom', function()
529531
assert.stub(cmd_stub).was_called_with('normal! zb')
530532
cmd_stub:revert()
531533
end)
534+
535+
it('does not leave the focused input window while following output at bottom', function()
536+
input_buf = vim.api.nvim_create_buf(false, true)
537+
vim.api.nvim_buf_set_lines(input_buf, 0, -1, false, { '中文输入' })
538+
input_win = vim.api.nvim_open_win(input_buf, true, {
539+
relative = 'editor',
540+
width = 40,
541+
height = 3,
542+
row = 12,
543+
col = 0,
544+
})
545+
state.ui.set_windows({ output_win = win, output_buf = buf, input_win = input_win, input_buf = input_buf })
546+
vim.api.nvim_set_current_win(input_win)
547+
548+
local winleave_count = 0
549+
local group = vim.api.nvim_create_augroup('OpencodeScrollImeRegression', { clear = true })
550+
vim.api.nvim_create_autocmd('WinLeave', {
551+
group = group,
552+
buffer = input_buf,
553+
callback = function()
554+
winleave_count = winleave_count + 1
555+
end,
556+
})
557+
558+
vim.api.nvim_win_set_height(win, 5)
559+
vim.api.nvim_win_set_cursor(win, { 1, 0 })
560+
config.values.ui.output.always_scroll_to_bottom = true
561+
562+
renderer.scroll_to_bottom()
563+
564+
assert.equals(input_win, vim.api.nvim_get_current_win())
565+
assert.equals(0, winleave_count)
566+
assert.equals(50, vim.api.nvim_win_get_cursor(win)[1])
567+
568+
config.values.ui.output.always_scroll_to_bottom = false
569+
pcall(vim.api.nvim_del_augroup_by_id, group)
570+
end)
532571
end)
533572

534573
describe('ui.focus_input', function()

0 commit comments

Comments
 (0)