feat(vim): Smart quit popup

space q will close the application if all buffers are saved, otherwise a
dialog box shows up with the buffers that are not saved.
This commit is contained in:
clover caruso 2025-08-21 01:57:32 -07:00
parent 1052a1ccc3
commit b34edc3a51
6 changed files with 130 additions and 16 deletions

View file

@ -0,0 +1,96 @@
vim.api.nvim_create_user_command("SmartQuit", function(opts)
-- scan all buffers for unsaved buffers
local unsaved = {}
local saved = {}
for _, buf in ipairs(vim.api.nvim_list_bufs()) do
if vim.api.nvim_buf_is_loaded(buf) and vim.api.nvim_buf_get_option(buf, "buflisted") then
if vim.api.nvim_buf_get_option(buf, "modified") then
table.insert(unsaved, buf)
else
table.insert(saved, buf)
end
end
end
if #unsaved == 0 then
vim.cmd("qa")
return
end
-- gui for resolving unsaved files
local lines = {}
for _, buf in ipairs(unsaved) do
local full_path = vim.api.nvim_buf_get_name(buf)
local name = full_path == "" and ("[No Name] #" .. buf) or vim.fn.fnamemodify(full_path, ":~:.")
table.insert(lines, "" .. name)
end
table.insert(lines, "")
table.insert(lines, " s - Save all and quit")
table.insert(lines, " c - Close saved; leave unsaved open")
table.insert(lines, " d - DISCARD ALL")
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_lines(buf, 0, -1, false, lines)
vim.api.nvim_buf_set_option(buf, "readonly", true)
local width = math.max(unpack(vim.tbl_map(function(l) return #l end, lines)))
local height = #lines
local win = vim.api.nvim_open_win(buf, true, {
relative = "editor",
width = width + 1,
height = height,
row = (vim.o.lines - height) / 2 - 1,
col = (vim.o.columns - width) / 2,
style = "minimal",
border = "rounded",
title = {
{ #unsaved .. " Unsaved Buffer" .. (#unsaved != 1 and "s" or "") .. "!", "ErrorMsg" },
},
title_pos = "center",
})
vim.wo[win].winhighlight = "Normal:Normal,FloatBorder:ErrorMsg"
vim.api.nvim_win_set_option(win, "cursorline", false)
local hl = vim.api.nvim_get_hl_by_name('Cursor', true)
hl.blend = 100
vim.api.nvim_set_hl(0, 'Cursor', hl)
vim.opt.guicursor:append('a:Cursor/lCursor')
function close()
local hl = vim.api.nvim_get_hl_by_name('Cursor', true)
hl.blend = 0
vim.api.nvim_set_hl(0, 'Cursor', hl)
vim.opt.guicursor:remove('a:Cursor/lCursor')
vim.api.nvim_win_close(win, true)
end
-- keyboard event handlers
vim.keymap.set("n", "s", function()
for _, buf in ipairs(unsaved) do
vim.api.nvim_buf_call(buf, function()
vim.cmd("write")
end)
end
close()
vim.cmd("qa")
end, { buffer = buf })
vim.keymap.set("n", "c", function()
for _, buf in ipairs(saved) do
vim.api.nvim_buf_delete(buf, {})
end
close()
end, { buffer = buf })
vim.keymap.set("n", "d", function()
vim.cmd("qa!")
end, { buffer = buf })
vim.keymap.set("n", "<esc>", function()
close()
end, { buffer = buf })
vim.api.nvim_create_autocmd({ 'WinLeave' }, {
buffer = buf,
callback = close,
})
end, { nargs = 0 })
vim.api.nvim_set_hl(0, "QuitPopUpBorder", { fg = "red" })

View file

@ -20,7 +20,8 @@
# which allows user configurations to disable/override anything here.
config.vim = lib.mkOverride 999 {
extraLuaFiles = [
./lib.lua
./commands/FullscreenTerm.lua
./commands/SmartQuit.lua
];
theme = {
@ -57,7 +58,7 @@
lspkind.enable = config.vim.autocomplete.blink-cmp.enable;
# Enables inlay hints (types info in rust and shit)
inlayHints.enable = true;
#Nice mappings that i use :3
# Nice mappings that I use :3
mappings = {
codeAction = "<leader>ca";
goToDeclaration = "gD";
@ -114,6 +115,19 @@
setupOpts = {
enable_cursor_hijack = true;
git_status_async = true;
close_if_last_window = true;
filesystem.filtered_items = {
# dotfiles are often used for project configuration
hide_dotfiles = false;
never_show = [
".DS_Store" # macOS
"thumbs.db" # Windows
];
never_show_by_pattern = [
".*.swp"
"._*" # macOS
];
};
};
};
fzf-lua = {

View file

@ -21,6 +21,7 @@ in
toggle-explorer = keyCmd "n" "<leader>e" "Neotree toggle";
reveal-active-file = keyCmd "n" "<leader>E" "Neotree reveal<CR>:Neotree focus";
lazygit = keyCmd "n" "<leader>gg" "FullscreenTerm ${pkgs.lazygit}/bin/lazygit";
smart-quit = keyCmd "n" "<leader>q" "SmartQuit";
# pickers
pick-file = keyCmd "n" "<leader><leader>" "FzfLua files";

View file

@ -1,20 +1,22 @@
_: {
vim = {
options = {
linebreak = true;
};
git = {
gitsigns.setupOpts = {
current_line_blame = false;
current_line_blame_opts = {
virt_text = true;
virt_text_pos = "right_align";
delay = 25;
ignore_whitespace = true;
virt_text_priority = 100;
use_focus = true;
};
languages.lua.enable = false; # broken
options.linebreak = true;
git.gitsigns.setupOpts = {
current_line_blame = false;
current_line_blame_opts = {
virt_text = true;
virt_text_pos = "right_align";
delay = 25;
ignore_whitespace = true;
virt_text_priority = 100;
use_focus = true;
};
};
# filetree.neo-tree.setupOpts.default_component_configs.indent = {
# padding = 0;
# indent_marker = "|";
# last_indent_marker = "+";
# };
};
}

View file

@ -11,6 +11,7 @@
python312Packages.pylatexenc
];
languages = {
lua.lsp.enable = true;
lua.lsp.lazydev.enable = true;
python = {
format.type = "ruff";