Skip to content

Commit 5b6b028

Browse files
committed
feat(hooks): hooks for edit filed, session loaded
Fixes #97
1 parent 8d45fbc commit 5b6b028

File tree

4 files changed

+129
-3
lines changed

4 files changed

+129
-3
lines changed

lua/opencode/config.lua

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ M.defaults = {
182182
show_ids = false,
183183
},
184184
prompt_guard = nil,
185+
hooks = {
186+
on_file_edited = nil,
187+
on_session_loaded = nil,
188+
},
185189
}
186190

187191
M.values = vim.deepcopy(M.defaults)

lua/opencode/types.lua

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,12 @@
135135
---@field capture_streamed_events boolean
136136
---@field show_ids boolean
137137

138-
--- @class OpencodeProviders
139-
--- @field [string] string[]
138+
---@class OpencodeHooks
139+
---@field on_file_edited? fun(file: string): nil
140+
---@field on_session_loaded? fun(session: Session): nil
141+
142+
---@class OpencodeProviders
143+
---@field [string] string[]
140144

141145
---@class OpencodeConfigModule
142146
---@field defaults OpencodeConfig
@@ -156,6 +160,7 @@
156160
---@field context OpencodeContextConfig
157161
---@field debug OpencodeDebugConfig
158162
---@field prompt_guard? fun(mentioned_files: string[]): boolean
163+
---@field hooks OpencodeHooks
159164
---@field legacy_commands boolean
160165

161166
---@class MessagePartState

lua/opencode/ui/renderer.lua

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,10 @@ function M._render_full_session_data(session_data)
166166
M._set_model_from_messages()
167167
end
168168
M.scroll_to_bottom()
169+
170+
if config.hooks and config.hooks.on_session_loaded then
171+
pcall(config.hooks.on_session_loaded, state.active_session)
172+
end
169173
end
170174

171175
---Render lines as the entire output buffer
@@ -805,8 +809,11 @@ function M.on_permission_replied(properties)
805809
end
806810
end
807811

808-
function M.on_file_edited(_)
812+
function M.on_file_edited(properties)
809813
vim.cmd('checktime')
814+
if config.hooks and config.hooks.on_file_edited then
815+
pcall(config.hooks.on_file_edited, properties.file)
816+
end
810817
end
811818

812819
---@param properties RestorePointCreatedEvent

tests/unit/hooks_spec.lua

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
local renderer = require('opencode.ui.renderer')
2+
local config = require('opencode.config')
3+
local state = require('opencode.state')
4+
local helpers = require('tests.helpers')
5+
local ui = require('opencode.ui.ui')
6+
7+
describe('hooks', function()
8+
before_each(function()
9+
helpers.replay_setup()
10+
config.hooks = {
11+
on_file_edited = nil,
12+
on_session_loaded = nil,
13+
}
14+
end)
15+
16+
after_each(function()
17+
if state.windows then
18+
ui.close_windows(state.windows)
19+
end
20+
config.hooks = {
21+
on_file_edited = nil,
22+
on_session_loaded = nil,
23+
}
24+
end)
25+
26+
describe('on_file_edited', function()
27+
it('should call hook when file is edited', function()
28+
local called = false
29+
local file_path = nil
30+
31+
config.hooks.on_file_edited = function(file)
32+
called = true
33+
file_path = file
34+
end
35+
36+
local test_event = { file = '/test/file.lua' }
37+
renderer.on_file_edited(test_event)
38+
39+
assert.is_true(called)
40+
assert.are.equal('/test/file.lua', file_path)
41+
end)
42+
43+
it('should not error when hook is nil', function()
44+
config.hooks.on_file_edited = nil
45+
46+
local test_event = { file = '/test/file.lua' }
47+
assert.has_no.errors(function()
48+
renderer.on_file_edited(test_event)
49+
end)
50+
end)
51+
52+
it('should not crash when hook throws error', function()
53+
config.hooks.on_file_edited = function()
54+
error('test error')
55+
end
56+
57+
local test_event = { file = '/test/file.lua' }
58+
assert.has_no.errors(function()
59+
renderer.on_file_edited(test_event)
60+
end)
61+
end)
62+
end)
63+
64+
describe('on_session_loaded', function()
65+
it('should call hook when session is loaded', function()
66+
local called = false
67+
local session_data = nil
68+
69+
config.hooks.on_session_loaded = function(session)
70+
called = true
71+
session_data = session
72+
end
73+
74+
local events = helpers.load_test_data('tests/data/simple-session.json')
75+
state.active_session = helpers.get_session_from_events(events, true)
76+
local loaded_session = helpers.load_session_from_events(events)
77+
78+
renderer._render_full_session_data(loaded_session)
79+
80+
assert.is_true(called)
81+
assert.are.same(state.active_session, session_data)
82+
end)
83+
84+
it('should not error when hook is nil', function()
85+
config.hooks.on_session_loaded = nil
86+
87+
local events = helpers.load_test_data('tests/data/simple-session.json')
88+
state.active_session = helpers.get_session_from_events(events, true)
89+
local loaded_session = helpers.load_session_from_events(events)
90+
91+
assert.has_no.errors(function()
92+
renderer._render_full_session_data(loaded_session)
93+
end)
94+
end)
95+
96+
it('should not crash when hook throws error', function()
97+
config.hooks.on_session_loaded = function()
98+
error('test error')
99+
end
100+
101+
local events = helpers.load_test_data('tests/data/simple-session.json')
102+
state.active_session = helpers.get_session_from_events(events, true)
103+
local loaded_session = helpers.load_session_from_events(events)
104+
105+
assert.has_no.errors(function()
106+
renderer._render_full_session_data(loaded_session)
107+
end)
108+
end)
109+
end)
110+
end)

0 commit comments

Comments
 (0)