Skip to content
Merged
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
82 changes: 0 additions & 82 deletions docs/performance-audit.md

This file was deleted.

59 changes: 59 additions & 0 deletions lua/opencode/lru_cache.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
---@generic T
---@class LruCache<T>
---@field private capacity integer
---@field private entries table<any, { value: T, used: integer }>
---@field private size integer
---@field private clock integer
local LruCache = {}
LruCache.__index = LruCache

---@param capacity integer
---@return LruCache<T>
function LruCache.new(capacity)
assert(capacity > 0, 'cache capacity must be positive')
return setmetatable({
capacity = capacity,
entries = {},
size = 0,
clock = 0,
}, LruCache)
end

---@param key any
---@return T
function LruCache:get(key)
local entry = self.entries[key]
if not entry then
return nil
end

self.clock = self.clock + 1
entry.used = self.clock
return entry.value
end

---@param key any
---@param value T
function LruCache:set(key, value)
local entry = self.entries[key]
if not entry and self.size >= self.capacity then
local oldest_key
local oldest_use = math.huge
for cached_key, cached_entry in pairs(self.entries) do
if cached_entry.used < oldest_use then
oldest_key = cached_key
oldest_use = cached_entry.used
end
end
self.entries[oldest_key] = nil
self.size = self.size - 1
end

if not entry then
self.size = self.size + 1
end
self.clock = self.clock + 1
self.entries[key] = { value = value, used = self.clock }
end

return LruCache
2 changes: 2 additions & 0 deletions lua/opencode/types.lua
Original file line number Diff line number Diff line change
Expand Up @@ -566,9 +566,11 @@
---@field order integer Smaller values appear earlier in the session message/part/text order.

---@class SymbolSnapshotCycle
---@field warm_path fun(self: SymbolSnapshotCycle, path: string)

---@class FormatterContext
---@field interactive boolean
---@field resolve_symbol_targets? boolean
---@field get_child_parts? fun(session_id: string): OpencodeMessagePart[]?
---@field current_refs? CodeReference[]
---@field current_files? string[]
Expand Down
2 changes: 1 addition & 1 deletion lua/opencode/ui/formatter.lua
Original file line number Diff line number Diff line change
Expand Up @@ -795,7 +795,7 @@ local function add_file_reference_targets(output, rendered, rendered_reference_r
end

local function add_symbol_reference_targets(output, rendered, rendered_mention_ranges, first_line_idx, context)
if not (context and context.interactive and context.symbol_cycle) then
if not (context and context.interactive and context.resolve_symbol_targets ~= false and context.symbol_cycle) then
return {}
end

Expand Down
10 changes: 2 additions & 8 deletions lua/opencode/ui/renderer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,6 @@ function M.event_subscriptions()
{ 'file.edited', events.on_file_edited },
{ 'file.watcher.updated', events.on_file_watcher_updated },
{ 'custom.restore_point.created', events.on_restore_points },
{ 'custom.emit_events.finished', M.on_emit_events_finished },
}
end

Expand Down Expand Up @@ -422,10 +421,10 @@ function M._render_full_session_data(session_data, opts)
events.on_part_updated({ part = revert_message.parts[1] })
end

local t_format_end = vim.uv.hrtime()
flush.flush()
flush.end_bulk_mode()
local t_flush_end = vim.uv.hrtime()

events.refresh_rendered_symbol_targets()

if opts.restore_model_from_messages then
require('opencode.services.agent_model').initialize_current_model({ restore_from_messages = true })
Expand Down Expand Up @@ -600,11 +599,6 @@ end
M.reconcile_rendered_message_limit = reconcile_rendered_message_limit
M.is_message_visible = is_message_visible

---Scroll to bottom after all queued events have been processed
function M.on_emit_events_finished()
M.scroll_to_bottom()
end

---Return all actions available at a given (0-indexed) line
---@param line integer
---@return table[]
Expand Down
7 changes: 7 additions & 0 deletions lua/opencode/ui/renderer/ctx.lua
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ local ctx = {
},
flush_scheduled = false, ---@type boolean
markdown_render_scheduled = false, ---@type boolean
symbol_refresh_pending = false, ---@type boolean
symbol_refresh_token = 0, ---@type integer
symbol_refresh_cycle = nil, ---@type table?
bulk_mode = false, ---@type boolean
bulk_buffer_lines = {},
bulk_extmarks_by_line = {},
Expand Down Expand Up @@ -77,6 +80,9 @@ function ctx:reset()
}
self.flush_scheduled = false
self.markdown_render_scheduled = false
self.symbol_refresh_pending = false
self.symbol_refresh_token = self.symbol_refresh_token + 1
self.symbol_refresh_cycle = nil
self.global_folds = {}
self.part_folds = {}
self:bulk_reset()
Expand All @@ -96,6 +102,7 @@ function ctx:has_pending_work(pending)
pending = pending or self.pending

return self.flush_scheduled
or self.symbol_refresh_pending
or self.bulk_mode
or #pending.dirty_message_order > 0
or #pending.dirty_part_order > 0
Expand Down
33 changes: 6 additions & 27 deletions lua/opencode/ui/renderer/events.lua
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ local ctx = require('opencode.ui.renderer.ctx')
local prompts = ctx.prompt_controllers
local flush = require('opencode.ui.renderer.flush')
local reference_facts = require('opencode.ui.reference_facts')
local symbol_refresh = require('opencode.ui.renderer.symbol_refresh')

---@param message OpencodeMessage|nil
---@return string|nil
Expand Down Expand Up @@ -85,31 +86,6 @@ local function mark_following_assistant_text_parts_dirty(message, changed_part_i
end
end

local function mark_rendered_assistant_text_parts_dirty()
local active_session_id = state.active_session and state.active_session.id
if not active_session_id then
return
end

for part_id, part_data in pairs(ctx.render_state._parts or {}) do
local part = part_data.part
if
part
and part.type == 'text'
and part.text
and not part.synthetic
and part_data.line_start
and part_data.line_end
then
local message_data = ctx.render_state:get_message(part_data.message_id)
local message = message_data and message_data.message or find_message_in_state(part_data.message_id)
if is_assistant_message(message) and message.info.sessionID == active_session_id then
flush.mark_part_dirty(part_id, part_data.message_id)
end
end
end
end

-- Lazy require to avoid circular dependency: renderer.lua <-> events.lua
---@param force? boolean
local function scroll(force)
Expand All @@ -118,9 +94,12 @@ end

local M = {}

function M.refresh_rendered_symbol_targets()
symbol_refresh.refresh()
end

function M.invalidate_reference_targets_for_file_change()
reference_facts.refresh_current_files()
mark_rendered_assistant_text_parts_dirty()
symbol_refresh.invalidate()
end

---@param message_id string
Expand Down
8 changes: 4 additions & 4 deletions lua/opencode/ui/renderer/flush.lua
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,7 @@ 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)
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
Expand Down Expand Up @@ -251,12 +249,13 @@ end
local function new_formatter_context()
return {
interactive = true,
resolve_symbol_targets = not ctx.bulk_mode,
get_child_parts = function(session_id)
return ctx.render_state:get_child_session_parts(session_id)
end,
current_refs = reference_facts.current_refs(),
current_files = reference_facts.available_files(),
symbol_cycle = symbol_snapshot.new_cycle(),
symbol_cycle = ctx.symbol_refresh_cycle or symbol_snapshot.new_cycle(),
}
end

Expand Down Expand Up @@ -543,6 +542,7 @@ function M.resume_deferred_rendering()
M.flush()
if ctx.bulk_mode then
M.end_bulk_mode()
require('opencode.ui.renderer.events').refresh_rendered_symbol_targets()
end
M.flush_pending_on_data_rendered()
end
Expand Down
Loading
Loading