mirror of
https://github.com/folke/snacks.nvim
synced 2025-12-23 08:47:57 +00:00
🤖 I have created a release *beep* *boop* --- ## [2.28.0](https://github.com/folke/snacks.nvim/compare/v2.27.0...v2.28.0) (2025-11-01) ### Features * **gh:** new `gh` (GitHub cli) integration ([85b8ec2](85b8ec2109)) * **image:** when opts.conceal, conceal remainig lines that are not covered by the image. See [#2391](https://github.com/folke/snacks.nvim/issues/2391) ([404027c](404027c973)) * **picker.buffers:** add filetype/buftype to search text ([a249c86](a249c86cf1)) * **picker.buffers:** added buftype and filetype for scratch buffers ([6a13271](6a132716af)) * **picker.diff:** moved git_diff finder to separate file so it can be re-used + made it more robust. Closes [#2366](https://github.com/folke/snacks.nvim/issues/2366) ([3049ad8](3049ad8beb)) * **picker.diff:** native diff now also highlights the language of the diffed code in the diff ([7260957](726095723d)) * **picker.git_diff:** `git_diff` now also shows staged hunks and added stage/unstage/restore actions for hunks. Closes [#2382](https://github.com/folke/snacks.nvim/issues/2382) ([1fb3f4d](1fb3f4de49)) * **picker.git_diff:** added `staged` flag ([118648c](118648ce93)) * **picker.highlights:** badges ([202e595](202e595e55)) * **picker.preview:** allow items to define a title used in the preview window ([4b572f4](4b572f4785)) * **picker.preview:** support for images and render markdown ([9585da6](9585da6c57)) * **picker.util:** cmdline parser used to properly parse diff args ([5025989](502598953f)) * **picker:** better integration with markview and render-markdown when previewing ([4708e9a](4708e9a386)) * **scratch:** store scratch info in meta files, instead of the filename + custom filekeys ([85f8e22](85f8e22281)) * **util.spawn:** `Proc:json()` ([5589c9d](5589c9d355)) * **util:** `Snacks.util.stop()` to safely stop/close a luv handle ([ce9e299](ce9e2993dd)) ### Bug Fixes * **explorer.tree:** only strip trasiling forward slashes if not at root. Closes [#2375](https://github.com/folke/snacks.nvim/issues/2375) ([72dc621](72dc6213f7)) * **explorer:** differentiate if file or folder when deleting on Windows ([#2373](https://github.com/folke/snacks.nvim/issues/2373)) ([59c5545](59c5545e91)) * **explorer:** do reveal in on_show if explorer is not open yet. Closes [#2388](https://github.com/folke/snacks.nvim/issues/2388) ([ba529d4](ba529d4f5d)) * **explorer:** schedule `on_find` (typically reveal), for both files finder and when git status updates the finder. Closes [#2388](https://github.com/folke/snacks.nvim/issues/2388) ([a9b57b2](a9b57b2a7e)) * **gh:** add action idx to `gh_actions` text ([d94184d](d94184d1d9)) * **image.inline:** off-by-one for finding visible images at the last line of the buffer ([04b3a54](04b3a54576)) * **image:** avoid nested math environments ([#2345](https://github.com/folke/snacks.nvim/issues/2345)) ([66e3dc4](66e3dc4619)) * **image:** check for invalid buffer ([9ad4178](9ad41782ec)) * **image:** check to update on BufWinEnter and attach to buffer changes ([e18e4f6](e18e4f6452)) * **image:** don't add placements to concealed lines. Closes [#2391](https://github.com/folke/snacks.nvim/issues/2391) ([13963b1](13963b1ec4)) * **image:** guard against invalid buffers. Closes [#2383](https://github.com/folke/snacks.nvim/issues/2383) ([4bb1ce1](4bb1ce16ed)) * keymap docs ([583a0c1](583a0c1c06)) * **keymap:** make sure opts are a table. Closes [#2392](https://github.com/folke/snacks.nvim/issues/2392) ([367d1bd](367d1bd385)) * **layout:** only max zindex for snacks windows/layouts ([8eddc0b](8eddc0b380)) * **picker.actions:** `drop` and `tabdrop` should never reload existing buffers ([#2368](https://github.com/folke/snacks.nvim/issues/2368)) ([6cf2fee](6cf2fee619)) * **picker.actions:** use `buffer!` instead of `buffer` for edit. Closes [#2378](https://github.com/folke/snacks.nvim/issues/2378) ([2a1a001](2a1a001d3a)) * **picker.diff:** better filename parsing. See [#2366](https://github.com/folke/snacks.nvim/issues/2366) ([377f3bf](377f3bfeca)) * **picker.diff:** first line of header ([fb011c2](fb011c257f)) * **picker.diff:** only process `---` diffs directly if it doesn't start with a diff header ([0a33aec](0a33aec0c6)) * **picker.filter:** get cwd from active tabpage if available ([c1b517f](c1b517f545)) * **picker.finder:** mutate existing opts ([c91e230](c91e23060c)) * **picker.finder:** tmp fix for [#2386](https://github.com/folke/snacks.nvim/issues/2386) ([5eea5f9](5eea5f9428)) * **picker.git_branches:** git log preview. Closes [#2360](https://github.com/folke/snacks.nvim/issues/2360) ([597ebd4](597ebd4115)) * **picker.git_diff:** remove `--default-prefix`, since that's no longer needed. See [#2382](https://github.com/folke/snacks.nvim/issues/2382) ([40774ae](40774ae6ca)) * **picker.git_diff:** set `group=false` by default, since we also have `git_status` ([530e591](530e591345)) * **picker.highlights:** modifiable for set_lines ([98345fb](98345fb667)) * **picker.keymaps:** try to locate neovim compiled lua source files for keymaps ([76160be](76160be5d3)) * **picker.lsp:** fixed `vim.str_byteindex` capability check. Closes [#2389](https://github.com/folke/snacks.nvim/issues/2389) ([46917d0](46917d0629)) * **picker.lsp:** some LSP servers notify completion before sending the actual result. See [#2372](https://github.com/folke/snacks.nvim/issues/2372) ([aa8a318](aa8a318779)) * **picker.lsp:** use `LspRequest` to track completed and cancelled requests. Fixes [#2364](https://github.com/folke/snacks.nvim/issues/2364) ([8afb609](8afb609333)) * **picker.preview:** again. docgen seems broken ([758bbfa](758bbfa13a)) * **picker.preview:** don't show locations for diff preview ([b064488](b0644884ef)) * **picker.preview:** fckup ([fd7795e](fd7795e9cd)) * **picker.preview:** fix ([e2c1c52](e2c1c527e4)) * **picker.preview:** show proper preview message for deleted scratch buffers ([4ad8a41](4ad8a41eac)) * **picker.util:** better relative time format ([3e30fb6](3e30fb6c70)) * **picker.util:** ignore errors from corrupted kv stores. Closes [#2394](https://github.com/folke/snacks.nvim/issues/2394) ([b3d01c5](b3d01c59ba)) * **picker.watch:** check again for closed picker after schedule. See [#2365](https://github.com/folke/snacks.nvim/issues/2365) ([8ad80de](8ad80de67b)) * **picker:** close picker when layout closes. Closes [#2365](https://github.com/folke/snacks.nvim/issues/2365) ([779746f](779746f9a8)) * **picker:** dont watch files for closed pickers. Fixes [#2365](https://github.com/folke/snacks.nvim/issues/2365) ([c4ec8b6](c4ec8b6d12)) * **picker:** increase default show_delay to 5s. Closes [#2364](https://github.com/folke/snacks.nvim/issues/2364) ([b3197e3](b3197e3a2a)) * **picker:** only trim space in the title if it's preceded by a word like character (skips icons) ([2439c49](2439c493a5)) * **picker:** pause input progress info for 60ms to prevent flickering when finder is too fast ([ecde81f](ecde81fc0c)) * **scratch:** make sure zindex of scratch window is higher than existing floating windows ([c8422da](c8422da50d)) * **scroll:** only reset count when needed ([551d79f](551d79f1c0)) * **util.job:** scroll to top when process exits ([b544157](b5441575e0)) * **util.job:** stop on BufWipeout and BufDelete ([c956b37](c956b37246)) * **util.job:** stop when attached buffer is no longer valid ([221d4b1](221d4b1747)) * **util.job:** use nvim_win_set_cursor instead of `gg` ([5faed2f](5faed2f7ab)) * **util.lsp:** `Snacks.util.lsp.on()` should trigger for each lsp client per buffer ([52f30a1](52f30a198a)) * **util:** color() should not create hl groups ([17033e6](17033e67ef)) * **win:** ignore errors on destroy. Closes [#2381](https://github.com/folke/snacks.nvim/issues/2381) ([a8930bd](a8930bdb61)) * **win:** scratch buffers were sometimes not deleted ([0387297](03872973b3)) * **win:** when fixbuf triggers in a floating window, just close it. Closes [#2380](https://github.com/folke/snacks.nvim/issues/2380) ([de35242](de352425f7)) ### Performance Improvements * **animate:** smoother animations ([b7a3fed](b7a3fed8d9)) * **notifier:** stop trying to fit more notifs in the layout after skipping max 10 ([3a8ecf5](3a8ecf5912)) * **picker.util:** cache badge hl groups ([cb85844](cb85844e84)) * **scroll:** combine all scrolling commands in one command + restore vim.v.count ([0fbea13](0fbea13c9d)) * **scroll:** smoother scrolling using new animations ([2221fe6](2221fe6166)) * **statuscolumn:** only calculate components that are actually needed ([bb80317](bb80317647)) ### Reverts * jump `buffer` -> `buffer!`. See [#2378](https://github.com/folke/snacks.nvim/issues/2378) ([143e9b5](143e9b58c7)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
227 lines
5.8 KiB
Lua
227 lines
5.8 KiB
Lua
---@class Snacks: snacks.plugins
|
|
local M = {}
|
|
|
|
setmetatable(M, {
|
|
__index = function(t, k)
|
|
---@diagnostic disable-next-line: no-unknown
|
|
t[k] = require("snacks." .. k)
|
|
return rawget(t, k)
|
|
end,
|
|
})
|
|
|
|
_G.Snacks = M
|
|
_G.svim = vim.fn.has("nvim-0.11") == 1 and vim or require("snacks.compat")
|
|
|
|
M.version = "2.28.0" -- x-release-please-version
|
|
|
|
---@class snacks.Config.base
|
|
---@field example? string
|
|
---@field config? fun(opts: table, defaults: table)
|
|
|
|
---@class snacks.Config: snacks.plugins.Config
|
|
---@field styles? table<string, snacks.win.Config>
|
|
---@field image? snacks.image.Config|{}
|
|
local config = {
|
|
image = {
|
|
-- define these here, so that we don't need to load the image module
|
|
formats = {
|
|
"png",
|
|
"jpg",
|
|
"jpeg",
|
|
"gif",
|
|
"bmp",
|
|
"webp",
|
|
"tiff",
|
|
"heic",
|
|
"avif",
|
|
"mp4",
|
|
"mov",
|
|
"avi",
|
|
"mkv",
|
|
"webm",
|
|
"pdf",
|
|
"icns",
|
|
},
|
|
},
|
|
}
|
|
config.styles = {}
|
|
|
|
---@class snacks.config: snacks.Config
|
|
M.config = setmetatable({}, {
|
|
__index = function(_, k)
|
|
config[k] = config[k] or {}
|
|
return config[k]
|
|
end,
|
|
__newindex = function(_, k, v)
|
|
config[k] = v
|
|
end,
|
|
})
|
|
|
|
local is_dict_like = function(v) -- has string and number keys
|
|
return type(v) == "table" and (vim.tbl_isempty(v) or not svim.islist(v))
|
|
end
|
|
local is_dict = function(v) -- has only string keys
|
|
return type(v) == "table" and (vim.tbl_isempty(v) or not v[1])
|
|
end
|
|
|
|
--- Merges the values similar to vim.tbl_deep_extend with the **force** behavior,
|
|
--- but the values can be any type
|
|
---@generic T
|
|
---@param ... T
|
|
---@return T
|
|
function M.config.merge(...)
|
|
local ret = select(1, ...)
|
|
for i = 2, select("#", ...) do
|
|
local value = select(i, ...)
|
|
if is_dict_like(ret) and is_dict(value) then
|
|
for k, v in pairs(value) do
|
|
ret[k] = M.config.merge(ret[k], v)
|
|
end
|
|
elseif value ~= nil then
|
|
ret = value
|
|
end
|
|
end
|
|
return ret
|
|
end
|
|
|
|
--- Get an example config from the docs/examples directory.
|
|
---@param snack string
|
|
---@param name string
|
|
---@param opts? table
|
|
function M.config.example(snack, name, opts)
|
|
local path = vim.fn.fnamemodify(debug.getinfo(1, "S").source:sub(2), ":h:h:h") .. "/docs/examples/" .. snack .. ".lua"
|
|
local ok, ret = pcall(function()
|
|
return loadfile(path)().examples[name] or error(("`%s` not found"):format(name))
|
|
end)
|
|
if not ok then
|
|
M.notify.error(("Failed to load `%s.%s`:\n%s"):format(snack, name, ret))
|
|
end
|
|
return ok and vim.tbl_deep_extend("force", {}, vim.deepcopy(ret), opts or {}) or {}
|
|
end
|
|
|
|
---@generic T: table
|
|
---@param snack string
|
|
---@param defaults T
|
|
---@param ... T[]
|
|
---@return T
|
|
function M.config.get(snack, defaults, ...)
|
|
local merge, todo = {}, { defaults, config[snack] or {}, ... }
|
|
for i = 1, select("#", ...) + 2 do
|
|
local v = todo[i] --[[@as snacks.Config.base]]
|
|
if type(v) == "table" then
|
|
if v.example then
|
|
table.insert(merge, vim.deepcopy(M.config.example(snack, v.example)))
|
|
v.example = nil
|
|
end
|
|
table.insert(merge, vim.deepcopy(v))
|
|
end
|
|
end
|
|
local ret = M.config.merge(unpack(merge))
|
|
if type(ret.config) == "function" then
|
|
ret.config(ret, defaults)
|
|
end
|
|
return ret
|
|
end
|
|
|
|
--- Register a new window style config.
|
|
---@param name string
|
|
---@param defaults snacks.win.Config|{}
|
|
---@return string
|
|
function M.config.style(name, defaults)
|
|
config.styles[name] = vim.tbl_deep_extend("force", vim.deepcopy(defaults), config.styles[name] or {})
|
|
return name
|
|
end
|
|
|
|
M.did_setup = false
|
|
M.did_setup_after_vim_enter = false
|
|
|
|
---@param opts snacks.Config?
|
|
function M.setup(opts)
|
|
if M.did_setup then
|
|
return vim.notify("snacks.nvim is already setup", vim.log.levels.ERROR, { title = "snacks.nvim" })
|
|
end
|
|
M.did_setup = true
|
|
|
|
if vim.fn.has("nvim-0.9.4") ~= 1 then
|
|
return vim.notify("snacks.nvim requires Neovim >= 0.9.4", vim.log.levels.ERROR, { title = "snacks.nvim" })
|
|
end
|
|
|
|
-- enable all by default when config is passed
|
|
opts = opts or {}
|
|
for k in pairs(opts) do
|
|
opts[k].enabled = opts[k].enabled == nil or opts[k].enabled
|
|
end
|
|
config = vim.tbl_deep_extend("force", config, opts or {})
|
|
|
|
local events = {
|
|
BufReadPre = { "bigfile", "image" },
|
|
BufReadPost = { "quickfile", "indent" },
|
|
BufEnter = { "explorer" },
|
|
LspAttach = { "words" },
|
|
UIEnter = { "dashboard", "scroll", "input", "scope", "picker" },
|
|
}
|
|
|
|
---@param event string
|
|
---@param ev? vim.api.keyset.create_autocmd.callback_args
|
|
local function load(event, ev)
|
|
local todo = events[event] or {}
|
|
events[event] = nil
|
|
for _, snack in ipairs(todo) do
|
|
if M.config[snack] and M.config[snack].enabled then
|
|
if M[snack].setup then
|
|
M[snack].setup(ev)
|
|
elseif M[snack].enable then
|
|
M[snack].enable()
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
if vim.v.vim_did_enter == 1 then
|
|
M.did_setup_after_vim_enter = true
|
|
load("UIEnter")
|
|
end
|
|
|
|
local group = vim.api.nvim_create_augroup("snacks", { clear = true })
|
|
vim.api.nvim_create_autocmd(vim.tbl_keys(events), {
|
|
group = group,
|
|
once = true,
|
|
nested = true,
|
|
callback = function(ev)
|
|
load(ev.event, ev)
|
|
end,
|
|
})
|
|
|
|
if M.config.image.enabled and #M.config.image.formats > 0 then
|
|
vim.api.nvim_create_autocmd("BufReadCmd", {
|
|
once = true,
|
|
pattern = "*." .. table.concat(M.config.image.formats, ",*."),
|
|
group = group,
|
|
callback = function(e)
|
|
require("snacks.image").setup(e)
|
|
end,
|
|
})
|
|
end
|
|
|
|
vim.api.nvim_create_autocmd("BufReadCmd", {
|
|
once = true,
|
|
pattern = "gh://*",
|
|
group = group,
|
|
callback = function(e)
|
|
require("snacks.gh").setup(e)
|
|
end,
|
|
})
|
|
|
|
if M.config.statuscolumn.enabled then
|
|
vim.o.statuscolumn = [[%!v:lua.require'snacks.statuscolumn'.get()]]
|
|
end
|
|
|
|
if M.config.notifier.enabled then
|
|
vim.notify = function(msg, level, o)
|
|
vim.notify = Snacks.notifier.notify
|
|
return Snacks.notifier.notify(msg, level, o)
|
|
end
|
|
end
|
|
end
|
|
|
|
return M
|