Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion lua/opencode/state.lua

This file was deleted.

4 changes: 2 additions & 2 deletions lua/opencode/ui/debug_helper.lua
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ function M.debug_output()
end

function M.debug_message()
local renderer = require('opencode.ui.renderer')
local render_state = require('opencode.ui.renderer.ctx').render_state
if not state.windows or not state.windows.output_win then
vim.notify('Output window not available', vim.log.levels.WARN)
return
Expand All @@ -38,7 +38,7 @@ function M.debug_message()

-- Search backwards from current line to find nearest message
for line = current_line, 1, -1 do
local message_data = renderer._render_state:get_message_at_line(line)
local message_data = render_state:get_message_at_line(line)
if message_data and message_data.message then
M.open_json_file(message_data.message)
return
Expand Down
74 changes: 53 additions & 21 deletions lua/opencode/ui/output_window.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,37 @@ local config = require('opencode.config')
local M = {}
M.namespace = vim.api.nvim_create_namespace('opencode_output')

local _update_depth = 0
local _update_buf = nil

---Begin a batch of buffer writes — toggle modifiable once for the whole batch.
---Returns true if the batch was opened (buffer is valid). Must be paired with end_update().
---@return boolean
function M.begin_update()
local windows = state.windows
if not windows or not windows.output_buf then
return false
end
if _update_depth == 0 then
_update_buf = windows.output_buf
vim.api.nvim_set_option_value('modifiable', true, { buf = _update_buf })
end
_update_depth = _update_depth + 1
return true
Comment on lines +13 to +23
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

begin_update() toggles modifiable without verifying the output buffer is still valid. If state.windows.output_buf is stale/deleted, nvim_set_option_value will error and can break rendering. Consider checking vim.api.nvim_buf_is_valid(windows.output_buf) (or M.buffer_valid) before setting options, and wrapping the option set in pcall similarly in end_update() to avoid leaving _update_depth/modifiable in a bad state.

Copilot uses AI. Check for mistakes.
end

---End a batch started by begin_update().
function M.end_update()
if _update_depth == 0 then
return
end
_update_depth = _update_depth - 1
if _update_depth == 0 and _update_buf then
vim.api.nvim_set_option_value('modifiable', false, { buf = _update_buf })
_update_buf = nil
end
end

function M.create_buf()
local output_buf = vim.api.nvim_create_buf(false, true)
local filetype = config.ui.output.filetype or 'opencode_output'
Expand Down Expand Up @@ -165,66 +196,67 @@ function M.update_dimensions(windows)
end

function M.get_buf_line_count()
if not M.buffer_valid() then
local windows = state.windows
if not windows or not windows.output_buf or not vim.api.nvim_buf_is_valid(windows.output_buf) then
return 0
end
---@cast state.windows { output_buf: integer }

return vim.api.nvim_buf_line_count(state.windows.output_buf)
return vim.api.nvim_buf_line_count(windows.output_buf)
end

---Set the output buffer contents
---@param lines string[] The lines to set
---@param start_line? integer The starting line to set, defaults to 0
---@param end_line? integer The last line to set, defaults to -1
function M.set_lines(lines, start_line, end_line)
if not M.buffer_valid() then
local windows = state.windows
if not windows or not windows.output_buf or not vim.api.nvim_buf_is_valid(windows.output_buf) then
return
end
---@cast state.windows { output_buf: integer }

local buf = windows.output_buf
start_line = start_line or 0
end_line = end_line or -1

vim.api.nvim_set_option_value('modifiable', true, { buf = state.windows.output_buf })
vim.api.nvim_buf_set_lines(state.windows.output_buf, start_line, end_line, false, lines)
vim.api.nvim_set_option_value('modifiable', false, { buf = state.windows.output_buf })
if _update_depth == 0 then
vim.api.nvim_set_option_value('modifiable', true, { buf = buf })
vim.api.nvim_buf_set_lines(buf, start_line, end_line, false, lines)
vim.api.nvim_set_option_value('modifiable', false, { buf = buf })
else
vim.api.nvim_buf_set_lines(buf, start_line, end_line, false, lines)
end
end

---Clear output buf extmarks
---@param start_line? integer Line to start clearing, defaults 0
---@param end_line? integer Line to clear until, defaults to -1
---@param clear_all? boolean If true, clears all extmarks in the buffer
function M.clear_extmarks(start_line, end_line, clear_all)
if not M.buffer_valid() then
local windows = state.windows
if not windows or not windows.output_buf or not vim.api.nvim_buf_is_valid(windows.output_buf) then
return
end
---@cast state.windows { output_buf: integer }

start_line = start_line or 0
end_line = end_line or -1

pcall(
vim.api.nvim_buf_clear_namespace,
state.windows.output_buf,
clear_all and -1 or M.namespace,
start_line,
end_line
)
pcall(vim.api.nvim_buf_clear_namespace, windows.output_buf, clear_all and -1 or M.namespace, start_line, end_line)
end

---Apply extmarks to the output buffer
---@param extmarks table<number, OutputExtmark[]> Extmarks indexed by line
---@param line_offset? integer Line offset to apply to extmarks, defaults to 0
function M.set_extmarks(extmarks, line_offset)
if not M.buffer_valid() or not extmarks or type(extmarks) ~= 'table' then
if not extmarks or type(extmarks) ~= 'table' then
return
end
local windows = state.windows
if not windows or not windows.output_buf or not vim.api.nvim_buf_is_valid(windows.output_buf) then
return
end
---@cast state.windows { output_buf: integer }

line_offset = line_offset or 0

local output_buf = state.windows.output_buf
local output_buf = windows.output_buf

for line_idx, marks in pairs(extmarks) do
for _, mark in ipairs(marks) do
Expand Down
4 changes: 2 additions & 2 deletions lua/opencode/ui/permission_window.lua
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ function M._setup_dialog()
end

M._processing = true
require('opencode.ui.renderer').render_permissions_display()
require('opencode.ui.renderer.events').render_permissions_display()
M._clear_dialog()

local api = require('opencode.api')
Expand All @@ -236,7 +236,7 @@ function M._setup_dialog()
end

local function on_navigate()
require('opencode.ui.renderer').render_permissions_display()
require('opencode.ui.renderer.events').render_permissions_display()
end

local function get_option_count()
Expand Down
4 changes: 2 additions & 2 deletions lua/opencode/ui/question_window.lua
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ M._answering = false
M._dialog = nil

local function render_question()
require('opencode.ui.renderer').render_question_display()
require('opencode.ui.renderer.events').render_question_display()
end

local function clear_question()
require('opencode.ui.renderer').clear_question_display()
require('opencode.ui.renderer.events').clear_question_display()
end

---@param question_request OpencodeQuestionRequest
Expand Down
Loading
Loading