diff --git a/lua/snacks/git.lua b/lua/snacks/git.lua index e86d3199..b52c4d44 100644 --- a/lua/snacks/git.lua +++ b/lua/snacks/git.lua @@ -1,6 +1,15 @@ ---@class snacks.git local M = {} +Snacks.config.style("blame_line", { + width = 0.6, + height = 0.6, + border = "rounded", + title = " Git Blame ", + title_pos = "center", + bo = { filetype = "git" }, +}) + --- Gets the git root for a buffer or path. --- Defaults to the current buffer. ---@param path? number|string buffer or path @@ -15,20 +24,10 @@ end --- Show git log for the current line. ---@param opts? snacks.terminal.Config | {count?: number} function M.blame_line(opts) - Snacks.config.view("blame_line", { - win = { - width = 0.6, - height = 0.6, - border = "rounded", - title = " Git Blame ", - title_pos = "center", - }, - bo = { filetype = "git" }, - }) opts = vim.tbl_deep_extend("force", { count = 5, interactive = false, - win = { view = "blame_line" }, + win = { style = "blame_line" }, }, opts or {}) local cursor = vim.api.nvim_win_get_cursor(0) local line = cursor[1] diff --git a/lua/snacks/init.lua b/lua/snacks/init.lua index abc8377f..1c9e7152 100644 --- a/lua/snacks/init.lua +++ b/lua/snacks/init.lua @@ -36,11 +36,11 @@ _G.Snacks = M ---@field statuscolumn? snacks.statuscolumn.Config | { enabled: boolean } ---@field terminal? snacks.terminal.Config ---@field toggle? snacks.toggle.Config ----@field views? table +---@field styles? table ---@field win? snacks.win.Config ---@field words? snacks.words.Config local config = { - views = {}, + styles = {}, bigfile = { enabled = true }, notifier = { enabled = true }, quickfile = { enabled = true }, @@ -67,11 +67,11 @@ function M.config.get(snack, defaults, ...) return vim.tbl_deep_extend("force", unpack(merge)) end ---- Register a new window view config. +--- Register a new window style config. ---@param name string ---@param defaults snacks.win.Config -function M.config.view(name, defaults) - config.views[name] = vim.tbl_deep_extend("force", vim.deepcopy(defaults), config.views[name] or {}) +function M.config.style(name, defaults) + config.styles[name] = vim.tbl_deep_extend("force", vim.deepcopy(defaults), config.styles[name] or {}) end ---@param opts snacks.Config? diff --git a/lua/snacks/lazygit.lua b/lua/snacks/lazygit.lua index 94d43225..4777de7c 100644 --- a/lua/snacks/lazygit.lua +++ b/lua/snacks/lazygit.lua @@ -38,10 +38,12 @@ local defaults = { unstagedChangesColor = { fg = "DiagnosticError" }, }, win = { - view = "lazygit", + style = "lazygit", }, } +Snacks.config.style("lazygit", {}) + -- re-create config file on startup local dirty = true local config_dir ---@type string? diff --git a/lua/snacks/notifier.lua b/lua/snacks/notifier.lua index 89bfaa04..480a0920 100644 --- a/lua/snacks/notifier.lua +++ b/lua/snacks/notifier.lua @@ -4,7 +4,7 @@ ---@field dirty boolean local M = {} -Snacks.config.view("notification", { +Snacks.config.style("notification", { border = "rounded", zindex = 100, wo = { @@ -173,14 +173,16 @@ function M:start() if #self.queue == 0 then return end - local ok, err = pcall(function() + xpcall(function() self:update() self:layout() - end) - if not ok then - vim.api.nvim_err_writeln("Snacks notifier failed. Dropping queue. Error:\n " .. err) + end, function(err) + local trace = debug.traceback(err, 2) + vim.api.nvim_err_writeln( + ("Snacks notifier failed. Dropping queue. Error:\n%s\n\nTrace:\n%s"):format(error, trace) + ) self.queue = {} - end + end) end) ) end @@ -280,7 +282,7 @@ function M:render(notif) local win = notif.win or Snacks.win({ show = false, - view = "notification", + style = "notification", enter = false, backdrop = false, bo = { filetype = notif.ft or "markdown", modifiable = false }, diff --git a/lua/snacks/terminal.lua b/lua/snacks/terminal.lua index aea1a671..75f1f830 100644 --- a/lua/snacks/terminal.lua +++ b/lua/snacks/terminal.lua @@ -15,38 +15,42 @@ local M = setmetatable({}, { ---@field interactive? boolean ---@field override? fun(cmd?: string|string[], opts?: snacks.terminal.Config) Use this to use a different terminal implementation local defaults = { - win = { - bo = { - filetype = "snacks_terminal", - }, - wo = {}, - keys = { - gf = function(self) - local f = vim.fn.findfile(vim.fn.expand("")) - if f ~= "" then - vim.cmd("close") - vim.cmd("e " .. f) + win = { style = "terminal" }, +} + +Snacks.config.style("terminal", { + bo = { + filetype = "snacks_terminal", + }, + wo = {}, + keys = { + gf = function(self) + local f = vim.fn.findfile(vim.fn.expand("")) + if f == "" then + Snacks.notify.warn("No file under cursor") + else + self:close() + vim.cmd("e " .. f) + end + end, + term_normal = { + "", + function(self) + self.esc_timer = self.esc_timer or (vim.uv or vim.loop).new_timer() + if self.esc_timer:is_active() then + self.esc_timer:stop() + vim.cmd("stopinsert") + else + self.esc_timer:start(200, 0, function() end) + return "" end end, - term_normal = { - "", - function(self) - self.esc_timer = self.esc_timer or (vim.uv or vim.loop).new_timer() - if self.esc_timer:is_active() then - self.esc_timer:stop() - vim.cmd("stopinsert") - else - self.esc_timer:start(200, 0, function() end) - return "" - end - end, - mode = "t", - expr = true, - desc = "Double escape to normal mode", - }, + mode = "t", + expr = true, + desc = "Double escape to normal mode", }, }, -} +}) ---@type table local terminals = {} @@ -56,8 +60,8 @@ local terminals = {} function M.open(cmd, opts) local id = vim.v.count1 ---@type snacks.terminal.Config - opts = Snacks.config.get("terminal", defaults, { win = Snacks.win.resolve(opts and opts.win) }, opts) - opts.win.position = opts.win.position or (cmd and "float" or "bottom") + opts = Snacks.config.get("terminal", defaults, { win = { position = cmd and "float" or "bottom" } }, opts) + opts.win = Snacks.win.resolve("terminal", opts.win) opts.win.wo.winbar = opts.win.wo.winbar or (opts.win.position == "float" and "" or (id .. ": %{b:term_title}")) if opts.override then diff --git a/lua/snacks/win.lua b/lua/snacks/win.lua index 56da9132..388925ec 100644 --- a/lua/snacks/win.lua +++ b/lua/snacks/win.lua @@ -18,7 +18,9 @@ local M = setmetatable({}, { ---@field mode? string|string[] ---@class snacks.win.Config: vim.api.keyset.win_config ----@field view? string merges with config from `Snacks.config.views[view]` +---@field style? string merges with config from `Snacks.config.views[view]` +---@field show? boolean Show the window immediately (default: true) +---@field minimal? boolean ---@field position? "float"|"bottom"|"top"|"left"|"right" ---@field buf? number ---@field file? string @@ -31,9 +33,10 @@ local M = setmetatable({}, { ---@field on_buf? fun(self: snacks.win) ---@field on_win? fun(self: snacks.win) local defaults = { - position = "float", + show = true, relative = "editor", - style = "minimal", + position = "float", + minimal = true, wo = { winhighlight = "Normal:NormalFloat,NormalNC:NormalFloat", }, @@ -43,19 +46,39 @@ local defaults = { }, } ----@type snacks.win.Config -local defaults_float = { +Snacks.config.style("float", { + position = "float", backdrop = 60, height = 0.9, width = 0.9, zindex = 50, -} +}) ----@type snacks.win.Config -local defaults_split = { +Snacks.config.style("split", { + position = "bottom", height = 0.4, width = 0.4, -} +}) + +Snacks.config.style("minimal", { + wo = { + cursorcolumn = false, + cursorline = false, + cursorlineopt = "both", + fillchars = "eob: ,lastline:…", + list = false, + listchars = "extends:…,tab: ", + number = false, + relativenumber = false, + signcolumn = "no", + spell = false, + winbar = "", + statuscolumn = "", + winfixheight = true, + winfixwidth = true, + wrap = false, + }, +}) local split_commands = { editor = { @@ -72,27 +95,6 @@ local split_commands = { }, } ----@type snacks.win.Config -local minimal = { - wo = { - cursorcolumn = false, - cursorline = false, - cursorlineopt = "both", - fillchars = "eob: ,lastline:…", - list = false, - listchars = "extends:…", - number = false, - relativenumber = false, - signcolumn = "no", - spell = false, - winbar = "", - statuscolumn = "", - winfixheight = true, - winfixwidth = true, - wrap = false, - }, -} - local win_opts = { "anchor", "border", @@ -120,40 +122,45 @@ vim.api.nvim_set_hl(0, "SnackFloatBackdrop", { bg = "#000000", default = true }) local id = 0 ----@param opts? snacks.win.Config +---@param ... snacks.win.Config|string ---@return snacks.win.Config -function M.resolve(opts) - opts = opts or {} +function M.resolve(...) local done = {} ---@type string[] - local views = { opts } ---@type snacks.win.Config[] - local view = opts.view - while view and not vim.tbl_contains(done, view) do - table.insert(done, view) - if not Snacks.config.views[view] then - break + local merge = {} ---@type snacks.win.Config[] + local stack = { ... } + while #stack > 0 do + local next = table.remove(stack) + next = type(next) == "table" and next or Snacks.config.styles[next] + ---@cast next snacks.win.Config? + if next then + table.insert(merge, 1, next) + if next.style and not vim.tbl_contains(done, next.style) then + table.insert(done, next.style) + table.insert(stack, next.style) + end end - table.insert(views, 1, Snacks.config.views[view]) - view = Snacks.config.views[view].view end - local ret = #views == 0 and {} or #views == 1 and views[1] or vim.tbl_deep_extend("force", {}, unpack(views)) - ret.view = nil + local ret = #merge == 0 and {} or #merge == 1 and merge[1] or vim.tbl_deep_extend("force", {}, unpack(merge)) + ret.style = nil return ret end ----@param opts? snacks.win.Config | { show?: boolean } +---@param opts? snacks.win.Config ---@return snacks.win function M.new(opts) local self = setmetatable({}, { __index = M }) id = id + 1 self.id = id - opts = Snacks.config.get("win", defaults, M.resolve(opts)) - opts = - vim.tbl_deep_extend("force", {}, vim.deepcopy(opts.position == "float" and defaults_float or defaults_split), opts) - ---@cast opts snacks.win.Config - if opts.style == "minimal" then - opts = vim.tbl_deep_extend("force", {}, vim.deepcopy(minimal), opts) --[[@as snacks.win.Config]] - opts.style = nil + opts = M.resolve(Snacks.config.get("win", defaults, opts)) + if opts.minimal then + opts = M.resolve("minimal", opts) end + if opts.position == "float" then + opts = M.resolve("float", opts) + else + opts = M.resolve("split", opts) + end + ---@cast opts snacks.win.Config self.opts = opts if opts.show ~= false then self:show()