Skip to content

Commit 23f384e

Browse files
Danilo Verde RibeiroDanilo Verde Ribeiro
authored andcommitted
fix(git): missing files in context module
1 parent df83776 commit 23f384e

3 files changed

Lines changed: 377 additions & 29 deletions

File tree

lua/opencode/context.lua

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -23,17 +23,17 @@ local function filter_path_privacy(path)
2323
if not path or path == '' then
2424
return path
2525
end
26-
26+
2727
-- Check if privacy filtering is enabled in config
2828
if config.context and config.context.privacy_filter and config.context.privacy_filter.enabled == false then
2929
return path
3030
end
31-
31+
3232
-- If path is within project, return as-is
3333
if is_in_cwd(path) then
3434
return path
3535
end
36-
36+
3737
-- Redact paths outside project root
3838
local basename = vim.fn.fnamemodify(path, ':t')
3939
return '[EXTERNAL]/' .. basename
@@ -44,12 +44,12 @@ local function contains_secret(content)
4444
if not content or content == '' then
4545
return false
4646
end
47-
47+
4848
-- Check if secret filtering is enabled in config
4949
if config.context and config.context.secret_filter and config.context.secret_filter.enabled == false then
5050
return false
5151
end
52-
52+
5353
-- Common secret patterns
5454
local secret_patterns = {
5555
-- API keys and tokens (long alphanumeric strings)
@@ -65,13 +65,13 @@ local function contains_secret(content)
6565
-- Private keys
6666
'%-%-%-%-%-BEGIN [%w%s]+ PRIVATE KEY%-%-%-%-%-',
6767
}
68-
68+
6969
for _, pattern in ipairs(secret_patterns) do
7070
if content:match(pattern) then
7171
return true
7272
end
7373
end
74-
74+
7575
return false
7676
end
7777

@@ -351,20 +351,20 @@ function M.get_current_file()
351351
if not file or file == '' or vim.fn.filereadable(file) ~= 1 then
352352
return nil
353353
end
354-
354+
355355
local bufnr = vim.api.nvim_get_current_buf()
356356
local result = {
357357
path = filter_path_privacy(file),
358358
name = vim.fn.fnamemodify(file, ':t'),
359359
extension = vim.fn.fnamemodify(file, ':e'),
360360
}
361-
361+
362362
-- Add filetype if available
363363
local filetype = vim.bo[bufnr].filetype
364364
if filetype and filetype ~= '' then
365365
result.filetype = filetype
366366
end
367-
367+
368368
-- Add LSP client names if available
369369
local clients = vim.lsp.get_active_clients({ bufnr = bufnr })
370370
if #clients > 0 then
@@ -373,7 +373,7 @@ function M.get_current_file()
373373
table.insert(result.lsp_clients, client.name)
374374
end
375375
end
376-
376+
377377
return result
378378
end
379379

@@ -529,7 +529,7 @@ function M.get_undo_history()
529529
-- Check if current buffer has unsaved changes
530530
local bufnr = vim.api.nvim_get_current_buf()
531531
local has_unsaved_changes = vim.bo[bufnr].modified
532-
532+
533533
-- Count total unsaved changes across all buffers
534534
local unsaved_buffers = {}
535535
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
@@ -544,12 +544,14 @@ function M.get_undo_history()
544544
end
545545
end
546546

547-
return #result > 0 and {
548-
entries = result,
549-
seq_cur = undotree.seq_cur,
550-
current_buffer_modified = has_unsaved_changes,
551-
unsaved_buffers = #unsaved_buffers > 0 and unsaved_buffers or nil,
552-
} or nil
547+
return #result > 0
548+
and {
549+
entries = result,
550+
seq_cur = undotree.seq_cur,
551+
current_buffer_modified = has_unsaved_changes,
552+
unsaved_buffers = #unsaved_buffers > 0 and unsaved_buffers or nil,
553+
}
554+
or nil
553555
end
554556

555557
-- Get window and tab context
@@ -909,27 +911,27 @@ function M.get_git_info()
909911
local status_ok, status = pcall(vim.fn.systemlist, 'git status --porcelain 2>/dev/null')
910912
if status_ok and status then
911913
result.is_dirty = #status > 0
912-
914+
913915
-- Count staged and unstaged changes
914916
local staged_count = 0
915917
local unstaged_count = 0
916918
for _, line in ipairs(status) do
917919
if line and line ~= '' then
918920
local index_status = line:sub(1, 1)
919921
local work_status = line:sub(2, 2)
920-
922+
921923
-- Staged changes (index column not space or ?)
922924
if index_status ~= ' ' and index_status ~= '?' then
923925
staged_count = staged_count + 1
924926
end
925-
927+
926928
-- Unstaged changes (work tree column not space)
927929
if work_status ~= ' ' then
928930
unstaged_count = unstaged_count + 1
929931
end
930932
end
931933
end
932-
934+
933935
if staged_count > 0 then
934936
result.staged_changes = staged_count
935937
end
@@ -1616,37 +1618,37 @@ function M.get_recent_buffers(prompt, opts)
16161618
lastused = buf.lastused,
16171619
changed = buf.changed == 1,
16181620
}
1619-
1621+
16201622
-- Add filetype if available (only for valid buffers)
16211623
if vim.api.nvim_buf_is_valid(buf.bufnr) then
16221624
local filetype = vim.bo[buf.bufnr].filetype
16231625
if filetype and filetype ~= '' then
16241626
buf_entry.filetype = filetype
16251627
end
1626-
1628+
16271629
-- Add LSP client names if available
16281630
local clients = vim.lsp.get_active_clients({ bufnr = buf.bufnr })
16291631
if #clients > 0 then
16301632
buf_entry.lsp_clients = {}
16311633
for _, client in ipairs(clients) do
1632-
table.insert(buf_entry.lsp_clients, client.name)
1634+
table.insert(buf_entry.lsp_clients, client.name)
1635+
end
16331636
end
16341637
end
1635-
end
1636-
1638+
16371639
table.insert(result, buf_entry)
16381640
end
16391641

16401642
local recent_conf = config.context.recent_buffers
16411643
if recent_conf and recent_conf.symbols_only then
16421644
for _, buf_entry in ipairs(result) do
16431645
local bufnr = buf_entry.bufnr
1644-
1646+
16451647
-- Skip if buffer is not valid
16461648
if not vim.api.nvim_buf_is_valid(bufnr) then
16471649
goto continue
16481650
end
1649-
1651+
16501652
local line_count = vim.api.nvim_buf_line_count(bufnr)
16511653
if line_count <= 100 then
16521654
goto continue

lua/opencode/context_cache.lua

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
-- TTL-based cache for context sections
2+
-- Provides per-section caching with configurable TTL and async job management
3+
--
4+
5+
local M = {}
6+
7+
-- Cache structure: { [section_name] = { timestamp = number, data = any, job_id = number|nil } }
8+
M.cache = {}
9+
10+
-- Default TTL in milliseconds (5 seconds)
11+
M.DEFAULT_TTL = 5000
12+
13+
-- Async job tracking
14+
M.jobs = {}
15+
16+
---Check if cached data is still valid
17+
---@param section_name string
18+
---@param ttl? number TTL in milliseconds (defaults to DEFAULT_TTL)
19+
---@return boolean
20+
function M.is_valid(section_name, ttl)
21+
ttl = ttl or M.DEFAULT_TTL
22+
local cached = M.cache[section_name]
23+
if not cached then
24+
return false
25+
end
26+
27+
local now = vim.loop.hrtime() / 1e6 -- Convert to milliseconds
28+
return (now - cached.timestamp) < ttl
29+
end
30+
31+
---Get cached data if valid
32+
---@param section_name string
33+
---@param ttl? number TTL in milliseconds
34+
---@return any|nil
35+
function M.get(section_name, ttl)
36+
if M.is_valid(section_name, ttl) then
37+
return M.cache[section_name].data
38+
end
39+
return nil
40+
end
41+
42+
---Set cached data
43+
---@param section_name string
44+
---@param data any
45+
function M.set(section_name, data)
46+
local now = vim.loop.hrtime() / 1e6
47+
M.cache[section_name] = {
48+
timestamp = now,
49+
data = data,
50+
job_id = nil,
51+
}
52+
end
53+
54+
---Clear specific cache entry
55+
---@param section_name string
56+
function M.clear(section_name)
57+
M.cache[section_name] = nil
58+
end
59+
60+
---Clear all cache
61+
function M.clear_all()
62+
M.cache = {}
63+
M.jobs = {}
64+
end
65+
66+
---Start async job for expensive operation
67+
---@param section_name string
68+
---@param cmd string|table Command to execute
69+
---@param callback function(data: any) Called with result
70+
---@param opts? table Options: { timeout: number, on_error: function }
71+
function M.start_job(section_name, cmd, callback, opts)
72+
opts = opts or {}
73+
local timeout = opts.timeout or 5000
74+
75+
-- Cancel existing job if any
76+
if M.jobs[section_name] then
77+
vim.fn.jobstop(M.jobs[section_name])
78+
end
79+
80+
local output = {}
81+
local job_id = vim.fn.jobstart(cmd, {
82+
stdout_buffered = true,
83+
stderr_buffered = true,
84+
on_stdout = function(_, data)
85+
if data then
86+
vim.list_extend(output, data)
87+
end
88+
end,
89+
on_stderr = function(_, data)
90+
if opts.on_error and data and #data > 0 then
91+
opts.on_error(data)
92+
end
93+
end,
94+
on_exit = function(_, exit_code)
95+
M.jobs[section_name] = nil
96+
if exit_code == 0 then
97+
local result = table.concat(output, '\n')
98+
callback(result)
99+
elseif opts.on_error then
100+
opts.on_error({ 'Job failed with exit code: ' .. exit_code })
101+
end
102+
end,
103+
})
104+
105+
M.jobs[section_name] = job_id
106+
107+
-- Set timeout
108+
vim.defer_fn(function()
109+
if M.jobs[section_name] == job_id then
110+
vim.fn.jobstop(job_id)
111+
M.jobs[section_name] = nil
112+
if opts.on_error then
113+
opts.on_error({ 'Job timeout after ' .. timeout .. 'ms' })
114+
end
115+
end
116+
end, timeout)
117+
118+
return job_id
119+
end
120+
121+
---Execute multiple jobs in parallel and call callback when all complete
122+
---@param tasks table[] Array of { name: string, cmd: string|table, ttl?: number }
123+
---@param callback function(results: table) Called with { [name] = data }
124+
---@param opts? table Options: { timeout: number }
125+
function M.parallel(tasks, callback, opts)
126+
opts = opts or {}
127+
local results = {}
128+
local pending = #tasks
129+
local completed = false
130+
131+
if pending == 0 then
132+
callback(results)
133+
return
134+
end
135+
136+
for _, task in ipairs(tasks) do
137+
local name = task.name
138+
139+
-- Check cache first
140+
local cached = M.get(name, task.ttl)
141+
if cached then
142+
results[name] = cached
143+
pending = pending - 1
144+
if pending == 0 and not completed then
145+
completed = true
146+
vim.schedule(function()
147+
callback(results)
148+
end)
149+
end
150+
else
151+
-- Start async job
152+
M.start_job(name, task.cmd, function(data)
153+
results[name] = data
154+
M.set(name, data)
155+
pending = pending - 1
156+
157+
if pending == 0 and not completed then
158+
completed = true
159+
vim.schedule(function()
160+
callback(results)
161+
end)
162+
end
163+
end, {
164+
timeout = opts.timeout or 3000,
165+
on_error = function(err)
166+
results[name] = nil
167+
pending = pending - 1
168+
169+
if pending == 0 and not completed then
170+
completed = true
171+
vim.schedule(function()
172+
callback(results)
173+
end)
174+
end
175+
end,
176+
})
177+
end
178+
end
179+
end
180+
181+
---Get cache statistics
182+
---@return table { total: number, by_section: table }
183+
function M.stats()
184+
local stats = { total = 0, by_section = {} }
185+
local now = vim.loop.hrtime() / 1e6
186+
187+
for section, cached in pairs(M.cache) do
188+
local age = now - cached.timestamp
189+
stats.by_section[section] = {
190+
age_ms = age,
191+
valid = age < M.DEFAULT_TTL,
192+
has_data = cached.data ~= nil,
193+
}
194+
stats.total = stats.total + 1
195+
end
196+
197+
return stats
198+
end
199+
200+
return M

0 commit comments

Comments
 (0)