Skip to content

Commit 62f8dd6

Browse files
committed
refactor(state): move UI/window APIs into state.ui
Move window state, cursor, visibility, and hidden-buffer helpers into a dedicated state.ui table. Update callers across modules and tests to reference state.ui.*, improving separation between core store logic and UI utilities.
1 parent 0be71d9 commit 62f8dd6

8 files changed

Lines changed: 60 additions & 90 deletions

File tree

lua/opencode/api.lua

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ end
6161

6262
---@return {status: 'closed'|'hidden'|'visible', position: string, windows: OpencodeWindowState|nil, cursor_positions: {input: integer[]|nil, output: integer[]|nil}}
6363
function M.get_window_state()
64-
return state.get_window_state()
64+
return state.ui.get_window_state()
6565
end
6666

6767
---@param hidden OpencodeHiddenBuffers|nil
@@ -82,7 +82,7 @@ end
8282
---@return {focus: 'input'|'output', open_action: 'reuse_visible'|'restore_hidden'|'create_fresh'}
8383
local function build_toggle_open_context(restore_hidden)
8484
if restore_hidden then
85-
local hidden = state.inspect_hidden_buffers()
85+
local hidden = state.ui.inspect_hidden_buffers()
8686
return {
8787
focus = resolve_hidden_focus(hidden),
8888
open_action = 'restore_hidden',
@@ -329,7 +329,7 @@ function M.set_review_breakpoint()
329329
end
330330

331331
function M.prev_history()
332-
if not state.is_visible() then
332+
if not state.ui.is_visible() then
333333
return
334334
end
335335
local prev_prompt = history.prev()
@@ -340,7 +340,7 @@ function M.prev_history()
340340
end
341341

342342
function M.next_history()
343-
if not state.is_visible() then
343+
if not state.ui.is_visible() then
344344
return
345345
end
346346
local next_prompt = history.next()
@@ -575,7 +575,7 @@ function M.help()
575575
'|--------------|-------------|',
576576
}, false)
577577

578-
if not state.is_visible() or not state.windows.output_win then
578+
if not state.ui.is_visible() or not state.windows.output_win then
579579
return
580580
end
581581

lua/opencode/core.lua

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ M.select_session = Promise.async(function(parent_id)
2525

2626
ui.select_session(filtered_sessions, function(selected_session)
2727
if not selected_session then
28-
if state.is_visible() then
28+
if state.ui.is_visible() then
2929
ui.focus_input()
3030
end
3131
return
@@ -43,7 +43,7 @@ M.switch_session = Promise.async(function(session_id)
4343

4444
state.session.set_active(selected_session)
4545
state.session.reset_restore_points()
46-
if state.is_visible() then
46+
if state.ui.is_visible() then
4747
ui.focus_input()
4848
else
4949
M.open()
@@ -52,7 +52,7 @@ end)
5252

5353
---@param opts? OpenOpts
5454
M.open_if_closed = Promise.async(function(opts)
55-
if not state.is_visible() then
55+
if not state.ui.is_visible() then
5656
M.open(opts):await()
5757
end
5858
end)
@@ -88,7 +88,7 @@ M.open = Promise.async(function(opts)
8888
require('opencode.context').load()
8989
end
9090

91-
local open_windows_action = opts.open_action or state.resolve_open_windows_action()
91+
local open_windows_action = opts.open_action or state.ui.resolve_open_windows_action()
9292
local are_windows_closed = open_windows_action ~= 'reuse_visible'
9393
local restoring_hidden = open_windows_action == 'restore_hidden'
9494

@@ -286,7 +286,7 @@ end
286286
function M.configure_provider()
287287
require('opencode.model_picker').select(function(selection)
288288
if not selection then
289-
if state.is_visible() then
289+
if state.ui.is_visible() then
290290
ui.focus_input()
291291
end
292292
return
@@ -298,7 +298,7 @@ function M.configure_provider()
298298
state.model.set_mode_model_override(state.current_mode, model_str)
299299
end
300300

301-
if state.is_visible() then
301+
if state.ui.is_visible() then
302302
ui.focus_input()
303303
else
304304
vim.notify('Changed provider to ' .. model_str, vim.log.levels.INFO)
@@ -309,15 +309,15 @@ end
309309
function M.configure_variant()
310310
require('opencode.variant_picker').select(function(selection)
311311
if not selection then
312-
if state.is_visible() then
312+
if state.ui.is_visible() then
313313
ui.focus_input()
314314
end
315315
return
316316
end
317317

318318
state.model.set_variant(selection.name)
319319

320-
if state.is_visible() then
320+
if state.ui.is_visible() then
321321
ui.focus_input()
322322
else
323323
vim.notify('Changed variant to ' .. selection.name, vim.log.levels.INFO)
@@ -417,7 +417,7 @@ M.cancel = Promise.async(function()
417417
end
418418
end
419419

420-
if state.is_visible() then
420+
if state.ui.is_visible() then
421421
require('opencode.ui.footer').clear()
422422
input_window.set_content('')
423423
require('opencode.history').index = nil

lua/opencode/state/init.lua

Lines changed: 0 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -105,21 +105,6 @@
105105
---@field subscribe fun(key:string|string[]|nil, cb:fun(key:string, new_val:any, old_val:any))
106106
---@field unsubscribe fun(key:string|nil, cb:fun(key:string, new_val:any, old_val:any))
107107
---@field is_running fun():boolean
108-
---@field get_window_state fun(): {status: 'closed'|'hidden'|'visible', position: string, windows: OpencodeWindowState|nil, cursor_positions: {input: integer[]|nil, output: integer[]|nil}}
109-
---@field is_window_in_current_tab fun(win_id: integer|nil): boolean
110-
---@field are_windows_in_current_tab fun(): boolean
111-
---@field get_window_cursor fun(win_id: integer|nil): integer[]|nil
112-
---@field set_cursor_position fun(win_type: 'input'|'output', pos: integer[]|nil)
113-
---@field get_cursor_position fun(win_type: 'input'|'output'): integer[]|nil
114-
---@field stash_hidden_buffers fun(hidden: OpencodeHiddenBuffers|nil)
115-
---@field inspect_hidden_buffers fun(): OpencodeHiddenBuffers|nil
116-
---@field is_hidden_snapshot_in_current_tab fun(): boolean
117-
---@field clear_hidden_window_state fun()
118-
---@field has_hidden_buffers fun(): boolean
119-
---@field consume_hidden_buffers fun(): OpencodeHiddenBuffers|nil
120-
---@field resolve_toggle_decision fun(persist_state: boolean, has_display_route: boolean): OpencodeToggleDecision
121-
---@field resolve_open_windows_action fun(): 'reuse_visible'|'restore_hidden'|'create_fresh'
122-
---@field get_window_cursor fun(win_id: integer|nil): integer[]|nil
123108
---@field session OpencodeSessionStateMutations
124109
---@field jobs OpencodeJobStateMutations
125110
---@field ui OpencodeUiStateMutations
@@ -139,21 +124,6 @@ local M = {
139124
ui = ui,
140125
model = model,
141126
test_helpers = test_helpers,
142-
is_window_in_current_tab = ui.is_window_in_current_tab,
143-
are_windows_in_current_tab = ui.are_windows_in_current_tab,
144-
is_visible = ui.is_visible,
145-
get_window_state = ui.get_window_state,
146-
get_window_cursor = ui.get_window_cursor,
147-
set_cursor_position = ui.set_cursor_position,
148-
get_cursor_position = ui.get_cursor_position,
149-
stash_hidden_buffers = ui.stash_hidden_buffers,
150-
inspect_hidden_buffers = ui.inspect_hidden_buffers,
151-
is_hidden_snapshot_in_current_tab = ui.is_hidden_snapshot_in_current_tab,
152-
clear_hidden_window_state = ui.clear_hidden_window_state,
153-
has_hidden_buffers = ui.has_hidden_buffers,
154-
consume_hidden_buffers = ui.consume_hidden_buffers,
155-
resolve_toggle_decision = ui.resolve_toggle_decision,
156-
resolve_open_windows_action = ui.resolve_open_windows_action,
157127
subscribe = store.subscribe,
158128
unsubscribe = store.unsubscribe,
159129
notify = store.notify,

lua/opencode/ui/input_window.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -559,9 +559,9 @@ function M._hide()
559559
M._hidden = true
560560
M._toggling = true
561561

562-
local pos = state.get_window_cursor(windows.input_win)
562+
local pos = state.ui.get_window_cursor(windows.input_win)
563563
if pos then
564-
state.set_cursor_position('input', pos)
564+
state.ui.set_cursor_position('input', pos)
565565
end
566566

567567
pcall(vim.api.nvim_win_close, windows.input_win, false)

lua/opencode/ui/output_window.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,9 @@ function M.setup_autocmds(windows, group)
318318

319319
if visible_bottom < line_count then
320320
pcall(vim.api.nvim_win_set_cursor, windows.output_win, { visible_bottom, 0 })
321-
local pos = state.get_window_cursor(windows.output_win)
321+
local pos = state.ui.get_window_cursor(windows.output_win)
322322
if pos then
323-
state.set_cursor_position('output', pos)
323+
state.ui.set_cursor_position('output', pos)
324324
end
325325
end
326326
end

lua/opencode/ui/ui.lua

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ local M = {}
1313
---@return {input: integer[]|nil, output: integer[]|nil}
1414
local function capture_cursors_position(windows)
1515
return {
16-
input = state.get_window_cursor(windows.input_win),
17-
output = state.get_window_cursor(windows.output_win),
16+
input = state.ui.get_window_cursor(windows.input_win),
17+
output = state.ui.get_window_cursor(windows.output_win),
1818
}
1919
end
2020

@@ -76,7 +76,7 @@ local function capture_hidden_snapshot(windows)
7676
output_view = ok and type(view) == 'table' and view or nil,
7777
focused_window = focused,
7878
position = config.ui.position,
79-
owner_tab = state.are_windows_in_current_tab() and vim.api.nvim_get_current_tabpage() or nil,
79+
owner_tab = state.ui.are_windows_in_current_tab() and vim.api.nvim_get_current_tabpage() or nil,
8080
}
8181
end
8282

@@ -160,7 +160,7 @@ function M.hide_visible_windows(windows)
160160
state.input_content = lines
161161
end
162162
end
163-
state.stash_hidden_buffers(snapshot)
163+
state.ui.stash_hidden_buffers(snapshot)
164164
if state.windows == windows then
165165
state.windows.input_win = nil
166166
state.windows.output_win = nil
@@ -192,7 +192,7 @@ end
192192
function M.drop_hidden_snapshot()
193193
renderer.teardown()
194194

195-
local hidden = state.inspect_hidden_buffers()
195+
local hidden = state.ui.inspect_hidden_buffers()
196196
if hidden then
197197
for _, buf in ipairs({ hidden.input_buf, hidden.output_buf, hidden.footer_buf }) do
198198
if buf and vim.api.nvim_buf_is_valid(buf) then
@@ -208,7 +208,7 @@ end
208208
---Restore windows using preserved buffers
209209
---@return boolean success
210210
function M.restore_hidden_windows()
211-
local hidden = state.inspect_hidden_buffers()
211+
local hidden = state.ui.inspect_hidden_buffers()
212212
if not hidden then
213213
return false
214214
end
@@ -263,7 +263,7 @@ function M.restore_hidden_windows()
263263
if hidden.output_was_at_bottom then
264264
renderer.scroll_to_bottom(true)
265265
else
266-
restore_window_cursor(w.output_win, w.output_buf, state.get_cursor_position('output'))
266+
restore_window_cursor(w.output_win, w.output_buf, state.ui.get_cursor_position('output'))
267267
if type(hidden.output_view) == 'table' then
268268
pcall(vim.api.nvim_win_call, w.output_win, function()
269269
vim.fn.winrestview(hidden.output_view)
@@ -272,7 +272,7 @@ function M.restore_hidden_windows()
272272
end
273273

274274
if not hidden.input_hidden then
275-
restore_window_cursor(w.input_win, w.input_buf, state.get_cursor_position('input'))
275+
restore_window_cursor(w.input_win, w.input_buf, state.ui.get_cursor_position('input'))
276276
end
277277
end)
278278

tests/unit/cursor_tracking_spec.lua

Lines changed: 31 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ local ui = require('opencode.ui.ui')
44

55
describe('cursor persistence (state)', function()
66
before_each(function()
7-
state.set_cursor_position('input', nil)
8-
state.set_cursor_position('output', nil)
7+
state.ui.set_cursor_position('input', nil)
8+
state.ui.set_cursor_position('output', nil)
99
end)
1010

1111
describe('renderer.scroll_to_bottom', function()
@@ -93,73 +93,73 @@ describe('cursor persistence (state)', function()
9393

9494
describe('set/get round-trip', function()
9595
it('stores and retrieves input cursor', function()
96-
state.set_cursor_position('input', { 5, 3 })
97-
assert.same({ 5, 3 }, state.get_cursor_position('input'))
96+
state.ui.set_cursor_position('input', { 5, 3 })
97+
assert.same({ 5, 3 }, state.ui.get_cursor_position('input'))
9898
end)
9999

100100
it('stores and retrieves output cursor', function()
101-
state.set_cursor_position('output', { 10, 0 })
102-
assert.same({ 10, 0 }, state.get_cursor_position('output'))
101+
state.ui.set_cursor_position('output', { 10, 0 })
102+
assert.same({ 10, 0 }, state.ui.get_cursor_position('output'))
103103
end)
104104

105105
it('input and output are independent', function()
106-
state.set_cursor_position('input', { 1, 0 })
107-
state.set_cursor_position('output', { 99, 5 })
108-
assert.same({ 1, 0 }, state.get_cursor_position('input'))
109-
assert.same({ 99, 5 }, state.get_cursor_position('output'))
106+
state.ui.set_cursor_position('input', { 1, 0 })
107+
state.ui.set_cursor_position('output', { 99, 5 })
108+
assert.same({ 1, 0 }, state.ui.get_cursor_position('input'))
109+
assert.same({ 99, 5 }, state.ui.get_cursor_position('output'))
110110
end)
111111

112112
it('returns nil for unknown win_type', function()
113-
assert.is_nil(state.get_cursor_position('footer'))
113+
assert.is_nil(state.ui.get_cursor_position('footer'))
114114
end)
115115
end)
116116

117117
describe('normalize_cursor edge cases', function()
118118
it('clamps negative line to 1', function()
119-
state.set_cursor_position('input', { -5, 3 })
120-
local pos = state.get_cursor_position('input')
119+
state.ui.set_cursor_position('input', { -5, 3 })
120+
local pos = state.ui.get_cursor_position('input')
121121
assert.equals(1, pos[1])
122122
end)
123123

124124
it('clamps negative col to 0', function()
125-
state.set_cursor_position('input', { 1, -1 })
126-
local pos = state.get_cursor_position('input')
125+
state.ui.set_cursor_position('input', { 1, -1 })
126+
local pos = state.ui.get_cursor_position('input')
127127
assert.equals(0, pos[2])
128128
end)
129129

130130
it('floors fractional values', function()
131-
state.set_cursor_position('input', { 3.7, 2.9 })
132-
local pos = state.get_cursor_position('input')
131+
state.ui.set_cursor_position('input', { 3.7, 2.9 })
132+
local pos = state.ui.get_cursor_position('input')
133133
assert.equals(3, pos[1])
134134
assert.equals(2, pos[2])
135135
end)
136136

137137
it('rejects non-table input', function()
138-
state.set_cursor_position('input', 'bad')
139-
assert.is_nil(state.get_cursor_position('input'))
138+
state.ui.set_cursor_position('input', 'bad')
139+
assert.is_nil(state.ui.get_cursor_position('input'))
140140
end)
141141

142142
it('rejects table with fewer than 2 elements', function()
143-
state.set_cursor_position('input', { 1 })
144-
assert.is_nil(state.get_cursor_position('input'))
143+
state.ui.set_cursor_position('input', { 1 })
144+
assert.is_nil(state.ui.get_cursor_position('input'))
145145
end)
146146

147147
it('rejects non-numeric elements', function()
148-
state.set_cursor_position('input', { 'a', 'b' })
149-
assert.is_nil(state.get_cursor_position('input'))
148+
state.ui.set_cursor_position('input', { 'a', 'b' })
149+
assert.is_nil(state.ui.get_cursor_position('input'))
150150
end)
151151

152152
it('clears position when set to nil', function()
153-
state.set_cursor_position('input', { 5, 3 })
154-
state.set_cursor_position('input', nil)
155-
assert.is_nil(state.get_cursor_position('input'))
153+
state.ui.set_cursor_position('input', { 5, 3 })
154+
state.ui.set_cursor_position('input', nil)
155+
assert.is_nil(state.ui.get_cursor_position('input'))
156156
end)
157157
end)
158158

159159
describe('get_window_cursor', function()
160160
it('returns nil for invalid window', function()
161-
assert.is_nil(state.get_window_cursor(nil))
162-
assert.is_nil(state.get_window_cursor(999999))
161+
assert.is_nil(state.ui.get_window_cursor(nil))
162+
assert.is_nil(state.ui.get_window_cursor(999999))
163163
end)
164164

165165
it('gets cursor from a real window', function()
@@ -174,12 +174,12 @@ describe('cursor persistence (state)', function()
174174
})
175175
vim.api.nvim_win_set_cursor(win, { 2, 3 })
176176

177-
local pos = state.get_window_cursor(win)
177+
local pos = state.ui.get_window_cursor(win)
178178
assert.same({ 2, 3 }, pos)
179179

180180
-- Manually save to verify persistence path
181-
state.set_cursor_position('output', pos)
182-
assert.same({ 2, 3 }, state.get_cursor_position('output'))
181+
state.ui.set_cursor_position('output', pos)
182+
assert.same({ 2, 3 }, state.ui.get_cursor_position('output'))
183183

184184
pcall(vim.api.nvim_win_close, win, true)
185185
pcall(vim.api.nvim_buf_delete, buf, { force = true })

0 commit comments

Comments
 (0)