Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ready to make pull request #8

Merged
merged 1 commit into from
Dec 18, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
138 changes: 137 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,79 @@
# bufresize.nvim

## Features

### Resize Terminal Window

https://user-images.githubusercontent.com/38927155/134293002-8b710772-3d7c-49fb-a06e-97f09010c104.mov

bufresize.nvim is a very simple plugin that does one thing, it keeps your buffers width and height in proportion when the terminal window is resized.
bufresize.nvim can keep your buffers width and height in proportion when the terminal window is resized.
For example, if you have two buffers side by side, with the left buffer taking up 70% of the terminal width and the right buffer taking up 30% of the terminal width.
Then if you resized the terminal window, the left buffer and right buffer will still take up 70% and 30% respectively(By default, resizing terminal window does not keep the buffers dimension in proportion).

### Resize After Opening Or Closing Window

In addition, bufresize.nvim also support proportional resize when opening or closing a window.

Normally, if you close window D in the below configuration, the height of window D goes to A and C.

```
Without bufresize
--------------- ---------------
| | | | | |
| | B | | | B |
| A |------| | A |------|
| | C | -> | | C |
| | | | | |
--------------- | | |
| D | | | |
--------------- ---------------
```

With bufresize.nvim, we can distribute the height proportionally to B and C as shown below

```
With bufresize
--------------- ---------------
| | | | | |
| | B | | | B |
| A |------| | A | |
| | C | -> | |------|
| | | | | |
--------------- | | C |
| D | | | |
--------------- ---------------
```

The same goes for opening a new window. For example, if you open [toggleterm](https://github.com/akinsho/toggleterm.nvim) in vertical direction (windows D in the below figure), the initial windows will go out of proportion.

```
Without bufresize
--------------- ---------------
| | | | | | |
| | B | | |B| |
| A | | | A | | |
| |------| -> | |-| D |
| | | | | | |
| | C | | |C| |
| | | | | | |
--------------- ---------------
```

With bufresize.nvim, we can resize the windows A, B, and C so that their proportion remains the same after opening toggleterm windows.

```
With bufresize
--------------- ---------------
| | | | | | |
| | B | | | B | |
| A | | | A | | |
| |------| -> | |---| D |
| | | | | | |
| | C | | | C | |
| | | | | | |
--------------- ---------------
```

## Prerequistes

- Neovim 0.5 or higher
Expand All @@ -31,6 +99,8 @@ use {

## Configuration

This section setup bufresize for resizing terminal window. To setup bufresize for opening and closing window, please see the **Exported Functions** section.

bufresize.nvim setup provides two options, `register` and `resize`. `register` and `resize` are tables with two keys, `keys` and `trigger_events`. `keys` is a list of keymappings and `trigger_events` are a list of vim events that will trigger the function.

`register` is use to register the current state of buffer windows in the vim, it records the layout, and dimension of each active buffer.
Expand Down Expand Up @@ -123,3 +193,69 @@ use({
end,
})
```

## Exported Functions

bufresize export the following functions

1. `register`: record the current windows layout and dimensions. This function is called internally by `resize`, `resize_open`, and `resize_close`
2. `resize`: apply resize using the registered state, then register the state after resizing. Should be use with `VimResized` event
3. `resize_open`: find the newly opened window that is in the current state but not in the registered state, adjust the proportion of registered windows accordingly, then apply resize to the registered windows. Should be use with `block_register` to prevent race condition
4. `resize_close`: find the newly closed window that is not in the registered state but not in the current state, adjust the proportion of registered windows accordingly, then apply resize to the registered windows. Should be use with `block_register` to prevent race condition
5. `setup`: setup the proportional resize functionality for resizing terminal window. Please see **Configuration** section for how to setup
6. `block_register`: prevent `register` from recording the state. This is useful for `resize_open` and `resize_close` if `WinEnter` and `BufWinEnter` is in the `trigger_events` for `register`. If we don't use `block_register`, when opening a new window, `register` might be called before `resize_open`, which will make `resize_open` unable to find the newly opened window
7. `unblock_register`: allow `register` to record the state. This function is called internally by `resize`, `resize_open`, and `resize_close` after applying the resize and before calling `register`

### Configuration for `resize_open` and `resize_close`

My personal usage for `resize_open` and `resize_close` is using with [toggleterm](https://github.com/akinsho/toggleterm.nvim).

Assuming you have the default configuration for `setup`, below is my configuration for toggleterm to not mess up the proportion when toggling.

```lua
opts = { noremap = true, silent = true }
map = vim.api.nvim_set_keymap
ToggleTerm = function(direction)
local command = "ToggleTerm"
if direction == "horizontal" then
command = command .. " direction=horizontal"
elseif direction == "vertical" then
command = command .. " direction=vertical"
end
if vim.bo.filetype == "toggleterm" then
require("bufresize").block_register()
vim.api.nvim_command(command)
require("bufresize").resize_close()
else
require("bufresize").block_register()
vim.api.nvim_command(command)
require("bufresize").resize_open()
cmd([[execute "normal! i"]])
end
end
map("n", "<C-s>", ":lua ToggleTerm()<cr>", opts)
map("n", "<leader>ot", [[:lua ToggleTerm("horizontal")<cr>]], opts)
map("n", "<leader>ol", [[:lua ToggleTerm("vertical")<cr>]], opts)
map("i", "<C-s>", "<esc>:lua ToggleTerm()<cr>", opts)
map("t", "<C-s>", "<C-\\><C-n>:lua ToggleTerm()<cr>", opts)
```

Here is the configuration for applying `resize_close` for closing windows.

```lua
map(
"t",
"<leader>wd",
"<C-\\><C-n>"
.. ":lua require('bufresize').block_register()<cr>"
.. "<C-w>c"
.. ":lua require('bufresize').resize_close()<cr>",
opts
)
map(
"n",
"<leader>wd",
":lua require('bufresize').block_register()<cr>" .. "<C-w>c" .. ":lua require('bufresize').resize_close()<cr>",
opts
)
```
186 changes: 174 additions & 12 deletions lua/bufresize.lua
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@ local vim_size = {}
local win_size = {}
local winlayout = {}
local can_register = true

local block_register = function()
can_register = false
end
local unblock_register = function()
can_register = true
end

local register = function()
if can_register == false then
return
Expand All @@ -26,6 +34,164 @@ end
local gototab = function(num)
vim.cmd([[execute "normal! ]] .. tostring(num) .. [[gt"]])
end

local function recurse_open(layout, old_width, old_height, new_width, new_height, tabnr)
if layout == nil then
return
end
local name, sublayout = layout[1], layout[2]
if name == "leaf" then
local winid = sublayout
local win_dim = win_size[tabnr][winid]
if win_dim ~= nil then
local width_percent = win_dim.width / old_width
-- minus one for the status line
local height_percent = win_dim.height / (old_height - 1)
-- +0.5 for rounding
pcall(function()
vim.api.nvim_win_set_width(winid, math.floor(width_percent * new_width + 0.5))
end)
pcall(function()
vim.api.nvim_win_set_height(winid, math.floor(height_percent * (new_height - 1) + 0.5))
end)
end
else
local newsublayout = {}
for id, elem in pairs(sublayout) do
-- A new window not in the registered layout
if elem[1] == "leaf" and win_size[tabnr][elem[2]] == nil then
if name == "row" then
new_width = new_width - vim.api.nvim_win_get_width(elem[2])
else
new_height = new_height - vim.api.nvim_win_get_height(elem[2])
end
else
newsublayout[id] = elem
end
end

if name == "row" then
old_width = old_width - #newsublayout + 1
new_width = new_width - #newsublayout + 1
else
old_height = old_height - #newsublayout + 1
new_height = new_height - #newsublayout + 1
end
for _, elem in pairs(newsublayout) do
recurse_open(elem, old_width, old_height, new_width, new_height, tabnr)
end
end
end

local apply_open = function()
local curtabnr = vim.fn.tabpagenr()
if winlayout[curtabnr] == nil then
vim.cmd("wincmd =")
else
local ui = vim.api.nvim_list_uis()[1]
local old_width, old_height = vim_size.width, vim_size.height
local layout = vim.fn.winlayout(curtabnr)
recurse_open(layout, old_width, old_height - vim.o.cmdheight, ui.width, ui.height - vim.o.cmdheight, curtabnr)
end
end

local resize_open = function()
if vim.fn.mode() == "t" then
-- have to use this workaround until normal! is supported
local command = [[<C-\><C-n><cmd>lua require('bufresize').resize_open()<cr>i]]
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(command, true, true, true), "n", true)
else
block_register()
apply_open()
unblock_register()
register()
end
end

local function recurse_close(layout, current_windows, old_width, old_height, new_width, new_height, tabnr)
if layout == nil then
return
end
local name, sublayout = layout[1], layout[2]
if name == "leaf" then
local winid = sublayout
local win_dim = win_size[tabnr][winid]
if win_dim ~= nil then
local width_percent = win_dim.width / old_width
-- minus one for the status line
local height_percent = win_dim.height / (old_height - 1)
-- +0.5 for rounding
pcall(function()
vim.api.nvim_win_set_width(winid, math.floor(width_percent * new_width + 0.5))
end)
pcall(function()
vim.api.nvim_win_set_height(winid, math.floor(height_percent * (new_height - 1) + 0.5))
end)
end
else
local newsublayout = {}
for id, elem in pairs(sublayout) do
if elem[1] == "leaf" and current_windows[elem[2]] == nil then
if name == "row" then
old_width = old_width - win_size[tabnr][elem[2]].width - 1
else
old_height = old_height - win_size[tabnr][elem[2]].height - 1
end
else
newsublayout[id] = elem
end
end

if name == "row" then
old_width = old_width - #newsublayout + 1
new_width = new_width - #newsublayout + 1
else
old_height = old_height - #newsublayout + 1
new_height = new_height - #newsublayout + 1
end
for _, elem in pairs(newsublayout) do
recurse_close(elem, current_windows, old_width, old_height, new_width, new_height, tabnr)
end
end
end

local apply_close = function()
local curtabnr = vim.fn.tabpagenr()
if winlayout[curtabnr] == nil then
vim.cmd("wincmd =")
else
local ui = vim.api.nvim_list_uis()[1]
local old_width, old_height = vim_size.width, vim_size.height
local current_windows_list = vim.fn.gettabinfo(curtabnr)[1].windows
local current_windows_set = {}
for _, winid in pairs(current_windows_list) do
current_windows_set[winid] = true
end
recurse_close(
winlayout[curtabnr],
current_windows_set,
old_width,
old_height - vim.o.cmdheight,
ui.width,
ui.height - vim.o.cmdheight,
curtabnr
)
end
end

local resize_close = function()
if vim.fn.mode() == "t" then
-- have to use this workaround until normal! is supported
local command = [[<C-\><C-n><cmd>lua require('bufresize').resize_close()<cr>i]]
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(command, true, true, true), "n", true)
else
block_register()
apply_close()
unblock_register()
register()
end
end

local function recurse(layout, old_width, old_height, new_width, new_height, tabnr)
if layout == nil then
return
Expand Down Expand Up @@ -60,38 +226,31 @@ local function recurse(layout, old_width, old_height, new_width, new_height, tab
end
end
local apply = function()
can_register = false
local curtabnr = vim.fn.tabpagenr()
if winlayout[curtabnr] == nil then
vim.cmd("wincmd =")
else
local ui = vim.api.nvim_list_uis()[1]
for tabnr, layout in pairs(winlayout) do
gototab(tabnr)
recurse(
layout,
vim_size.width,
vim_size.height - vim.o.cmdheight,
ui.width,
ui.height - vim.o.cmdheight,
tabnr
)
local old_width, old_height = vim_size.width, vim_size.height
recurse(layout, old_width, old_height - vim.o.cmdheight, ui.width, ui.height - vim.o.cmdheight, tabnr)
end
gototab(curtabnr)
end
can_register = true
end
local resize = function()
if vim.fn.mode() == "t" then
-- have to use this workaround until normal! is supported
local command = [[<C-\><C-n><cmd>lua require('bufresize').resize()<cr>i]]
vim.api.nvim_feedkeys(vim.api.nvim_replace_termcodes(command, true, true, true), "n", true)
else
block_register()
apply()
unblock_register()
register()
end
end

local function create_augroup(name, events, func)
vim.cmd("augroup " .. name)
vim.cmd("autocmd!")
Expand Down Expand Up @@ -136,9 +295,12 @@ local setup = function(cfg)
create_keymap(key[1], key[2], key[3], "<cmd>lua require('bufresize').resize()<cr>", key[4])
end
end

return {
register = register,
resize = resize,
resize_open = resize_open,
resize_close = resize_close,
setup = setup,
block_register = block_register,
unblock_register = unblock_register,
}