Skip to content

Commit cdc20c2

Browse files
authored
fix(ui): preserve zoom state on redraw (#224)
* fix(ui): preserve zoom state on redraw * test(ui): ensure zoom state behavior
1 parent 4b950e0 commit cdc20c2

3 files changed

Lines changed: 201 additions & 2 deletions

File tree

lua/opencode/ui/input_window.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ local function apply_dimensions(windows, height)
5050
end
5151

5252
local total_width = vim.api.nvim_get_option_value('columns', {})
53-
local width = math.floor(total_width * config.ui.window_width)
53+
local width_ratio = state.pre_zoom_width and config.ui.zoom_width or config.ui.window_width
54+
local width = math.floor(total_width * width_ratio)
5455

5556
vim.api.nvim_win_set_config(windows.input_win, { width = width, height = height })
5657
end

lua/opencode/ui/output_window.lua

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,8 @@ function M.update_dimensions(windows)
115115
return
116116
end
117117
local total_width = vim.api.nvim_get_option_value('columns', {})
118-
local width = math.floor(total_width * config.ui.window_width)
118+
local width_ratio = state.pre_zoom_width and config.ui.zoom_width or config.ui.window_width
119+
local width = math.floor(total_width * width_ratio)
119120

120121
vim.api.nvim_win_set_config(windows.output_win, { width = width })
121122
end

tests/unit/zoom_spec.lua

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
local state = require('opencode.state')
2+
local config = require('opencode.config')
3+
local input_window = require('opencode.ui.input_window')
4+
local output_window = require('opencode.ui.output_window')
5+
local ui = require('opencode.ui.ui')
6+
7+
describe('ui zoom state', function()
8+
local windows
9+
local original_columns
10+
11+
before_each(function()
12+
original_columns = vim.o.columns
13+
vim.o.columns = 200
14+
15+
config.setup({
16+
ui = {
17+
window_width = 0.4,
18+
zoom_width = 0.8,
19+
position = 'right',
20+
},
21+
})
22+
23+
local input_buf = vim.api.nvim_create_buf(false, true)
24+
local output_buf = vim.api.nvim_create_buf(false, true)
25+
local input_win = vim.api.nvim_open_win(input_buf, true, {
26+
relative = 'editor',
27+
width = 80,
28+
height = 10,
29+
row = 0,
30+
col = 0,
31+
})
32+
local output_win = vim.api.nvim_open_win(output_buf, false, {
33+
relative = 'editor',
34+
width = 80,
35+
height = 20,
36+
row = 11,
37+
col = 0,
38+
})
39+
40+
windows = {
41+
input_buf = input_buf,
42+
input_win = input_win,
43+
output_buf = output_buf,
44+
output_win = output_win,
45+
}
46+
state.windows = windows
47+
state.pre_zoom_width = nil
48+
end)
49+
50+
after_each(function()
51+
vim.o.columns = original_columns
52+
state.pre_zoom_width = nil
53+
54+
if windows then
55+
pcall(vim.api.nvim_win_close, windows.input_win, true)
56+
pcall(vim.api.nvim_win_close, windows.output_win, true)
57+
pcall(vim.api.nvim_buf_delete, windows.input_buf, { force = true })
58+
pcall(vim.api.nvim_buf_delete, windows.output_buf, { force = true })
59+
end
60+
state.windows = nil
61+
end)
62+
63+
describe('toggle_zoom', function()
64+
it('sets zoom width when not zoomed', function()
65+
local initial_width = vim.api.nvim_win_get_width(windows.output_win)
66+
67+
ui.toggle_zoom()
68+
69+
local expected_zoom_width = math.floor(config.ui.zoom_width * vim.o.columns)
70+
local actual_width = vim.api.nvim_win_get_width(windows.output_win)
71+
72+
assert.is_not_nil(state.pre_zoom_width)
73+
assert.equals(initial_width, state.pre_zoom_width)
74+
assert.equals(expected_zoom_width, actual_width)
75+
end)
76+
77+
it('restores original width when zoomed', function()
78+
local initial_width = vim.api.nvim_win_get_width(windows.output_win)
79+
80+
ui.toggle_zoom()
81+
ui.toggle_zoom()
82+
83+
local actual_width = vim.api.nvim_win_get_width(windows.output_win)
84+
85+
assert.is_nil(state.pre_zoom_width)
86+
assert.equals(initial_width, actual_width)
87+
end)
88+
89+
it('sets zoom width for both input and output windows', function()
90+
ui.toggle_zoom()
91+
92+
local expected_zoom_width = math.floor(config.ui.zoom_width * vim.o.columns)
93+
local input_width = vim.api.nvim_win_get_width(windows.input_win)
94+
local output_width = vim.api.nvim_win_get_width(windows.output_win)
95+
96+
assert.equals(expected_zoom_width, input_width)
97+
assert.equals(expected_zoom_width, output_width)
98+
end)
99+
end)
100+
101+
describe('input_window.update_dimensions', function()
102+
it('uses default window_width when not zoomed', function()
103+
input_window.update_dimensions(windows)
104+
105+
local expected_width = math.floor(config.ui.window_width * vim.o.columns)
106+
local actual_width = vim.api.nvim_win_get_width(windows.input_win)
107+
108+
assert.equals(expected_width, actual_width)
109+
end)
110+
111+
it('uses zoom_width when zoomed', function()
112+
state.pre_zoom_width = 80
113+
114+
input_window.update_dimensions(windows)
115+
116+
local expected_width = math.floor(config.ui.zoom_width * vim.o.columns)
117+
local actual_width = vim.api.nvim_win_get_width(windows.input_win)
118+
119+
assert.equals(expected_width, actual_width)
120+
end)
121+
122+
it('preserves zoom state after update_dimensions', function()
123+
state.pre_zoom_width = 80
124+
125+
input_window.update_dimensions(windows)
126+
127+
assert.equals(80, state.pre_zoom_width)
128+
end)
129+
end)
130+
131+
describe('output_window.update_dimensions', function()
132+
it('uses default window_width when not zoomed', function()
133+
output_window.update_dimensions(windows)
134+
135+
local expected_width = math.floor(config.ui.window_width * vim.o.columns)
136+
local actual_width = vim.api.nvim_win_get_width(windows.output_win)
137+
138+
assert.equals(expected_width, actual_width)
139+
end)
140+
141+
it('uses zoom_width when zoomed', function()
142+
state.pre_zoom_width = 80
143+
144+
output_window.update_dimensions(windows)
145+
146+
local expected_width = math.floor(config.ui.zoom_width * vim.o.columns)
147+
local actual_width = vim.api.nvim_win_get_width(windows.output_win)
148+
149+
assert.equals(expected_width, actual_width)
150+
end)
151+
152+
it('preserves zoom state after update_dimensions', function()
153+
state.pre_zoom_width = 80
154+
155+
output_window.update_dimensions(windows)
156+
157+
assert.equals(80, state.pre_zoom_width)
158+
end)
159+
end)
160+
161+
describe('zoom state persistence', function()
162+
it('maintains zoom width after input window show/hide cycle', function()
163+
ui.toggle_zoom()
164+
165+
local expected_zoom_width = math.floor(config.ui.zoom_width * vim.o.columns)
166+
167+
input_window.update_dimensions(windows)
168+
169+
local actual_width = vim.api.nvim_win_get_width(windows.input_win)
170+
assert.equals(expected_zoom_width, actual_width)
171+
assert.is_not_nil(state.pre_zoom_width)
172+
end)
173+
174+
it('maintains zoom width after output window update_dimensions', function()
175+
ui.toggle_zoom()
176+
177+
local expected_zoom_width = math.floor(config.ui.zoom_width * vim.o.columns)
178+
179+
output_window.update_dimensions(windows)
180+
181+
local actual_width = vim.api.nvim_win_get_width(windows.output_win)
182+
assert.equals(expected_zoom_width, actual_width)
183+
assert.is_not_nil(state.pre_zoom_width)
184+
end)
185+
186+
it('correctly un-zooms after multiple update_dimensions calls', function()
187+
ui.toggle_zoom()
188+
189+
input_window.update_dimensions(windows)
190+
output_window.update_dimensions(windows)
191+
192+
ui.toggle_zoom()
193+
194+
assert.is_nil(state.pre_zoom_width)
195+
end)
196+
end)
197+
end)

0 commit comments

Comments
 (0)