Skip to content

Commit 2b1eeda

Browse files
committed
refactor(renderer): remove prev_line_count; extract scrolling logic and simplify flush
Remove unused ctx.prev_line_count from the renderer context and reset. Simplify M.flush() to only request data-rendered when changes were applied and the renderer is not in bulk_mode. Extract window-scrolling into M.scroll_win_to_bottom() and reuse it from post_flush(). tests: make replay deterministic and robust for bulk mode Sort extmarks deterministically in test helpers, clear/reset UI and renderer state during replay setup, and force synchronous completion (flush.end_bulk_mode()) in replay tests when ctx.bulk_mode is active so assertions run against final output.
1 parent 5510dc2 commit 2b1eeda

5 files changed

Lines changed: 78 additions & 41 deletions

File tree

lua/opencode/ui/renderer/ctx.lua

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ local RenderState = require('opencode.ui.render_state')
66
local ctx = {
77
---@type RenderState
88
render_state = RenderState.new(),
9-
---@type integer
10-
prev_line_count = 0,
119
---@type { part_id: string|nil, formatted_data: Output|nil }
1210
last_part_formatted = { part_id = nil, formatted_data = nil },
1311
---@type table<string, Output>
@@ -34,7 +32,6 @@ local ctx = {
3432

3533
function ctx:reset()
3634
self.render_state:reset()
37-
self.prev_line_count = 0
3835
self.last_part_formatted = { part_id = nil, formatted_data = nil }
3936
self.formatted_parts = {}
4037
self.formatted_messages = {}

lua/opencode/ui/renderer/flush.lua

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -418,18 +418,8 @@ end
418418
function M.flush()
419419
local pending = snapshot_pending()
420420
local applied = apply_pending(pending)
421-
if applied then
422-
local windows = state.windows
423-
local output_buf = windows and windows.output_buf
424-
if output_buf and vim.api.nvim_buf_is_valid(output_buf) then
425-
local ok, line_count = pcall(vim.api.nvim_buf_line_count, output_buf)
426-
if ok then
427-
ctx.prev_line_count = line_count
428-
end
429-
end
430-
if not ctx.bulk_mode then
431-
M.request_on_data_rendered()
432-
end
421+
if applied and not ctx.bulk_mode then
422+
M.request_on_data_rendered()
433423
end
434424
end
435425

lua/opencode/ui/renderer/scroll.lua

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,21 @@ function M.get_output_win()
1414
return win
1515
end
1616

17+
---Move the cursor in `win` to the last line of `buf` and scroll so it's visible.
18+
---@param win integer
19+
---@param buf integer
20+
function M.scroll_win_to_bottom(win, buf)
21+
local line_count = vim.api.nvim_buf_line_count(buf)
22+
if line_count == 0 then
23+
return
24+
end
25+
local last_line = vim.api.nvim_buf_get_lines(buf, line_count - 1, line_count, false)[1] or ''
26+
vim.api.nvim_win_set_cursor(win, { line_count, #last_line })
27+
vim.api.nvim_win_call(win, function()
28+
vim.cmd('normal! zb')
29+
end)
30+
end
31+
1732
---@param buf integer|nil
1833
---@return { win: integer, follow: boolean }|nil
1934
function M.pre_flush(buf)
@@ -41,17 +56,7 @@ function M.post_flush(snapshot, buf)
4156
if not vim.api.nvim_win_is_valid(snapshot.win) or vim.api.nvim_win_get_buf(snapshot.win) ~= buf then
4257
return
4358
end
44-
45-
local line_count = vim.api.nvim_buf_line_count(buf)
46-
if line_count == 0 then
47-
return
48-
end
49-
50-
local last_line = vim.api.nvim_buf_get_lines(buf, line_count - 1, line_count, false)[1] or ''
51-
vim.api.nvim_win_set_cursor(snapshot.win, { line_count, #last_line })
52-
vim.api.nvim_win_call(snapshot.win, function()
53-
vim.cmd('normal! zb')
54-
end)
59+
M.scroll_win_to_bottom(snapshot.win, buf)
5560
end
5661

5762
return M

tests/helpers.lua

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,28 @@ function M.replay_setup()
1111
local state = require('opencode.state')
1212
local ui = require('opencode.ui.ui')
1313
local renderer = require('opencode.ui.renderer')
14+
local permission_window = require('opencode.ui.permission_window')
15+
local question_window = require('opencode.ui.question_window')
16+
local reference_picker = require('opencode.ui.reference_picker')
1417

1518
local empty_promise = require('opencode.promise').new():resolve(nil)
1619
config_file.config_promise = empty_promise
1720
config_file.project_promise = empty_promise
1821
config_file.providers_promise = empty_promise
1922

23+
if state.windows then
24+
ui.close_windows(state.windows)
25+
end
26+
27+
renderer.reset()
28+
permission_window.clear_all()
29+
question_window._clear_dialog()
30+
question_window._current_question = nil
31+
question_window._current_question_index = 1
32+
question_window._collected_answers = {}
33+
question_window._answering = false
34+
reference_picker.clear_all()
35+
2036
---@diagnostic disable-next-line: duplicate-set-field
2137
require('opencode.session').project_id = function()
2238
return nil
@@ -323,9 +339,28 @@ function M.normalize_namespace_ids(extmarks)
323339
end
324340

325341
function M.capture_output(output_buf, namespace)
342+
local extmarks = vim.api.nvim_buf_get_extmarks(output_buf, namespace, 0, -1, { details = true }) or {}
343+
table.sort(extmarks, function(a, b)
344+
if a[2] ~= b[2] then
345+
return a[2] < b[2]
346+
end
347+
348+
if a[3] ~= b[3] then
349+
return a[3] < b[3]
350+
end
351+
352+
local a_priority = a[4] and a[4].priority or 0
353+
local b_priority = b[4] and b[4].priority or 0
354+
if a_priority ~= b_priority then
355+
return a_priority > b_priority
356+
end
357+
358+
return a[1] < b[1]
359+
end)
360+
326361
return {
327362
lines = vim.api.nvim_buf_get_lines(output_buf, 0, -1, false) or {},
328-
extmarks = vim.api.nvim_buf_get_extmarks(output_buf, namespace, 0, -1, { details = true }) or {},
363+
extmarks = extmarks,
329364
actions = vim.deepcopy(require('opencode.ui.renderer.ctx').render_state:get_all_actions()),
330365
}
331366
end

tests/replay/renderer_spec.lua

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -277,20 +277,30 @@ describe('renderer functional tests', function()
277277
)
278278
end
279279

280-
if not vim.tbl_contains(skip_full_session, name) then
281-
it('replays ' .. name .. ' correctly (session)', function()
282-
local renderer = require('opencode.ui.renderer')
283-
local events = helpers.load_test_data(filepath)
284-
state.session.set_active(helpers.get_session_from_events(events, true))
285-
local expected = helpers.load_test_data(expected_path)
286-
287-
local session_data = helpers.load_session_from_events(events)
288-
renderer._render_full_session_data(session_data)
289-
290-
local actual = helpers.capture_output(state.windows and state.windows.output_buf, output_window.namespace)
291-
assert_output_matches(expected, actual, name)
292-
end)
293-
end
280+
if not vim.tbl_contains(skip_full_session, name) then
281+
it('replays ' .. name .. ' correctly (session)', function()
282+
local renderer = require('opencode.ui.renderer')
283+
local flush = require('opencode.ui.renderer.flush')
284+
local ctx = require('opencode.ui.renderer.ctx')
285+
local events = helpers.load_test_data(filepath)
286+
state.session.set_active(helpers.get_session_from_events(events, true))
287+
local expected = helpers.load_test_data(expected_path)
288+
289+
local session_data = helpers.load_session_from_events(events)
290+
renderer._render_full_session_data(session_data)
291+
292+
-- If bulk mode is active (async writing), wait for it to complete
293+
-- by forcing synchronous completion
294+
if ctx.bulk_mode then
295+
-- Force synchronous completion by calling end_bulk_mode directly
296+
-- This ensures all content is written before we check
297+
flush.end_bulk_mode()
298+
end
299+
300+
local actual = helpers.capture_output(state.windows and state.windows.output_buf, output_window.namespace)
301+
assert_output_matches(expected, actual, name)
302+
end)
303+
end
294304
end
295305
end
296306
end

0 commit comments

Comments
 (0)