Skip to content

Commit 49dd82c

Browse files
committed
WIP: remove path/project info from session
1 parent bc85505 commit 49dd82c

File tree

10 files changed

+198
-96
lines changed

10 files changed

+198
-96
lines changed

lua/opencode/config_file.lua

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,21 +23,33 @@ function M.get_opencode_config()
2323
end
2424

2525
---@return OpencodeProject|nil
26-
function M.get_opencode_project()
26+
M.get_opencode_project = Promise.async_wrap(function()
2727
if not M.project_promise then
2828
local state = require('opencode.state')
2929
M.project_promise = state.api_client:get_current_project()
3030
end
3131
local ok, result = pcall(function()
32-
return M.project_promise:wait()
32+
return M.project_promise:await()
3333
end)
3434
if not ok then
3535
vim.notify('Error fetching Opencode project: ' .. vim.inspect(result), vim.log.levels.ERROR)
3636
return nil
3737
end
3838

3939
return result --[[@as OpencodeProject|nil]]
40-
end
40+
end)
41+
42+
---Get the snapshot storage path for the current workspace
43+
---@return string
44+
M.get_workspace_snapshot_path = Promise.async_wrap(function()
45+
local project = M.get_opencode_project():await()
46+
if not project then
47+
vim.notify('No OpenCode project found in the current directory', vim.log.levels.ERROR)
48+
return ''
49+
end
50+
local home = vim.uv.os_homedir()
51+
return home .. '/.local/share/opencode/snapshot/' .. project_id
52+
end)
4153

4254
---@return OpencodeProvidersResponse|nil
4355
function M.get_opencode_providers()

lua/opencode/core.lua

Lines changed: 31 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,21 @@ M._abort_count = 0
1414

1515
---@param parent_id string?
1616
function M.select_session(parent_id)
17-
local all_sessions = session.get_all_workspace_sessions() or {}
18-
local filtered_sessions = vim.tbl_filter(function(s)
19-
return s.title ~= '' and s ~= nil and s.parentID == parent_id
20-
end, all_sessions)
17+
session.get_all_workspace_sessions():and_then(function(all_sessions)
18+
all_sessions = all_sessions or {}
19+
local filtered_sessions = vim.tbl_filter(function(s)
20+
return s.title ~= '' and s ~= nil and s.parentID == parent_id
21+
end, all_sessions)
2122

22-
ui.select_session(filtered_sessions, function(selected_session)
23-
if not selected_session then
24-
if state.windows then
25-
ui.focus_input()
23+
ui.select_session(filtered_sessions, function(selected_session)
24+
if not selected_session then
25+
if state.windows then
26+
ui.focus_input()
27+
end
28+
return
2629
end
27-
return
28-
end
29-
M.switch_session(selected_session.id)
30+
M.switch_session(selected_session.id)
31+
end)
3032
end)
3133
end
3234

@@ -94,10 +96,12 @@ function M.open(opts)
9496
state.active_session = M.create_new_session()
9597
else
9698
if not state.active_session then
97-
state.active_session = session.get_last_workspace_session()
98-
if not state.active_session then
99-
state.active_session = M.create_new_session()
100-
end
99+
session.get_last_workspace_session():and_then(function(last_session)
100+
state.active_session = last_session
101+
if not state.active_session then
102+
state.active_session = M.create_new_session()
103+
end
104+
end)
101105
else
102106
if not state.display_route and are_windows_closed then
103107
-- We're not displaying /help or something like that but we have an active session
@@ -408,16 +412,18 @@ end
408412

409413
function M._on_user_message_count_change(_, new, old)
410414
if config.hooks and config.hooks.on_done_thinking then
411-
local all_sessions = session.get_all_workspace_sessions() or {}
412-
local done_sessions = vim.tbl_filter(function(s)
413-
local msg_count = new[s.id] or 0
414-
local old_msg_count = (old and old[s.id]) or 0
415-
return msg_count == 0 and old_msg_count > 0
416-
end, all_sessions)
417-
418-
for _, done_session in ipairs(done_sessions) do
419-
pcall(config.hooks.on_done_thinking, done_session)
420-
end
415+
session.get_all_workspace_sessions():and_then(function(all_sessions)
416+
all_sessions = all_sessions or {}
417+
local done_sessions = vim.tbl_filter(function(s)
418+
local msg_count = new[s.id] or 0
419+
local old_msg_count = (old and old[s.id]) or 0
420+
return msg_count == 0 and old_msg_count > 0
421+
end, all_sessions)
422+
423+
for _, done_session in ipairs(done_sessions) do
424+
pcall(config.hooks.on_done_thinking, done_session)
425+
end
426+
end)
421427
end
422428
end
423429

lua/opencode/git_review.lua

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,19 @@ local snapshot = require('opencode.snapshot')
44
local diff_tab = require('opencode.ui.diff_tab')
55
local utils = require('opencode.util')
66
local session = require('opencode.session')
7+
local config_file = require('opencode.config_file')
78

89
local M = {}
910

1011
---@param cmd_args string[]
1112
---@param opts? vim.SystemOpts
1213
---@return string|nil, string|nil
1314
local function snapshot_git(cmd_args, opts)
14-
local snapshot_dir = state.active_session and state.active_session.snapshot_path
15-
if not snapshot_dir then
15+
if not M.__snapshot_path then
1616
vim.notify('No snapshot path for the active session.')
1717
return nil, nil
1818
end
19-
local args = { 'git', '-C', snapshot_dir }
19+
local args = { 'git', '-C', M.__snapshot_path }
2020
vim.list_extend(args, cmd_args)
2121
local result = vim.system(args, opts or {}):wait()
2222
if result and result.code == 0 then
@@ -26,6 +26,7 @@ local function snapshot_git(cmd_args, opts)
2626
end
2727
end
2828

29+
M.__snapshot_path = nil
2930
M.__changed_files = nil
3031
M.__current_file_index = nil
3132
M.__diff_tab = nil
@@ -76,11 +77,13 @@ local function require_git_project(fn, silent)
7677
end
7778
return
7879
end
79-
if not state.active_session.snapshot_path or vim.fn.isdirectory(state.active_session.snapshot_path) == 0 then
80-
if not silent then
80+
if not M.__snapshot_path then
81+
M.__snapshot_path = config_file.get_workspace_snapshot_path():wait()
82+
if vim.fn.isdirectory(M.__snapshot_path) == 0 and not silent then
8183
vim.notify('Error: No snapshot path for the active session.')
84+
else
85+
return
8286
end
83-
return
8487
end
8588
return fn(...)
8689
end

lua/opencode/promise.lua

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,30 @@
77
---@field wait fun(self: self, timeout?: integer, interval?: integer): any
88
---@field is_resolved fun(self: self): boolean
99
---@field is_rejected fun(self: self): boolean
10+
---@field await fun(self: self): any
1011
---@field _resolved boolean
1112
---@field _value any
1213
---@field _error any
1314
---@field _then_callbacks fun(value: any)[]
1415
---@field _catch_callbacks fun(err: any)[]
16+
---@field _coroutines thread[]
1517
local Promise = {}
1618
Promise.__index = Promise
1719

20+
---Resume waiting coroutines with result
21+
---@param coroutines thread[]
22+
---@param value any
23+
---@param err any
24+
local function resume_coroutines(coroutines, value, err)
25+
for _, co in ipairs(coroutines) do
26+
vim.schedule(function()
27+
if coroutine.status(co) == 'suspended' then
28+
coroutine.resume(co, value, err)
29+
end
30+
end)
31+
end
32+
end
33+
1834
---Create a waitable promise that can be resolved or rejected later
1935
---@generic T
2036
---@return Promise<T>
@@ -25,6 +41,7 @@ function Promise.new()
2541
_error = nil,
2642
_then_callbacks = {},
2743
_catch_callbacks = {},
44+
_coroutines = {},
2845
}, Promise)
2946
return self
3047
end
@@ -45,6 +62,9 @@ function Promise:resolve(value)
4562
for _, callback in ipairs(self._then_callbacks) do
4663
schedule_then(callback, value)
4764
end
65+
66+
resume_coroutines(self._coroutines, value, nil)
67+
4868
return self
4969
end
5070

@@ -64,6 +84,9 @@ function Promise:reject(err)
6484
for _, callback in ipairs(self._catch_callbacks) do
6585
schedule_catch(callback, err)
6686
end
87+
88+
resume_coroutines(self._coroutines, nil, err)
89+
6790
return self
6891
end
6992

@@ -193,6 +216,39 @@ function Promise:is_rejected()
193216
return self._resolved and self._error ~= nil
194217
end
195218

219+
---Await the promise from within a coroutine
220+
---This will yield the coroutine until the promise resolves or rejects
221+
---@generic T
222+
---@return T
223+
function Promise:await()
224+
-- If already resolved, return immediately
225+
if self._resolved then
226+
if self._error then
227+
error(self._error)
228+
end
229+
return self._value
230+
end
231+
232+
-- Get the current coroutine
233+
local co = coroutine.running()
234+
if not co then
235+
error('await() can only be called from within a coroutine')
236+
end
237+
238+
-- Register the coroutine to be resumed when promise settles
239+
table.insert(self._coroutines, co)
240+
241+
-- Yield and wait for resume
242+
---@diagnostic disable-next-line: await-in-sync
243+
local value, err = coroutine.yield()
244+
245+
if err then
246+
error(err)
247+
end
248+
249+
return value
250+
end
251+
196252
---@generic T
197253
---@param obj T
198254
---@return_cast obj Promise<T>
@@ -211,4 +267,54 @@ function Promise.wrap(obj)
211267
end
212268
end
213269

270+
---Run an async function in a coroutine
271+
---The function can use promise:await() to wait for promises
272+
---@generic T
273+
---@param fn fun(): T
274+
---@return Promise<T>
275+
---@return_cast T Promise<T>
276+
function Promise.async(fn)
277+
local promise = Promise.new()
278+
279+
local co = coroutine.create(function()
280+
local ok, result = pcall(fn)
281+
if not ok then
282+
promise:reject(result)
283+
else
284+
if Promise.is_promise(result) then
285+
result
286+
:and_then(function(val)
287+
promise:resolve(val)
288+
end)
289+
:catch(function(err)
290+
promise:reject(err)
291+
end)
292+
else
293+
promise:resolve(result)
294+
end
295+
end
296+
end)
297+
298+
local ok, err = coroutine.resume(co)
299+
if not ok then
300+
promise:reject(err)
301+
end
302+
303+
return promise
304+
end
305+
306+
---Wrap a function to run asynchronously
307+
---Takes a function and returns a wrapped version that returns a Promise
308+
---@generic T
309+
---@param fn fun(...): T
310+
---@return fun(...): Promise<T>
311+
function Promise.async_wrap(fn)
312+
return function(...)
313+
local args = { ... }
314+
return Promise.async(function()
315+
return fn(unpack(args))
316+
end)
317+
end
318+
end
319+
214320
return Promise

0 commit comments

Comments
 (0)