From 063ce4fc809a2f4d749cd019c19e31bea47be6fe Mon Sep 17 00:00:00 2001 From: Jensen Date: Tue, 10 Mar 2026 23:45:00 +0800 Subject: [PATCH] feat: persist window width across hide/restore Save user-adjusted window width ratio when hiding and restore it when restoring hidden windows. --- lua/opencode/state.lua | 1 + lua/opencode/ui/input_window.lua | 6 +-- lua/opencode/ui/output_window.lua | 12 +++++- lua/opencode/ui/ui.lua | 10 +++++ tests/unit/zoom_spec.lua | 62 +++++++++++++++++++++++++++---- 5 files changed, 77 insertions(+), 14 deletions(-) diff --git a/lua/opencode/state.lua b/lua/opencode/state.lua index b192a9ae..47561ef4 100644 --- a/lua/opencode/state.lua +++ b/lua/opencode/state.lua @@ -58,6 +58,7 @@ ---@field api_client OpencodeApiClient ---@field event_manager EventManager|nil ---@field pre_zoom_width integer|nil +---@field last_window_width_ratio number|nil ---@field required_version string ---@field opencode_cli_version string|nil ---@field current_cwd string|nil diff --git a/lua/opencode/ui/input_window.lua b/lua/opencode/ui/input_window.lua index a55e923c..903b61b3 100644 --- a/lua/opencode/ui/input_window.lua +++ b/lua/opencode/ui/input_window.lua @@ -58,11 +58,7 @@ local function apply_dimensions(windows, height) return end - local total_width = vim.api.nvim_get_option_value('columns', {}) - local width_ratio = state.pre_zoom_width and config.ui.zoom_width or config.ui.window_width - local width = math.floor(total_width * width_ratio) - - pcall(vim.api.nvim_win_set_config, windows.input_win, { width = width, height = height }) + pcall(vim.api.nvim_win_set_config, windows.input_win, { height = height }) end function M.create_buf() diff --git a/lua/opencode/ui/output_window.lua b/lua/opencode/ui/output_window.lua index 17127452..0f74a541 100644 --- a/lua/opencode/ui/output_window.lua +++ b/lua/opencode/ui/output_window.lua @@ -133,7 +133,17 @@ function M.update_dimensions(windows) return end local total_width = vim.api.nvim_get_option_value('columns', {}) - local width_ratio = state.pre_zoom_width and config.ui.zoom_width or config.ui.window_width + + local width_ratio + if windows.saved_width_ratio then + width_ratio = windows.saved_width_ratio + windows.saved_width_ratio = nil + elseif state.pre_zoom_width then + width_ratio = config.ui.zoom_width + else + width_ratio = config.ui.window_width + end + local width = math.floor(total_width * width_ratio) vim.api.nvim_win_set_config(windows.output_win, { width = width }) diff --git a/lua/opencode/ui/ui.lua b/lua/opencode/ui/ui.lua index 887afccc..92724e08 100644 --- a/lua/opencode/ui/ui.lua +++ b/lua/opencode/ui/ui.lua @@ -134,6 +134,14 @@ function M.hide_visible_windows(windows) end local snapshot = capture_hidden_snapshot(windows) + + -- Only save width ratio for split modes (not dialog/current mode) + if config.ui.position ~= 'current' then + local total_cols = vim.o.columns + local current_width = vim.api.nvim_win_get_width(windows.output_win) + state.last_window_width_ratio = current_width / total_cols + end + state.clear_hidden_window_state() prepare_window_close() @@ -227,6 +235,7 @@ function M.restore_hidden_windows() windows.output_win = win_ids.output_win windows.footer_win = nil windows.output_was_at_bottom = hidden.output_was_at_bottom == true + windows.saved_width_ratio = state.last_window_width_ratio state.set_cursor_position('input', hidden.input_cursor) state.set_cursor_position('output', hidden.output_cursor) @@ -349,6 +358,7 @@ function M.create_windows() windows.input_win = win_ids.input_win windows.output_win = win_ids.output_win + windows.saved_width_ratio = state.last_window_width_ratio input_window.setup(windows) output_window.setup(windows) diff --git a/tests/unit/zoom_spec.lua b/tests/unit/zoom_spec.lua index 2a76bef9..7282d760 100644 --- a/tests/unit/zoom_spec.lua +++ b/tests/unit/zoom_spec.lua @@ -99,24 +99,23 @@ describe('ui zoom state', function() end) describe('input_window.update_dimensions', function() - it('uses default window_width when not zoomed', function() + it('does not change input window width', function() + local original_width = vim.api.nvim_win_get_width(windows.input_win) + input_window.update_dimensions(windows) - local expected_width = math.floor(config.ui.window_width * vim.o.columns) local actual_width = vim.api.nvim_win_get_width(windows.input_win) - - assert.equals(expected_width, actual_width) + assert.equals(original_width, actual_width) end) - it('uses zoom_width when zoomed', function() + it('does not change input window width when zoomed', function() state.pre_zoom_width = 80 + local original_width = vim.api.nvim_win_get_width(windows.input_win) input_window.update_dimensions(windows) - local expected_width = math.floor(config.ui.zoom_width * vim.o.columns) local actual_width = vim.api.nvim_win_get_width(windows.input_win) - - assert.equals(expected_width, actual_width) + assert.equals(original_width, actual_width) end) it('preserves zoom state after update_dimensions', function() @@ -267,4 +266,51 @@ describe('ui zoom state', function() assert.is_nil(state.pre_zoom_width) end) end) + + describe('window width persistence', function() + it('saves width ratio when hiding windows', function() + local custom_width = 100 + vim.api.nvim_win_set_width(windows.output_win, custom_width) + ui.hide_visible_windows(windows) + + local expected_ratio = custom_width / vim.o.columns + assert.is_not_nil(state.last_window_width_ratio) + assert.near(expected_ratio, state.last_window_width_ratio, 0.001) + end) + + it('does not save width in dialog mode (position=current)', function() + local original_position = config.ui.position + config.ui.position = 'current' + state.last_window_width_ratio = nil + + ui.hide_visible_windows(windows) + assert.is_nil(state.last_window_width_ratio) + + config.ui.position = original_position + end) + + it('uses saved width ratio in output_window.update_dimensions', function() + local saved_ratio = 0.6 + windows.saved_width_ratio = saved_ratio + + output_window.update_dimensions(windows) + + local expected_width = math.floor(saved_ratio * vim.o.columns) + local actual_width = vim.api.nvim_win_get_width(windows.output_win) + assert.equals(expected_width, actual_width) + assert.is_nil(windows.saved_width_ratio) + end) + + it('prefers saved width over zoom width', function() + state.pre_zoom_width = 80 + local saved_ratio = 0.5 + windows.saved_width_ratio = saved_ratio + + output_window.update_dimensions(windows) + + local expected_width = math.floor(saved_ratio * vim.o.columns) + local actual_width = vim.api.nvim_win_get_width(windows.output_win) + assert.equals(expected_width, actual_width) + end) + end) end)