From df432dd12165643bda4149d13383d258c7e79d05 Mon Sep 17 00:00:00 2001 From: Josh Lane Date: Thu, 13 Nov 2025 13:47:02 -0800 Subject: [PATCH 1/3] feat(autoresize): add equalise_min_cols and equalise_min_rows config options Adds new configuration options to use equal-size splits when the terminal has sufficient columns and/or rows. When thresholds are met: - Uses equal splits for all windows (via wincmd =) - All windows have equal width and height When thresholds are not met: - Uses golden ratio autoresize - Focused window gets more space Configuration: - equalise_min_cols: Use equal splits when columns >= this value (0 = ignore) - equalise_min_rows: Use equal splits when rows >= this value (0 = ignore) - If both are set, both conditions must be met (AND logic) - If only one is set, only that condition needs to be met Default: 0 (feature disabled, always use golden ratio) This allows users to prefer equal splits on large terminals while using focused window resizing on smaller terminals. --- README.md | 20 ++++++++++++++++++++ lua/focus/init.lua | 10 ++++++++++ lua/focus/modules/resizer.lua | 28 ++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) diff --git a/README.md b/README.md index 5d4b3816..02e59e3d 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,8 @@ require("focus").setup({ focusedwindow_minwidth = 0, --Force minimum width for the focused window focusedwindow_minheight = 0, --Force minimum height for the focused window height_quickfix = 10, -- Set the height of quickfix panel + equalise_min_cols = 0, -- Use equal splits when columns >= this value (0 = ignore) + equalise_min_rows = 0, -- Use equal splits when rows >= this value (0 = ignore) }, split = { bufnew = false, -- Create blank buffer for new split windows @@ -225,6 +227,24 @@ require("focus").setup({ autoresize = { focusedwindow_minheight = 80} }) require("focus").setup({ autoresize = { height_quickfix = 10 }) ``` +**Set Minimum Terminal Size for Equal Splits** +```lua +-- Use equal-size splits when terminal has sufficient columns and/or rows +-- When thresholds are met: all splits are equal size (via wincmd =) +-- When thresholds are not met: use golden ratio autoresize (focused window larger) +-- Useful for large terminals where equal splits are preferred +-- Default: 0 (always use golden ratio autoresize) + +-- Equal splits on wide terminals (120+ columns) +require("focus").setup({ autoresize = { equalise_min_cols = 120 }) + +-- Equal splits on tall terminals (40+ rows) +require("focus").setup({ autoresize = { equalise_min_rows = 40 }) + +-- Equal splits only when BOTH wide AND tall (120+ columns AND 40+ rows) +require("focus").setup({ autoresize = { equalise_min_cols = 120, equalise_min_rows = 40 }) +``` + **When creating a new split window, do/don't initialise it as an empty buffer** ```lua -- True: When a :Focus.. command creates a new split window, initialise it as a new blank buffer diff --git a/lua/focus/init.lua b/lua/focus/init.lua index 39d5da41..9986291d 100644 --- a/lua/focus/init.lua +++ b/lua/focus/init.lua @@ -30,6 +30,8 @@ Focus.config = { focusedwindow_minwidth = 0, -- Force minimum width for the focused window focusedwindow_minheight = 0, -- Force minimum height for the focused window height_quickfix = 10, -- Set the height of quickfix panel + equalise_min_cols = 0, -- Use equal splits when columns >= this value (0 = ignore) + equalise_min_rows = 0, -- Use equal splits when rows >= this value (0 = ignore) }, split = { bufnew = false, -- Create blank buffer for new split windows @@ -210,6 +212,14 @@ H.setup_config = function(config) config.autoresize.height_quickfix, 'number', }, + ['autoresize.equalise_min_cols'] = { + config.autoresize.equalise_min_cols, + 'number', + }, + ['autoresize.equalise_min_rows'] = { + config.autoresize.equalise_min_rows, + 'number', + }, }) vim.validate({ diff --git a/lua/focus/modules/resizer.lua b/lua/focus/modules/resizer.lua index 1d83cdc7..2fe3b467 100644 --- a/lua/focus/modules/resizer.lua +++ b/lua/focus/modules/resizer.lua @@ -49,6 +49,34 @@ local function restore_fixed_win_dims(fixed_dims) end function M.autoresize(config) + -- Check if we should use equal splits based on terminal size + -- When columns >= equalise_min_cols AND/OR rows >= equalise_min_rows, use equal splits (wincmd =) + -- Otherwise, use golden ratio autoresize + local should_equalise = false + local cols_check = config.autoresize.equalise_min_cols > 0 and vim.o.columns >= config.autoresize.equalise_min_cols + local rows_check = config.autoresize.equalise_min_rows > 0 and vim.o.lines >= config.autoresize.equalise_min_rows + + -- If both thresholds are set, both conditions must be met + -- If only one is set, only that condition needs to be met + if config.autoresize.equalise_min_cols > 0 and config.autoresize.equalise_min_rows > 0 then + should_equalise = cols_check and rows_check + elseif config.autoresize.equalise_min_cols > 0 then + should_equalise = cols_check + elseif config.autoresize.equalise_min_rows > 0 then + should_equalise = rows_check + end + + -- If equalise mode, just equalize all windows and return + if should_equalise then + local cmdheight = vim.o.cmdheight + local fixed = save_fixed_win_dims() + vim.api.nvim_exec2('wincmd =', { output = false }) + restore_fixed_win_dims(fixed) + vim.o.cmdheight = cmdheight + return + end + + -- Otherwise, use golden ratio autoresize local width if config.autoresize.width > 0 then width = config.autoresize.width From 1d2fdd467a6efb8adecc1a76f86891e85d317ad0 Mon Sep 17 00:00:00 2001 From: Josh Lane Date: Fri, 14 Nov 2025 16:33:07 -0800 Subject: [PATCH 2/3] test(autoresize): add tests for equalise_min config --- tests/test_autoresize.lua | 229 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) diff --git a/tests/test_autoresize.lua b/tests/test_autoresize.lua index a70d6f37..89ade0fb 100644 --- a/tests/test_autoresize.lua +++ b/tests/test_autoresize.lua @@ -545,4 +545,233 @@ T['autoresize']['does not resize floating windows'] = function() eq(child.api.nvim_win_get_height(win_id), 10) end +-- Test equalise_min_cols when terminal is wide enough +T['autoresize']['equalise_min_cols when wide enough'] = function() + reload_module({ autoresize = { equalise_min_cols = 80 } }) + child.set_size(25, 80) + edit(lorem_ipsum_file) + child.cmd('vsplit') + local resize_state = child.get_resize_state() + + local win_id_left = resize_state.windows[1] + local win_id_right = resize_state.windows[2] + + eq(win_id_left, child.api.nvim_get_current_win()) + + -- Since terminal width (80) >= equalise_min_cols (80), windows should be equal + -- Total width is 80, minus 1 for separator = 79, divided by 2 = 39.5 + -- Both windows should be approximately equal (39 and 40) + local left_width = child.api.nvim_win_get_width(win_id_left) + local right_width = child.api.nvim_win_get_width(win_id_right) + + -- Check that windows are approximately equal (difference <= 1) + expect.no_error(function() + local diff = math.abs(left_width - right_width) + if diff > 1 then + error('Windows not equal: ' .. left_width .. ' vs ' .. right_width) + end + end) + + -- Switch windows and verify equal sizes are maintained + child.cmd('wincmd w') + + local new_left_width = child.api.nvim_win_get_width(win_id_left) + local new_right_width = child.api.nvim_win_get_width(win_id_right) + + -- After switching, windows should still be equal (not golden ratio) + expect.no_error(function() + local diff = math.abs(new_left_width - new_right_width) + if diff > 1 then + error('Windows not equal after switch: ' .. new_left_width .. ' vs ' .. new_right_width) + end + end) +end + +-- Test equalise_min_cols when terminal is NOT wide enough +T['autoresize']['equalise_min_cols when not wide enough'] = function() + reload_module({ autoresize = { equalise_min_cols = 100 } }) + child.set_size(25, 80) + edit(lorem_ipsum_file) + child.cmd('vsplit') + local resize_state = child.get_resize_state() + + local win_id_left = resize_state.windows[1] + local win_id_right = resize_state.windows[2] + + eq(win_id_left, child.api.nvim_get_current_win()) + + -- Since terminal width (80) < equalise_min_cols (100), use golden ratio + -- Should match the default golden ratio behavior + validate_win_dims(win_id_left, { 49, 23 }) + validate_win_dims(win_id_right, { 30, 23 }) + + -- Switch windows and verify golden ratio behavior + child.cmd('wincmd w') + + validate_win_dims(win_id_left, { 30, 23 }) + validate_win_dims(win_id_right, { 49, 23 }) +end + +-- Test equalise_min_rows when terminal is tall enough +T['autoresize']['equalise_min_rows when tall enough'] = function() + reload_module({ autoresize = { equalise_min_rows = 25 } }) + child.set_size(25, 80) + edit(lorem_ipsum_file) + child.cmd('split') + local resize_state = child.get_resize_state() + + local win_id_upper = resize_state.windows[1] + local win_id_lower = resize_state.windows[2] + + eq(win_id_upper, child.api.nvim_get_current_win()) + + -- Since terminal height (25) >= equalise_min_rows (25), windows should be equal + local upper_height = child.api.nvim_win_get_height(win_id_upper) + local lower_height = child.api.nvim_win_get_height(win_id_lower) + + -- Check that windows are approximately equal (difference <= 1) + expect.no_error(function() + local diff = math.abs(upper_height - lower_height) + if diff > 1 then + error('Windows not equal: ' .. upper_height .. ' vs ' .. lower_height) + end + end) + + -- Switch windows and verify equal sizes are maintained + child.cmd('wincmd w') + + local new_upper_height = child.api.nvim_win_get_height(win_id_upper) + local new_lower_height = child.api.nvim_win_get_height(win_id_lower) + + -- After switching, windows should still be equal (not golden ratio) + expect.no_error(function() + local diff = math.abs(new_upper_height - new_lower_height) + if diff > 1 then + error('Windows not equal after switch: ' .. new_upper_height .. ' vs ' .. new_lower_height) + end + end) +end + +-- Test equalise_min_rows when terminal is NOT tall enough +T['autoresize']['equalise_min_rows when not tall enough'] = function() + reload_module({ autoresize = { equalise_min_rows = 30 } }) + child.set_size(25, 80) + edit(lorem_ipsum_file) + child.cmd('split') + local resize_state = child.get_resize_state() + + local win_id_upper = resize_state.windows[1] + local win_id_lower = resize_state.windows[2] + + eq(win_id_upper, child.api.nvim_get_current_win()) + + -- Since terminal height (25) < equalise_min_rows (30), use golden ratio + -- Should match the default golden ratio behavior + validate_win_dims(win_id_upper, { 80, 15 }) + validate_win_dims(win_id_lower, { 80, 7 }) + + -- Switch windows and verify golden ratio behavior + child.cmd('wincmd w') + + validate_win_dims(win_id_upper, { 80, 7 }) + validate_win_dims(win_id_lower, { 80, 15 }) +end + +-- Test when both equalise_min_cols and equalise_min_rows are set and both met +T['autoresize']['equalise both conditions met'] = function() + reload_module({ autoresize = { equalise_min_cols = 80, equalise_min_rows = 25 } }) + child.set_size(25, 80) + edit(lorem_ipsum_file) + child.cmd('vsplit') + child.cmd('split') + local resize_state = child.get_resize_state() + + local win_id_upper_left = resize_state.windows[1] + local win_id_lower_left = resize_state.windows[2] + local win_id_right = resize_state.windows[3] + + eq(win_id_upper_left, child.api.nvim_get_current_win()) + + -- Since both conditions are met, all windows should be approximately equal + local ul_width = child.api.nvim_win_get_width(win_id_upper_left) + local ll_width = child.api.nvim_win_get_width(win_id_lower_left) + local r_width = child.api.nvim_win_get_width(win_id_right) + + -- Left windows should have same width + eq(ul_width, ll_width) + + -- All columns should be approximately equal + expect.no_error(function() + local diff = math.abs(ul_width - r_width) + if diff > 1 then + error('Columns not equal: ' .. ul_width .. ' vs ' .. r_width) + end + end) + + -- Heights should also be approximately equal for the left column + local ul_height = child.api.nvim_win_get_height(win_id_upper_left) + local ll_height = child.api.nvim_win_get_height(win_id_lower_left) + + expect.no_error(function() + local diff = math.abs(ul_height - ll_height) + if diff > 1 then + error('Heights not equal: ' .. ul_height .. ' vs ' .. ll_height) + end + end) +end + +-- Test when both are set but only cols condition is met +T['autoresize']['equalise both set only cols met'] = function() + reload_module({ autoresize = { equalise_min_cols = 80, equalise_min_rows = 30 } }) + child.set_size(25, 80) + edit(lorem_ipsum_file) + child.cmd('vsplit') + local resize_state = child.get_resize_state() + + local win_id_left = resize_state.windows[1] + local win_id_right = resize_state.windows[2] + + eq(win_id_left, child.api.nvim_get_current_win()) + + -- Since cols met (80 >= 80) but rows not met (25 < 30), use golden ratio + validate_win_dims(win_id_left, { 49, 23 }) + validate_win_dims(win_id_right, { 30, 23 }) +end + +-- Test when both are set but only rows condition is met +T['autoresize']['equalise both set only rows met'] = function() + reload_module({ autoresize = { equalise_min_cols = 100, equalise_min_rows = 25 } }) + child.set_size(25, 80) + edit(lorem_ipsum_file) + child.cmd('split') + local resize_state = child.get_resize_state() + + local win_id_upper = resize_state.windows[1] + local win_id_lower = resize_state.windows[2] + + eq(win_id_upper, child.api.nvim_get_current_win()) + + -- Since rows met (25 >= 25) but cols not met (80 < 100), use golden ratio + validate_win_dims(win_id_upper, { 80, 15 }) + validate_win_dims(win_id_lower, { 80, 7 }) +end + +-- Test when both are set and neither condition is met +T['autoresize']['equalise both set neither met'] = function() + reload_module({ autoresize = { equalise_min_cols = 100, equalise_min_rows = 30 } }) + child.set_size(25, 80) + edit(lorem_ipsum_file) + child.cmd('vsplit') + local resize_state = child.get_resize_state() + + local win_id_left = resize_state.windows[1] + local win_id_right = resize_state.windows[2] + + eq(win_id_left, child.api.nvim_get_current_win()) + + -- Since neither condition met, use golden ratio + validate_win_dims(win_id_left, { 49, 23 }) + validate_win_dims(win_id_right, { 30, 23 }) +end + return T From cb2a68d89c509dc0687fe796283cb18992b21426 Mon Sep 17 00:00:00 2001 From: Josh Lane Date: Sat, 15 Nov 2025 08:12:34 -0800 Subject: [PATCH 3/3] style: format code with stylua --- lua/focus/modules/resizer.lua | 11 ++++++++--- tests/test_autoresize.lua | 34 +++++++++++++++++++++++++++------- 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/lua/focus/modules/resizer.lua b/lua/focus/modules/resizer.lua index 2fe3b467..03cd7ea1 100644 --- a/lua/focus/modules/resizer.lua +++ b/lua/focus/modules/resizer.lua @@ -53,12 +53,17 @@ function M.autoresize(config) -- When columns >= equalise_min_cols AND/OR rows >= equalise_min_rows, use equal splits (wincmd =) -- Otherwise, use golden ratio autoresize local should_equalise = false - local cols_check = config.autoresize.equalise_min_cols > 0 and vim.o.columns >= config.autoresize.equalise_min_cols - local rows_check = config.autoresize.equalise_min_rows > 0 and vim.o.lines >= config.autoresize.equalise_min_rows + local cols_check = config.autoresize.equalise_min_cols > 0 + and vim.o.columns >= config.autoresize.equalise_min_cols + local rows_check = config.autoresize.equalise_min_rows > 0 + and vim.o.lines >= config.autoresize.equalise_min_rows -- If both thresholds are set, both conditions must be met -- If only one is set, only that condition needs to be met - if config.autoresize.equalise_min_cols > 0 and config.autoresize.equalise_min_rows > 0 then + if + config.autoresize.equalise_min_cols > 0 + and config.autoresize.equalise_min_rows > 0 + then should_equalise = cols_check and rows_check elseif config.autoresize.equalise_min_cols > 0 then should_equalise = cols_check diff --git a/tests/test_autoresize.lua b/tests/test_autoresize.lua index 89ade0fb..6b3a84e2 100644 --- a/tests/test_autoresize.lua +++ b/tests/test_autoresize.lua @@ -582,7 +582,12 @@ T['autoresize']['equalise_min_cols when wide enough'] = function() expect.no_error(function() local diff = math.abs(new_left_width - new_right_width) if diff > 1 then - error('Windows not equal after switch: ' .. new_left_width .. ' vs ' .. new_right_width) + error( + 'Windows not equal after switch: ' + .. new_left_width + .. ' vs ' + .. new_right_width + ) end end) end @@ -633,7 +638,9 @@ T['autoresize']['equalise_min_rows when tall enough'] = function() expect.no_error(function() local diff = math.abs(upper_height - lower_height) if diff > 1 then - error('Windows not equal: ' .. upper_height .. ' vs ' .. lower_height) + error( + 'Windows not equal: ' .. upper_height .. ' vs ' .. lower_height + ) end end) @@ -647,7 +654,12 @@ T['autoresize']['equalise_min_rows when tall enough'] = function() expect.no_error(function() local diff = math.abs(new_upper_height - new_lower_height) if diff > 1 then - error('Windows not equal after switch: ' .. new_upper_height .. ' vs ' .. new_lower_height) + error( + 'Windows not equal after switch: ' + .. new_upper_height + .. ' vs ' + .. new_lower_height + ) end end) end @@ -679,7 +691,9 @@ end -- Test when both equalise_min_cols and equalise_min_rows are set and both met T['autoresize']['equalise both conditions met'] = function() - reload_module({ autoresize = { equalise_min_cols = 80, equalise_min_rows = 25 } }) + reload_module({ + autoresize = { equalise_min_cols = 80, equalise_min_rows = 25 }, + }) child.set_size(25, 80) edit(lorem_ipsum_file) child.cmd('vsplit') @@ -722,7 +736,9 @@ end -- Test when both are set but only cols condition is met T['autoresize']['equalise both set only cols met'] = function() - reload_module({ autoresize = { equalise_min_cols = 80, equalise_min_rows = 30 } }) + reload_module({ + autoresize = { equalise_min_cols = 80, equalise_min_rows = 30 }, + }) child.set_size(25, 80) edit(lorem_ipsum_file) child.cmd('vsplit') @@ -740,7 +756,9 @@ end -- Test when both are set but only rows condition is met T['autoresize']['equalise both set only rows met'] = function() - reload_module({ autoresize = { equalise_min_cols = 100, equalise_min_rows = 25 } }) + reload_module({ + autoresize = { equalise_min_cols = 100, equalise_min_rows = 25 }, + }) child.set_size(25, 80) edit(lorem_ipsum_file) child.cmd('split') @@ -758,7 +776,9 @@ end -- Test when both are set and neither condition is met T['autoresize']['equalise both set neither met'] = function() - reload_module({ autoresize = { equalise_min_cols = 100, equalise_min_rows = 30 } }) + reload_module({ + autoresize = { equalise_min_cols = 100, equalise_min_rows = 30 }, + }) child.set_size(25, 80) edit(lorem_ipsum_file) child.cmd('vsplit')