├── 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 |
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 |
--------------------------------------------------------------------------------