Skip to content

Commit 9151cda

Browse files
committed
refactor(renderer): remove viewport rendering and simplify virtualization
1 parent 3b7d2ef commit 9151cda

File tree

10 files changed

+102
-290
lines changed

10 files changed

+102
-290
lines changed

lua/opencode/config.lua

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,10 +151,6 @@ M.defaults = {
151151
show_reasoning_output = true,
152152
},
153153
always_scroll_to_bottom = false,
154-
viewport = {
155-
enabled = true,
156-
overscan = 8,
157-
},
158154
},
159155
questions = {
160156
use_vim_ui_select = false, -- If true, render questions with vim.ui.select instead of in the output buffer

lua/opencode/ui/renderer.lua

Lines changed: 4 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -52,41 +52,8 @@ local function normalize_session_messages(session_data)
5252
return messages
5353
end
5454

55-
local function restore_output_view(visible_render, restore_state)
56-
local windows = state.windows
57-
local output_win = windows and windows.output_win
58-
if not restore_state or not output_win or not vim.api.nvim_win_is_valid(output_win) then
59-
return false
60-
end
61-
62-
local visible_line_count = visible_render.visible_line_count or 0
63-
if visible_line_count <= 0 then
64-
return false
65-
end
66-
67-
local relative_topline = restore_state.absolute_scroll_top - (visible_render.start_line_offset or 0) + 1
68-
relative_topline = math.max(relative_topline, 1)
69-
relative_topline = math.min(relative_topline, visible_line_count)
70-
71-
local cursor_delta = math.max((restore_state.view.lnum or 1) - (restore_state.view.topline or 1), 0)
72-
local relative_cursor = math.min(relative_topline + cursor_delta, visible_line_count)
73-
relative_cursor = math.max(relative_cursor, relative_topline)
74-
75-
local view = vim.deepcopy(restore_state.view)
76-
view.topline = relative_topline
77-
view.lnum = relative_cursor
78-
view.col = math.max(view.col or 0, 0)
79-
view.leftcol = 0
80-
81-
return pcall(with_suppressed_scroll_tracking, function()
82-
vim.api.nvim_win_call(output_win, function()
83-
vim.fn.winrestview(view)
84-
end)
85-
end)
86-
end
87-
8855
---@param render_plan { strategy: 'noop'|'full'|'patch', visible_render: table, patch?: table }
89-
local function apply_render_plan(render_plan, should_scroll, restore_state)
56+
local function apply_render_plan(render_plan, should_scroll)
9057
if not render_plan or render_plan.strategy == 'noop' then
9158
return false
9259
end
@@ -98,18 +65,6 @@ local function apply_render_plan(render_plan, should_scroll, restore_state)
9865
end)
9966

10067
ctx.prev_visible_render = visible_render
101-
ctx.current_visible_render = {
102-
start_line_offset = visible_render.start_line_offset or 0,
103-
total_line_count = visible_render.total_line_count or 0,
104-
visible_line_count = visible_render.visible_line_count or 0,
105-
start_index = visible_render.start_index or 1,
106-
end_index = visible_render.end_index or #visible_render.blocks,
107-
}
108-
109-
if restore_output_view(visible_render, restore_state) then
110-
trigger_on_data_rendered()
111-
return true
112-
end
11368

11469
if should_scroll then
11570
M.scroll_to_bottom()
@@ -120,47 +75,6 @@ local function apply_render_plan(render_plan, should_scroll, restore_state)
12075
return true
12176
end
12277

123-
function M.get_viewport_render_request()
124-
local viewport_config = config.ui.output.viewport or {}
125-
if viewport_config.enabled == false or output_window.is_at_bottom() then
126-
return nil
127-
end
128-
129-
local windows = state.windows
130-
local output_win = windows and windows.output_win
131-
if not output_win or not vim.api.nvim_win_is_valid(output_win) then
132-
return nil
133-
end
134-
135-
local visible_render = ctx.current_visible_render
136-
if not visible_render then
137-
return nil
138-
end
139-
140-
local ok, view = pcall(vim.api.nvim_win_call, output_win, vim.fn.winsaveview)
141-
if not ok or type(view) ~= 'table' then
142-
return nil
143-
end
144-
145-
local win_height = vim.api.nvim_win_get_height(output_win)
146-
if win_height <= 0 then
147-
return nil
148-
end
149-
150-
local scroll_top = visible_render.start_line_offset + math.max((view.topline or 1) - 1, 0)
151-
local overscan = viewport_config.overscan
152-
if overscan == nil then
153-
overscan = math.max(win_height, 8)
154-
end
155-
156-
return {
157-
scroll_top = scroll_top,
158-
win_height = win_height,
159-
overscan = math.max(math.floor(overscan), 0),
160-
view = view,
161-
}
162-
end
163-
16478
-- Expose event handlers on M so tests can call them directly and subscriptions
16579
-- can be stubbed cleanly (e.g. stub(renderer, '_render_full_session_data'))
16680
M.on_session_updated = events.on_session_updated
@@ -315,7 +229,7 @@ function M._render_full_session_data(session_data)
315229
local set_mode_from_messages = not state.current_model
316230

317231
update_current_message_state(state.messages)
318-
local plan = planner.plan_update(nil, ctx.prev_visible_render, state.messages, M.get_viewport_render_request())
232+
local plan = planner.plan_update(nil, ctx.prev_visible_render, state.messages)
319233
apply_render_plan(plan, true)
320234

321235
if set_mode_from_messages then
@@ -334,14 +248,9 @@ function M.perform_scheduled_render(snapshot)
334248
end
335249

336250
local should_scroll = snapshot == nil or snapshot.full_render == true or output_window.is_at_bottom()
337-
local viewport_request = M.get_viewport_render_request()
338-
local restore_state = viewport_request and {
339-
absolute_scroll_top = viewport_request.scroll_top,
340-
view = viewport_request.view,
341-
} or nil
342251
update_current_message_state(state.messages)
343-
local plan = planner.plan_update(snapshot, ctx.prev_visible_render, state.messages, viewport_request)
344-
if not apply_render_plan(plan, should_scroll, restore_state) then
252+
local plan = planner.plan_update(snapshot, ctx.prev_visible_render, state.messages)
253+
if not apply_render_plan(plan, should_scroll) then
345254
return
346255
end
347256
ctx.last_rendered_session_id = state.active_session.id

lua/opencode/ui/renderer/ctx.lua

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@ local ctx = {
1010
prev_line_count = 0,
1111
---@type { blocks?: RenderBlock[] }|nil
1212
prev_visible_render = nil,
13-
---@type { start_line_offset: integer, total_line_count: integer, visible_line_count: integer, start_index: integer, end_index: integer }|nil
14-
current_visible_render = nil,
1513
---@type string|nil
1614
last_rendered_session_id = nil,
1715
---@type table<string, boolean>
@@ -30,7 +28,6 @@ function ctx:reset()
3028
self.render_state:reset()
3129
self.prev_line_count = 0
3230
self.prev_visible_render = nil
33-
self.current_visible_render = nil
3431
self.last_rendered_session_id = nil
3532
self.dirty_messages = {}
3633
self.dirty_parts = {}

lua/opencode/ui/renderer/planner.lua

Lines changed: 32 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,23 @@ local virtualize = require('opencode.ui.renderer.virtualize')
77

88
local M = {}
99

10+
local function is_idle_render()
11+
return state.jobs.is_running() == false
12+
end
13+
14+
local function get_effective_max_messages()
15+
if not is_idle_render() then
16+
return nil
17+
end
18+
19+
local max_messages = config.ui.output.max_rendered_messages
20+
if type(max_messages) ~= 'number' or max_messages <= 0 then
21+
return nil
22+
end
23+
24+
return max_messages
25+
end
26+
1027
local function same_block_output(left, right)
1128
if left == right then
1229
return true
@@ -81,23 +98,20 @@ local function same_visible_render(left, right)
8198
end
8299
end
83100

84-
return (left and left.start_line_offset or 0) == (right and right.start_line_offset or 0)
85-
and (left and left.total_line_count or 0) == (right and right.total_line_count or 0)
101+
return (left and left.total_line_count or 0) == (right and right.total_line_count or 0)
86102
and (left and left.visible_line_count or 0) == (right and right.visible_line_count or 0)
87103
and (left and left.start_index or 1) == (right and right.start_index or 1)
88104
and (left and left.end_index or #left_blocks) == (right and right.end_index or #right_blocks)
89105
and (left and left.hidden_message_count or 0) == (right and right.hidden_message_count or 0)
90106
end
91107

92108
local function get_visible_message_id_set_for_tail(messages)
93-
local max_messages = config.ui.output.max_rendered_messages
94-
if type(max_messages) ~= 'number' or max_messages <= 0 then
109+
local max_messages = get_effective_max_messages()
110+
if max_messages == nil then
95111
return nil
96112
end
97113

98-
if config.ui.output.max_rendered_lines ~= nil then
99-
return nil
100-
end
114+
-- no-op: max_rendered_lines support removed
101115

102116
local visible_ids = {}
103117
local visible_count = math.min(max_messages, #(messages or {}))
@@ -114,12 +128,12 @@ local function get_visible_message_id_set_for_tail(messages)
114128
return visible_ids
115129
end
116130

117-
local function snapshot_affects_visible_tail(snapshot, messages, viewport_request)
131+
local function snapshot_affects_visible_tail(snapshot, messages)
118132
if not snapshot then
119133
return true
120134
end
121135

122-
if snapshot.full_render == true or viewport_request ~= nil then
136+
if snapshot.full_render == true then
123137
return true
124138
end
125139

@@ -167,24 +181,18 @@ local function find_revert_index(messages)
167181
end
168182

169183
---@param messages OpencodeMessage[]
170-
---@param viewport_request? { scroll_top?: integer, win_height?: integer, overscan?: integer, view?: table }
171184
---@return {
172185
--- blocks: RenderBlock[],
173186
--- start_index: integer,
174187
--- end_index: integer,
175-
--- start_line_offset: integer,
176188
--- hidden_block_count: integer,
177-
--- leading_hidden_block_count: integer,
178-
--- trailing_hidden_block_count: integer,
179189
--- hidden_message_count: integer,
180-
--- leading_hidden_message_count: integer,
181-
--- trailing_hidden_message_count: integer,
182190
--- total_line_count: integer,
183191
--- visible_line_count: integer
184192
---}
185-
function M.plan_visible_render(messages, viewport_request)
193+
function M.plan_visible_render(messages)
186194
local revert_index = find_revert_index(messages)
187-
local max_messages = config.ui.output.max_rendered_messages
195+
local max_messages = get_effective_max_messages()
188196

189197
local visible_messages = messages
190198
if revert_index then
@@ -200,7 +208,7 @@ function M.plan_visible_render(messages, viewport_request)
200208
end
201209

202210
local base_blocks = session_view.build_blocks(visible_messages, {
203-
max_messages = viewport_request and nil or max_messages,
211+
max_messages = max_messages,
204212
get_child_parts = function(session_id)
205213
return ctx.render_state:get_child_session_parts(session_id)
206214
end,
@@ -211,11 +219,10 @@ function M.plan_visible_render(messages, viewport_request)
211219
})
212220

213221
local visible_render = virtualize.select_visible_blocks(base_blocks, {
214-
max_lines = viewport_request and nil or config.ui.output.max_rendered_lines,
215-
viewport = viewport_request,
222+
max_messages = max_messages,
216223
})
217224

218-
if not viewport_request and tail_hidden_message_count > 0 then
225+
if tail_hidden_message_count > 0 then
219226
local hidden_block = overlays.render_hidden_history({
220227
hidden_count = tail_hidden_message_count,
221228
})
@@ -225,9 +232,7 @@ function M.plan_visible_render(messages, viewport_request)
225232
end
226233
end
227234

228-
if not viewport_request then
229-
visible_render.hidden_message_count = tail_hidden_message_count
230-
end
235+
visible_render.hidden_message_count = tail_hidden_message_count
231236

232237
return visible_render
233238
end
@@ -282,17 +287,16 @@ end
282287
---@param snapshot? { full_render?: boolean, dirty_messages?: table<string, boolean>, dirty_parts?: table<string, boolean> }
283288
---@param prev_visible_render? { blocks?: RenderBlock[] }
284289
---@param messages OpencodeMessage[]
285-
---@param viewport_request? { scroll_top?: integer, win_height?: integer, overscan?: integer, view?: table }
286290
---@return { strategy: 'noop'|'full'|'patch', visible_render: table, patch?: table }
287-
function M.plan_update(snapshot, prev_visible_render, messages, viewport_request)
288-
if snapshot_affects_visible_tail(snapshot, messages, viewport_request) == false then
291+
function M.plan_update(snapshot, prev_visible_render, messages)
292+
if snapshot_affects_visible_tail(snapshot, messages) == false then
289293
return {
290294
strategy = 'noop',
291295
visible_render = prev_visible_render or { blocks = {} },
292296
}
293297
end
294298

295-
local next_visible_render = M.plan_visible_render(messages, viewport_request)
299+
local next_visible_render = M.plan_visible_render(messages)
296300
return M.plan_apply(prev_visible_render, next_visible_render)
297301
end
298302

lua/opencode/ui/renderer/viewport.lua

Lines changed: 0 additions & 56 deletions
This file was deleted.

0 commit comments

Comments
 (0)