Skip to content

Commit efccdb8

Browse files
committed
fix(renderer): skip hidden-messages notice in message navigation
Problem: when max_messages truncates older messages, the synthetic hidden-messages notice (appended to state.messages via on_message_updated) is matched by get_prev_rendered_message, so [[ yanks the cursor to the buffer top. Solution: skip entries where is_renderer_synthetic_message is true in both get_prev_rendered_message and get_next_rendered_message.
1 parent 3f1baf4 commit efccdb8

2 files changed

Lines changed: 77 additions & 7 deletions

File tree

lua/opencode/ui/renderer.lua

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -619,10 +619,12 @@ function M.get_next_rendered_message(current_line)
619619
local next_message = nil
620620

621621
for _, message in ipairs(state.messages or {}) do
622-
local rendered = message.info and message.info.id and ctx.render_state:get_message(message.info.id) or nil
623-
if rendered and rendered.line_start and rendered.line_start + 1 > current_line then
624-
next_message = rendered
625-
break
622+
if not is_renderer_synthetic_message(message) then
623+
local rendered = message.info and message.info.id and ctx.render_state:get_message(message.info.id) or nil
624+
if rendered and rendered.line_start and rendered.line_start + 1 > current_line then
625+
next_message = rendered
626+
break
627+
end
626628
end
627629
end
628630

@@ -634,9 +636,11 @@ end
634636
function M.get_prev_rendered_message(current_line)
635637
for i = #(state.messages or {}), 1, -1 do
636638
local message = state.messages[i]
637-
local rendered = message and message.info and message.info.id and ctx.render_state:get_message(message.info.id)
638-
if rendered and rendered.line_start and rendered.line_start + 1 < current_line then
639-
return rendered
639+
if message and not is_renderer_synthetic_message(message) then
640+
local rendered = message.info and message.info.id and ctx.render_state:get_message(message.info.id)
641+
if rendered and rendered.line_start and rendered.line_start + 1 < current_line then
642+
return rendered
643+
end
640644
end
641645
end
642646

tests/unit/navigation_spec.lua

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,3 +656,69 @@ describe('navigation jumplist preservation', function()
656656
assert.equals(0, mark[2])
657657
end)
658658
end)
659+
660+
describe('navigation hidden-messages-notice handling', function()
661+
local output_buf, output_win
662+
local original_windows
663+
664+
before_each(function()
665+
original_windows = state.store.get('windows')
666+
state.ui.clear_hidden_window_state()
667+
668+
output_buf = vim.api.nvim_create_buf(false, true)
669+
output_win = vim.api.nvim_open_win(output_buf, true, {
670+
relative = 'editor',
671+
width = 80,
672+
height = 20,
673+
row = 0,
674+
col = 0,
675+
})
676+
state.ui.set_windows({ output_buf = output_buf, output_win = output_win })
677+
end)
678+
679+
after_each(function()
680+
state.ui.clear_hidden_window_state()
681+
pcall(vim.api.nvim_win_close, output_win, true)
682+
pcall(vim.api.nvim_buf_delete, output_buf, { force = true })
683+
684+
if original_windows ~= nil then
685+
state.ui.set_windows(original_windows)
686+
else
687+
state.ui.clear_windows()
688+
end
689+
end)
690+
691+
it('does not jump [[ to the hidden-messages notice when max_messages truncates', function()
692+
local ctx = require('opencode.ui.renderer.ctx')
693+
-- Simulate a session where max_messages truncates older messages: the
694+
-- hidden notice was emitted into state.messages via on_message_updated,
695+
-- so it lives in state.messages with a real line_start. The bug was that
696+
-- get_prev_rendered_message iterated state.messages in reverse and matched
697+
-- the notice first, jumping the user back to the buffer top.
698+
state.renderer.set_messages({
699+
{ info = { id = 'real_old', role = 'assistant', sessionID = 's1' } },
700+
{ info = { id = 'real_mid', role = 'user', sessionID = 's1' } },
701+
{ info = { id = '__opencode_hidden_messages_notice__', role = 'system', sessionID = 's1' } },
702+
})
703+
ctx.render_state:set_message(
704+
{ info = { id = '__opencode_hidden_messages_notice__', role = 'system', sessionID = 's1' } },
705+
1,
706+
2
707+
)
708+
ctx.render_state:set_message({ info = { id = 'real_old', role = 'assistant', sessionID = 's1' } }, 4, 8)
709+
ctx.render_state:set_message({ info = { id = 'real_mid', role = 'user', sessionID = 's1' } }, 10, 18)
710+
711+
vim.api.nvim_buf_set_lines(output_buf, 0, -1, false, vim.fn['repeat']({ 'line' }, 25))
712+
-- Cursor at the first line of `real_mid` (line 11). Without the fix,
713+
-- get_prev_rendered_message(11) iterated state.messages in reverse and
714+
-- landed on the hidden notice at line 1, returning it (since its
715+
-- line_start=1 satisfies `1+1 < 11`), so the user was yanked to line 2.
716+
-- With the fix, the synthetic notice is skipped and `real_old` is chosen.
717+
vim.api.nvim_win_set_cursor(output_win, { 11, 0 })
718+
719+
navigation.goto_prev_message()
720+
721+
local cursor = vim.api.nvim_win_get_cursor(output_win)
722+
assert.equals(5, cursor[1])
723+
end)
724+
end)

0 commit comments

Comments
 (0)