├── LICENSE ├── doc └── mini-bufremove.txt ├── README.md └── lua └── mini └── bufremove.lua /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Evgeni Chasnovski 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /doc/mini-bufremove.txt: -------------------------------------------------------------------------------- 1 | *mini.bufremove* Remove buffers 2 | 3 | MIT License Copyright (c) 2021 Evgeni Chasnovski 4 | 5 | ------------------------------------------------------------------------------ 6 | *MiniBufremove* 7 | Features: 8 | - Unshow, delete, and wipeout buffer while saving window layout 9 | (opposite to builtin Neovim's commands). 10 | 11 | # Setup ~ 12 | 13 | This module doesn't need setup, but it can be done to improve usability. 14 | Setup with `require('mini.bufremove').setup({})` (replace `{}` with your 15 | `config` table). It will create global Lua table `MiniBufremove` which you 16 | can use for scripting or manually (with `:lua MiniBufremove.*`). 17 | 18 | See |MiniBufremove.config| for `config` structure and default values. 19 | 20 | This module doesn't have runtime options, so using `vim.b.minibufremove_config` 21 | will have no effect here. 22 | 23 | To stop module from showing non-error feedback, set `config.silent = true`. 24 | 25 | # Notes ~ 26 | 27 | 1. Which buffer to show in window(s) after its current buffer is removed is 28 | decided by the algorithm: 29 | - If alternate buffer (see |CTRL-^|) is listed (see |buflisted()|), use it. 30 | - If previous listed buffer (see |:bprevious|) is different, use it. 31 | - Otherwise create a new one with `nvim_create_buf(true, false)` and use it. 32 | 33 | # Disabling ~ 34 | 35 | To disable core functionality, set `vim.g.minibufremove_disable` (globally) or 36 | `vim.b.minibufremove_disable` (for a buffer) to `true`. Considering high 37 | number of different scenarios and customization intentions, writing exact 38 | rules for disabling module's functionality is left to user. See 39 | |mini.nvim-disabling-recipes| for common recipes. 40 | 41 | ------------------------------------------------------------------------------ 42 | *MiniBufremove.setup()* 43 | `MiniBufremove.setup`({config}) 44 | Module setup 45 | 46 | Parameters ~ 47 | {config} `(table|nil)` Module config table. See |MiniBufremove.config|. 48 | 49 | Usage ~ 50 | >lua 51 | require('mini.bufremove').setup() -- use default config 52 | -- OR 53 | require('mini.bufremove').setup({}) -- replace {} with your config table 54 | < 55 | ------------------------------------------------------------------------------ 56 | *MiniBufremove.config* 57 | `MiniBufremove.config` 58 | Defaults ~ 59 | >lua 60 | MiniBufremove.config = { 61 | -- Whether to disable showing non-error feedback 62 | silent = false, 63 | } 64 | < 65 | ------------------------------------------------------------------------------ 66 | *MiniBufremove.delete()* 67 | `MiniBufremove.delete`({buf_id}, {force}) 68 | Delete buffer `buf_id` with |:bdelete| after unshowing it 69 | 70 | Parameters ~ 71 | {buf_id} `(number|nil)` Buffer identifier (see |bufnr()|) to use. 72 | Default: 0 for current. 73 | {force} `(boolean|nil)` Whether to ignore unsaved changes (using `!` version of 74 | command). If `false`, calling with unsaved changes will prompt confirm dialog. 75 | Default: `false`. 76 | 77 | Return ~ 78 | `(boolean|nil)` Whether operation was successful. If `nil`, no operation was done. 79 | 80 | ------------------------------------------------------------------------------ 81 | *MiniBufremove.wipeout()* 82 | `MiniBufremove.wipeout`({buf_id}, {force}) 83 | Wipeout buffer `buf_id` with |:bwipeout| after unshowing it 84 | 85 | Parameters ~ 86 | {buf_id} `(number|nil)` Buffer identifier (see |bufnr()|) to use. 87 | Default: 0 for current. 88 | {force} `(boolean|nil)` Whether to ignore unsaved changes (using `!` version of 89 | command). If `false`, calling with unsaved changes will prompt confirm dialog. 90 | Default: `false`. 91 | 92 | Return ~ 93 | `(boolean|nil)` Whether operation was successful. If `nil`, no operation was done. 94 | 95 | ------------------------------------------------------------------------------ 96 | *MiniBufremove.unshow()* 97 | `MiniBufremove.unshow`({buf_id}) 98 | Stop showing buffer `buf_id` in all windows 99 | 100 | Parameters ~ 101 | {buf_id} `(number|nil)` Buffer identifier (see |bufnr()|) to use. 102 | Default: 0 for current. 103 | 104 | Return ~ 105 | `(boolean|nil)` Whether operation was successful. If `nil`, no operation was done. 106 | 107 | ------------------------------------------------------------------------------ 108 | *MiniBufremove.unshow_in_window()* 109 | `MiniBufremove.unshow_in_window`({win_id}) 110 | Stop showing current buffer of window `win_id` 111 | 112 | Notes: 113 | - If `win_id` represents |cmdline-window|, this function will close it. 114 | 115 | Parameters ~ 116 | {win_id} `(number|nil)` Window identifier (see |win_getid()|) to use. 117 | Default: 0 for current. 118 | 119 | Return ~ 120 | `(boolean|nil)` Whether operation was successful. If `nil`, no operation was done. 121 | 122 | 123 | vim:tw=78:ts=8:noet:ft=help:norl: -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

mini.bufremove

2 | 3 | ### Buffer removing (unshow, delete, wipeout), which saves window layout 4 | 5 | See more details in [Features](#features) and [Documentation](doc/mini-bufremove.txt). 6 | 7 | --- 8 | 9 | > [!NOTE] 10 | > This was previously hosted at a personal `echasnovski` GitHub account. It was transferred to a dedicated organization to improve long term project stability. See more details [here](https://github.com/nvim-mini/mini.nvim/discussions/1970). 11 | 12 | ⦿ This is a part of [mini.nvim](https://nvim-mini.org/mini.nvim) library. Please use [this link](https://nvim-mini.org/mini.nvim/readmes/mini-bufremove) if you want to mention this module. 13 | 14 | ⦿ All contributions (issues, pull requests, discussions, etc.) are done inside of 'mini.nvim'. 15 | 16 | ⦿ See [whole library documentation](https://nvim-mini.org/mini.nvim/doc/mini-nvim) to learn about general design principles, disable/configuration recipes, and more. 17 | 18 | ⦿ See [MiniMax](https://nvim-mini.org/MiniMax) for a full config example that uses this module. 19 | 20 | --- 21 | 22 | If you want to help this project grow but don't know where to start, check out [contributing guides of 'mini.nvim'](https://nvim-mini.org/mini.nvim/CONTRIBUTING) or leave a Github star for 'mini.nvim' project and/or any its standalone Git repositories. 23 | 24 | ## Demo 25 | 26 | 27 | https://user-images.githubusercontent.com/24854248/173044032-7874cf95-2e41-49fb-8abe-3aa73526972f.mp4 28 | 29 | ## Features 30 | 31 | Which buffer to show in window(s) after its current buffer is removed is decided by the algorithm: 32 | 33 | - If alternate buffer is listed, use it. 34 | - If previous listed buffer is different, use it. 35 | - Otherwise create a scratch one with `nvim_create_buf(true, true)` and use it. 36 | 37 | ## Installation 38 | 39 | This plugin can be installed as part of 'mini.nvim' library (**recommended**) or as a standalone Git repository. 40 | 41 | There are two branches to install from: 42 | 43 | - `main` (default, **recommended**) will have latest development version of plugin. All changes since last stable release should be perceived as being in beta testing phase (meaning they already passed alpha-testing and are moderately settled). 44 | - `stable` will be updated only upon releases with code tested during public beta-testing phase in `main` branch. 45 | 46 | Here are code snippets for some common installation methods (use only one): 47 | 48 |
49 | With mini.deps 50 | 51 | - 'mini.nvim' library: 52 | 53 | | Branch | Code snippet | 54 | |--------|-----------------------------------------------| 55 | | Main | *Follow recommended ‘mini.deps’ installation* | 56 | | Stable | *Follow recommended ‘mini.deps’ installation* | 57 | 58 | - Standalone plugin: 59 | 60 | | Branch | Code snippet | 61 | |--------|---------------------------------------------------------------------| 62 | | Main | `add(‘nvim-mini/mini.bufremove’)` | 63 | | Stable | `add({ source = ‘nvim-mini/mini.bufremove’, checkout = ‘stable’ })` | 64 | 65 |
66 | 67 |
68 | With folke/lazy.nvim 69 | 70 | - 'mini.nvim' library: 71 | 72 | | Branch | Code snippet | 73 | |--------|-----------------------------------------------| 74 | | Main | `{ 'nvim-mini/mini.nvim', version = false },` | 75 | | Stable | `{ 'nvim-mini/mini.nvim', version = '*' },` | 76 | 77 | - Standalone plugin: 78 | 79 | | Branch | Code snippet | 80 | |--------|----------------------------------------------------| 81 | | Main | `{ 'nvim-mini/mini.bufremove', version = false },` | 82 | | Stable | `{ 'nvim-mini/mini.bufremove', version = '*' },` | 83 | 84 |
85 | 86 |
87 | With junegunn/vim-plug 88 | 89 | - 'mini.nvim' library: 90 | 91 | | Branch | Code snippet | 92 | |--------|------------------------------------------------------| 93 | | Main | `Plug 'nvim-mini/mini.nvim'` | 94 | | Stable | `Plug 'nvim-mini/mini.nvim', { 'branch': 'stable' }` | 95 | 96 | - Standalone plugin: 97 | 98 | | Branch | Code snippet | 99 | |--------|-----------------------------------------------------------| 100 | | Main | `Plug 'nvim-mini/mini.bufremove'` | 101 | | Stable | `Plug 'nvim-mini/mini.bufremove', { 'branch': 'stable' }` | 102 | 103 |
104 | 105 | **Important**: no need to call `require('mini.bufremove').setup()`, but it can be done to improve usability. 106 | 107 | **Note**: if you are on Windows, there might be problems with too long file paths (like `error: unable to create file : Filename too long`). Try doing one of the following: 108 | 109 | - Enable corresponding git global config value: `git config --system core.longpaths true`. Then try to reinstall. 110 | - Install plugin in other place with shorter path. 111 | 112 | ## Default config 113 | 114 | ```lua 115 | -- No need to copy this inside `setup()`. Will be used automatically. 116 | { 117 | -- Whether to disable showing non-error feedback 118 | silent = false, 119 | } 120 | ``` 121 | 122 | ## Similar plugins 123 | 124 | - [mhinz/vim-sayonara](https://github.com/mhinz/vim-sayonara) 125 | - [moll/vim-bbye](https://github.com/moll/vim-bbye) 126 | -------------------------------------------------------------------------------- /lua/mini/bufremove.lua: -------------------------------------------------------------------------------- 1 | --- *mini.bufremove* Remove buffers 2 | --- 3 | --- MIT License Copyright (c) 2021 Evgeni Chasnovski 4 | 5 | --- Features: 6 | --- - Unshow, delete, and wipeout buffer while saving window layout 7 | --- (opposite to builtin Neovim's commands). 8 | --- 9 | --- # Setup ~ 10 | --- 11 | --- This module doesn't need setup, but it can be done to improve usability. 12 | --- Setup with `require('mini.bufremove').setup({})` (replace `{}` with your 13 | --- `config` table). It will create global Lua table `MiniBufremove` which you 14 | --- can use for scripting or manually (with `:lua MiniBufremove.*`). 15 | --- 16 | --- See |MiniBufremove.config| for `config` structure and default values. 17 | --- 18 | --- This module doesn't have runtime options, so using `vim.b.minibufremove_config` 19 | --- will have no effect here. 20 | --- 21 | --- To stop module from showing non-error feedback, set `config.silent = true`. 22 | --- 23 | --- # Notes ~ 24 | --- 25 | --- 1. Which buffer to show in window(s) after its current buffer is removed is 26 | --- decided by the algorithm: 27 | --- - If alternate buffer (see |CTRL-^|) is listed (see |buflisted()|), use it. 28 | --- - If previous listed buffer (see |:bprevious|) is different, use it. 29 | --- - Otherwise create a new one with `nvim_create_buf(true, false)` and use it. 30 | --- 31 | --- # Disabling ~ 32 | --- 33 | --- To disable core functionality, set `vim.g.minibufremove_disable` (globally) or 34 | --- `vim.b.minibufremove_disable` (for a buffer) to `true`. Considering high 35 | --- number of different scenarios and customization intentions, writing exact 36 | --- rules for disabling module's functionality is left to user. See 37 | --- |mini.nvim-disabling-recipes| for common recipes. 38 | ---@tag MiniBufremove 39 | 40 | ---@alias __bufremove_return boolean|nil Whether operation was successful. If `nil`, no operation was done. 41 | ---@alias __bufremove_buf_id number|nil Buffer identifier (see |bufnr()|) to use. 42 | --- Default: 0 for current. 43 | ---@alias __bufremove_force boolean|nil Whether to ignore unsaved changes (using `!` version of 44 | --- command). If `false`, calling with unsaved changes will prompt confirm dialog. 45 | --- Default: `false`. 46 | 47 | -- Module definition ========================================================== 48 | local MiniBufremove = {} 49 | local H = {} 50 | 51 | --- Module setup 52 | --- 53 | ---@param config table|nil Module config table. See |MiniBufremove.config|. 54 | --- 55 | ---@usage >lua 56 | --- require('mini.bufremove').setup() -- use default config 57 | --- -- OR 58 | --- require('mini.bufremove').setup({}) -- replace {} with your config table 59 | --- < 60 | MiniBufremove.setup = function(config) 61 | -- Export module 62 | _G.MiniBufremove = MiniBufremove 63 | 64 | -- Setup config 65 | config = H.setup_config(config) 66 | 67 | -- Apply config 68 | H.apply_config(config) 69 | end 70 | 71 | --- Defaults ~ 72 | ---@eval return MiniDoc.afterlines_to_code(MiniDoc.current.eval_section) 73 | MiniBufremove.config = { 74 | -- Whether to disable showing non-error feedback 75 | silent = false, 76 | } 77 | --minidoc_afterlines_end 78 | 79 | -- Module functionality ======================================================= 80 | --- Delete buffer `buf_id` with |:bdelete| after unshowing it 81 | --- 82 | ---@param buf_id __bufremove_buf_id 83 | ---@param force __bufremove_force 84 | --- 85 | ---@return __bufremove_return 86 | MiniBufremove.delete = function(buf_id, force) 87 | if H.is_disabled() then return end 88 | 89 | return H.unshow_and_cmd(buf_id, force, 'bdelete') 90 | end 91 | 92 | --- Wipeout buffer `buf_id` with |:bwipeout| after unshowing it 93 | --- 94 | ---@param buf_id __bufremove_buf_id 95 | ---@param force __bufremove_force 96 | --- 97 | ---@return __bufremove_return 98 | MiniBufremove.wipeout = function(buf_id, force) 99 | if H.is_disabled() then return end 100 | 101 | return H.unshow_and_cmd(buf_id, force, 'bwipeout') 102 | end 103 | 104 | --- Stop showing buffer `buf_id` in all windows 105 | --- 106 | ---@param buf_id __bufremove_buf_id 107 | --- 108 | ---@return __bufremove_return 109 | MiniBufremove.unshow = function(buf_id) 110 | if H.is_disabled() then return end 111 | 112 | buf_id = H.normalize_buf_id(buf_id) 113 | 114 | if not H.is_valid_id(buf_id, 'buffer') then return false end 115 | 116 | vim.tbl_map(MiniBufremove.unshow_in_window, vim.fn.win_findbuf(buf_id)) 117 | 118 | return true 119 | end 120 | 121 | --- Stop showing current buffer of window `win_id` 122 | --- 123 | --- Notes: 124 | --- - If `win_id` represents |cmdline-window|, this function will close it. 125 | --- 126 | ---@param win_id number|nil Window identifier (see |win_getid()|) to use. 127 | --- Default: 0 for current. 128 | --- 129 | ---@return __bufremove_return 130 | MiniBufremove.unshow_in_window = function(win_id) 131 | if H.is_disabled() then return nil end 132 | 133 | win_id = (win_id == nil) and 0 or win_id 134 | 135 | if not H.is_valid_id(win_id, 'window') then return false end 136 | 137 | local cur_buf = vim.api.nvim_win_get_buf(win_id) 138 | 139 | -- Temporary use window `win_id` as current to have Vim's functions working 140 | vim.api.nvim_win_call(win_id, function() 141 | if vim.fn.getcmdwintype() ~= '' then 142 | vim.cmd('close!') 143 | return 144 | end 145 | 146 | -- Try using alternate buffer 147 | local alt_buf = vim.fn.bufnr('#') 148 | if alt_buf ~= cur_buf and vim.fn.buflisted(alt_buf) == 1 then 149 | vim.api.nvim_win_set_buf(win_id, alt_buf) 150 | return 151 | end 152 | 153 | -- Try using previous buffer 154 | local has_previous = pcall(vim.cmd, 'bprevious') 155 | if has_previous and cur_buf ~= vim.api.nvim_win_get_buf(win_id) then return end 156 | 157 | -- Create new listed scratch buffer 158 | -- NOTE: leave it unnamed to allow `:h buffer-reuse` 159 | local new_buf = vim.api.nvim_create_buf(true, false) 160 | vim.api.nvim_win_set_buf(win_id, new_buf) 161 | end) 162 | 163 | return true 164 | end 165 | 166 | -- Helper data ================================================================ 167 | -- Module default config 168 | H.default_config = vim.deepcopy(MiniBufremove.config) 169 | 170 | -- Helper functionality ======================================================= 171 | -- Settings ------------------------------------------------------------------- 172 | H.setup_config = function(config) 173 | H.check_type('config', config, 'table', true) 174 | config = vim.tbl_deep_extend('force', vim.deepcopy(H.default_config), config or {}) 175 | 176 | H.check_type('silent', config.silent, 'boolean') 177 | 178 | return config 179 | end 180 | 181 | H.apply_config = function(config) MiniBufremove.config = config end 182 | 183 | H.is_disabled = function() return vim.g.minibufremove_disable == true or vim.b.minibufremove_disable == true end 184 | 185 | -- Removing implementation ---------------------------------------------------- 186 | H.unshow_and_cmd = function(buf_id, force, cmd) 187 | buf_id = H.normalize_buf_id(buf_id) 188 | if not H.is_valid_id(buf_id, 'buffer') then 189 | H.message(buf_id .. ' is not a valid buffer id.') 190 | return false 191 | end 192 | 193 | if force == nil then force = false end 194 | if type(force) ~= 'boolean' then 195 | H.message('`force` should be boolean.') 196 | return false 197 | end 198 | 199 | local fun_name = ({ ['bdelete'] = 'delete', ['bwipeout'] = 'wipeout' })[cmd] 200 | if not H.can_remove(buf_id, force, fun_name) then return false end 201 | 202 | -- Unshow buffer from all windows 203 | MiniBufremove.unshow(buf_id) 204 | 205 | -- Execute command 206 | local command = string.format('%s! %d', cmd, buf_id) 207 | -- Use `pcall` here to take care of case where `unshow()` was enough. This 208 | -- can happen with 'bufhidden' option values: 209 | -- - If `delete` then `unshow()` already `bdelete`d buffer. Without `pcall` 210 | -- it gives E516 for `MiniBufremove.delete()` (`wipeout` works). 211 | -- - If `wipe` then `unshow()` already `bwipeout`ed buffer. Without `pcall` 212 | -- it gives E517 for module's `wipeout()` (still E516 for `delete()`). 213 | -- 214 | -- Also account for executing command in command-line window. 215 | -- It gives E11 if trying to execute command. The `unshow()` call should 216 | -- close such window but somehow it doesn't seem to happen immediately. 217 | local ok, result = pcall(vim.cmd, command) 218 | if not (ok or result:find('E516%D') or result:find('E517%D') or result:find('E11%D')) then 219 | H.message(result) 220 | return false 221 | end 222 | 223 | return true 224 | end 225 | 226 | -- Utilities ------------------------------------------------------------------ 227 | H.error = function(msg) error('(mini.bufremove) ' .. msg, 0) end 228 | 229 | H.check_type = function(name, val, ref, allow_nil) 230 | if type(val) == ref or (ref == 'callable' and vim.is_callable(val)) or (allow_nil and val == nil) then return end 231 | H.error(string.format('`%s` should be %s, not %s', name, ref, type(val))) 232 | end 233 | 234 | H.echo = function(msg, is_important) 235 | if MiniBufremove.config.silent then return end 236 | 237 | -- Construct message chunks 238 | msg = type(msg) == 'string' and { { msg } } or msg 239 | table.insert(msg, 1, { '(mini.bufremove) ', 'WarningMsg' }) 240 | 241 | -- Echo. Force redraw to ensure that it is effective (`:h echo-redraw`) 242 | vim.cmd([[echo '' | redraw]]) 243 | vim.api.nvim_echo(msg, is_important, {}) 244 | end 245 | 246 | H.message = function(msg) H.echo(msg, true) end 247 | 248 | H.is_valid_id = function(x, type) 249 | local is_valid = false 250 | if type == 'buffer' then 251 | is_valid = vim.api.nvim_buf_is_valid(x) 252 | elseif type == 'window' then 253 | is_valid = vim.api.nvim_win_is_valid(x) 254 | end 255 | 256 | if not is_valid then H.message(string.format('%s is not a valid %s id.', tostring(x), type)) end 257 | return is_valid 258 | end 259 | 260 | -- Check if buffer can be removed with `MiniBufremove.fun_name` function 261 | H.can_remove = function(buf_id, force, fun_name) 262 | if force or not vim.bo[buf_id].modified then return true end 263 | local msg = string.format('Buffer %d has unsaved changes. Do you want to force %s?', buf_id, fun_name) 264 | return vim.fn.confirm(msg, '&No\n&Yes', 1, 'Question') == 2 265 | end 266 | 267 | -- Compute 'true' buffer id (strictly positive integer). Treat `nil` and 0 as 268 | -- current buffer. 269 | H.normalize_buf_id = function(buf_id) 270 | if buf_id == nil or buf_id == 0 then return vim.api.nvim_get_current_buf() end 271 | return buf_id 272 | end 273 | 274 | return MiniBufremove 275 | --------------------------------------------------------------------------------