diff --git a/lua/opencode/ui/output_window.lua b/lua/opencode/ui/output_window.lua index 5bda4196..8833584d 100644 --- a/lua/opencode/ui/output_window.lua +++ b/lua/opencode/ui/output_window.lua @@ -732,6 +732,15 @@ function M.setup_autocmds(windows, group) end, }) + vim.api.nvim_create_autocmd('TabEnter', { + group = group, + callback = function() + if state.ui.is_window_in_current_tab(windows.output_win) then + require('opencode.ui.renderer.flush').resume_deferred_rendering() + end + end, + }) + vim.api.nvim_create_autocmd('BufEnter', { group = group, buffer = windows.output_buf, diff --git a/lua/opencode/ui/renderer.lua b/lua/opencode/ui/renderer.lua index 52fce068..bba36d58 100644 --- a/lua/opencode/ui/renderer.lua +++ b/lua/opencode/ui/renderer.lua @@ -551,6 +551,9 @@ function M.scroll_to_bottom(force) if not vim.api.nvim_win_is_valid(output_win) then return end + if not state.ui.is_window_in_current_tab(output_win) then + return + end if force or config.ui.output.always_scroll_to_bottom or output_window.is_at_bottom(output_win) then scroll.scroll_win_to_bottom(output_win, output_buf) diff --git a/lua/opencode/ui/renderer/flush.lua b/lua/opencode/ui/renderer/flush.lua index 1fa54ce5..bc5ee14c 100644 --- a/lua/opencode/ui/renderer/flush.lua +++ b/lua/opencode/ui/renderer/flush.lua @@ -12,6 +12,13 @@ local append = require('opencode.ui.renderer.append') local M = {} local warned_part_render_error = false +local function output_window_is_in_background_tab() + local output_win = state.windows and state.windows.output_win + return output_win + and vim.api.nvim_win_is_valid(output_win) + and not state.ui.is_window_in_current_tab(output_win) +end + ---@param part_id string ---@param message_id string|nil ---@param err any @@ -462,15 +469,27 @@ local function do_trigger_on_data_rendered() if not state.windows or not state.windows.output_buf or not state.windows.output_win then return end - vim.b[state.windows.output_buf].opencode_markdown_namespace = output_window.markdown_namespace + local output_buf = state.windows.output_buf + local output_win = state.windows.output_win + if + not vim.api.nvim_buf_is_valid(output_buf) + or not vim.api.nvim_win_is_valid(output_win) + or vim.api.nvim_win_get_buf(output_win) ~= output_buf + then + return + end + vim.b[output_buf].opencode_markdown_namespace = output_window.markdown_namespace if cb_type == 'function' then - pcall(config.ui.output.rendering.on_data_rendered, state.windows.output_buf, state.windows.output_win) + pcall(config.ui.output.rendering.on_data_rendered, output_buf, output_win) + elseif not state.ui.is_window_in_current_tab(output_win) then + ctx.markdown_render_scheduled = true + return elseif vim.fn.exists(':RenderMarkdown') > 0 then - vim.api.nvim_buf_call(state.windows.output_buf, function() + vim.api.nvim_win_call(output_win, function() vim.cmd(':RenderMarkdown buf_enable') end) elseif vim.fn.exists(':Markview') > 0 then - vim.cmd(':Markview render ' .. state.windows.output_buf) + vim.cmd(':Markview render ' .. output_buf) end end @@ -509,6 +528,9 @@ function M.end_bulk_mode() if not ctx.bulk_mode then return end + if output_window_is_in_background_tab() then + return + end ctx.bulk_mode = false local lines = ctx.bulk_buffer_lines if #lines == 0 then @@ -554,6 +576,9 @@ end ---Flush all pending renderer changes to the output buffer. function M.flush() + if output_window_is_in_background_tab() then + return + end local pending = snapshot_pending() local applied = apply_pending(pending, new_formatter_context()) if applied and not ctx.bulk_mode then @@ -561,4 +586,13 @@ function M.flush() end end +---Apply renderer work deferred while the output window was in another tab. +function M.resume_deferred_rendering() + M.flush() + if ctx.bulk_mode then + M.end_bulk_mode() + end + M.flush_pending_on_data_rendered() +end + return M diff --git a/tests/unit/services_session_runtime_spec.lua b/tests/unit/services_session_runtime_spec.lua index 9cebc615..9df6066e 100644 --- a/tests/unit/services_session_runtime_spec.lua +++ b/tests/unit/services_session_runtime_spec.lua @@ -656,7 +656,7 @@ describe('opencode.services.session_runtime', function() end) describe('markdown rendering metadata', function() - it('stores the markdown namespace on the output buffer before rendering', function() + it('defers markdown rendering until the output tab becomes current', function() local output_window = require('opencode.ui.output_window') local buf = vim.api.nvim_create_buf(false, true) local win = vim.api.nvim_open_win(buf, false, { @@ -671,9 +671,19 @@ describe('opencode.services.session_runtime', function() state.ui.set_windows({ output_buf = buf, output_win = win }) vim.api.nvim_buf_set_var(buf, 'opencode_markdown_namespace', 0) + local output_tab = vim.api.nvim_get_current_tabpage() + vim.cmd('tabnew') + local current_tab = vim.api.nvim_get_current_tabpage() + local current_win = vim.api.nvim_get_current_win() + local defer_stub = stub(vim, 'defer_fn').invokes(function(cb) cb() - return 1 + return { + is_closing = function() + return false + end, + close = function() end, + } end) local original_exists = vim.fn.exists vim.fn.exists = function(name) @@ -687,12 +697,60 @@ describe('opencode.services.session_runtime', function() flush.trigger_on_data_rendered() assert.equals(output_window.markdown_namespace, vim.b[buf].opencode_markdown_namespace) + assert.stub(cmd_stub).was_not_called() + assert.equals(current_tab, vim.api.nvim_get_current_tabpage()) + assert.equals(current_win, vim.api.nvim_get_current_win()) + + vim.api.nvim_set_current_tabpage(output_tab) + flush.flush_pending_on_data_rendered() + assert.stub(cmd_stub).was_called_with(':RenderMarkdown buf_enable') cmd_stub:revert() defer_stub:revert() vim.fn.exists = original_exists state.ui.set_windows(nil) + vim.api.nvim_set_current_tabpage(current_tab) + vim.cmd('tabclose') + pcall(vim.api.nvim_win_close, win, true) + pcall(vim.api.nvim_buf_delete, buf, { force = true }) + end) + + it('defers output buffer writes while the output window is in another tab', function() + local ctx = require('opencode.ui.renderer.ctx') + local buf = vim.api.nvim_create_buf(false, true) + local win = vim.api.nvim_open_win(buf, false, { + relative = 'editor', + width = 20, + height = 5, + row = 0, + col = 0, + style = 'minimal', + }) + local output_tab = vim.api.nvim_get_current_tabpage() + + state.ui.set_windows({ output_buf = buf, output_win = win }) + ctx:reset() + flush.begin_bulk_mode() + ctx.bulk_buffer_lines = { 'deferred output' } + + vim.cmd('tabnew') + local background_tab = vim.api.nvim_get_current_tabpage() + flush.end_bulk_mode() + + assert.same({ '' }, vim.api.nvim_buf_get_lines(buf, 0, -1, false)) + assert.is_true(ctx.bulk_mode) + + vim.api.nvim_set_current_tabpage(output_tab) + flush.resume_deferred_rendering() + + assert.same({ 'deferred output', '' }, vim.api.nvim_buf_get_lines(buf, 0, -1, false)) + assert.is_false(ctx.bulk_mode) + + ctx:reset() + state.ui.set_windows(nil) + vim.api.nvim_set_current_tabpage(background_tab) + vim.cmd('tabclose') pcall(vim.api.nvim_win_close, win, true) pcall(vim.api.nvim_buf_delete, buf, { force = true }) end)