Skip to content

Commit 06c709a

Browse files
authored
feat(ui/output): add max_messages option to limit rendered messages (#354)
* feat(ui/output): add max_messages option to limit rendered messages Add support for ui.output.max_messages to cap how many messages are kept rendered in the output buffer. When the configured limit is exceeded the oldest messages for the active session are evicted and a hidden-messages notice is inserted/updated to indicate how many older messages are not displayed. This should fix #320 * feat(output): add toggle for maximum rendered messages Add a workflow action and command to toggle ui.output.max_messages between a finite number and nil (no limit). Wire the action into the API, keymaps (<leader>otm), types, and UI so users can enable/disable the message limit from the output notice or via command. The toggle notifies the user of the current state. Also ensure render_output returns its promise/value so callers can await rendering when requested. * fix: re-render ui when toggling max messages
1 parent 140ea77 commit 06c709a

11 files changed

Lines changed: 585 additions & 17 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,7 @@ require('opencode').setup({
243243
markdown_debounce_ms = 250, -- Debounce time for markdown rendering on new data (default: 250ms)
244244
on_data_rendered = nil, -- Called when new data is rendered; set to false to disable default RenderMarkdown/Markview behavior
245245
},
246+
max_messages = nil, -- Max number of messages to keep in the output buffer; older messages will be removed as new ones arrive (default: nil, which means no limit)
246247
},
247248
input = {
248249
min_height = 0.10, -- min height of prompt input as percentage of window height

lua/opencode/api.lua

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ local action_groups = {
8989
debug_session = workflow.debug_session,
9090
toggle_tool_output = workflow.toggle_tool_output,
9191
toggle_reasoning_output = workflow.toggle_reasoning_output,
92+
toggle_max_messages = workflow.toggle_max_messages,
9293
submit_input_prompt = workflow.submit_input_prompt,
9394
run = workflow.run,
9495
run_new_session = workflow.run_new_session,

lua/opencode/commands/handlers/workflow.lua

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,23 @@ function M.actions.toggle_reasoning_output()
283283
ui.render_output()
284284
end
285285

286+
local original_max_messages = config.ui.output.max_messages
287+
function M.actions.toggle_max_messages()
288+
local current = config.ui.output.max_messages
289+
local next_val
290+
if type(current) == 'number' and current > 0 then
291+
next_val = nil
292+
else
293+
next_val = original_max_messages or 20
294+
end
295+
296+
local action_text = next_val == nil and 'Disabling' or 'Enabling'
297+
local val_text = next_val == nil and 'none' or tostring(next_val)
298+
vim.notify(action_text .. ' message limit to ' .. val_text, vim.log.levels.INFO)
299+
config.values.ui.output.max_messages = next_val
300+
ui.render_output()
301+
end
302+
286303
M.actions.review = Promise.async(function(args)
287304
local new_session = core.create_new_session('Code review checklist for diffs and PRs'):await()
288305
if not new_session then
@@ -453,6 +470,10 @@ M.command_defs = {
453470
desc = 'Toggle reasoning output visibility in the output window',
454471
execute = M.actions.toggle_reasoning_output,
455472
},
473+
toggle_max_messages = {
474+
desc = 'Toggle maximum number of rendered messages',
475+
execute = M.actions.toggle_max_messages,
476+
},
456477
paste_image = {
457478
desc = 'Paste image from clipboard and add to context',
458479
execute = M.actions.paste_image,

lua/opencode/config.lua

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ M.defaults = {
5656
['<leader>ox'] = { 'swap_position', desc = 'Swap window position' },
5757
['<leader>otr'] = { 'toggle_reasoning_output', desc = 'Toggle reasoning output' },
5858
['<leader>ott'] = { 'toggle_tool_output', desc = 'Toggle tool output' },
59+
['<leader>otm'] = { 'toggle_max_messages', desc = 'Toggle max messages' },
5960
['<leader>o/'] = { 'quick_chat', mode = { 'n', 'x' }, desc = 'Quick chat with current context' },
61+
6062
},
6163
output_window = {
6264
['<esc>'] = { 'close', desc = 'Close Opencode windows' },
@@ -155,6 +157,7 @@ M.defaults = {
155157
show_output = true,
156158
show_reasoning_output = true,
157159
},
160+
max_messages = nil,
158161
always_scroll_to_bottom = false,
159162
},
160163
questions = {

lua/opencode/types.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -244,6 +244,7 @@
244244
---@class OpencodeUIOutputConfig
245245
---@field tools { show_output: boolean, show_reasoning_output: boolean }
246246
---@field rendering OpencodeUIOutputRenderingConfig
247+
---@field max_messages integer|nil
247248
---@field always_scroll_to_bottom boolean
248249
---@field filetype string
249250
---@field compact_assistant_headers boolean | 'minimal' | 'hidden' | 'full'
@@ -498,7 +499,7 @@
498499

499500
---@class OutputAction
500501
---@field text string Action text
501-
---@field type 'diff_revert_all'|'diff_revert_selected_file'|'diff_open'|'diff_restore_snapshot_file'|'diff_restore_snapshot_all'|'select_child_session' Type of action
502+
---@field type 'diff_revert_all'|'diff_revert_selected_file'|'diff_open'|'diff_restore_snapshot_file'|'diff_restore_snapshot_all'|'select_child_session'|'toggle_max_messages'
502503
---@field args? string[] Optional arguments for the command
503504
---@field key string keybinding for the action
504505
---@field display_line number Line number to display the action

lua/opencode/ui/formatter.lua

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,26 @@ function M._format_revert_message(session_data, start_idx)
9999
return output
100100
end
101101

102+
---@param hidden_count integer
103+
---@return Output
104+
function M._format_hidden_messages_notice(hidden_count)
105+
local output = Output.new()
106+
local message_text = hidden_count == 1 and 'message is' or 'messages are'
107+
108+
output:add_line(string.format('> %d older %s not displayed.', hidden_count, message_text))
109+
output:add_action({
110+
text = 'Show [A]ll messages',
111+
type = 'toggle_max_messages',
112+
args = {},
113+
key = 'A',
114+
display_line = output:get_line_count() - 1,
115+
range = { from = output:get_line_count() - 1, to = output:get_line_count() - 1 },
116+
})
117+
output:add_empty_line()
118+
119+
return output
120+
end
121+
102122
---@param output Output
103123
---@param text string
104124
---@param action_type string
@@ -167,6 +187,10 @@ function M.format_message_header(message, previous_message)
167187
return output
168188
end
169189

190+
if message.info and message.info.id == '__opencode_hidden_messages_notice__' then
191+
return output
192+
end
193+
170194
local role = message.info.role or 'unknown'
171195
local icon = message.info.role == 'user' and icons.get('header_user') or icons.get('header_assistant')
172196

@@ -674,6 +698,12 @@ function M.format_part(part, message, is_last_part, get_child_parts)
674698
output = M._format_revert_message(state.messages or {}, revert_index)
675699
content_added = output:get_line_count() > 0
676700
end
701+
elseif part.type == 'hidden-messages-display' then
702+
local hidden_count = part.state and part.state.hidden_count
703+
if type(hidden_count) == 'number' and hidden_count > 0 then
704+
output = M._format_hidden_messages_notice(hidden_count)
705+
content_added = output:get_line_count() > 0
706+
end
677707
end
678708
end
679709

0 commit comments

Comments
 (0)