Skip to content

Commit bdbe7fa

Browse files
committed
fix(render): fix gg E565 and gate load_more on has_unrendered
- Remove expr=true from gg keymap (causes E565 during buffer write) - Gate scroll-to-top load_more on has_unrendered, not bare line number - Fix load_more_messages treating nil as 'needs loading'
1 parent bdfd9d0 commit bdbe7fa

3 files changed

Lines changed: 87 additions & 19 deletions

File tree

lua/opencode/ui/output_window.lua

Lines changed: 18 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -596,12 +596,11 @@ function M.setup_keymaps(windows)
596596

597597
-- When lazy-render is active, gg only reaches the top of rendered content.
598598
-- Load all messages first so gg reaches the true start of history.
599-
-- See tests/unit/renderer_lazy_spec.lua: "gg loads all messages and makes them searchable"
600599
vim.keymap.set('n', 'gg', function()
601600
local renderer = require('opencode.ui.renderer')
602601
renderer.load_all_messages()
603-
return 'gg'
604-
end, { buffer = windows.output_buf, expr = true, remap = true })
602+
vim.api.nvim_win_set_cursor(0, { 1, 0 })
603+
end, { buffer = windows.output_buf })
605604
end
606605

607606
---@param windows OpencodeWindowState
@@ -678,17 +677,22 @@ function M.setup_autocmds(windows, group)
678677
pcall(vim.api.nvim_win_set_cursor, windows.output_win, { 1, 0 })
679678
end
680679
end, 150)
681-
vim.api.nvim_create_autocmd('WinScrolled', {
682-
group = group,
683-
buffer = windows.output_buf,
684-
callback = function()
685-
M.sync_cursor_with_viewport(windows.output_win)
686-
local ok, cursor = pcall(vim.api.nvim_win_get_cursor, windows.output_win)
687-
if ok and cursor and cursor[1] <= 3 then
688-
debounced_load_more()
689-
end
690-
end,
691-
})
680+
vim.api.nvim_create_autocmd('WinScrolled', {
681+
group = group,
682+
buffer = windows.output_buf,
683+
callback = function()
684+
M.sync_cursor_with_viewport(windows.output_win)
685+
local ctx = require('opencode.ui.renderer.ctx')
686+
local has_unrendered = ctx.lazy_render_count ~= nil
687+
and ctx.lazy_render_count < #state.messages
688+
if has_unrendered then
689+
local ok, cursor = pcall(vim.api.nvim_win_get_cursor, windows.output_win)
690+
if ok and cursor and cursor[1] <= 3 then
691+
debounced_load_more()
692+
end
693+
end
694+
end,
695+
})
692696
end
693697

694698
---Clear the output buffer and all namespaces.

lua/opencode/ui/renderer.lua

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -448,17 +448,20 @@ function M.load_more_messages()
448448
if not state.messages then
449449
return false
450450
end
451+
-- nil means no lazy limit → all messages already rendered
452+
if not ctx.lazy_render_count then
453+
return false
454+
end
451455
local total = #get_visible_session_messages(state.messages)
452456
if total == 0 then
453457
return false
454458
end
455-
local current = ctx.lazy_render_count or math.min(get_initial_render_count(), total)
456-
if current >= total then
459+
if ctx.lazy_render_count >= total then
457460
return false
458461
end
459462

460463
-- Load another viewport's worth
461-
ctx.lazy_render_count = math.min(current + get_initial_render_count(), total)
464+
ctx.lazy_render_count = math.min(ctx.lazy_render_count + get_initial_render_count(), total)
462465
M.render_from_cache(state.messages)
463466
return true
464467
end
@@ -475,8 +478,8 @@ function M.load_all_messages()
475478
if total == 0 then
476479
return false
477480
end
478-
local current = ctx.lazy_render_count or 0
479-
if current >= total then
481+
-- nil means no lazy limit → all messages already rendered
482+
if not ctx.lazy_render_count or ctx.lazy_render_count >= total then
480483
return false
481484
end
482485

tests/unit/renderer_lazy_spec.lua

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,67 @@ describe('lazy render', function()
209209
end
210210
end)
211211

212+
it('load_more_messages returns false when all messages already rendered', function()
213+
-- No lazy_render_count means all messages rendered — no unrendered content
214+
local session_data = make_session_data(50) -- 100 messages total
215+
renderer._render_full_session_data(session_data)
216+
217+
-- lazy_render_count was set by _render_full_session_data; verify the guard
218+
-- After full render with a lazy limit that covers everything, load_more returns false
219+
ctx.lazy_render_count = 100
220+
assert.is_false(renderer.load_more_messages(),
221+
'should return false when lazy_render_count covers all messages')
222+
223+
-- nil means no lazy limit at all → nothing to load
224+
ctx.lazy_render_count = nil
225+
assert.is_false(renderer.load_more_messages(),
226+
'should return false when lazy_render_count is nil')
227+
end)
228+
229+
it('load_more_messages returns true only when unrendered messages exist', function()
230+
local session_data = make_session_data(50) -- 100 messages total
231+
232+
ctx.lazy_render_count = 10
233+
renderer._render_full_session_data(session_data)
234+
235+
-- Stub render_from_cache to avoid test-env dependency
236+
local stub = require('luassert.stub')
237+
local _rfc = stub(renderer, 'render_from_cache')
238+
239+
-- 10 rendered out of 100 → has unrendered → load_more should work
240+
assert.is_true(renderer.load_more_messages(),
241+
'should return true when unrendered messages exist')
242+
243+
_rfc:revert()
244+
end)
245+
246+
it('has_unrendered gates scroll-to-top load_more', function()
247+
-- When lazy_render_count covers all messages, scrolling to top
248+
-- should NOT trigger load_more. This tests the guard condition
249+
-- that prevents spurious loads when everything is already rendered.
250+
local session_data = make_session_data(50) -- 100 messages total
251+
252+
-- Case 1: all rendered (lazy_render_count covers everything)
253+
ctx.lazy_render_count = 100
254+
renderer._render_full_session_data(session_data)
255+
assert.is_false(renderer.load_more_messages(),
256+
'no load_more when lazy_render_count covers all messages')
257+
258+
-- Case 2: partial render → load_more returns true
259+
local stub = require('luassert.stub')
260+
local _rfc = stub(renderer, 'render_from_cache')
261+
ctx.lazy_render_count = 10
262+
renderer._render_full_session_data(session_data)
263+
assert.is_true(renderer.load_more_messages(),
264+
'load_more returns true when unrendered messages exist')
265+
_rfc:revert()
266+
267+
-- Case 3: nil (never set) → load_more returns false
268+
ctx.lazy_render_count = nil
269+
assert.is_false(renderer.load_more_messages(),
270+
'no load_more when lazy_render_count is nil')
271+
end)
272+
212273
it('load_all_messages renders everything and makes it searchable', function()
213274
local session_data = make_session_data(50) -- 100 messages total
214275

0 commit comments

Comments
 (0)