From e62575407dda9de7a8758b1ff11f3927684ff4f9 Mon Sep 17 00:00:00 2001 From: punkfairie Date: Thu, 28 Nov 2024 20:30:20 -0800 Subject: [PATCH] feat: Snacks.nvim --- nix/neovim-overlay.nix | 5 +- nvim/init.lua | 4 + nvim/lua/lib/lazy/util.lua | 21 ++++ nvim/lua/lib/lazyvim/format.lua | 53 +++++++++ nvim/lua/lib/lazyvim/lsp.lua | 27 +++++ nvim/lua/lib/lazyvim/root.lua | 193 ++++++++++++++++++++++++++++++++ nvim/lua/snacks-nvim.lua | 88 +++++++++++++++ 7 files changed, 390 insertions(+), 1 deletion(-) create mode 100644 nvim/lua/lib/lazy/util.lua create mode 100644 nvim/lua/lib/lazyvim/format.lua create mode 100644 nvim/lua/lib/lazyvim/lsp.lua create mode 100644 nvim/lua/lib/lazyvim/root.lua create mode 100644 nvim/lua/snacks-nvim.lua diff --git a/nix/neovim-overlay.nix b/nix/neovim-overlay.nix index b1eb4bc..fe9b999 100644 --- a/nix/neovim-overlay.nix +++ b/nix/neovim-overlay.nix @@ -28,11 +28,14 @@ with final.pkgs.lib; let all-plugins = with pkgs.vimPlugins; [ # bleeding-edge plugins from flake inputs # (mkNvimPlugin inputs.wf-nvim "wf.nvim") # (example) keymap hints + lz-n + snacks-nvim ]; extraPackages = with pkgs; [ ripgrep - # language servers, etc. + lazygit + # language servers lua-language-server nixd ]; diff --git a/nvim/init.lua b/nvim/init.lua index ecbb6a4..8399c54 100644 --- a/nvim/init.lua +++ b/nvim/init.lua @@ -6,3 +6,7 @@ vim.g.sqlite_clib_path = require('luv').os_getenv('LIBSQLITE') require('options') require('keymaps') + +require('snacks-nvim') + +-- require('lz.n').load('plugins') diff --git a/nvim/lua/lib/lazy/util.lua b/nvim/lua/lib/lazy/util.lua new file mode 100644 index 0000000..b71ee06 --- /dev/null +++ b/nvim/lua/lib/lazy/util.lua @@ -0,0 +1,21 @@ +# https://github.com/folke/lazy.nvim/blob/main/lua/lazy/core/util.lua + +---@class lib.lazy.util +local M = {} + +---@return string +function M.norm(path) + if path:sub(1, 1) == "~" then + local home = vim.uv.os_homedir() or "" + + if home:sub(-1) == "\\" or home:sub(-1) == '/' then + home = home:sub(1, -2) + end + + path = home .. path:sub(2) + end + + path = path:gsub("\\", "/"):gsub("/+", "/") + + return path:sub(-1) == "/" and path:sub(1, -2) or path +end diff --git a/nvim/lua/lib/lazyvim/format.lua b/nvim/lua/lib/lazyvim/format.lua new file mode 100644 index 0000000..cc447db --- /dev/null +++ b/nvim/lua/lib/lazyvim/format.lua @@ -0,0 +1,53 @@ +-- https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/util/format.lua + +---@class lib.lazyvim.format +local M = {} + +---@param buf? number The buffer to enable for +function M.enabled(buf) + buf = (buf == nil or buf == 0) and vim.api.nvim_get_current_buf() or buf + local gaf = vim.g.autoformat + local baf = vim.b[buf].autoformat + + -- If the buffer has a local value, use that. + if baf ~= nil then + return baf + end + + -- Otherwise use the global value if set, or true by default. + return gaf == nil or gaf +end + +---@param enable? boolean Whether to enable or disable +---@param buf? boolean Whether to enable for current buffer only +function M.enable(enable, buf) + if enable == nil then + enable = true + end + + if buf then + vim.b.autoformat = enable + else + vim.g.autoformat = enable + vim.b.autoformat = nil + end +end + +---@param buf? boolean Whether to toggle for current buffer only +function M.snacks_toggle(buf) + return Snacks.toggle({ + name = 'auto format (' .. (buf and 'buffer' or 'global') .. ')', + get = function() + if not buf then + return vim.g.autoformat == nil or vim.g.autoformat + end + + return M.enabled() + end, + set = function(state) + M.enable(state, buf) + end, + }) +end + +return M diff --git a/nvim/lua/lib/lazyvim/lsp.lua b/nvim/lua/lib/lazyvim/lsp.lua new file mode 100644 index 0000000..39b6394 --- /dev/null +++ b/nvim/lua/lib/lazyvim/lsp.lua @@ -0,0 +1,27 @@ +-- https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/util/lsp.lua + +---@class lib.lazyvim.lsp +local M = {} + +---@alias lsp.Client.filter {id?:number, bufnr?:number, name?:string, method?:string, filter?:fun(client:lsp.Client):boolean} + +---@param opts? lsp.Client.filter +function M.get_clients(opts) + local ret = {} ---@type vim.lsp.Client[] + + if vim.lsp.get_clients then + ret = vim.lsp.get_clients(opts) + else + ---@diagnostic disable-next-line: deprecated + ret = vim.lsp.get_active_clients(opts) + + if opts and opts.method then + ---@param client vim.lsp.Client + ret = vim.tbl_filter(function(client) + return client.supports_method(opts.method, { bufnr = opts.bufnr }) + end, ret) + end + end + + return opts and opts.filter and vim.tbl_filter(opts.filter, ret) or ret +end diff --git a/nvim/lua/lib/lazyvim/root.lua b/nvim/lua/lib/lazyvim/root.lua new file mode 100644 index 0000000..6348c60 --- /dev/null +++ b/nvim/lua/lib/lazyvim/root.lua @@ -0,0 +1,193 @@ +-- https://github.com/LazyVim/LazyVim/blob/main/lua/lazyvim/util/root.lua + +local lazy_util = require('lib.lazy.util') +local lazyvim_lsp = require('lib.lazyvim.lsp') + +---@class lib.lazyvim.root +---@overload fun(): string +local M = setmetatable({}, { + __call = function(m) + return m.get() + end, +}) + +---@class LazyRoot +---@field paths string[] +---@field spec LazyRootSpec + +---@alias LazyRootFn fun(buf:number): (string|string[]) +---@alias LazyRootSpec string|string[]|LazyRootFn + +---@type LazyRootSpec[] +M.spec = { 'lsp', { '.git', 'lua' }, 'cwd' } + +M.detectors = {} + +function M.detectors.cwd() + return { vim.uv.cwd() } +end + +function M.realpath(path) + if path == '' or path == nil then + return nil + end + + path = vim.uv.fs_realpath(path) or path + + return lazy_util.norm(path) +end + +function M.bufpath(buf) + return M.realpath(vim.api.nvim_buf_get_name(assert(buf))) +end + +function M.detectors.lsp(buf) + local bufpath = M.bufpath(buf) + + if not bufpath then + return {} + end + + local roots = {} ---@type string[] + local clients = lazyvim_lsp.get_clients({ bufnr = buf }) + + clients = vim.tbl_filter(function(client) + return not vim.tbl_contains(vim.g.root_lsp_ignore or {}, client.name) + end, clients) + + for _, client in pairs(clients) do + local workspace = client.config.workspace_folders + + for _, ws in pairs(workspace or {}) do + roots[#roots + 1] = vim.uri_to_fname(ws.uri) + end + + if client.root_dir then + roots[#roots + 1] = client.root_dir + end + end + + return vim.tbl_filter(function(path) + path = lazy_util.norm(path) + + return path and bufpath:find(path, 1, true) == 1 + end, roots) +end + +---@param patterns string[]|string +function M.detectors.pattern(buf, patterns) + if type(patterns) == 'string' then + patterns = { patterns } + end + + local path = M.bufpath(buf) or vim.uv.cwd() + local pattern = vim.fs.find(function(name) + for _, p in ipairs(patterns) do + if name == p then + return true + end + + if p:sub(1, 1) == '*' and name:find(vim.pesc(p:sub(2)) .. '$') then + return true + end + end + + return false + end, { path = path, upward = true })[1] + + return pattern and { vim.fs.dirname(pattern) } or {} +end + +---@param spec LazyRootSpec +---@return LazyRootFn +function M.resolve(spec) + if M.detectors[spec] then + return M.detectors[spec] + elseif type(spec) == 'function' then + return spec + end + + return function(buf) + return M.detectors.pattern(buf, spec) + end +end + +---@param opts? {buf?:number, spec?:LazyRootSpec[], all?:boolean} +function M.detect(opts) + opts = opts or {} + opts.spec = opts.spec + or type(vim.g.root_spec) == 'table' and vim.g.root_spec + or M.spec + opts.buf = (opts.buf == nil or opts.buf == 0) + and vim.api.nvim_get_current_buf() + or opts.buf + + local ret = {} ---@type LazyRoot[] + + for _, spec in ipairs(opts.spec) do + local paths = M.resolve(spec)(opts.buf) + + paths = paths or {} + paths = type(paths) == 'table' and paths or { paths } + + local roots = {} ---@type string[] + + for _, p in ipairs(paths) do + local pp = M.realpath(p) + + if pp and not vim.tbl_contains(roots, pp) then + roots[#roots + 1] = pp + end + end + + table.sort(roots, function(a, b) + return #a > #b + end) + + if #roots > 0 then + ret[#ret + 1] = { spec = spec, paths = roots } + + if opts.all == false then + break + end + end + end + + return ret +end + +---@type table +M.cache = {} + +-- Returns the root directory based on: +-- * lsp workspace folders +-- * lsp root_dir +-- * root pattern of filename of the current buffer +-- * root pattern of cwd +---@param opts? {normalize?:boolean, buf?:number} +function M.get(opts) + opts = opts or {} + + local buf = opts.buf or vim.api.nvim_get_current_buf() + local ret = M.cache[buf] + + if not ret then + local roots = M.detect({ all = false, buf = buf }) + + ret = roots[1] and roots[1].paths[1] or vim.uv.cwd() + + M.cache[buf] = ret + end + + return ret +end + +function M.git() + local root = M.get() + local git_root = vim.fs.find('.git', { path = root, upward = true })[1] + local ret = git_root and vim.fn.fnamemodify(git_root, ':h') or root + + return ret +end + +return M diff --git a/nvim/lua/snacks-nvim.lua b/nvim/lua/snacks-nvim.lua new file mode 100644 index 0000000..fc026fb --- /dev/null +++ b/nvim/lua/snacks-nvim.lua @@ -0,0 +1,88 @@ +-- Snacks needs to be loaded very early, so it gets its own special file. +local set = vim.keymap.set +local lazyvim_format = require('lib.lazyvim.format') +local lazyvim_root = require('lib.lazyvim.root') + +require('snacks').setup({ + bigfile = { enabled = true }, + dashboard = { enabled = true }, +}) + +-- Buffers -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- +set({ 'n' }, 'bd', function() + Snacks.bufdelete() +end, { desc = 'Delete buffer' }) + +set({ 'n' }, 'bo', function() + Snacks.bufdelete.other() +end, { desc = 'Delete other buffers' }) + +-- Toggles -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + +local toggle = Snacks.toggle + +lazyvim_format.snacks_toggle():map('uf') +lazyvim_format.snacks_toggle(true):map('uF') -- current buffer only + +toggle.option('spell', { name = 'spelling' }):map('us') +toggle.option('wrap', { name = 'wrap' }):map('uw') + +toggle.option('relativenumber', { name = 'relative number' }):map('uL') +toggle.line_number():map('ul') + +toggle.diagnostics():map('ud') + +toggle + .option( + 'conceallevel', + { off = 0, on = vim.o.conceallevel > 0 and vim.o.conceallevel or 2 } + ) + :map('uc') + +toggle.treesitter():map('uT') + +if vim.lsp.inlay_hint then + toggle.inlay_hints():map('uh') +end + +-- Git -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + +set({ 'n' }, 'gb', function() + Snacks.git.blame_line() +end, { desc = 'Git blame line' }) + +set({ 'n' }, 'gB', function() + Snacks.gitbrowse() +end, { desc = 'Git browse (open)' }) + +set({ 'n' }, 'gY', function() + Snacks.gitbrowse({ + open = function(url) + vim.fn.setreg('+', url) + end, + }) +end, { desc = 'Git browse (copy)' }) + +-- LazyGit -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- + +if vim.fn.executable('lazygit') == 1 then + set({ 'n' }, 'gg', function() + Snacks.lazygit({ cwd = lazyvim_root.git() }) + end, { desc = 'Lazygit (root dir)' }) + + -- set({ 'n' }, 'gG', function() + -- Snacks.lazygit() + -- end, { desc = 'Lazygit (cwd)' }) + + set({ 'n' }, 'gf', function() + Snacks.lazygit.log_file() + end, { desc = 'Lazygit current file history' }) + + set({ 'n' }, 'gl', function() + Snacks.lazygit.log({ cwd = lazyvim_root.git() }) + end, { desc = 'Lazygit log' }) + + -- set({ 'n' }, 'gL', function() + -- Snacks.lazygit.log() + -- end, { desc = 'Lazygit log (cwd)' }) +end