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..03cd7ea1 100644 --- a/lua/focus/modules/resizer.lua +++ b/lua/focus/modules/resizer.lua @@ -49,6 +49,39 @@ 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 diff --git a/tests/test_autoresize.lua b/tests/test_autoresize.lua index a70d6f37..6b3a84e2 100644 --- a/tests/test_autoresize.lua +++ b/tests/test_autoresize.lua @@ -545,4 +545,253 @@ 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