snacks.nvim/lua/snacks/init.lua
github-actions[bot] eadb0ca316
chore(main): release 2.28.0
🤖 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).
2025-11-01 13:43:16 +01:00

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