Currently, I'm using NeoVim as my primary code editor. Best part of the editor is its endless extensibility. You're encouraged to make it Your editor, tailor it to your specific needs and preferences. NeoVim has an excellent API for achieving virtually any kind of feature you could think of.
Recently, I was having trouble keeping track of my open buffers while working on my personal website. Normally this isn’t an issue, but I was renaming and moving lots of files and some of the buffers were pointing to non-existent paths. At that moment, I wished that I had a key combination or a command to delete all but the current buffer. So, naturally, I did a quick Google search to see if there was an existing solution for my problem.
Doing research revealed that there are some plugins and certain built-in commands that achieved the behavior I was looking for. But I didn’t like any of them. To be fair, I didn’t check any packages that were suggested by people on forums and Reddit, because I’m currently driving this mantra of “no plugins unless absolutely necessary”. And the built-in commands seemed very confusing and limited. To make it a bit more interesting, I decided to write my own custom command!
So, I would like to take you into this small adventure where most of the job is done by Copilot but it's still fun, I promise!
So, I would like to take you into this small adventure where most of the job is done by Copilot but it's still fun, I promise!
To achieve our goal, we can use NeoVim’s nvim_create_user_command API:
vim.api.nvim_create_user_command( "CommandName", function() -- implementation... end, { nargs = "?", desc = "Command description", } )
Note: the command name must be an uppercased single word so it doesn’t conflict with NeoVim’s built-in commands, otherwise you will get an error when starting the editor.
Very straightforward, so let’s define the behavior that we want:
- Get the list of open buffers.
- Iterate through them, skip if we match the current buffer.
- Check if buffer has unsaved changes:
- If so, prompt on what to do with the buffer:
- Write and delete
- Cancel
- Skip
- Force delete
- If so, prompt on what to do with the buffer:
- Delete the buffer if not canceled or skipped in the previous step.
- Print the message with the amount of deleted buffers.
First, let’s define the command:
vim.api.nvim_create_user_command( "DeleteOtherBuffers", delete_other_buffers, { nargs = "?", desc = "Delete all buffers except the current one", complete = function() return { "force" } end } )
As you can see, we’re defining a custom user command called DeleteOtherBuffers that accepts 0 or 1 argument which has an autocompletion of force, which indicates whether we want to force delete all of our open buffers.
Now, let’s define the delete_other_buffers function. Let’s start by parsing our arguments to see if we have the force parameter set:
function delete_other_buffers(opts) local initial_force = opts.fargs[1] == "force" local force = initial_force
We will use the initial_force variable to reset the force option later in the loop.
After that, we can get the current buffer:
local current_buf = vim.api.nvim_get_current_buf()
Let’s initialize the count variable and start iterating over the list of open buffers:
local count = 0 for _, buf in ipairs(vim.api.nvim_list_bufs()) do
As mentioned in our spec, we want to skip the buffer if it matches the current one:
if buf == current_buf then goto continue end
We will define the continue label at the end of our loop. Now, let’s get the name of the current buffer and see if it has been modified:
local bufname = vim.api.nvim_buf_get_name(buf) local unsaved_changes = vim.api.nvim_buf_get_option(buf, "modified")
If we do have some changes that aren’t written and we don’t have the force option set, we can summon a prompt with possible actions per our spec:
if unsaved_changes and not force then local choice = vim.fn.confirm( "Buffer " .. bufname .. " has unsaved changes, what would you like to do?", "&Write and delete\n&Cancel\n&Skip\n&Force delete", 4 )
We act based on the choice. If "Write and delete” option is chosen, we switch to the target buffer, write it, and switch back to the current buffer:
if choice == 1 then vim.api.nvim_command("buffer " .. buf) vim.api.nvim_command("write") vim.api.nvim_command("buffer " .. current_buf)
In case “Cancel” is chosen, we just return from our function. This will cancel the operation altogether, however, any buffers that were written before the cancellation, will not return to their previous state:
elseif choice == 2 then return
If “Skip” is chosen, we just go to the next buffer:
elseif choice == 3 then goto continue
And finally, if “Force delete” is chosen, we simply set the force option to be true:
elseif choice == 4 then force = true end end
With all possible cases resolved, we proceed to the actual deletion. Obviously, these lines of code will not be reached if “Skip” or “Cancel” options are chosen.
vim.api.nvim_buf_delete(buf, { force = force }) count = count + 1 force = initial_force ::continue:: end
Here, we are also resetting the force option so the choice we made in the current iteration doesn't affect the next one.
As the finishing touch, we can print the amount of deleted buffers and end our function:
print("Deleted " .. count .. " buffers.") end
Here is the full, uninterrupted code:
function delete_other_buffers(opts) local initial_force = opts.fargs[1] == "force" local force = initial_force local current_buf = vim.api.nvim_get_current_buf() local count = 0 for _, buf in ipairs(vim.api.nvim_list_bufs()) do if buf == current_buf then goto continue end local bufname = vim.api.nvim_buf_get_name(buf) local unsaved_changes = vim.api.nvim_buf_get_option(buf, "modified") if unsaved_changes and not force then local choice = vim.fn.confirm( "Buffer " .. bufname .. " has unsaved changes, what would you like to do?", "&Write and delete\n&Cancel\n&Skip\n&Force delete", 4 ) if choice == 1 then vim.api.nvim_command("buffer " .. buf) vim.api.nvim_command("write") vim.api.nvim_command("buffer " .. current_buf) elseif choice == 2 then return elseif choice == 3 then goto continue elseif choice == 4 then force = true end end vim.api.nvim_buf_delete(buf, { force = force }) count = count + 1 force = initial_force ::continue:: end print("Deleted " .. count .. " buffers.") end
I haven’t tested it for more than a couple of runs, but the core logic seems to be correct. The function might be a bit slow if you have hundreds of buffers open, so a coroutine optimization might be considered? Not sure. But for my basic use-case, this is more than sufficient.
This code and the rest of my NeoVim config is available on my GitHub. Check it out if you’re interested.
Niza ✌️