snacks.nvim/lua/snacks/init.lua
github-actions[bot] a4e46becca
Some checks are pending
CI / ci (push) Waiting to run
chore(main): release 2.30.0
🤖 I have created a release *beep* *boop*
---


## [2.30.0](https://github.com/folke/snacks.nvim/compare/v2.29.0...v2.30.0) (2025-11-06)


### Features

* **diff:** prettier commit rendering (git show, diff with header) ([dc2186e](dc2186e572))
* **gh:** add inline review comment annotations to diff viewer ([c83ff8d](c83ff8d598))
* **gh:** create review comments in GitHub PR diff, on diff lines. Closes [#2446](https://github.com/folke/snacks.nvim/issues/2446) ([85bf3f0](85bf3f0123))
* **layout:** allow resizing split layouts. See [#2390](https://github.com/folke/snacks.nvim/issues/2390) ([913379c](913379ccd2))
* **picker.gh_diff:** you can now reply to review comments in diffs with `a`. See [#2446](https://github.com/folke/snacks.nvim/issues/2446) ([c3bda87](c3bda8709a))
* **picker.icons:** make it easier to add custom icon sources ([82e6966](82e69661cd))
* **picker.marks:** added `<c-x>` to delete a mark from the list. See [#2390](https://github.com/folke/snacks.nvim/issues/2390) ([9a04605](9a04605664))
* **picker:** when picker was started from insert mode, return to insert after paste ([a417630](a4176301e3))
* **util.async:** add proper backtrace to unhandled async errors ([01f6cac](01f6cac48f))


### Bug Fixes

* **gh:** force `fancy` diff style for gh pr diff, since that's needed to render/interact with review comments ([bd71cd4](bd71cd4b00))
* **gh:** only skip empty comment reviews. Closes [#2445](https://github.com/folke/snacks.nvim/issues/2445) ([1848d74](1848d74e7c))
* **gh:** pass correct context in gh_perform_action. Closes [#2442](https://github.com/folke/snacks.nvim/issues/2442). Closes [#2443](https://github.com/folke/snacks.nvim/issues/2443) ([13edbc6](13edbc681c))
* **gh:** properly handly pending requests ([7a15e16](7a15e16d01))
* **gh:** use lua to parse dates so we can do this in a fast context ([cd0d6fe](cd0d6fe864))
* **image:** run terminal capability detection synchronous when needed. Closes [#2439](https://github.com/folke/snacks.nvim/issues/2439) ([58f1152](58f11527fe))
* **layout:** ignore very zindex windows for calulating layout zindex, so that it stays below things like notifications ([47340e6](47340e6b0b))
* **lsp:** properly detach buffers on LspDetach. Closes [#2457](https://github.com/folke/snacks.nvim/issues/2457) ([beb995e](beb995e1c6))
* **picker.actions:** don't open a new tab if the current tab is empty. Closes [#2461](https://github.com/folke/snacks.nvim/issues/2461) ([4e2424e](4e2424eca7))
* **picker.diff:** added `showbreak=""` for fancy diff. Closes [#2441](https://github.com/folke/snacks.nvim/issues/2441) ([fb55f7b](fb55f7bf2b))
* **picker.format:** use file for icon. fallback to ft when buffer is not a file. See [#2390](https://github.com/folke/snacks.nvim/issues/2390) ([20ac8bf](20ac8bfc4a))
* **picker.grep_word:** pass `--word-regexp` to `ripgrep` ([6aad368](6aad36810a))
* **picker.highlight:** resolve ([4438ee4](4438ee4770))
* **picker.input:** startinsert when starting the picker from terminal mode. See [#2390](https://github.com/folke/snacks.nvim/issues/2390) ([b2054a3](b2054a3a73))
* **picker.lsp_config:** nil on lsp info ([#2459](https://github.com/folke/snacks.nvim/issues/2459)) ([c5257fa](c5257fa690))
* **scope:** textobjects should use synchronous treesitter parsing. Closes [#2448](https://github.com/folke/snacks.nvim/issues/2448) ([9737c25](9737c25f29))
* **toggle:** set/get raw values for option toggles. See [#2390](https://github.com/folke/snacks.nvim/issues/2390) ([41da728](41da728f02))
* **win:** set `foldcolumn='0'` for minimal style. See [#2390](https://github.com/folke/snacks.nvim/issues/2390) ([195faa0](195faa0646))

---
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-06 22:23:04 +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.30.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