From 8643ccb276e1a95f40621598bda37e5d64fdfffa Mon Sep 17 00:00:00 2001 From: Masaaki Hirotsu Date: Tue, 24 Feb 2026 19:57:12 +0900 Subject: [PATCH] feat(ui): add stack view resize on screen size change --- lua/peekstack/core/events.lua | 1 + lua/peekstack/ui/stack_view/init.lua | 10 +++ tests/layout_spec.lua | 95 ++++++++++++++++++++++++++++ tests/stack_view_spec.lua | 23 +++++++ tests/toggle_visibility_spec.lua | 21 ++++++ 5 files changed, 150 insertions(+) diff --git a/lua/peekstack/core/events.lua b/lua/peekstack/core/events.lua index c08e9d0..dd92e40 100644 --- a/lua/peekstack/core/events.lua +++ b/lua/peekstack/core/events.lua @@ -29,6 +29,7 @@ local function debounced_reflow() reflow_timer:stop() vim.schedule(function() stack.reflow_all() + require("peekstack.ui.stack_view").resize_all() end) end) end diff --git a/lua/peekstack/ui/stack_view/init.lua b/lua/peekstack/ui/stack_view/init.lua index aa2b4bc..b7baf10 100644 --- a/lua/peekstack/ui/stack_view/init.lua +++ b/lua/peekstack/ui/stack_view/init.lua @@ -297,6 +297,16 @@ function M.refresh_all() end end +---Resize and re-render all open stack views (called on VimResized/WinResized). +function M.resize_all() + for _, s in pairs(states) do + if is_open(s) and s.winid and vim.api.nvim_win_is_valid(s.winid) then + vim.api.nvim_win_set_config(s.winid, stack_view_win_config()) + render_state(s) + end + end +end + ---Get stack view state (for testing). ---@return PeekstackStackViewState function M._get_state() diff --git a/tests/layout_spec.lua b/tests/layout_spec.lua index 38dcffb..cc0ea4a 100644 --- a/tests/layout_spec.lua +++ b/tests/layout_spec.lua @@ -90,6 +90,101 @@ describe("layout.compute", function() assert.equals(first.row, second.row) assert.equals(first.col, second.col) end) + + it("clamps to min_size on very small screen", function() + vim.o.columns = 30 + vim.o.lines = 10 + config.setup({ + ui = { + layout = { + style = "stack", + offset = { row = 1, col = 4 }, + shrink = { w = 4, h = 2 }, + min_size = { w = 60, h = 12 }, + max_ratio = 0.65, + zindex_base = 50, + }, + }, + }) + + local result = layout.compute(1) + assert.equals(30, result.width) -- clamped to columns (< min_size.w) + assert.equals(9, result.height) -- clamped to lines (< min_size.h) + assert.is_true(result.row >= 0) + assert.is_true(result.col >= 0) + end) + + it("produces non-negative row and col for all indices", function() + vim.o.columns = 40 + vim.o.lines = 15 + config.setup({ + ui = { + layout = { + style = "stack", + offset = { row = 1, col = 4 }, + shrink = { w = 4, h = 2 }, + min_size = { w = 20, h = 6 }, + max_ratio = 0.65, + zindex_base = 50, + }, + }, + }) + + for idx = 1, 5 do + local result = layout.compute(idx) + assert.is_true(result.width > 0, "width should be positive at index " .. idx) + assert.is_true(result.height > 0, "height should be positive at index " .. idx) + assert.is_true(result.row >= 0, "row should be non-negative at index " .. idx) + assert.is_true(result.col >= 0, "col should be non-negative at index " .. idx) + end + end) +end) + +describe("layout.reflow", function() + before_each(function() + stack._reset() + config.setup({}) + end) + + after_each(function() + local s = stack.current_stack() + for i = #s.popups, 1, -1 do + stack.close(s.popups[i].id) + end + stack._reset() + end) + + it("does not error on empty stack", function() + local s = stack.current_stack() + assert.equals(0, #s.popups) + assert.has_no.errors(function() + layout.reflow(s) + end) + end) + + it("recalculates popup dimensions after screen size change", function() + local original_columns = vim.o.columns + local original_lines = vim.o.lines + + local loc = helpers.make_location() + local m1 = stack.push(loc) + assert.is_not_nil(m1) + + local cfg_before = vim.api.nvim_win_get_config(m1.winid) + + -- Simulate screen resize + vim.o.columns = math.floor(original_columns / 2) + vim.o.lines = math.floor(original_lines / 2) + + local s = stack.current_stack() + layout.reflow(s) + + local cfg_after = vim.api.nvim_win_get_config(m1.winid) + assert.is_true(cfg_after.width <= cfg_before.width) + + vim.o.columns = original_columns + vim.o.lines = original_lines + end) end) describe("layout.update_focus_zindex", function() diff --git a/tests/stack_view_spec.lua b/tests/stack_view_spec.lua index a94216f..2d5f1d7 100644 --- a/tests/stack_view_spec.lua +++ b/tests/stack_view_spec.lua @@ -29,6 +29,29 @@ describe("peekstack.ui.stack_view", function() stack._reset() end) + it("resize_all updates open stack view dimensions", function() + local original_columns = vim.o.columns + + stack_view.open() + local state = stack_view._get_state() + local cfg_before = vim.api.nvim_win_get_config(state.winid) + + -- Simulate wider screen + vim.o.columns = original_columns + 40 + stack_view.resize_all() + + local cfg_after = vim.api.nvim_win_get_config(state.winid) + assert.is_true(cfg_after.width >= cfg_before.width) + + vim.o.columns = original_columns + end) + + it("resize_all does not error when stack view is closed", function() + assert.has_no.errors(function() + stack_view.resize_all() + end) + end) + it("filters stack entries by query", function() local root_winid = vim.api.nvim_get_current_win() local s = stack.current_stack(root_winid) diff --git a/tests/toggle_visibility_spec.lua b/tests/toggle_visibility_spec.lua index b1d5c92..8b549df 100644 --- a/tests/toggle_visibility_spec.lua +++ b/tests/toggle_visibility_spec.lua @@ -119,6 +119,27 @@ describe("stack.toggle_visibility", function() assert.equals(0, #s.popups) end) + it("reflow_all skips hidden stack without error", function() + local loc = helpers.make_location() + stack.push(loc) + stack.push(loc) + + stack.toggle_visibility() + assert.is_true(stack.is_hidden()) + + -- reflow_all should skip hidden popups (winid=nil) safely + assert.has_no.errors(function() + stack.reflow_all() + end) + + -- Popups should still be in the stack + local s = stack.current_stack() + assert.equals(2, #s.popups) + for _, item in ipairs(s.popups) do + assert.is_nil(item.winid) + end + end) + it("does not leak popups to history when hiding", function() local loc = helpers.make_location() stack.push(loc)