---@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") ---@class snacks.Config.base ---@field example? string ---@field config? fun(opts: table, defaults: table) ---@class snacks.Config: snacks.plugins.Config ---@field styles? table ---@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", }, }, } 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 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