diff --git a/lua/lazyvim/config/autocmds.lua b/lua/lazyvim/config/autocmds.lua new file mode 100644 index 0000000..4cc6f1e --- /dev/null +++ b/lua/lazyvim/config/autocmds.lua @@ -0,0 +1,94 @@ +-- This file is automatically loaded by lazyvim.config.init. + +local function augroup(name) + return vim.api.nvim_create_augroup("lazyvim_" .. name, { clear = true }) +end + +-- Check if we need to reload the file when it changed +vim.api.nvim_create_autocmd({ "FocusGained", "TermClose", "TermLeave" }, { + group = augroup("checktime"), + command = "checktime", +}) + +-- Highlight on yank +vim.api.nvim_create_autocmd("TextYankPost", { + group = augroup("highlight_yank"), + callback = function() + vim.highlight.on_yank() + end, +}) + +-- resize splits if window got resized +vim.api.nvim_create_autocmd({ "VimResized" }, { + group = augroup("resize_splits"), + callback = function() + local current_tab = vim.fn.tabpagenr() + vim.cmd("tabdo wincmd =") + vim.cmd("tabnext " .. current_tab) + end, +}) + +-- go to last loc when opening a buffer +vim.api.nvim_create_autocmd("BufReadPost", { + group = augroup("last_loc"), + callback = function(event) + local exclude = { "gitcommit" } + local buf = event.buf + if vim.tbl_contains(exclude, vim.bo[buf].filetype) or vim.b[buf].lazyvim_last_loc then + return + end + vim.b[buf].lazyvim_last_loc = true + local mark = vim.api.nvim_buf_get_mark(buf, '"') + local lcount = vim.api.nvim_buf_line_count(buf) + if mark[1] > 0 and mark[1] <= lcount then + pcall(vim.api.nvim_win_set_cursor, 0, mark) + end + end, +}) + +-- close some filetypes with +vim.api.nvim_create_autocmd("FileType", { + group = augroup("close_with_q"), + pattern = { + "PlenaryTestPopup", + "help", + "lspinfo", + "man", + "notify", + "qf", + "query", + "spectre_panel", + "startuptime", + "tsplayground", + "neotest-output", + "checkhealth", + "neotest-summary", + "neotest-output-panel", + }, + callback = function(event) + vim.bo[event.buf].buflisted = false + vim.keymap.set("n", "q", "close", { buffer = event.buf, silent = true }) + end, +}) + +-- wrap and check for spell in text filetypes +vim.api.nvim_create_autocmd("FileType", { + group = augroup("wrap_spell"), + pattern = { "gitcommit", "markdown" }, + callback = function() + vim.opt_local.wrap = true + vim.opt_local.spell = true + end, +}) + +-- Auto create dir when saving a file, in case some intermediate directory does not exist +vim.api.nvim_create_autocmd({ "BufWritePre" }, { + group = augroup("auto_create_dir"), + callback = function(event) + if event.match:match("^%w%w+://") then + return + end + local file = vim.loop.fs_realpath(event.match) or event.match + vim.fn.mkdir(vim.fn.fnamemodify(file, ":p:h"), "p") + end, +}) diff --git a/lua/lazyvim/config/init.lua b/lua/lazyvim/config/init.lua new file mode 100644 index 0000000..a296ef5 --- /dev/null +++ b/lua/lazyvim/config/init.lua @@ -0,0 +1,283 @@ +local Util = require("lazyvim.util") + +---@class LazyVimConfig: LazyVimOptions +local M = {} + +M.version = "10.8.2" -- x-release-please-version + +---@class LazyVimOptions +local defaults = { + -- colorscheme can be a string like `catppuccin` or a function that will load the colorscheme + ---@type string|fun() + colorscheme = function() + require("tokyonight").load() + end, + -- load the default settings + defaults = { + autocmds = true, -- lazyvim.config.autocmds + keymaps = true, -- lazyvim.config.keymaps + -- lazyvim.config.options can't be configured here since that's loaded before lazyvim setup + -- if you want to disable loading options, add `package.loaded["lazyvim.config.options"] = true` to the top of your init.lua + }, + news = { + -- When enabled, NEWS.md will be shown when changed. + -- This only contains big new features and breaking changes. + lazyvim = true, + -- Same but for Neovim's news.txt + neovim = false, + }, + -- icons used by other plugins + -- stylua: ignore + icons = { + misc = { + dots = "󰇘", + }, + dap = { + Stopped = { "󰁕 ", "DiagnosticWarn", "DapStoppedLine" }, + Breakpoint = " ", + BreakpointCondition = " ", + BreakpointRejected = { " ", "DiagnosticError" }, + LogPoint = ".>", + }, + diagnostics = { + Error = " ", + Warn = " ", + Hint = " ", + Info = " ", + }, + git = { + added = " ", + modified = " ", + removed = " ", + }, + kinds = { + Array = " ", + Boolean = "󰨙 ", + Class = " ", + Codeium = "󰘦 ", + Color = " ", + Control = " ", + Collapsed = " ", + Constant = "󰏿 ", + Constructor = " ", + Copilot = " ", + Enum = " ", + EnumMember = " ", + Event = " ", + Field = " ", + File = " ", + Folder = " ", + Function = "󰊕 ", + Interface = " ", + Key = " ", + Keyword = " ", + Method = "󰊕 ", + Module = " ", + Namespace = "󰦮 ", + Null = " ", + Number = "󰎠 ", + Object = " ", + Operator = " ", + Package = " ", + Property = " ", + Reference = " ", + Snippet = " ", + String = " ", + Struct = "󰆼 ", + TabNine = "󰏚 ", + Text = " ", + TypeParameter = " ", + Unit = " ", + Value = " ", + Variable = "󰀫 ", + }, + }, + ---@type table? + kind_filter = { + default = { + "Class", + "Constructor", + "Enum", + "Field", + "Function", + "Interface", + "Method", + "Module", + "Namespace", + "Package", + "Property", + "Struct", + "Trait", + }, + markdown = false, + help = false, + -- you can specify a different filter for each filetype + lua = { + "Class", + "Constructor", + "Enum", + "Field", + "Function", + "Interface", + "Method", + "Module", + "Namespace", + -- "Package", -- remove package since luals uses it for control flow structures + "Property", + "Struct", + "Trait", + }, + }, +} + +M.json = { + version = 2, + data = { + version = nil, ---@type string? + news = {}, ---@type table + extras = {}, ---@type string[] + }, +} + +function M.json.load() + local path = vim.fn.stdpath("config") .. "/lazyvim.json" + local f = io.open(path, "r") + if f then + local data = f:read("*a") + f:close() + local ok, json = pcall(vim.json.decode, data, { luanil = { object = true, array = true } }) + if ok then + M.json.data = vim.tbl_deep_extend("force", M.json.data, json or {}) + if M.json.data.version ~= M.json.version then + Util.json.migrate() + end + end + end +end + +---@type LazyVimOptions +local options + +---@param opts? LazyVimOptions +function M.setup(opts) + options = vim.tbl_deep_extend("force", defaults, opts or {}) or {} + + -- autocmds can be loaded lazily when not opening a file + local lazy_autocmds = vim.fn.argc(-1) == 0 + if not lazy_autocmds then + M.load("autocmds") + end + + local group = vim.api.nvim_create_augroup("LazyVim", { clear = true }) + vim.api.nvim_create_autocmd("User", { + group = group, + pattern = "VeryLazy", + callback = function() + if lazy_autocmds then + M.load("autocmds") + end + M.load("keymaps") + + Util.format.setup() + Util.news.setup() + Util.root.setup() + + vim.api.nvim_create_user_command("LazyExtras", function() + Util.extras.show() + end, { desc = "Manage LazyVim extras" }) + end, + }) + + Util.track("colorscheme") + Util.try(function() + if type(M.colorscheme) == "function" then + M.colorscheme() + else + vim.cmd.colorscheme(M.colorscheme) + end + end, { + msg = "Could not load your colorscheme", + on_error = function(msg) + Util.error(msg) + vim.cmd.colorscheme("habamax") + end, + }) + Util.track() +end + +---@param buf? number +---@return string[]? +function M.get_kind_filter(buf) + buf = (buf == nil or buf == 0) and vim.api.nvim_get_current_buf() or buf + local ft = vim.bo[buf].filetype + if M.kind_filter == false then + return + end + if M.kind_filter[ft] == false then + return + end + ---@diagnostic disable-next-line: return-type-mismatch + return type(M.kind_filter) == "table" and type(M.kind_filter.default) == "table" and M.kind_filter.default or nil +end + +---@param name "autocmds" | "options" | "keymaps" +function M.load(name) + local function _load(mod) + if require("lazy.core.cache").find(mod)[1] then + Util.try(function() + require(mod) + end, { msg = "Failed loading " .. mod }) + end + end + -- always load lazyvim, then user file + if M.defaults[name] or name == "options" then + _load("lazyvim.config." .. name) + end + _load("config." .. name) + if vim.bo.filetype == "lazy" then + -- HACK: LazyVim may have overwritten options of the Lazy ui, so reset this here + vim.cmd([[do VimResized]]) + end + local pattern = "LazyVim" .. name:sub(1, 1):upper() .. name:sub(2) + vim.api.nvim_exec_autocmds("User", { pattern = pattern, modeline = false }) +end + +M.did_init = false +function M.init() + if M.did_init then + return + end + M.did_init = true + local plugin = require("lazy.core.config").spec.plugins.LazyVim + if plugin then + vim.opt.rtp:append(plugin.dir) + end + + package.preload["lazyvim.plugins.lsp.format"] = function() + Util.deprecate([[require("lazyvim.plugins.lsp.format")]], [[require("lazyvim.util").format]]) + return Util.format + end + + -- delay notifications till vim.notify was replaced or after 500ms + require("lazyvim.util").lazy_notify() + + -- load options here, before lazy init while sourcing plugin modules + -- this is needed to make sure options will be correctly applied + -- after installing missing plugins + M.load("options") + + Util.plugin.setup() + M.json.load() +end + +setmetatable(M, { + __index = function(_, key) + if options == nil then + return vim.deepcopy(defaults)[key] + end + ---@cast options LazyVimConfig + return options[key] + end, +}) + +return M diff --git a/lua/lazyvim/config/keymaps.lua b/lua/lazyvim/config/keymaps.lua new file mode 100644 index 0000000..f7fe57d --- /dev/null +++ b/lua/lazyvim/config/keymaps.lua @@ -0,0 +1,170 @@ +-- This file is automatically loaded by lazyvim.config.init +local Util = require("lazyvim.util") + +-- DO NOT USE THIS IN YOU OWN CONFIG!! +-- use `vim.keymap.set` instead +local map = Util.safe_keymap_set + +-- better up/down +map({ "n", "x" }, "j", "v:count == 0 ? 'gj' : 'j'", { expr = true, silent = true }) +map({ "n", "x" }, "", "v:count == 0 ? 'gj' : 'j'", { expr = true, silent = true }) +map({ "n", "x" }, "k", "v:count == 0 ? 'gk' : 'k'", { expr = true, silent = true }) +map({ "n", "x" }, "", "v:count == 0 ? 'gk' : 'k'", { expr = true, silent = true }) + +-- Move to window using the hjkl keys +map("n", "", "h", { desc = "Go to left window", remap = true }) +map("n", "", "j", { desc = "Go to lower window", remap = true }) +map("n", "", "k", { desc = "Go to upper window", remap = true }) +map("n", "", "l", { desc = "Go to right window", remap = true }) + +-- Resize window using arrow keys +map("n", "", "resize +2", { desc = "Increase window height" }) +map("n", "", "resize -2", { desc = "Decrease window height" }) +map("n", "", "vertical resize -2", { desc = "Decrease window width" }) +map("n", "", "vertical resize +2", { desc = "Increase window width" }) + +-- Move Lines +map("n", "", "m .+1==", { desc = "Move down" }) +map("n", "", "m .-2==", { desc = "Move up" }) +map("i", "", "m .+1==gi", { desc = "Move down" }) +map("i", "", "m .-2==gi", { desc = "Move up" }) +map("v", "", ":m '>+1gv=gv", { desc = "Move down" }) +map("v", "", ":m '<-2gv=gv", { desc = "Move up" }) + +-- buffers +map("n", "", "bprevious", { desc = "Prev buffer" }) +map("n", "", "bnext", { desc = "Next buffer" }) +map("n", "[b", "bprevious", { desc = "Prev buffer" }) +map("n", "]b", "bnext", { desc = "Next buffer" }) +map("n", "bb", "e #", { desc = "Switch to Other Buffer" }) +map("n", "`", "e #", { desc = "Switch to Other Buffer" }) + +-- Clear search with +map({ "i", "n" }, "", "noh", { desc = "Escape and clear hlsearch" }) + +-- Clear search, diff update and redraw +-- taken from runtime/lua/_editor.lua +map( + "n", + "ur", + "nohlsearchdiffupdatenormal! ", + { desc = "Redraw / clear hlsearch / diff update" } +) + +-- https://github.com/mhinz/vim-galore#saner-behavior-of-n-and-n +map("n", "n", "'Nn'[v:searchforward].'zv'", { expr = true, desc = "Next search result" }) +map("x", "n", "'Nn'[v:searchforward]", { expr = true, desc = "Next search result" }) +map("o", "n", "'Nn'[v:searchforward]", { expr = true, desc = "Next search result" }) +map("n", "N", "'nN'[v:searchforward].'zv'", { expr = true, desc = "Prev search result" }) +map("x", "N", "'nN'[v:searchforward]", { expr = true, desc = "Prev search result" }) +map("o", "N", "'nN'[v:searchforward]", { expr = true, desc = "Prev search result" }) + +-- Add undo break-points +map("i", ",", ",u") +map("i", ".", ".u") +map("i", ";", ";u") + +-- save file +map({ "i", "x", "n", "s" }, "", "w", { desc = "Save file" }) + +--keywordprg +map("n", "K", "norm! K", { desc = "Keywordprg" }) + +-- better indenting +map("v", "<", "", ">gv") + +-- lazy +map("n", "l", "Lazy", { desc = "Lazy" }) + +-- new file +map("n", "fn", "enew", { desc = "New File" }) + +map("n", "xl", "lopen", { desc = "Location List" }) +map("n", "xq", "copen", { desc = "Quickfix List" }) + +map("n", "[q", vim.cmd.cprev, { desc = "Previous quickfix" }) +map("n", "]q", vim.cmd.cnext, { desc = "Next quickfix" }) + +-- formatting +map({ "n", "v" }, "cf", function() + Util.format({ force = true }) +end, { desc = "Format" }) + +-- diagnostic +local diagnostic_goto = function(next, severity) + local go = next and vim.diagnostic.goto_next or vim.diagnostic.goto_prev + severity = severity and vim.diagnostic.severity[severity] or nil + return function() + go({ severity = severity }) + end +end +map("n", "cd", vim.diagnostic.open_float, { desc = "Line Diagnostics" }) +map("n", "]d", diagnostic_goto(true), { desc = "Next Diagnostic" }) +map("n", "[d", diagnostic_goto(false), { desc = "Prev Diagnostic" }) +map("n", "]e", diagnostic_goto(true, "ERROR"), { desc = "Next Error" }) +map("n", "[e", diagnostic_goto(false, "ERROR"), { desc = "Prev Error" }) +map("n", "]w", diagnostic_goto(true, "WARN"), { desc = "Next Warning" }) +map("n", "[w", diagnostic_goto(false, "WARN"), { desc = "Prev Warning" }) + +-- stylua: ignore start + +-- toggle options +map("n", "uf", function() Util.format.toggle() end, { desc = "Toggle auto format (global)" }) +map("n", "uF", function() Util.format.toggle(true) end, { desc = "Toggle auto format (buffer)" }) +map("n", "us", function() Util.toggle("spell") end, { desc = "Toggle Spelling" }) +map("n", "uw", function() Util.toggle("wrap") end, { desc = "Toggle Word Wrap" }) +map("n", "uL", function() Util.toggle("relativenumber") end, { desc = "Toggle Relative Line Numbers" }) +map("n", "ul", function() Util.toggle.number() end, { desc = "Toggle Line Numbers" }) +map("n", "ud", function() Util.toggle.diagnostics() end, { desc = "Toggle Diagnostics" }) +local conceallevel = vim.o.conceallevel > 0 and vim.o.conceallevel or 3 +map("n", "uc", function() Util.toggle("conceallevel", false, {0, conceallevel}) end, { desc = "Toggle Conceal" }) +if vim.lsp.buf.inlay_hint or vim.lsp.inlay_hint then + map( "n", "uh", function() Util.toggle.inlay_hints() end, { desc = "Toggle Inlay Hints" }) +end +map("n", "uT", function() if vim.b.ts_highlight then vim.treesitter.stop() else vim.treesitter.start() end end, { desc = "Toggle Treesitter Highlight" }) + +-- lazygit +map("n", "gg", function() Util.terminal({ "lazygit" }, { cwd = Util.root(), esc_esc = false, ctrl_hjkl = false }) end, { desc = "Lazygit (root dir)" }) +map("n", "gG", function() Util.terminal({ "lazygit" }, {esc_esc = false, ctrl_hjkl = false}) end, { desc = "Lazygit (cwd)" }) + +-- quit +map("n", "qq", "qa", { desc = "Quit all" }) + +-- highlights under cursor +map("n", "ui", vim.show_pos, { desc = "Inspect Pos" }) + +-- LazyVim Changelog +map("n", "L", function() Util.news.changelog() end, { desc = "LazyVim Changelog" }) + +-- floating terminal +local lazyterm = function() Util.terminal(nil, { cwd = Util.root() }) end +map("n", "ft", lazyterm, { desc = "Terminal (root dir)" }) +map("n", "fT", function() Util.terminal() end, { desc = "Terminal (cwd)" }) +map("n", "", lazyterm, { desc = "Terminal (root dir)" }) +map("n", "", lazyterm, { desc = "which_key_ignore" }) + +-- Terminal Mappings +map("t", "", "", { desc = "Enter Normal Mode" }) +map("t", "", "wincmd h", { desc = "Go to left window" }) +map("t", "", "wincmd j", { desc = "Go to lower window" }) +map("t", "", "wincmd k", { desc = "Go to upper window" }) +map("t", "", "wincmd l", { desc = "Go to right window" }) +map("t", "", "close", { desc = "Hide Terminal" }) +map("t", "", "close", { desc = "which_key_ignore" }) + +-- windows +map("n", "ww", "p", { desc = "Other window", remap = true }) +map("n", "wd", "c", { desc = "Delete window", remap = true }) +map("n", "w-", "s", { desc = "Split window below", remap = true }) +map("n", "w|", "v", { desc = "Split window right", remap = true }) +map("n", "-", "s", { desc = "Split window below", remap = true }) +map("n", "|", "v", { desc = "Split window right", remap = true }) + +-- tabs +map("n", "l", "tablast", { desc = "Last Tab" }) +map("n", "f", "tabfirst", { desc = "First Tab" }) +map("n", "", "tabnew", { desc = "New Tab" }) +map("n", "]", "tabnext", { desc = "Next Tab" }) +map("n", "d", "tabclose", { desc = "Close Tab" }) +map("n", "[", "tabprevious", { desc = "Previous Tab" }) diff --git a/lua/lazyvim/config/options.lua b/lua/lazyvim/config/options.lua new file mode 100644 index 0000000..3c9686b --- /dev/null +++ b/lua/lazyvim/config/options.lua @@ -0,0 +1,93 @@ +-- This file is automatically loaded by plugins.core +vim.g.mapleader = " " +vim.g.maplocalleader = "\\" + +-- Enable LazyVim auto format +vim.g.autoformat = true + +-- LazyVim root dir detection +-- Each entry can be: +-- * the name of a detector function like `lsp` or `cwd` +-- * a pattern or array of patterns like `.git` or `lua`. +-- * a function with signature `function(buf) -> string|string[]` +vim.g.root_spec = { "lsp", { ".git", "lua" }, "cwd" } + +local opt = vim.opt + +opt.autowrite = true -- Enable auto write +opt.clipboard = "unnamedplus" -- Sync with system clipboard +opt.completeopt = "menu,menuone,noselect" +opt.conceallevel = 3 -- Hide * markup for bold and italic +opt.confirm = true -- Confirm to save changes before exiting modified buffer +opt.cursorline = true -- Enable highlighting of the current line +opt.expandtab = true -- Use spaces instead of tabs +opt.formatoptions = "jcroqlnt" -- tcqj +opt.grepformat = "%f:%l:%c:%m" +opt.grepprg = "rg --vimgrep" +opt.ignorecase = true -- Ignore case +opt.inccommand = "nosplit" -- preview incremental substitute +opt.laststatus = 3 -- global statusline +opt.list = true -- Show some invisible characters (tabs... +opt.mouse = "a" -- Enable mouse mode +opt.number = true -- Print line number +opt.pumblend = 10 -- Popup blend +opt.pumheight = 10 -- Maximum number of entries in a popup +opt.relativenumber = true -- Relative line numbers +opt.scrolloff = 4 -- Lines of context +opt.sessionoptions = { "buffers", "curdir", "tabpages", "winsize", "help", "globals", "skiprtp", "folds" } +opt.shiftround = true -- Round indent +opt.shiftwidth = 2 -- Size of an indent +opt.shortmess:append({ W = true, I = true, c = true, C = true }) +opt.showmode = false -- Dont show mode since we have a statusline +opt.sidescrolloff = 8 -- Columns of context +opt.signcolumn = "yes" -- Always show the signcolumn, otherwise it would shift the text each time +opt.smartcase = true -- Don't ignore case with capitals +opt.smartindent = true -- Insert indents automatically +opt.spelllang = { "en" } +opt.splitbelow = true -- Put new windows below current +opt.splitkeep = "screen" +opt.splitright = true -- Put new windows right of current +opt.tabstop = 2 -- Number of spaces tabs count for +opt.termguicolors = true -- True color support +opt.timeoutlen = 300 +opt.undofile = true +opt.undolevels = 10000 +opt.updatetime = 200 -- Save swap file and trigger CursorHold +opt.virtualedit = "block" -- Allow cursor to move where there is no text in visual block mode +opt.wildmode = "longest:full,full" -- Command-line completion mode +opt.winminwidth = 5 -- Minimum window width +opt.wrap = false -- Disable line wrap +opt.fillchars = { + foldopen = "", + foldclose = "", + -- fold = "⸱", + fold = " ", + foldsep = " ", + diff = "╱", + eob = " ", +} + +if vim.fn.has("nvim-0.10") == 1 then + opt.smoothscroll = true +end + +-- Folding +vim.opt.foldlevel = 99 +vim.opt.foldtext = "v:lua.require'lazyvim.util'.ui.foldtext()" + +if vim.fn.has("nvim-0.9.0") == 1 then + vim.opt.statuscolumn = [[%!v:lua.require'lazyvim.util'.ui.statuscolumn()]] +end + +-- HACK: causes freezes on <= 0.9, so only enable on >= 0.10 for now +if vim.fn.has("nvim-0.10") == 1 then + vim.opt.foldmethod = "expr" + vim.opt.foldexpr = "v:lua.require'lazyvim.util'.ui.foldexpr()" +else + vim.opt.foldmethod = "indent" +end + +vim.o.formatexpr = "v:lua.require'lazyvim.util'.format.formatexpr()" + +-- Fix markdown indentation settings +vim.g.markdown_recommended_style = 0 diff --git a/lua/lazyvim/health.lua b/lua/lazyvim/health.lua new file mode 100644 index 0000000..a23c90f --- /dev/null +++ b/lua/lazyvim/health.lua @@ -0,0 +1,38 @@ +local M = {} + +local start = vim.health.start or vim.health.report_start +local ok = vim.health.ok or vim.health.report_ok +local warn = vim.health.warn or vim.health.report_warn +local error = vim.health.error or vim.health.report_error + +function M.check() + start("LazyVim") + + if vim.fn.has("nvim-0.9.0") == 1 then + ok("Using Neovim >= 0.9.0") + else + error("Neovim >= 0.9.0 is required") + end + + for _, cmd in ipairs({ "git", "rg", { "fd", "fdfind" }, "lazygit" }) do + local name = type(cmd) == "string" and cmd or vim.inspect(cmd) + local commands = type(cmd) == "string" and { cmd } or cmd + ---@cast commands string[] + local found = false + + for _, c in ipairs(commands) do + if vim.fn.executable(c) == 1 then + name = c + found = true + end + end + + if found then + ok(("`%s` is installed"):format(name)) + else + warn(("`%s` is not installed"):format(name)) + end + end +end + +return M diff --git a/lua/lazyvim/init.lua b/lua/lazyvim/init.lua new file mode 100644 index 0000000..dceae58 --- /dev/null +++ b/lua/lazyvim/init.lua @@ -0,0 +1,8 @@ +local M = {} + +---@param opts? LazyVimConfig +function M.setup(opts) + require("lazyvim.config").setup(opts) +end + +return M diff --git a/lua/lazyvim/plugins/coding.lua b/lua/lazyvim/plugins/coding.lua new file mode 100644 index 0000000..c657143 --- /dev/null +++ b/lua/lazyvim/plugins/coding.lua @@ -0,0 +1,256 @@ +return { + + -- snippets + { + "L3MON4D3/LuaSnip", + build = (not jit.os:find("Windows")) + and "echo 'NOTE: jsregexp is optional, so not a big deal if it fails to build'; make install_jsregexp" + or nil, + dependencies = { + "rafamadriz/friendly-snippets", + config = function() + require("luasnip.loaders.from_vscode").lazy_load() + end, + }, + opts = { + history = true, + delete_check_events = "TextChanged", + }, + -- stylua: ignore + keys = { + { + "", + function() + return require("luasnip").jumpable(1) and "luasnip-jump-next" or "" + end, + expr = true, silent = true, mode = "i", + }, + { "", function() require("luasnip").jump(1) end, mode = "s" }, + { "", function() require("luasnip").jump(-1) end, mode = { "i", "s" } }, + }, + }, + + -- auto completion + { + "hrsh7th/nvim-cmp", + version = false, -- last release is way too old + event = "InsertEnter", + dependencies = { + "hrsh7th/cmp-nvim-lsp", + "hrsh7th/cmp-buffer", + "hrsh7th/cmp-path", + "saadparwaiz1/cmp_luasnip", + }, + opts = function() + vim.api.nvim_set_hl(0, "CmpGhostText", { link = "Comment", default = true }) + local cmp = require("cmp") + local defaults = require("cmp.config.default")() + return { + completion = { + completeopt = "menu,menuone,noinsert", + }, + snippet = { + expand = function(args) + require("luasnip").lsp_expand(args.body) + end, + }, + mapping = cmp.mapping.preset.insert({ + [""] = cmp.mapping.select_next_item({ behavior = cmp.SelectBehavior.Insert }), + [""] = cmp.mapping.select_prev_item({ behavior = cmp.SelectBehavior.Insert }), + [""] = cmp.mapping.scroll_docs(-4), + [""] = cmp.mapping.scroll_docs(4), + [""] = cmp.mapping.complete(), + [""] = cmp.mapping.abort(), + [""] = cmp.mapping.confirm({ select = true }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items. + [""] = cmp.mapping.confirm({ + behavior = cmp.ConfirmBehavior.Replace, + select = true, + }), -- Accept currently selected item. Set `select` to `false` to only confirm explicitly selected items. + [""] = function(fallback) + cmp.abort() + fallback() + end, + }), + sources = cmp.config.sources({ + { name = "nvim_lsp" }, + { name = "luasnip" }, + { name = "path" }, + }, { + { name = "buffer" }, + }), + formatting = { + format = function(_, item) + local icons = require("lazyvim.config").icons.kinds + if icons[item.kind] then + item.kind = icons[item.kind] .. item.kind + end + return item + end, + }, + experimental = { + ghost_text = { + hl_group = "CmpGhostText", + }, + }, + sorting = defaults.sorting, + } + end, + ---@param opts cmp.ConfigSchema + config = function(_, opts) + for _, source in ipairs(opts.sources) do + source.group_index = source.group_index or 1 + end + require("cmp").setup(opts) + end, + }, + + -- auto pairs + { + "echasnovski/mini.pairs", + event = "VeryLazy", + opts = {}, + keys = { + { + "up", + function() + local Util = require("lazy.core.util") + vim.g.minipairs_disable = not vim.g.minipairs_disable + if vim.g.minipairs_disable then + Util.warn("Disabled auto pairs", { title = "Option" }) + else + Util.info("Enabled auto pairs", { title = "Option" }) + end + end, + desc = "Toggle auto pairs", + }, + }, + }, + + -- Fast and feature-rich surround actions. For text that includes + -- surrounding characters like brackets or quotes, this allows you + -- to select the text inside, change or modify the surrounding characters, + -- and more. + { + "echasnovski/mini.surround", + keys = function(_, keys) + -- Populate the keys based on the user's options + local plugin = require("lazy.core.config").spec.plugins["mini.surround"] + local opts = require("lazy.core.plugin").values(plugin, "opts", false) + local mappings = { + { opts.mappings.add, desc = "Add surrounding", mode = { "n", "v" } }, + { opts.mappings.delete, desc = "Delete surrounding" }, + { opts.mappings.find, desc = "Find right surrounding" }, + { opts.mappings.find_left, desc = "Find left surrounding" }, + { opts.mappings.highlight, desc = "Highlight surrounding" }, + { opts.mappings.replace, desc = "Replace surrounding" }, + { opts.mappings.update_n_lines, desc = "Update `MiniSurround.config.n_lines`" }, + } + mappings = vim.tbl_filter(function(m) + return m[1] and #m[1] > 0 + end, mappings) + return vim.list_extend(mappings, keys) + end, + opts = { + mappings = { + add = "gsa", -- Add surrounding in Normal and Visual modes + delete = "gsd", -- Delete surrounding + find = "gsf", -- Find surrounding (to the right) + find_left = "gsF", -- Find surrounding (to the left) + highlight = "gsh", -- Highlight surrounding + replace = "gsr", -- Replace surrounding + update_n_lines = "gsn", -- Update `n_lines` + }, + }, + }, + + -- comments + { + "JoosepAlviste/nvim-ts-context-commentstring", + lazy = true, + opts = { + enable_autocmd = false, + }, + }, + { + "echasnovski/mini.comment", + event = "VeryLazy", + opts = { + options = { + custom_commentstring = function() + return require("ts_context_commentstring.internal").calculate_commentstring() or vim.bo.commentstring + end, + }, + }, + }, + + -- Better text-objects + { + "echasnovski/mini.ai", + -- keys = { + -- { "a", mode = { "x", "o" } }, + -- { "i", mode = { "x", "o" } }, + -- }, + event = "VeryLazy", + opts = function() + local ai = require("mini.ai") + return { + n_lines = 500, + custom_textobjects = { + o = ai.gen_spec.treesitter({ + a = { "@block.outer", "@conditional.outer", "@loop.outer" }, + i = { "@block.inner", "@conditional.inner", "@loop.inner" }, + }, {}), + f = ai.gen_spec.treesitter({ a = "@function.outer", i = "@function.inner" }, {}), + c = ai.gen_spec.treesitter({ a = "@class.outer", i = "@class.inner" }, {}), + t = { "<([%p%w]-)%f[^<%w][^<>]->.-", "^<.->().*()$" }, + }, + } + end, + config = function(_, opts) + require("mini.ai").setup(opts) + -- register all text objects with which-key + require("lazyvim.util").on_load("which-key.nvim", function() + ---@type table + local i = { + [" "] = "Whitespace", + ['"'] = 'Balanced "', + ["'"] = "Balanced '", + ["`"] = "Balanced `", + ["("] = "Balanced (", + [")"] = "Balanced ) including white-space", + [">"] = "Balanced > including white-space", + [""] = "Balanced <", + ["]"] = "Balanced ] including white-space", + ["["] = "Balanced [", + ["}"] = "Balanced } including white-space", + ["{"] = "Balanced {", + ["?"] = "User Prompt", + _ = "Underscore", + a = "Argument", + b = "Balanced ), ], }", + c = "Class", + f = "Function", + o = "Block, conditional, loop", + q = "Quote `, \", '", + t = "Tag", + } + local a = vim.deepcopy(i) + for k, v in pairs(a) do + a[k] = v:gsub(" including.*", "") + end + + local ic = vim.deepcopy(i) + local ac = vim.deepcopy(a) + for key, name in pairs({ n = "Next", l = "Last" }) do + i[key] = vim.tbl_extend("force", { name = "Inside " .. name .. " textobject" }, ic) + a[key] = vim.tbl_extend("force", { name = "Around " .. name .. " textobject" }, ac) + end + require("which-key").register({ + mode = { "o", "x" }, + i = i, + a = a, + }) + end) + end, + }, +} diff --git a/lua/lazyvim/plugins/colorscheme.lua b/lua/lazyvim/plugins/colorscheme.lua new file mode 100644 index 0000000..bb63326 --- /dev/null +++ b/lua/lazyvim/plugins/colorscheme.lua @@ -0,0 +1,53 @@ +return { + + -- tokyonight + { + "folke/tokyonight.nvim", + lazy = true, + opts = { style = "moon" }, + }, + + -- catppuccin + { + "catppuccin/nvim", + lazy = true, + name = "catppuccin", + opts = { + integrations = { + aerial = true, + alpha = true, + cmp = true, + dashboard = true, + flash = true, + gitsigns = true, + headlines = true, + illuminate = true, + indent_blankline = { enabled = true }, + leap = true, + lsp_trouble = true, + mason = true, + markdown = true, + mini = true, + native_lsp = { + enabled = true, + underlines = { + errors = { "undercurl" }, + hints = { "undercurl" }, + warnings = { "undercurl" }, + information = { "undercurl" }, + }, + }, + navic = { enabled = true, custom_bg = "lualine" }, + neotest = true, + neotree = true, + noice = true, + notify = true, + semantic_tokens = true, + telescope = true, + treesitter = true, + treesitter_context = true, + which_key = true, + }, + }, + }, +} diff --git a/lua/lazyvim/plugins/editor.lua b/lua/lazyvim/plugins/editor.lua new file mode 100644 index 0000000..b5f529e --- /dev/null +++ b/lua/lazyvim/plugins/editor.lua @@ -0,0 +1,496 @@ +local Util = require("lazyvim.util") + +return { + + -- file explorer + { + "nvim-neo-tree/neo-tree.nvim", + branch = "v3.x", + cmd = "Neotree", + keys = { + { + "fe", + function() + require("neo-tree.command").execute({ toggle = true, dir = Util.root() }) + end, + desc = "Explorer NeoTree (root dir)", + }, + { + "fE", + function() + require("neo-tree.command").execute({ toggle = true, dir = vim.loop.cwd() }) + end, + desc = "Explorer NeoTree (cwd)", + }, + { "e", "fe", desc = "Explorer NeoTree (root dir)", remap = true }, + { "E", "fE", desc = "Explorer NeoTree (cwd)", remap = true }, + { + "ge", + function() + require("neo-tree.command").execute({ source = "git_status", toggle = true }) + end, + desc = "Git explorer", + }, + { + "be", + function() + require("neo-tree.command").execute({ source = "buffers", toggle = true }) + end, + desc = "Buffer explorer", + }, + }, + deactivate = function() + vim.cmd([[Neotree close]]) + end, + init = function() + if vim.fn.argc(-1) == 1 then + local stat = vim.loop.fs_stat(vim.fn.argv(0)) + if stat and stat.type == "directory" then + require("neo-tree") + end + end + end, + opts = { + sources = { "filesystem", "buffers", "git_status", "document_symbols" }, + open_files_do_not_replace_types = { "terminal", "Trouble", "trouble", "qf", "Outline" }, + filesystem = { + bind_to_cwd = false, + follow_current_file = { enabled = true }, + use_libuv_file_watcher = true, + }, + window = { + mappings = { + [""] = "none", + }, + }, + default_component_configs = { + indent = { + with_expanders = true, -- if nil and file nesting is enabled, will enable expanders + expander_collapsed = "", + expander_expanded = "", + expander_highlight = "NeoTreeExpander", + }, + }, + }, + config = function(_, opts) + local function on_move(data) + Util.lsp.on_rename(data.source, data.destination) + end + + local events = require("neo-tree.events") + opts.event_handlers = opts.event_handlers or {} + vim.list_extend(opts.event_handlers, { + { event = events.FILE_MOVED, handler = on_move }, + { event = events.FILE_RENAMED, handler = on_move }, + }) + require("neo-tree").setup(opts) + vim.api.nvim_create_autocmd("TermClose", { + pattern = "*lazygit", + callback = function() + if package.loaded["neo-tree.sources.git_status"] then + require("neo-tree.sources.git_status").refresh() + end + end, + }) + end, + }, + + -- search/replace in multiple files + { + "nvim-pack/nvim-spectre", + build = false, + cmd = "Spectre", + opts = { open_cmd = "noswapfile vnew" }, + -- stylua: ignore + keys = { + { "sr", function() require("spectre").open() end, desc = "Replace in files (Spectre)" }, + }, + }, + + -- Fuzzy finder. + -- The default key bindings to find files will use Telescope's + -- `find_files` or `git_files` depending on whether the + -- directory is a git repo. + { + "nvim-telescope/telescope.nvim", + cmd = "Telescope", + version = false, -- telescope did only one release, so use HEAD for now + dependencies = { + { + "nvim-telescope/telescope-fzf-native.nvim", + build = "make", + enabled = vim.fn.executable("make") == 1, + config = function() + Util.on_load("telescope.nvim", function() + require("telescope").load_extension("fzf") + end) + end, + }, + }, + keys = { + { + ",", + "Telescope buffers sort_mru=true sort_lastused=true", + desc = "Switch Buffer", + }, + { "/", Util.telescope("live_grep"), desc = "Grep (root dir)" }, + { ":", "Telescope command_history", desc = "Command History" }, + { "", Util.telescope("files"), desc = "Find Files (root dir)" }, + -- find + { "fb", "Telescope buffers sort_mru=true sort_lastused=true", desc = "Buffers" }, + { "fc", Util.telescope.config_files(), desc = "Find Config File" }, + { "ff", Util.telescope("files"), desc = "Find Files (root dir)" }, + { "fF", Util.telescope("files", { cwd = false }), desc = "Find Files (cwd)" }, + { "fr", "Telescope oldfiles", desc = "Recent" }, + { "fR", Util.telescope("oldfiles", { cwd = vim.loop.cwd() }), desc = "Recent (cwd)" }, + -- git + { "gc", "Telescope git_commits", desc = "commits" }, + { "gs", "Telescope git_status", desc = "status" }, + -- search + { 's"', "Telescope registers", desc = "Registers" }, + { "sa", "Telescope autocommands", desc = "Auto Commands" }, + { "sb", "Telescope current_buffer_fuzzy_find", desc = "Buffer" }, + { "sc", "Telescope command_history", desc = "Command History" }, + { "sC", "Telescope commands", desc = "Commands" }, + { "sd", "Telescope diagnostics bufnr=0", desc = "Document diagnostics" }, + { "sD", "Telescope diagnostics", desc = "Workspace diagnostics" }, + { "sg", Util.telescope("live_grep"), desc = "Grep (root dir)" }, + { "sG", Util.telescope("live_grep", { cwd = false }), desc = "Grep (cwd)" }, + { "sh", "Telescope help_tags", desc = "Help Pages" }, + { "sH", "Telescope highlights", desc = "Search Highlight Groups" }, + { "sk", "Telescope keymaps", desc = "Key Maps" }, + { "sM", "Telescope man_pages", desc = "Man Pages" }, + { "sm", "Telescope marks", desc = "Jump to Mark" }, + { "so", "Telescope vim_options", desc = "Options" }, + { "sR", "Telescope resume", desc = "Resume" }, + { "sw", Util.telescope("grep_string", { word_match = "-w" }), desc = "Word (root dir)" }, + { "sW", Util.telescope("grep_string", { cwd = false, word_match = "-w" }), desc = "Word (cwd)" }, + { "sw", Util.telescope("grep_string"), mode = "v", desc = "Selection (root dir)" }, + { "sW", Util.telescope("grep_string", { cwd = false }), mode = "v", desc = "Selection (cwd)" }, + { "uC", Util.telescope("colorscheme", { enable_preview = true }), desc = "Colorscheme with preview" }, + { + "ss", + function() + require("telescope.builtin").lsp_document_symbols({ + symbols = require("lazyvim.config").get_kind_filter(), + }) + end, + desc = "Goto Symbol", + }, + { + "sS", + function() + require("telescope.builtin").lsp_dynamic_workspace_symbols({ + symbols = require("lazyvim.config").get_kind_filter(), + }) + end, + desc = "Goto Symbol (Workspace)", + }, + }, + opts = function() + local actions = require("telescope.actions") + + local open_with_trouble = function(...) + return require("trouble.providers.telescope").open_with_trouble(...) + end + local open_selected_with_trouble = function(...) + return require("trouble.providers.telescope").open_selected_with_trouble(...) + end + local find_files_no_ignore = function() + local action_state = require("telescope.actions.state") + local line = action_state.get_current_line() + Util.telescope("find_files", { no_ignore = true, default_text = line })() + end + local find_files_with_hidden = function() + local action_state = require("telescope.actions.state") + local line = action_state.get_current_line() + Util.telescope("find_files", { hidden = true, default_text = line })() + end + + return { + defaults = { + prompt_prefix = " ", + selection_caret = " ", + -- open files in the first window that is an actual file. + -- use the current window if no other window is available. + get_selection_window = function() + local wins = vim.api.nvim_list_wins() + table.insert(wins, 1, vim.api.nvim_get_current_win()) + for _, win in ipairs(wins) do + local buf = vim.api.nvim_win_get_buf(win) + if vim.bo[buf].buftype == "" then + return win + end + end + return 0 + end, + mappings = { + i = { + [""] = open_with_trouble, + [""] = open_selected_with_trouble, + [""] = find_files_no_ignore, + [""] = find_files_with_hidden, + [""] = actions.cycle_history_next, + [""] = actions.cycle_history_prev, + [""] = actions.preview_scrolling_down, + [""] = actions.preview_scrolling_up, + }, + n = { + ["q"] = actions.close, + }, + }, + }, + } + end, + }, + + -- Flash enhances the built-in search functionality by showing labels + -- at the end of each match, letting you quickly jump to a specific + -- location. + { + "folke/flash.nvim", + event = "VeryLazy", + vscode = true, + ---@type Flash.Config + opts = {}, + -- stylua: ignore + keys = { + { "s", mode = { "n", "x", "o" }, function() require("flash").jump() end, desc = "Flash" }, + { "S", mode = { "n", "o", "x" }, function() require("flash").treesitter() end, desc = "Flash Treesitter" }, + { "r", mode = "o", function() require("flash").remote() end, desc = "Remote Flash" }, + { "R", mode = { "o", "x" }, function() require("flash").treesitter_search() end, desc = "Treesitter Search" }, + { "", mode = { "c" }, function() require("flash").toggle() end, desc = "Toggle Flash Search" }, + }, + }, + + -- Flash Telescope config + { + "nvim-telescope/telescope.nvim", + optional = true, + opts = function(_, opts) + if not Util.has("flash.nvim") then + return + end + local function flash(prompt_bufnr) + require("flash").jump({ + pattern = "^", + label = { after = { 0, 0 } }, + search = { + mode = "search", + exclude = { + function(win) + return vim.bo[vim.api.nvim_win_get_buf(win)].filetype ~= "TelescopeResults" + end, + }, + }, + action = function(match) + local picker = require("telescope.actions.state").get_current_picker(prompt_bufnr) + picker:set_selection(match.pos[1] - 1) + end, + }) + end + opts.defaults = vim.tbl_deep_extend("force", opts.defaults or {}, { + mappings = { n = { s = flash }, i = { [""] = flash } }, + }) + end, + }, + + -- which-key helps you remember key bindings by showing a popup + -- with the active keybindings of the command you started typing. + { + "folke/which-key.nvim", + event = "VeryLazy", + opts = { + plugins = { spelling = true }, + defaults = { + mode = { "n", "v" }, + ["g"] = { name = "+goto" }, + ["gs"] = { name = "+surround" }, + ["]"] = { name = "+next" }, + ["["] = { name = "+prev" }, + [""] = { name = "+tabs" }, + ["b"] = { name = "+buffer" }, + ["c"] = { name = "+code" }, + ["f"] = { name = "+file/find" }, + ["g"] = { name = "+git" }, + ["gh"] = { name = "+hunks" }, + ["q"] = { name = "+quit/session" }, + ["s"] = { name = "+search" }, + ["u"] = { name = "+ui" }, + ["w"] = { name = "+windows" }, + ["x"] = { name = "+diagnostics/quickfix" }, + }, + }, + config = function(_, opts) + local wk = require("which-key") + wk.setup(opts) + wk.register(opts.defaults) + end, + }, + + -- git signs highlights text that has changed since the list + -- git commit, and also lets you interactively stage & unstage + -- hunks in a commit. + { + "lewis6991/gitsigns.nvim", + event = "LazyFile", + opts = { + signs = { + add = { text = "▎" }, + change = { text = "▎" }, + delete = { text = "" }, + topdelete = { text = "" }, + changedelete = { text = "▎" }, + untracked = { text = "▎" }, + }, + on_attach = function(buffer) + local gs = package.loaded.gitsigns + + local function map(mode, l, r, desc) + vim.keymap.set(mode, l, r, { buffer = buffer, desc = desc }) + end + + -- stylua: ignore start + map("n", "]h", gs.next_hunk, "Next Hunk") + map("n", "[h", gs.prev_hunk, "Prev Hunk") + map({ "n", "v" }, "ghs", ":Gitsigns stage_hunk", "Stage Hunk") + map({ "n", "v" }, "ghr", ":Gitsigns reset_hunk", "Reset Hunk") + map("n", "ghS", gs.stage_buffer, "Stage Buffer") + map("n", "ghu", gs.undo_stage_hunk, "Undo Stage Hunk") + map("n", "ghR", gs.reset_buffer, "Reset Buffer") + map("n", "ghp", gs.preview_hunk, "Preview Hunk") + map("n", "ghb", function() gs.blame_line({ full = true }) end, "Blame Line") + map("n", "ghd", gs.diffthis, "Diff This") + map("n", "ghD", function() gs.diffthis("~") end, "Diff This ~") + map({ "o", "x" }, "ih", ":Gitsigns select_hunk", "GitSigns Select Hunk") + end, + }, + }, + + -- Automatically highlights other instances of the word under your cursor. + -- This works with LSP, Treesitter, and regexp matching to find the other + -- instances. + { + "RRethy/vim-illuminate", + event = "LazyFile", + opts = { + delay = 200, + large_file_cutoff = 2000, + large_file_overrides = { + providers = { "lsp" }, + }, + }, + config = function(_, opts) + require("illuminate").configure(opts) + + local function map(key, dir, buffer) + vim.keymap.set("n", key, function() + require("illuminate")["goto_" .. dir .. "_reference"](false) + end, { desc = dir:sub(1, 1):upper() .. dir:sub(2) .. " Reference", buffer = buffer }) + end + + map("]]", "next") + map("[[", "prev") + + -- also set it after loading ftplugins, since a lot overwrite [[ and ]] + vim.api.nvim_create_autocmd("FileType", { + callback = function() + local buffer = vim.api.nvim_get_current_buf() + map("]]", "next", buffer) + map("[[", "prev", buffer) + end, + }) + end, + keys = { + { "]]", desc = "Next Reference" }, + { "[[", desc = "Prev Reference" }, + }, + }, + + -- buffer remove + { + "echasnovski/mini.bufremove", + + keys = { + { + "bd", + function() + local bd = require("mini.bufremove").delete + if vim.bo.modified then + local choice = vim.fn.confirm(("Save changes to %q?"):format(vim.fn.bufname()), "&Yes\n&No\n&Cancel") + if choice == 1 then -- Yes + vim.cmd.write() + bd(0) + elseif choice == 2 then -- No + bd(0, true) + end + else + bd(0) + end + end, + desc = "Delete Buffer", + }, + -- stylua: ignore + { "bD", function() require("mini.bufremove").delete(0, true) end, desc = "Delete Buffer (Force)" }, + }, + }, + + -- better diagnostics list and others + { + "folke/trouble.nvim", + cmd = { "TroubleToggle", "Trouble" }, + opts = { use_diagnostic_signs = true }, + keys = { + { "xx", "TroubleToggle document_diagnostics", desc = "Document Diagnostics (Trouble)" }, + { "xX", "TroubleToggle workspace_diagnostics", desc = "Workspace Diagnostics (Trouble)" }, + { "xL", "TroubleToggle loclist", desc = "Location List (Trouble)" }, + { "xQ", "TroubleToggle quickfix", desc = "Quickfix List (Trouble)" }, + { + "[q", + function() + if require("trouble").is_open() then + require("trouble").previous({ skip_groups = true, jump = true }) + else + local ok, err = pcall(vim.cmd.cprev) + if not ok then + vim.notify(err, vim.log.levels.ERROR) + end + end + end, + desc = "Previous trouble/quickfix item", + }, + { + "]q", + function() + if require("trouble").is_open() then + require("trouble").next({ skip_groups = true, jump = true }) + else + local ok, err = pcall(vim.cmd.cnext) + if not ok then + vim.notify(err, vim.log.levels.ERROR) + end + end + end, + desc = "Next trouble/quickfix item", + }, + }, + }, + + -- Finds and lists all of the TODO, HACK, BUG, etc comment + -- in your project and loads them into a browsable list. + { + "folke/todo-comments.nvim", + cmd = { "TodoTrouble", "TodoTelescope" }, + event = "LazyFile", + config = true, + -- stylua: ignore + keys = { + { "]t", function() require("todo-comments").jump_next() end, desc = "Next todo comment" }, + { "[t", function() require("todo-comments").jump_prev() end, desc = "Previous todo comment" }, + { "xt", "TodoTrouble", desc = "Todo (Trouble)" }, + { "xT", "TodoTrouble keywords=TODO,FIX,FIXME", desc = "Todo/Fix/Fixme (Trouble)" }, + { "st", "TodoTelescope", desc = "Todo" }, + { "sT", "TodoTelescope keywords=TODO,FIX,FIXME", desc = "Todo/Fix/Fixme" }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/coding/codeium.lua b/lua/lazyvim/plugins/extras/coding/codeium.lua new file mode 100644 index 0000000..a9f4062 --- /dev/null +++ b/lua/lazyvim/plugins/extras/coding/codeium.lua @@ -0,0 +1,33 @@ +return { + + -- codeium cmp source + { + "nvim-cmp", + dependencies = { + -- codeium + { + "Exafunction/codeium.nvim", + cmd = "Codeium", + build = ":Codeium Auth", + opts = {}, + }, + }, + ---@param opts cmp.ConfigSchema + opts = function(_, opts) + table.insert(opts.sources, 1, { + name = "codeium", + group_index = 1, + priority = 100, + }) + end, + }, + + { + "nvim-lualine/lualine.nvim", + optional = true, + event = "VeryLazy", + opts = function(_, opts) + table.insert(opts.sections.lualine_x, 2, require("lazyvim.util").lualine.cmp_source("codeium")) + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/coding/copilot.lua b/lua/lazyvim/plugins/extras/coding/copilot.lua new file mode 100644 index 0000000..279a974 --- /dev/null +++ b/lua/lazyvim/plugins/extras/coding/copilot.lua @@ -0,0 +1,86 @@ +return { + + -- copilot + { + "zbirenbaum/copilot.lua", + cmd = "Copilot", + build = ":Copilot auth", + opts = { + suggestion = { enabled = false }, + panel = { enabled = false }, + filetypes = { + markdown = true, + help = true, + }, + }, + }, + { + "nvim-lualine/lualine.nvim", + optional = true, + event = "VeryLazy", + opts = function(_, opts) + local Util = require("lazyvim.util") + local colors = { + [""] = Util.ui.fg("Special"), + ["Normal"] = Util.ui.fg("Special"), + ["Warning"] = Util.ui.fg("DiagnosticError"), + ["InProgress"] = Util.ui.fg("DiagnosticWarn"), + } + table.insert(opts.sections.lualine_x, 2, { + function() + local icon = require("lazyvim.config").icons.kinds.Copilot + local status = require("copilot.api").status.data + return icon .. (status.message or "") + end, + cond = function() + if not package.loaded["copilot"] then + return + end + local ok, clients = pcall(require("lazyvim.util").lsp.get_clients, { name = "copilot", bufnr = 0 }) + if not ok then + return false + end + return ok and #clients > 0 + end, + color = function() + if not package.loaded["copilot"] then + return + end + local status = require("copilot.api").status.data + return colors[status.status] or colors[""] + end, + }) + end, + }, + + -- copilot cmp source + { + "nvim-cmp", + dependencies = { + { + "zbirenbaum/copilot-cmp", + dependencies = "copilot.lua", + opts = {}, + config = function(_, opts) + local copilot_cmp = require("copilot_cmp") + copilot_cmp.setup(opts) + -- attach cmp source whenever copilot attaches + -- fixes lazy-loading issues with the copilot cmp source + require("lazyvim.util").lsp.on_attach(function(client) + if client.name == "copilot" then + copilot_cmp._on_insert_enter({}) + end + end) + end, + }, + }, + ---@param opts cmp.ConfigSchema + opts = function(_, opts) + table.insert(opts.sources, 1, { + name = "copilot", + group_index = 1, + priority = 100, + }) + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/coding/tabnine.lua b/lua/lazyvim/plugins/extras/coding/tabnine.lua new file mode 100644 index 0000000..aac8519 --- /dev/null +++ b/lua/lazyvim/plugins/extras/coding/tabnine.lua @@ -0,0 +1,51 @@ +local Util = require("lazyvim.util") + +return { + -- Tabnine cmp source + { + "nvim-cmp", + dependencies = { + { + "tzachar/cmp-tabnine", + build = { + Util.is_win() and "pwsh -noni .\\install.ps1" or "./install.sh", + ":CmpTabnineHub", + }, + dependencies = "hrsh7th/nvim-cmp", + opts = { + max_lines = 1000, + max_num_results = 3, + sort = true, + }, + config = function(_, opts) + require("cmp_tabnine.config"):setup(opts) + end, + }, + }, + ---@param opts cmp.ConfigSchema + opts = function(_, opts) + table.insert(opts.sources, 1, { + name = "cmp_tabnine", + group_index = 1, + priority = 100, + }) + + opts.formatting.format = Util.inject.args(opts.formatting.format, function(entry, item) + -- Hide percentage in the menu + if entry.source.name == "cmp_tabnine" then + item.menu = "" + end + end) + end, + }, + -- Show TabNine status in lualine + { + "nvim-lualine/lualine.nvim", + optional = true, + event = "VeryLazy", + opts = function(_, opts) + local icon = require("lazyvim.config").icons.kinds.TabNine + table.insert(opts.sections.lualine_x, 2, require("lazyvim.util").lualine.cmp_source("cmp_tabnine", icon)) + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/coding/yanky.lua b/lua/lazyvim/plugins/extras/coding/yanky.lua new file mode 100644 index 0000000..00b675e --- /dev/null +++ b/lua/lazyvim/plugins/extras/coding/yanky.lua @@ -0,0 +1,32 @@ +return { + -- better yank/paste + { + "gbprod/yanky.nvim", + dependencies = { { "kkharji/sqlite.lua", enabled = not jit.os:find("Windows") } }, + opts = { + highlight = { timer = 250 }, + ring = { storage = jit.os:find("Windows") and "shada" or "sqlite" }, + }, + keys = { + -- stylua: ignore + { "p", function() require("telescope").extensions.yank_history.yank_history({ }) end, desc = "Open Yank History" }, + { "y", "(YankyYank)", mode = { "n", "x" }, desc = "Yank text" }, + { "p", "(YankyPutAfter)", mode = { "n", "x" }, desc = "Put yanked text after cursor" }, + { "P", "(YankyPutBefore)", mode = { "n", "x" }, desc = "Put yanked text before cursor" }, + { "gp", "(YankyGPutAfter)", mode = { "n", "x" }, desc = "Put yanked text after selection" }, + { "gP", "(YankyGPutBefore)", mode = { "n", "x" }, desc = "Put yanked text before selection" }, + { "[y", "(YankyCycleForward)", desc = "Cycle forward through yank history" }, + { "]y", "(YankyCycleBackward)", desc = "Cycle backward through yank history" }, + { "]p", "(YankyPutIndentAfterLinewise)", desc = "Put indented after cursor (linewise)" }, + { "[p", "(YankyPutIndentBeforeLinewise)", desc = "Put indented before cursor (linewise)" }, + { "]P", "(YankyPutIndentAfterLinewise)", desc = "Put indented after cursor (linewise)" }, + { "[P", "(YankyPutIndentBeforeLinewise)", desc = "Put indented before cursor (linewise)" }, + { ">p", "(YankyPutIndentAfterShiftRight)", desc = "Put and indent right" }, + { "(YankyPutIndentAfterShiftLeft)", desc = "Put and indent left" }, + { ">P", "(YankyPutIndentBeforeShiftRight)", desc = "Put before and indent right" }, + { "(YankyPutIndentBeforeShiftLeft)", desc = "Put before and indent left" }, + { "=p", "(YankyPutAfterFilter)", desc = "Put after applying a filter" }, + { "=P", "(YankyPutBeforeFilter)", desc = "Put before applying a filter" }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/dap/core.lua b/lua/lazyvim/plugins/extras/dap/core.lua new file mode 100644 index 0000000..c9aa291 --- /dev/null +++ b/lua/lazyvim/plugins/extras/dap/core.lua @@ -0,0 +1,118 @@ +---@param config {args?:string[]|fun():string[]?} +local function get_args(config) + local args = type(config.args) == "function" and (config.args() or {}) or config.args or {} + config = vim.deepcopy(config) + ---@cast args string[] + config.args = function() + local new_args = vim.fn.input("Run with args: ", table.concat(args, " ")) --[[@as string]] + return vim.split(vim.fn.expand(new_args) --[[@as string]], " ") + end + return config +end + +return { + "mfussenegger/nvim-dap", + + dependencies = { + + -- fancy UI for the debugger + { + "rcarriga/nvim-dap-ui", + -- stylua: ignore + keys = { + { "du", function() require("dapui").toggle({ }) end, desc = "Dap UI" }, + { "de", function() require("dapui").eval() end, desc = "Eval", mode = {"n", "v"} }, + }, + opts = {}, + config = function(_, opts) + -- setup dap config by VsCode launch.json file + -- require("dap.ext.vscode").load_launchjs() + local dap = require("dap") + local dapui = require("dapui") + dapui.setup(opts) + dap.listeners.after.event_initialized["dapui_config"] = function() + dapui.open({}) + end + dap.listeners.before.event_terminated["dapui_config"] = function() + dapui.close({}) + end + dap.listeners.before.event_exited["dapui_config"] = function() + dapui.close({}) + end + end, + }, + + -- virtual text for the debugger + { + "theHamsta/nvim-dap-virtual-text", + opts = {}, + }, + + -- which key integration + { + "folke/which-key.nvim", + optional = true, + opts = { + defaults = { + ["d"] = { name = "+debug" }, + }, + }, + }, + + -- mason.nvim integration + { + "jay-babu/mason-nvim-dap.nvim", + dependencies = "mason.nvim", + cmd = { "DapInstall", "DapUninstall" }, + opts = { + -- Makes a best effort to setup the various debuggers with + -- reasonable debug configurations + automatic_installation = true, + + -- You can provide additional configuration to the handlers, + -- see mason-nvim-dap README for more information + handlers = {}, + + -- You'll need to check that you have the required things installed + -- online, please don't ask me how to install them :) + ensure_installed = { + -- Update this to ensure that you have the debuggers for the langs you want + }, + }, + }, + }, + + -- stylua: ignore + keys = { + { "dB", function() require("dap").set_breakpoint(vim.fn.input('Breakpoint condition: ')) end, desc = "Breakpoint Condition" }, + { "db", function() require("dap").toggle_breakpoint() end, desc = "Toggle Breakpoint" }, + { "dc", function() require("dap").continue() end, desc = "Continue" }, + { "da", function() require("dap").continue({ before = get_args }) end, desc = "Run with Args" }, + { "dC", function() require("dap").run_to_cursor() end, desc = "Run to Cursor" }, + { "dg", function() require("dap").goto_() end, desc = "Go to line (no execute)" }, + { "di", function() require("dap").step_into() end, desc = "Step Into" }, + { "dj", function() require("dap").down() end, desc = "Down" }, + { "dk", function() require("dap").up() end, desc = "Up" }, + { "dl", function() require("dap").run_last() end, desc = "Run Last" }, + { "do", function() require("dap").step_out() end, desc = "Step Out" }, + { "dO", function() require("dap").step_over() end, desc = "Step Over" }, + { "dp", function() require("dap").pause() end, desc = "Pause" }, + { "dr", function() require("dap").repl.toggle() end, desc = "Toggle REPL" }, + { "ds", function() require("dap").session() end, desc = "Session" }, + { "dt", function() require("dap").terminate() end, desc = "Terminate" }, + { "dw", function() require("dap.ui.widgets").hover() end, desc = "Widgets" }, + }, + + config = function() + local Config = require("lazyvim.config") + vim.api.nvim_set_hl(0, "DapStoppedLine", { default = true, link = "Visual" }) + + for name, sign in pairs(Config.icons.dap) do + sign = type(sign) == "table" and sign or { sign } + vim.fn.sign_define( + "Dap" .. name, + { text = sign[1], texthl = sign[2] or "DiagnosticInfo", linehl = sign[3], numhl = sign[3] } + ) + end + end, +} diff --git a/lua/lazyvim/plugins/extras/dap/nlua.lua b/lua/lazyvim/plugins/extras/dap/nlua.lua new file mode 100644 index 0000000..9cf0e63 --- /dev/null +++ b/lua/lazyvim/plugins/extras/dap/nlua.lua @@ -0,0 +1,43 @@ +return { + "mfussenegger/nvim-dap", + dependencies = { + { + "jbyuki/one-small-step-for-vimkind", + -- stylua: ignore + config = function() + local dap = require("dap") + dap.adapters.nlua = function(callback, conf) + local adapter = { + type = "server", + host = conf.host or "127.0.0.1", + port = conf.port or 8086, + } + if conf.start_neovim then + local dap_run = dap.run + dap.run = function(c) + adapter.port = c.port + adapter.host = c.host + end + require("osv").run_this() + dap.run = dap_run + end + callback(adapter) + end + dap.configurations.lua = { + { + type = "nlua", + request = "attach", + name = "Run this file", + start_neovim = {}, + }, + { + type = "nlua", + request = "attach", + name = "Attach to running Neovim instance (port = 8086)", + port = 8086, + }, + } + end, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/editor/aerial.lua b/lua/lazyvim/plugins/extras/editor/aerial.lua new file mode 100644 index 0000000..440c9fd --- /dev/null +++ b/lua/lazyvim/plugins/extras/editor/aerial.lua @@ -0,0 +1,123 @@ +local Config = require("lazyvim.config") +local Util = require("lazyvim.util") + +return { + desc = "Aerial Symbol Browser", + { + "stevearc/aerial.nvim", + event = "LazyFile", + opts = function() + local icons = vim.deepcopy(Config.icons.kinds) + + -- HACK: fix lua's weird choice for `Package` for control + -- structures like if/else/for/etc. + icons.lua = { Package = icons.Control } + + ---@type table|false + local filter_kind = false + if Config.kind_filter then + filter_kind = assert(vim.deepcopy(Config.kind_filter)) + filter_kind._ = filter_kind.default + filter_kind.default = nil + end + + local opts = { + attach_mode = "global", + backends = { "lsp", "treesitter", "markdown", "man" }, + show_guides = true, + layout = { + resize_to_content = false, + win_opts = { + winhl = "Normal:NormalFloat,FloatBorder:NormalFloat,SignColumn:SignColumnSB", + signcolumn = "yes", + statuscolumn = " ", + }, + }, + icons = icons, + filter_kind = filter_kind, + -- stylua: ignore + guides = { + mid_item = "├╴", + last_item = "└╴", + nested_top = "│ ", + whitespace = " ", + }, + } + return opts + end, + keys = { + { "cs", "AerialToggle", desc = "Aerial (Symbols)" }, + }, + }, + + -- Telescope integration + { + "nvim-telescope/telescope.nvim", + optional = true, + opts = function() + Util.on_load("telescope.nvim", function() + require("telescope").load_extension("aerial") + end) + end, + keys = { + { + "ss", + "Telescope aerial", + desc = "Goto Symbol (Aerial)", + }, + }, + }, + + -- edgy integration + { + "folke/edgy.nvim", + optional = true, + opts = function(_, opts) + local edgy_idx = Util.plugin.extra_idx("ui.edgy") + local aerial_idx = Util.plugin.extra_idx("editor.aerial") + + if edgy_idx and edgy_idx > aerial_idx then + Util.warn("The `edgy.nvim` extra must be **imported** before the `aerial.nvim` extra to work properly.", { + title = "LazyVim", + }) + end + + opts.right = opts.right or {} + table.insert(opts.right, { + title = "Aerial", + ft = "aerial", + pinned = true, + open = "AerialOpen", + }) + end, + }, + + -- lualine integration + { + "nvim-lualine/lualine.nvim", + optional = true, + opts = function(_, opts) + table.insert(opts.sections.lualine_c, { + "aerial", + sep = " ", -- separator between symbols + sep_icon = "", -- separator between icon and symbol + + -- The number of symbols to render top-down. In order to render only 'N' last + -- symbols, negative numbers may be supplied. For instance, 'depth = -1' can + -- be used in order to render only current symbol. + depth = 5, + + -- When 'dense' mode is on, icons are not rendered near their symbols. Only + -- a single icon that represents the kind of current symbol is rendered at + -- the beginning of status line. + dense = false, + + -- The separator to be used to separate symbols in dense mode. + dense_sep = ".", + + -- Color the symbol icons. + colored = true, + }) + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/editor/leap.lua b/lua/lazyvim/plugins/extras/editor/leap.lua new file mode 100644 index 0000000..ab422ab --- /dev/null +++ b/lua/lazyvim/plugins/extras/editor/leap.lua @@ -0,0 +1,56 @@ +return { + -- disable flash + { "folke/flash.nvim", enabled = false, optional = true }, + + -- easily jump to any location and enhanced f/t motions for Leap + { + "ggandor/flit.nvim", + enabled = true, + keys = function() + ---@type LazyKeys[] + local ret = {} + for _, key in ipairs({ "f", "F", "t", "T" }) do + ret[#ret + 1] = { key, mode = { "n", "x", "o" }, desc = key } + end + return ret + end, + opts = { labeled_modes = "nx" }, + }, + { + "ggandor/leap.nvim", + enabled = true, + keys = { + { "s", mode = { "n", "x", "o" }, desc = "Leap forward to" }, + { "S", mode = { "n", "x", "o" }, desc = "Leap backward to" }, + { "gs", mode = { "n", "x", "o" }, desc = "Leap from windows" }, + }, + config = function(_, opts) + local leap = require("leap") + for k, v in pairs(opts) do + leap.opts[k] = v + end + leap.add_default_mappings(true) + vim.keymap.del({ "x", "o" }, "x") + vim.keymap.del({ "x", "o" }, "X") + end, + }, + + -- rename surround mappings from gs to gz to prevent conflict with leap + { + "echasnovski/mini.surround", + opts = { + mappings = { + add = "gza", -- Add surrounding in Normal and Visual modes + delete = "gzd", -- Delete surrounding + find = "gzf", -- Find surrounding (to the right) + find_left = "gzF", -- Find surrounding (to the left) + highlight = "gzh", -- Highlight surrounding + replace = "gzr", -- Replace surrounding + update_n_lines = "gzn", -- Update `n_lines` + }, + }, + }, + + -- makes some plugins dot-repeatable like leap + { "tpope/vim-repeat", event = "VeryLazy" }, +} diff --git a/lua/lazyvim/plugins/extras/editor/mini-files.lua b/lua/lazyvim/plugins/extras/editor/mini-files.lua new file mode 100644 index 0000000..55fc768 --- /dev/null +++ b/lua/lazyvim/plugins/extras/editor/mini-files.lua @@ -0,0 +1,64 @@ +return { + "echasnovski/mini.files", + opts = { + windows = { + preview = true, + width_focus = 30, + width_preview = 30, + }, + options = { + -- Whether to use for editing directories + -- Disabled by default in LazyVim because neo-tree is used for that + use_as_default_explorer = false, + }, + }, + keys = { + { + "fm", + function() + require("mini.files").open(vim.api.nvim_buf_get_name(0), true) + end, + desc = "Open mini.files (directory of current file)", + }, + { + "fM", + function() + require("mini.files").open(vim.loop.cwd(), true) + end, + desc = "Open mini.files (cwd)", + }, + }, + config = function(_, opts) + require("mini.files").setup(opts) + + local show_dotfiles = true + local filter_show = function(fs_entry) + return true + end + local filter_hide = function(fs_entry) + return not vim.startswith(fs_entry.name, ".") + end + + local toggle_dotfiles = function() + show_dotfiles = not show_dotfiles + local new_filter = show_dotfiles and filter_show or filter_hide + require("mini.files").refresh({ content = { filter = new_filter } }) + end + + vim.api.nvim_create_autocmd("User", { + pattern = "MiniFilesBufferCreate", + callback = function(args) + local buf_id = args.data.buf_id + -- Tweak left-hand side of mapping to your liking + vim.keymap.set("n", "g.", toggle_dotfiles, { buffer = buf_id }) + end, + }) + + vim.api.nvim_create_autocmd("User", { + pattern = "MiniFilesActionRename", + callback = function(event) + require("lazyvim.util").lsp.on_rename(event.data.from, event.data.to) + end, + }) + end, +} diff --git a/lua/lazyvim/plugins/extras/editor/navic.lua b/lua/lazyvim/plugins/extras/editor/navic.lua new file mode 100644 index 0000000..359c42d --- /dev/null +++ b/lua/lazyvim/plugins/extras/editor/navic.lua @@ -0,0 +1,42 @@ +return { + -- lsp symbol navigation for lualine. This shows where + -- in the code structure you are - within functions, classes, + -- etc - in the statusline. + { + "SmiteshP/nvim-navic", + lazy = true, + init = function() + vim.g.navic_silence = true + require("lazyvim.util").lsp.on_attach(function(client, buffer) + if client.supports_method("textDocument/documentSymbol") then + require("nvim-navic").attach(client, buffer) + end + end) + end, + opts = function() + return { + separator = " ", + highlight = true, + depth_limit = 5, + icons = require("lazyvim.config").icons.kinds, + lazy_update_context = true, + } + end, + }, + + -- lualine integration + { + "nvim-lualine/lualine.nvim", + optional = true, + opts = function(_, opts) + table.insert(opts.sections.lualine_c, { + function() + return require("nvim-navic").get_location() + end, + cond = function() + return package.loaded["nvim-navic"] and require("nvim-navic").is_available() + end, + }) + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/editor/symbols-outline.lua b/lua/lazyvim/plugins/extras/editor/symbols-outline.lua new file mode 100644 index 0000000..32cdd17 --- /dev/null +++ b/lua/lazyvim/plugins/extras/editor/symbols-outline.lua @@ -0,0 +1,61 @@ +local Util = require("lazyvim.util") + +return { + { + "simrat39/symbols-outline.nvim", + keys = { { "cs", "SymbolsOutline", desc = "Symbols Outline" } }, + cmd = "SymbolsOutline", + opts = function() + local Config = require("lazyvim.config") + local defaults = require("symbols-outline.config").defaults + local opts = { + symbols = {}, + symbol_blacklist = {}, + } + local filter = Config.kind_filter + + if type(filter) == "table" then + filter = filter.default + if type(filter) == "table" then + for kind, symbol in pairs(defaults.symbols) do + opts.symbols[kind] = { + icon = Config.icons.kinds[kind] or symbol.icon, + hl = symbol.hl, + } + if not vim.tbl_contains(filter, kind) then + table.insert(opts.symbol_blacklist, kind) + end + end + end + end + return opts + end, + }, + + -- edgy integration + { + "folke/edgy.nvim", + optional = true, + opts = function(_, opts) + local edgy_idx = Util.plugin.extra_idx("ui.edgy") + local symbols_idx = Util.plugin.extra_idx("editor.symbols-outline") + + if edgy_idx and edgy_idx > symbols_idx then + Util.warn( + "The `edgy.nvim` extra must be **imported** before the `symbols-outline.nvim` extra to work properly.", + { + title = "LazyVim", + } + ) + end + + opts.right = opts.right or {} + table.insert(opts.right, { + title = "Symbols Outline", + ft = "Outline", + pinned = true, + open = "SymbolsOutline", + }) + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/formatting/black.lua b/lua/lazyvim/plugins/extras/formatting/black.lua new file mode 100644 index 0000000..769c300 --- /dev/null +++ b/lua/lazyvim/plugins/extras/formatting/black.lua @@ -0,0 +1,26 @@ +return { + { + "williamboman/mason.nvim", + opts = function(_, opts) + table.insert(opts.ensure_installed, "black") + end, + }, + { + "nvimtools/none-ls.nvim", + optional = true, + opts = function(_, opts) + local nls = require("null-ls") + opts.sources = opts.sources or {} + table.insert(opts.sources, nls.builtins.formatting.black) + end, + }, + { + "stevearc/conform.nvim", + optional = true, + opts = { + formatters_by_ft = { + ["python"] = { "black" }, + }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/formatting/prettier.lua b/lua/lazyvim/plugins/extras/formatting/prettier.lua new file mode 100644 index 0000000..a93f6ec --- /dev/null +++ b/lua/lazyvim/plugins/extras/formatting/prettier.lua @@ -0,0 +1,41 @@ +return { + { + "williamboman/mason.nvim", + opts = function(_, opts) + table.insert(opts.ensure_installed, "prettier") + end, + }, + { + "nvimtools/none-ls.nvim", + optional = true, + opts = function(_, opts) + local nls = require("null-ls") + opts.sources = opts.sources or {} + table.insert(opts.sources, nls.builtins.formatting.prettier) + end, + }, + { + "stevearc/conform.nvim", + optional = true, + opts = { + formatters_by_ft = { + ["javascript"] = { "prettier" }, + ["javascriptreact"] = { "prettier" }, + ["typescript"] = { "prettier" }, + ["typescriptreact"] = { "prettier" }, + ["vue"] = { "prettier" }, + ["css"] = { "prettier" }, + ["scss"] = { "prettier" }, + ["less"] = { "prettier" }, + ["html"] = { "prettier" }, + ["json"] = { "prettier" }, + ["jsonc"] = { "prettier" }, + ["yaml"] = { "prettier" }, + ["markdown"] = { "prettier" }, + ["markdown.mdx"] = { "prettier" }, + ["graphql"] = { "prettier" }, + ["handlebars"] = { "prettier" }, + }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/clangd.lua b/lua/lazyvim/plugins/extras/lang/clangd.lua new file mode 100644 index 0000000..0801770 --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/clangd.lua @@ -0,0 +1,154 @@ +return { + + -- Add C/C++ to treesitter + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "c", "cpp" }) + end + end, + }, + + { + "p00f/clangd_extensions.nvim", + lazy = true, + config = function() end, + opts = { + inlay_hints = { + inline = false, + }, + ast = { + --These require codicons (https://github.com/microsoft/vscode-codicons) + role_icons = { + type = "", + declaration = "", + expression = "", + specifier = "", + statement = "", + ["template argument"] = "", + }, + kind_icons = { + Compound = "", + Recovery = "", + TranslationUnit = "", + PackExpansion = "", + TemplateTypeParm = "", + TemplateTemplateParm = "", + TemplateParamObject = "", + }, + }, + }, + }, + + -- Correctly setup lspconfig for clangd 🚀 + { + "neovim/nvim-lspconfig", + opts = { + servers = { + -- Ensure mason installs the server + clangd = { + keys = { + { "cR", "ClangdSwitchSourceHeader", desc = "Switch Source/Header (C/C++)" }, + }, + root_dir = function(fname) + return require("lspconfig.util").root_pattern( + "Makefile", + "configure.ac", + "configure.in", + "config.h.in", + "meson.build", + "meson_options.txt", + "build.ninja" + )(fname) or require("lspconfig.util").root_pattern("compile_commands.json", "compile_flags.txt")( + fname + ) or require("lspconfig.util").find_git_ancestor(fname) + end, + capabilities = { + offsetEncoding = { "utf-16" }, + }, + cmd = { + "clangd", + "--background-index", + "--clang-tidy", + "--header-insertion=iwyu", + "--completion-style=detailed", + "--function-arg-placeholders", + "--fallback-style=llvm", + }, + init_options = { + usePlaceholders = true, + completeUnimported = true, + clangdFileStatus = true, + }, + }, + }, + setup = { + clangd = function(_, opts) + local clangd_ext_opts = require("lazyvim.util").opts("clangd_extensions.nvim") + require("clangd_extensions").setup(vim.tbl_deep_extend("force", clangd_ext_opts or {}, { server = opts })) + return false + end, + }, + }, + }, + + { + "nvim-cmp", + opts = function(_, opts) + table.insert(opts.sorting.comparators, 1, require("clangd_extensions.cmp_scores")) + end, + }, + + { + "mfussenegger/nvim-dap", + optional = true, + dependencies = { + -- Ensure C/C++ debugger is installed + "williamboman/mason.nvim", + optional = true, + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "codelldb" }) + end + end, + }, + opts = function() + local dap = require("dap") + if not dap.adapters["codelldb"] then + require("dap").adapters["codelldb"] = { + type = "server", + host = "localhost", + port = "${port}", + executable = { + command = "codelldb", + args = { + "--port", + "${port}", + }, + }, + } + end + for _, lang in ipairs({ "c", "cpp" }) do + dap.configurations[lang] = { + { + type = "codelldb", + request = "launch", + name = "Launch file", + program = function() + return vim.fn.input("Path to executable: ", vim.fn.getcwd() .. "/", "file") + end, + cwd = "${workspaceFolder}", + }, + { + type = "codelldb", + request = "attach", + name = "Attach to process", + processId = require("dap.utils").pick_process, + cwd = "${workspaceFolder}", + }, + } + end + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/cmake.lua b/lua/lazyvim/plugins/extras/lang/cmake.lua new file mode 100644 index 0000000..e95dbfd --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/cmake.lua @@ -0,0 +1,49 @@ +return { + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "cmake" }) + end + end, + }, + { + "nvimtools/none-ls.nvim", + optional = true, + opts = function(_, opts) + local nls = require("null-ls") + opts.sources = vim.list_extend(opts.sources or {}, { + nls.builtins.diagnostics.cmake_lint, + }) + end, + }, + { + "mfussenegger/nvim-lint", + optional = true, + opts = { + linters_by_ft = { + cmake = { "cmakelint" }, + }, + }, + }, + { + "mason.nvim", + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + vim.list_extend(opts.ensure_installed, { "cmakelang", "cmakelint" }) + end, + }, + { + "neovim/nvim-lspconfig", + opts = { + servers = { + neocmake = {}, + }, + }, + }, + { + "Civitasv/cmake-tools.nvim", + opts = {}, + event = "LazyFile", + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/docker.lua b/lua/lazyvim/plugins/extras/lang/docker.lua new file mode 100644 index 0000000..da9605e --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/docker.lua @@ -0,0 +1,45 @@ +return { + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "dockerfile" }) + end + end, + }, + { + "mason.nvim", + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + vim.list_extend(opts.ensure_installed, { "hadolint" }) + end, + }, + { + "nvimtools/none-ls.nvim", + optional = true, + opts = function(_, opts) + local nls = require("null-ls") + opts.sources = vim.list_extend(opts.sources or {}, { + nls.builtins.diagnostics.hadolint, + }) + end, + }, + { + "mfussenegger/nvim-lint", + optional = true, + opts = { + linters_by_ft = { + dockerfile = { "hadolint" }, + }, + }, + }, + { + "neovim/nvim-lspconfig", + opts = { + servers = { + dockerls = {}, + docker_compose_language_service = {}, + }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/elixir.lua b/lua/lazyvim/plugins/extras/lang/elixir.lua new file mode 100644 index 0000000..f60dc18 --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/elixir.lua @@ -0,0 +1,57 @@ +return { + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + vim.list_extend(opts.ensure_installed, { + "elixir", + "heex", + "eex", + }) + end, + }, + { + "williamboman/mason.nvim", + opts = function(_, opts) + vim.list_extend(opts.ensure_installed, { + "elixir-ls", + }) + end, + }, + { + "nvim-neotest/neotest", + optional = true, + dependencies = { + "jfpedroza/neotest-elixir", + }, + opts = { + adapters = { + ["neotest-elixir"] = {}, + }, + }, + }, + { + "nvimtools/none-ls.nvim", + optional = true, + opts = function(_, opts) + if vim.fn.executable("credo") == 0 then + return + end + local nls = require("null-ls") + opts.sources = vim.list_extend(opts.sources or {}, { + nls.builtins.diagnostics.credo, + }) + end, + }, + { + "mfussenegger/nvim-lint", + optional = true, + opts = function(_, opts) + if vim.fn.executable("credo") == 0 then + return + end + opts.linters_by_ft = { + elixir = { "credo" }, + } + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/go.lua b/lua/lazyvim/plugins/extras/lang/go.lua new file mode 100644 index 0000000..aaad012 --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/go.lua @@ -0,0 +1,155 @@ +return { + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + vim.list_extend(opts.ensure_installed, { + "go", + "gomod", + "gowork", + "gosum", + }) + end, + }, + { + "neovim/nvim-lspconfig", + opts = { + servers = { + gopls = { + keys = { + -- Workaround for the lack of a DAP strategy in neotest-go: https://github.com/nvim-neotest/neotest-go/issues/12 + { "td", "lua require('dap-go').debug_test()", desc = "Debug Nearest (Go)" }, + }, + settings = { + gopls = { + gofumpt = true, + codelenses = { + gc_details = false, + generate = true, + regenerate_cgo = true, + run_govulncheck = true, + test = true, + tidy = true, + upgrade_dependency = true, + vendor = true, + }, + hints = { + assignVariableTypes = true, + compositeLiteralFields = true, + compositeLiteralTypes = true, + constantValues = true, + functionTypeParameters = true, + parameterNames = true, + rangeVariableTypes = true, + }, + analyses = { + fieldalignment = true, + nilness = true, + unusedparams = true, + unusedwrite = true, + useany = true, + }, + usePlaceholders = true, + completeUnimported = true, + staticcheck = true, + directoryFilters = { "-.git", "-.vscode", "-.idea", "-.vscode-test", "-node_modules" }, + semanticTokens = true, + }, + }, + }, + }, + setup = { + gopls = function(_, opts) + -- workaround for gopls not supporting semanticTokensProvider + -- https://github.com/golang/go/issues/54531#issuecomment-1464982242 + require("lazyvim.util").lsp.on_attach(function(client, _) + if client.name == "gopls" then + if not client.server_capabilities.semanticTokensProvider then + local semantic = client.config.capabilities.textDocument.semanticTokens + client.server_capabilities.semanticTokensProvider = { + full = true, + legend = { + tokenTypes = semantic.tokenTypes, + tokenModifiers = semantic.tokenModifiers, + }, + range = true, + } + end + end + end) + -- end workaround + end, + }, + }, + }, + -- Ensure Go tools are installed + { + "williamboman/mason.nvim", + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + vim.list_extend(opts.ensure_installed, { "goimports", "gofumpt" }) + end, + }, + { + "nvimtools/none-ls.nvim", + optional = true, + dependencies = { + { + "williamboman/mason.nvim", + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + vim.list_extend(opts.ensure_installed, { "gomodifytags", "impl" }) + end, + }, + }, + opts = function(_, opts) + local nls = require("null-ls") + opts.sources = vim.list_extend(opts.sources or {}, { + nls.builtins.code_actions.gomodifytags, + nls.builtins.code_actions.impl, + nls.builtins.formatting.goimports, + nls.builtins.formatting.gofumpt, + }) + end, + }, + { + "stevearc/conform.nvim", + optional = true, + opts = { + formatters_by_ft = { + go = { "goimports", "gofumpt" }, + }, + }, + }, + { + "mfussenegger/nvim-dap", + optional = true, + dependencies = { + { + "williamboman/mason.nvim", + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + vim.list_extend(opts.ensure_installed, { "delve" }) + end, + }, + { + "leoluz/nvim-dap-go", + config = true, + }, + }, + }, + { + "nvim-neotest/neotest", + optional = true, + dependencies = { + "nvim-neotest/neotest-go", + }, + opts = { + adapters = { + ["neotest-go"] = { + -- Here we can set options for neotest-go, e.g. + -- args = { "-tags=integration" } + }, + }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/java.lua b/lua/lazyvim/plugins/extras/lang/java.lua new file mode 100644 index 0000000..f7e7ca5 --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/java.lua @@ -0,0 +1,227 @@ +local Util = require("lazyvim.util") + +-- This is the same as in lspconfig.server_configurations.jdtls, but avoids +-- needing to require that when this module loads. +local java_filetypes = { "java" } + +-- Utility function to extend or override a config table, similar to the way +-- that Plugin.opts works. +---@param config table +---@param custom function | table | nil +local function extend_or_override(config, custom, ...) + if type(custom) == "function" then + config = custom(config, ...) or config + elseif custom then + config = vim.tbl_deep_extend("force", config, custom) --[[@as table]] + end + return config +end + +return { + -- Add java to treesitter. + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + vim.list_extend(opts.ensure_installed, { "java" }) + end, + }, + + -- Ensure java debugger and test packages are installed. + { + "mfussenegger/nvim-dap", + optional = true, + dependencies = { + { + "williamboman/mason.nvim", + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + vim.list_extend(opts.ensure_installed, { "java-test", "java-debug-adapter" }) + end, + }, + }, + }, + + -- Configure nvim-lspconfig to install the server automatically via mason, but + -- defer actually starting it to our configuration of nvim-jtdls below. + { + "neovim/nvim-lspconfig", + opts = { + -- make sure mason installs the server + servers = { + jdtls = {}, + }, + setup = { + jdtls = function() + return true -- avoid duplicate servers + end, + }, + }, + }, + + -- Set up nvim-jdtls to attach to java files. + { + "mfussenegger/nvim-jdtls", + dependencies = { "folke/which-key.nvim" }, + ft = java_filetypes, + opts = function() + return { + -- How to find the root dir for a given filename. The default comes from + -- lspconfig which provides a function specifically for java projects. + root_dir = require("lspconfig.server_configurations.jdtls").default_config.root_dir, + + -- How to find the project name for a given root dir. + project_name = function(root_dir) + return root_dir and vim.fs.basename(root_dir) + end, + + -- Where are the config and workspace dirs for a project? + jdtls_config_dir = function(project_name) + return vim.fn.stdpath("cache") .. "/jdtls/" .. project_name .. "/config" + end, + jdtls_workspace_dir = function(project_name) + return vim.fn.stdpath("cache") .. "/jdtls/" .. project_name .. "/workspace" + end, + + -- How to run jdtls. This can be overridden to a full java command-line + -- if the Python wrapper script doesn't suffice. + cmd = { vim.fn.exepath("jdtls") }, + full_cmd = function(opts) + local fname = vim.api.nvim_buf_get_name(0) + local root_dir = opts.root_dir(fname) + local project_name = opts.project_name(root_dir) + local cmd = vim.deepcopy(opts.cmd) + if project_name then + vim.list_extend(cmd, { + "-configuration", + opts.jdtls_config_dir(project_name), + "-data", + opts.jdtls_workspace_dir(project_name), + }) + end + return cmd + end, + + -- These depend on nvim-dap, but can additionally be disabled by setting false here. + dap = { hotcodereplace = "auto", config_overrides = {} }, + test = true, + } + end, + config = function() + local opts = Util.opts("nvim-jdtls") or {} + + -- Find the extra bundles that should be passed on the jdtls command-line + -- if nvim-dap is enabled with java debug/test. + local mason_registry = require("mason-registry") + local bundles = {} ---@type string[] + if opts.dap and Util.has("nvim-dap") and mason_registry.is_installed("java-debug-adapter") then + local java_dbg_pkg = mason_registry.get_package("java-debug-adapter") + local java_dbg_path = java_dbg_pkg:get_install_path() + local jar_patterns = { + java_dbg_path .. "/extension/server/com.microsoft.java.debug.plugin-*.jar", + } + -- java-test also depends on java-debug-adapter. + if opts.test and mason_registry.is_installed("java-test") then + local java_test_pkg = mason_registry.get_package("java-test") + local java_test_path = java_test_pkg:get_install_path() + vim.list_extend(jar_patterns, { + java_test_path .. "/extension/server/*.jar", + }) + end + for _, jar_pattern in ipairs(jar_patterns) do + for _, bundle in ipairs(vim.split(vim.fn.glob(jar_pattern), "\n")) do + table.insert(bundles, bundle) + end + end + end + + local function attach_jdtls() + local fname = vim.api.nvim_buf_get_name(0) + + -- Configuration can be augmented and overridden by opts.jdtls + local config = extend_or_override({ + cmd = opts.full_cmd(opts), + root_dir = opts.root_dir(fname), + init_options = { + bundles = bundles, + }, + -- enable CMP capabilities + capabilities = require("cmp_nvim_lsp").default_capabilities(), + }, opts.jdtls) + + -- Existing server will be reused if the root_dir matches. + require("jdtls").start_or_attach(config) + -- not need to require("jdtls.setup").add_commands(), start automatically adds commands + end + + -- Attach the jdtls for each java buffer. HOWEVER, this plugin loads + -- depending on filetype, so this autocmd doesn't run for the first file. + -- For that, we call directly below. + vim.api.nvim_create_autocmd("FileType", { + pattern = java_filetypes, + callback = attach_jdtls, + }) + + -- Setup keymap and dap after the lsp is fully attached. + -- https://github.com/mfussenegger/nvim-jdtls#nvim-dap-configuration + -- https://neovim.io/doc/user/lsp.html#LspAttach + vim.api.nvim_create_autocmd("LspAttach", { + callback = function(args) + local client = vim.lsp.get_client_by_id(args.data.client_id) + if client and client.name == "jdtls" then + local wk = require("which-key") + wk.register({ + ["cx"] = { name = "+extract" }, + ["cxv"] = { require("jdtls").extract_variable_all, "Extract Variable" }, + ["cxc"] = { require("jdtls").extract_constant, "Extract Constant" }, + ["gs"] = { require("jdtls").super_implementation, "Goto Super" }, + ["gS"] = { require("jdtls.tests").goto_subjects, "Goto Subjects" }, + ["co"] = { require("jdtls").organize_imports, "Organize Imports" }, + }, { mode = "n", buffer = args.buf }) + wk.register({ + ["c"] = { name = "+code" }, + ["cx"] = { name = "+extract" }, + ["cxm"] = { + [[lua require('jdtls').extract_method(true)]], + "Extract Method", + }, + ["cxv"] = { + [[lua require('jdtls').extract_variable_all(true)]], + "Extract Variable", + }, + ["cxc"] = { + [[lua require('jdtls').extract_constant(true)]], + "Extract Constant", + }, + }, { mode = "v", buffer = args.buf }) + + if opts.dap and Util.has("nvim-dap") and mason_registry.is_installed("java-debug-adapter") then + -- custom init for Java debugger + require("jdtls").setup_dap(opts.dap) + require("jdtls.dap").setup_dap_main_class_configs() + + -- Java Test require Java debugger to work + if opts.test and mason_registry.is_installed("java-test") then + -- custom keymaps for Java test runner (not yet compatible with neotest) + wk.register({ + ["t"] = { name = "+test" }, + ["tt"] = { require("jdtls.dap").test_class, "Run All Test" }, + ["tr"] = { require("jdtls.dap").test_nearest_method, "Run Nearest Test" }, + ["tT"] = { require("jdtls.dap").pick_test, "Run Test" }, + }, { mode = "n", buffer = args.buf }) + end + end + + -- User can set additional keymaps in opts.on_attach + if opts.on_attach then + opts.on_attach(args) + end + end + end, + }) + + -- Avoid race condition by calling attach the first time, since the autocmd won't fire. + attach_jdtls() + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/json.lua b/lua/lazyvim/plugins/extras/lang/json.lua new file mode 100644 index 0000000..ff41ba4 --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/json.lua @@ -0,0 +1,44 @@ +return { + + -- add json to treesitter + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "json", "json5", "jsonc" }) + end + end, + }, + + -- yaml schema support + { + "b0o/SchemaStore.nvim", + lazy = true, + version = false, -- last release is way too old + }, + + -- correctly setup lspconfig + { + "neovim/nvim-lspconfig", + opts = { + -- make sure mason installs the server + servers = { + jsonls = { + -- lazy-load schemastore when needed + on_new_config = function(new_config) + new_config.settings.json.schemas = new_config.settings.json.schemas or {} + vim.list_extend(new_config.settings.json.schemas, require("schemastore").json.schemas()) + end, + settings = { + json = { + format = { + enable = true, + }, + validate = { enable = true }, + }, + }, + }, + }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/markdown.lua b/lua/lazyvim/plugins/extras/lang/markdown.lua new file mode 100644 index 0000000..5aa9dc3 --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/markdown.lua @@ -0,0 +1,90 @@ +return { + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "markdown", "markdown_inline" }) + end + end, + }, + { + "williamboman/mason.nvim", + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + vim.list_extend(opts.ensure_installed, { "markdownlint", "marksman" }) + end, + }, + { + "nvimtools/none-ls.nvim", + optional = true, + opts = function(_, opts) + local nls = require("null-ls") + opts.sources = vim.list_extend(opts.sources or {}, { + nls.builtins.diagnostics.markdownlint, + }) + end, + }, + { + "mfussenegger/nvim-lint", + optional = true, + opts = { + linters_by_ft = { + markdown = { "markdownlint" }, + }, + }, + }, + { + "neovim/nvim-lspconfig", + opts = { + servers = { + marksman = {}, + }, + }, + }, + + -- Markdown preview + { + "iamcco/markdown-preview.nvim", + cmd = { "MarkdownPreviewToggle", "MarkdownPreview", "MarkdownPreviewStop" }, + build = function() + vim.fn["mkdp#util#install"]() + end, + keys = { + { + "cp", + ft = "markdown", + "MarkdownPreviewToggle", + desc = "Markdown Preview", + }, + }, + config = function() + vim.cmd([[do FileType]]) + end, + }, + + { + "lukas-reineke/headlines.nvim", + opts = function() + local opts = {} + for _, ft in ipairs({ "markdown", "norg", "rmd", "org" }) do + opts[ft] = { + headline_highlights = {}, + } + for i = 1, 6 do + local hl = "Headline" .. i + vim.api.nvim_set_hl(0, hl, { link = "Headline", default = true }) + table.insert(opts[ft].headline_highlights, hl) + end + end + return opts + end, + ft = { "markdown", "norg", "rmd", "org" }, + config = function(_, opts) + -- PERF: schedule to prevent headlines slowing down opening a file + vim.schedule(function() + require("headlines").setup(opts) + require("headlines").refresh() + end) + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/omnisharp.lua b/lua/lazyvim/plugins/extras/lang/omnisharp.lua new file mode 100644 index 0000000..31162e0 --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/omnisharp.lua @@ -0,0 +1,68 @@ +return { + { "Hoffs/omnisharp-extended-lsp.nvim", lazy = true }, + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "c_sharp" }) + end + end, + }, + { + "nvimtools/none-ls.nvim", + optional = true, + opts = function(_, opts) + local nls = require("null-ls") + opts.sources = opts.sources or {} + table.insert(opts.sources, nls.builtins.formatting.csharpier) + end, + }, + { + "stevearc/conform.nvim", + optional = true, + opts = { + formatters_by_ft = { + cs = { "csharpier" }, + }, + formatters = { + csharpier = { + command = "dotnet-csharpier", + args = { "--write-stdout" }, + }, + }, + }, + }, + { + "williamboman/mason.nvim", + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + table.insert(opts.ensure_installed, "csharpier") + end, + }, + { + "neovim/nvim-lspconfig", + opts = { + servers = { + omnisharp = { + handlers = { + ["textDocument/definition"] = function(...) + return require("omnisharp_extended").handler(...) + end, + }, + keys = { + { + "gd", + function() + require("omnisharp_extended").telescope_lsp_definitions() + end, + desc = "Goto Definition", + }, + }, + enable_roslyn_analyzers = true, + organize_imports_on_format = true, + enable_import_completion = true, + }, + }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/python-semshi.lua b/lua/lazyvim/plugins/extras/lang/python-semshi.lua new file mode 100644 index 0000000..e4e32c1 --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/python-semshi.lua @@ -0,0 +1,32 @@ +return { + -- "numiras/semshi", + "wookayin/semshi", -- use a maintained fork + ft = "python", + build = ":UpdateRemotePlugins", + init = function() + -- Disabled these features better provided by LSP or other more general plugins + vim.g["semshi#error_sign"] = false + vim.g["semshi#simplify_markup"] = false + vim.g["semshi#mark_selected_nodes"] = false + vim.g["semshi#update_delay_factor"] = 0.001 + + -- This autocmd must be defined in init to take effect + vim.api.nvim_create_autocmd({ "VimEnter", "ColorScheme" }, { + group = vim.api.nvim_create_augroup("SemanticHighlight", {}), + callback = function() + -- Only add style, inherit or link to the LSP's colors + vim.cmd([[ + highlight! semshiGlobal gui=italic + highlight! link semshiImported @none + highlight! link semshiParameter @lsp.type.parameter + highlight! link semshiParameterUnused DiagnosticUnnecessary + highlight! link semshiBuiltin @function.builtin + highlight! link semshiAttribute @field + highlight! link semshiSelf @lsp.type.selfKeyword + highlight! link semshiUnresolved @lsp.type.unresolvedReference + highlight! link semshiFree @none + ]]) + end, + }) + end, +} diff --git a/lua/lazyvim/plugins/extras/lang/python.lua b/lua/lazyvim/plugins/extras/lang/python.lua new file mode 100644 index 0000000..cc9bc52 --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/python.lua @@ -0,0 +1,95 @@ +return { + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "ninja", "python", "rst", "toml" }) + end + end, + }, + { + "neovim/nvim-lspconfig", + opts = { + servers = { + pyright = {}, + ruff_lsp = { + keys = { + { + "co", + function() + vim.lsp.buf.code_action({ + apply = true, + context = { + only = { "source.organizeImports" }, + diagnostics = {}, + }, + }) + end, + desc = "Organize Imports", + }, + }, + }, + }, + setup = { + ruff_lsp = function() + require("lazyvim.util").lsp.on_attach(function(client, _) + if client.name == "ruff_lsp" then + -- Disable hover in favor of Pyright + client.server_capabilities.hoverProvider = false + end + end) + end, + }, + }, + }, + { + "nvim-neotest/neotest", + optional = true, + dependencies = { + "nvim-neotest/neotest-python", + }, + opts = { + adapters = { + ["neotest-python"] = { + -- Here you can specify the settings for the adapter, i.e. + -- runner = "pytest", + -- python = ".venv/bin/python", + }, + }, + }, + }, + { + "mfussenegger/nvim-dap", + optional = true, + dependencies = { + "mfussenegger/nvim-dap-python", + -- stylua: ignore + keys = { + { "dPt", function() require('dap-python').test_method() end, desc = "Debug Method", ft = "python" }, + { "dPc", function() require('dap-python').test_class() end, desc = "Debug Class", ft = "python" }, + }, + config = function() + local path = require("mason-registry").get_package("debugpy"):get_install_path() + require("dap-python").setup(path .. "/venv/bin/python") + end, + }, + }, + { + "linux-cultist/venv-selector.nvim", + cmd = "VenvSelect", + opts = function(_, opts) + if require("lazyvim.util").has("nvim-dap-python") then + opts.dap_enabled = true + end + return vim.tbl_deep_extend("force", opts, { + name = { + "venv", + ".venv", + "env", + ".env", + }, + }) + end, + keys = { { "cv", ":VenvSelect", desc = "Select VirtualEnv" } }, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/ruby.lua b/lua/lazyvim/plugins/extras/lang/ruby.lua new file mode 100644 index 0000000..a789469 --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/ruby.lua @@ -0,0 +1,57 @@ +return { + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + vim.list_extend(opts.ensure_installed, { + "ruby", + }) + end, + }, + { + "williamboman/mason.nvim", + opts = function(_, opts) + vim.list_extend(opts.ensure_installed, { + "solargraph", + }) + end, + }, + { + "neovim/nvim-lspconfig", + opts = { + servers = { + solargraph = {}, + }, + }, + }, + { + "mfussenegger/nvim-dap", + optional = true, + dependencies = { + "suketa/nvim-dap-ruby", + config = function() + require("dap-ruby").setup() + end, + }, + }, + { + "nvim-neotest/neotest", + optional = true, + dependencies = { + "olimorris/neotest-rspec", + }, + opts = { + adapters = { + ["neotest-rspec"] = { + -- NOTE: By default neotest-rspec uses the system wide rspec gem instead of the one through bundler + -- rspec_cmd = function() + -- return vim.tbl_flatten({ + -- "bundle", + -- "exec", + -- "rspec", + -- }) + -- end, + }, + }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/rust.lua b/lua/lazyvim/plugins/extras/lang/rust.lua new file mode 100644 index 0000000..ece3825 --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/rust.lua @@ -0,0 +1,162 @@ +return { + + -- Extend auto completion + { + "hrsh7th/nvim-cmp", + dependencies = { + { + "Saecki/crates.nvim", + event = { "BufRead Cargo.toml" }, + opts = { + src = { + cmp = { enabled = true }, + }, + }, + }, + }, + ---@param opts cmp.ConfigSchema + opts = function(_, opts) + local cmp = require("cmp") + opts.sources = cmp.config.sources(vim.list_extend(opts.sources, { + { name = "crates" }, + })) + end, + }, + + -- Add Rust & related to treesitter + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "ron", "rust", "toml" }) + end + end, + }, + + -- Ensure Rust debugger is installed + { + "williamboman/mason.nvim", + optional = true, + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "codelldb" }) + end + end, + }, + + { + "simrat39/rust-tools.nvim", + lazy = true, + opts = function() + local ok, mason_registry = pcall(require, "mason-registry") + local adapter ---@type any + if ok then + -- rust tools configuration for debugging support + local codelldb = mason_registry.get_package("codelldb") + local extension_path = codelldb:get_install_path() .. "/extension/" + local codelldb_path = extension_path .. "adapter/codelldb" + local liblldb_path = "" + if vim.loop.os_uname().sysname:find("Windows") then + liblldb_path = extension_path .. "lldb\\bin\\liblldb.dll" + elseif vim.fn.has("mac") == 1 then + liblldb_path = extension_path .. "lldb/lib/liblldb.dylib" + else + liblldb_path = extension_path .. "lldb/lib/liblldb.so" + end + adapter = require("rust-tools.dap").get_codelldb_adapter(codelldb_path, liblldb_path) + end + return { + dap = { + adapter = adapter, + }, + tools = { + on_initialized = function() + vim.cmd([[ + augroup RustLSP + autocmd CursorHold *.rs silent! lua vim.lsp.buf.document_highlight() + autocmd CursorMoved,InsertEnter *.rs silent! lua vim.lsp.buf.clear_references() + autocmd BufEnter,CursorHold,InsertLeave *.rs silent! lua vim.lsp.codelens.refresh() + augroup END + ]]) + end, + }, + } + end, + config = function() end, + }, + + -- Correctly setup lspconfig for Rust 🚀 + { + "neovim/nvim-lspconfig", + opts = { + servers = { + -- Ensure mason installs the server + rust_analyzer = { + keys = { + { "cK", "RustHoverActions", desc = "Hover Actions (Rust)" }, + { "cR", "RustCodeAction", desc = "Code Action (Rust)" }, + { "dr", "RustDebuggables", desc = "Run Debuggables (Rust)" }, + }, + settings = { + ["rust-analyzer"] = { + cargo = { + allFeatures = true, + loadOutDirsFromCheck = true, + runBuildScripts = true, + }, + -- Add clippy lints for Rust. + checkOnSave = { + allFeatures = true, + command = "clippy", + extraArgs = { "--no-deps" }, + }, + procMacro = { + enable = true, + ignored = { + ["async-trait"] = { "async_trait" }, + ["napi-derive"] = { "napi" }, + ["async-recursion"] = { "async_recursion" }, + }, + }, + }, + }, + }, + taplo = { + keys = { + { + "cK", + function() + if vim.fn.expand("%:t") == "Cargo.toml" and require("crates").popup_available() then + require("crates").show_popup() + else + vim.lsp.buf.hover() + end + end, + desc = "Show Crate Documentation", + }, + }, + }, + }, + setup = { + rust_analyzer = function(_, opts) + local rust_tools_opts = require("lazyvim.util").opts("rust-tools.nvim") + require("rust-tools").setup(vim.tbl_deep_extend("force", rust_tools_opts or {}, { server = opts })) + return true + end, + }, + }, + }, + + { + "nvim-neotest/neotest", + optional = true, + dependencies = { + "rouge8/neotest-rust", + }, + opts = { + adapters = { + ["neotest-rust"] = {}, + }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/tailwind.lua b/lua/lazyvim/plugins/extras/lang/tailwind.lua new file mode 100644 index 0000000..795449e --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/tailwind.lua @@ -0,0 +1,49 @@ +return { + { + "neovim/nvim-lspconfig", + opts = { + servers = { + tailwindcss = { + -- exclude a filetype from the default_config + filetypes_exclude = { "markdown" }, + -- add additional filetypes to the default_config + filetypes_include = {}, + -- to fully override the default_config, change the below + -- filetypes = {} + }, + }, + setup = { + tailwindcss = function(_, opts) + local tw = require("lspconfig.server_configurations.tailwindcss") + opts.filetypes = opts.filetypes or {} + + -- Add default filetypes + vim.list_extend(opts.filetypes, tw.default_config.filetypes) + + -- Remove excluded filetypes + --- @param ft string + opts.filetypes = vim.tbl_filter(function(ft) + return not vim.tbl_contains(opts.filetypes_exclude or {}, ft) + end, opts.filetypes) + + -- Add additional filetypes + vim.list_extend(opts.filetypes, opts.filetypes_include or {}) + end, + }, + }, + }, + { + "hrsh7th/nvim-cmp", + dependencies = { + { "roobert/tailwindcss-colorizer-cmp.nvim", config = true }, + }, + opts = function(_, opts) + -- original LazyVim kind icon formatter + local format_kinds = opts.formatting.format + opts.formatting.format = function(entry, item) + format_kinds(entry, item) -- add icons + return require("tailwindcss-colorizer-cmp").formatter(entry, item) + end + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/terraform.lua b/lua/lazyvim/plugins/extras/lang/terraform.lua new file mode 100644 index 0000000..ea7bd5d --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/terraform.lua @@ -0,0 +1,59 @@ +vim.api.nvim_create_autocmd("FileType", { + pattern = { "hcl", "terraform" }, + desc = "terraform/hcl commentstring configuration", + command = "setlocal commentstring=#\\ %s", +}) + +return { + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { + "terraform", + "hcl", + }) + end + end, + }, + { + "neovim/nvim-lspconfig", + opts = { + servers = { + terraformls = {}, + }, + }, + }, + { + "nvimtools/none-ls.nvim", + optional = true, + opts = function(_, opts) + local null_ls = require("null-ls") + opts.sources = vim.list_extend(opts.sources or {}, { + null_ls.builtins.formatting.terraform_fmt, + null_ls.builtins.diagnostics.terraform_validate, + }) + end, + }, + { + "mfussenegger/nvim-lint", + optional = true, + opts = { + linters_by_ft = { + terraform = { "terraform_validate" }, + tf = { "terraform_validate" }, + }, + }, + }, + { + "stevearc/conform.nvim", + optional = true, + opts = { + formatters_by_ft = { + terraform = { "terraform_fmt" }, + tf = { "terraform_fmt" }, + ["terraform-vars"] = { "terraform_fmt" }, + }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/tex.lua b/lua/lazyvim/plugins/extras/lang/tex.lua new file mode 100644 index 0000000..e8d4fef --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/tex.lua @@ -0,0 +1,58 @@ +return { + { + "folke/which-key.nvim", + optional = true, + opts = { + defaults = { + ["l"] = { name = "+vimtex" }, + }, + }, + }, + + -- Add BibTeX/LaTeX to treesitter + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "bibtex", "latex" }) + end + if type(opts.highlight.disable) == "table" then + vim.list_extend(opts.highlight.disable, { "latex" }) + else + opts.highlight.disable = { "latex" } + end + end, + }, + + { + "lervag/vimtex", + lazy = false, -- lazy-loading will disable inverse search + config = function() + vim.api.nvim_create_autocmd({ "FileType" }, { + group = vim.api.nvim_create_augroup("lazyvim_vimtex_conceal", { clear = true }), + pattern = { "bib", "tex" }, + callback = function() + vim.wo.conceallevel = 2 + end, + }) + + vim.g.vimtex_mappings_disable = { ["n"] = { "K" } } -- disable `K` as it conflicts with LSP hover + vim.g.vimtex_quickfix_method = vim.fn.executable("pplatex") == 1 and "pplatex" or "latexlog" + end, + }, + + -- Correctly setup lspconfig for LaTeX 🚀 + { + "neovim/nvim-lspconfig", + optional = true, + opts = { + servers = { + texlab = { + keys = { + { "K", "(vimtex-doc-package)", desc = "Vimtex Docs", silent = true }, + }, + }, + }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/typescript.lua b/lua/lazyvim/plugins/extras/lang/typescript.lua new file mode 100644 index 0000000..4f53ae8 --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/typescript.lua @@ -0,0 +1,124 @@ +return { + + -- add typescript to treesitter + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "typescript", "tsx" }) + end + end, + }, + + -- correctly setup lspconfig + { + "neovim/nvim-lspconfig", + opts = { + -- make sure mason installs the server + servers = { + ---@type lspconfig.options.tsserver + tsserver = { + keys = { + { + "co", + function() + vim.lsp.buf.code_action({ + apply = true, + context = { + only = { "source.organizeImports.ts" }, + diagnostics = {}, + }, + }) + end, + desc = "Organize Imports", + }, + { + "cR", + function() + vim.lsp.buf.code_action({ + apply = true, + context = { + only = { "source.removeUnused.ts" }, + diagnostics = {}, + }, + }) + end, + desc = "Remove Unused Imports", + }, + }, + settings = { + typescript = { + format = { + indentSize = vim.o.shiftwidth, + convertTabsToSpaces = vim.o.expandtab, + tabSize = vim.o.tabstop, + }, + }, + javascript = { + format = { + indentSize = vim.o.shiftwidth, + convertTabsToSpaces = vim.o.expandtab, + tabSize = vim.o.tabstop, + }, + }, + completions = { + completeFunctionCalls = true, + }, + }, + }, + }, + }, + }, + { + "mfussenegger/nvim-dap", + optional = true, + dependencies = { + { + "williamboman/mason.nvim", + opts = function(_, opts) + opts.ensure_installed = opts.ensure_installed or {} + table.insert(opts.ensure_installed, "js-debug-adapter") + end, + }, + }, + opts = function() + local dap = require("dap") + if not dap.adapters["pwa-node"] then + require("dap").adapters["pwa-node"] = { + type = "server", + host = "localhost", + port = "${port}", + executable = { + command = "node", + -- 💀 Make sure to update this path to point to your installation + args = { + require("mason-registry").get_package("js-debug-adapter"):get_install_path() + .. "/js-debug/src/dapDebugServer.js", + "${port}", + }, + }, + } + end + for _, language in ipairs({ "typescript", "javascript", "typescriptreact", "javascriptreact" }) do + if not dap.configurations[language] then + dap.configurations[language] = { + { + type = "pwa-node", + request = "launch", + name = "Launch file", + program = "${file}", + cwd = "${workspaceFolder}", + }, + { + type = "pwa-node", + request = "attach", + name = "Attach", + processId = require("dap.utils").pick_process, + cwd = "${workspaceFolder}", + }, + } + end + end + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/lang/yaml.lua b/lua/lazyvim/plugins/extras/lang/yaml.lua new file mode 100644 index 0000000..5afe1b8 --- /dev/null +++ b/lua/lazyvim/plugins/extras/lang/yaml.lua @@ -0,0 +1,77 @@ +return { + + -- add yaml specific modules to treesitter + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + if type(opts.ensure_installed) == "table" then + vim.list_extend(opts.ensure_installed, { "yaml" }) + end + end, + }, + + -- yaml schema support + { + "b0o/SchemaStore.nvim", + lazy = true, + version = false, -- last release is way too old + }, + + -- correctly setup lspconfig + { + "neovim/nvim-lspconfig", + opts = { + -- make sure mason installs the server + servers = { + yamlls = { + -- Have to add this for yamlls to understand that we support line folding + capabilities = { + textDocument = { + foldingRange = { + dynamicRegistration = false, + lineFoldingOnly = true, + }, + }, + }, + -- lazy-load schemastore when needed + on_new_config = function(new_config) + new_config.settings.yaml.schemas = vim.tbl_deep_extend( + "force", + new_config.settings.yaml.schemas or {}, + require("schemastore").yaml.schemas() + ) + end, + settings = { + redhat = { telemetry = { enabled = false } }, + yaml = { + keyOrdering = false, + format = { + enable = true, + }, + validate = true, + schemaStore = { + -- Must disable built-in schemaStore support to use + -- schemas from SchemaStore.nvim plugin + enable = false, + -- Avoid TypeError: Cannot read properties of undefined (reading 'length') + url = "", + }, + }, + }, + }, + }, + setup = { + yamlls = function() + -- Neovim < 0.10 does not have dynamic registration for formatting + if vim.fn.has("nvim-0.10") == 0 then + require("lazyvim.util").lsp.on_attach(function(client, _) + if client.name == "yamlls" then + client.server_capabilities.documentFormattingProvider = true + end + end) + end + end, + }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/linting/eslint.lua b/lua/lazyvim/plugins/extras/linting/eslint.lua new file mode 100644 index 0000000..9537303 --- /dev/null +++ b/lua/lazyvim/plugins/extras/linting/eslint.lua @@ -0,0 +1,52 @@ +return { + { + "neovim/nvim-lspconfig", + -- other settings removed for brevity + opts = { + ---@type lspconfig.options + servers = { + eslint = { + settings = { + -- helps eslint find the eslintrc when it's placed in a subfolder instead of the cwd root + workingDirectory = { mode = "auto" }, + }, + }, + }, + setup = { + eslint = function() + local function get_client(buf) + return require("lazyvim.util").lsp.get_clients({ name = "eslint", bufnr = buf })[1] + end + + local formatter = require("lazyvim.util").lsp.formatter({ + name = "eslint: lsp", + primary = false, + priority = 200, + filter = "eslint", + }) + + -- Use EslintFixAll on Neovim < 0.10.0 + if not pcall(require, "vim.lsp._dynamic") then + formatter.name = "eslint: EslintFixAll" + formatter.sources = function(buf) + local client = get_client(buf) + return client and { "eslint" } or {} + end + formatter.format = function(buf) + local client = get_client(buf) + if client then + local diag = vim.diagnostic.get(buf, { namespace = vim.lsp.diagnostic.get_namespace(client.id) }) + if #diag > 0 then + vim.cmd("EslintFixAll") + end + end + end + end + + -- register the formatter with LazyVim + require("lazyvim.util").format.register(formatter) + end, + }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/lsp/none-ls.lua b/lua/lazyvim/plugins/extras/lsp/none-ls.lua new file mode 100644 index 0000000..aa907b8 --- /dev/null +++ b/lua/lazyvim/plugins/extras/lsp/none-ls.lua @@ -0,0 +1,45 @@ +local Util = require("lazyvim.util") + +return { + -- none-ls + { + "nvimtools/none-ls.nvim", + event = "LazyFile", + dependencies = { "mason.nvim" }, + init = function() + Util.on_very_lazy(function() + -- register the formatter with LazyVim + require("lazyvim.util").format.register({ + name = "none-ls.nvim", + priority = 200, -- set higher than conform, the builtin formatter + primary = true, + format = function(buf) + return Util.lsp.format({ + bufnr = buf, + filter = function(client) + return client.name == "null-ls" + end, + }) + end, + sources = function(buf) + local ret = require("null-ls.sources").get_available(vim.bo[buf].filetype, "NULL_LS_FORMATTING") or {} + return vim.tbl_map(function(source) + return source.name + end, ret) + end, + }) + end) + end, + opts = function(_, opts) + local nls = require("null-ls") + opts.root_dir = opts.root_dir + or require("null-ls.utils").root_pattern(".null-ls-root", ".neoconf.json", "Makefile", ".git") + opts.sources = vim.list_extend(opts.sources or {}, { + nls.builtins.formatting.fish_indent, + nls.builtins.diagnostics.fish, + nls.builtins.formatting.stylua, + nls.builtins.formatting.shfmt, + }) + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/test/core.lua b/lua/lazyvim/plugins/extras/test/core.lua new file mode 100644 index 0000000..321955a --- /dev/null +++ b/lua/lazyvim/plugins/extras/test/core.lua @@ -0,0 +1,127 @@ +return { + { + "folke/which-key.nvim", + optional = true, + opts = { + defaults = { + ["t"] = { name = "+test" }, + }, + }, + }, + { + "nvim-neotest/neotest", + opts = { + -- Can be a list of adapters like what neotest expects, + -- or a list of adapter names, + -- or a table of adapter names, mapped to adapter configs. + -- The adapter will then be automatically loaded with the config. + adapters = {}, + -- Example for loading neotest-go with a custom config + -- adapters = { + -- ["neotest-go"] = { + -- args = { "-tags=integration" }, + -- }, + -- }, + status = { virtual_text = true }, + output = { open_on_run = true }, + quickfix = { + open = function() + if require("lazyvim.util").has("trouble.nvim") then + require("trouble").open({ mode = "quickfix", focus = false }) + else + vim.cmd("copen") + end + end, + }, + }, + config = function(_, opts) + local neotest_ns = vim.api.nvim_create_namespace("neotest") + vim.diagnostic.config({ + virtual_text = { + format = function(diagnostic) + -- Replace newline and tab characters with space for more compact diagnostics + local message = diagnostic.message:gsub("\n", " "):gsub("\t", " "):gsub("%s+", " "):gsub("^%s+", "") + return message + end, + }, + }, neotest_ns) + + if require("lazyvim.util").has("trouble.nvim") then + opts.consumers = opts.consumers or {} + -- Refresh and auto close trouble after running tests + ---@type neotest.Consumer + opts.consumers.trouble = function(client) + client.listeners.results = function(adapter_id, results, partial) + if partial then + return + end + local tree = assert(client:get_position(nil, { adapter = adapter_id })) + + local failed = 0 + for pos_id, result in pairs(results) do + if result.status == "failed" and tree:get_key(pos_id) then + failed = failed + 1 + end + end + vim.schedule(function() + local trouble = require("trouble") + if trouble.is_open() then + trouble.refresh() + if failed == 0 then + trouble.close() + end + end + end) + return {} + end + end + end + + if opts.adapters then + local adapters = {} + for name, config in pairs(opts.adapters or {}) do + if type(name) == "number" then + if type(config) == "string" then + config = require(config) + end + adapters[#adapters + 1] = config + elseif config ~= false then + local adapter = require(name) + if type(config) == "table" and not vim.tbl_isempty(config) then + local meta = getmetatable(adapter) + if adapter.setup then + adapter.setup(config) + elseif meta and meta.__call then + adapter(config) + else + error("Adapter " .. name .. " does not support setup") + end + end + adapters[#adapters + 1] = adapter + end + end + opts.adapters = adapters + end + + require("neotest").setup(opts) + end, + -- stylua: ignore + keys = { + { "tt", function() require("neotest").run.run(vim.fn.expand("%")) end, desc = "Run File" }, + { "tT", function() require("neotest").run.run(vim.loop.cwd()) end, desc = "Run All Test Files" }, + { "tr", function() require("neotest").run.run() end, desc = "Run Nearest" }, + { "ts", function() require("neotest").summary.toggle() end, desc = "Toggle Summary" }, + { "to", function() require("neotest").output.open({ enter = true, auto_close = true }) end, desc = "Show Output" }, + { "tO", function() require("neotest").output_panel.toggle() end, desc = "Toggle Output Panel" }, + { "tS", function() require("neotest").run.stop() end, desc = "Stop" }, + }, + }, + { + "mfussenegger/nvim-dap", + optional = true, + -- stylua: ignore + keys = { + { "td", function() require("neotest").run.run({strategy = "dap"}) end, desc = "Debug Nearest" }, + }, + }, +} diff --git a/lua/lazyvim/plugins/extras/ui/alpha.lua b/lua/lazyvim/plugins/extras/ui/alpha.lua new file mode 100644 index 0000000..8feee18 --- /dev/null +++ b/lua/lazyvim/plugins/extras/ui/alpha.lua @@ -0,0 +1,79 @@ +return { + + { "nvimdev/dashboard-nvim", enabled = false }, + { "echasnovski/mini.starter", enabled = false }, + -- Dashboard. This runs when neovim starts, and is what displays + -- the "LAZYVIM" banner. + { + "goolord/alpha-nvim", + event = "VimEnter", + enabled = true, + init = false, + opts = function() + local dashboard = require("alpha.themes.dashboard") + local logo = [[ + ██╗ █████╗ ███████╗██╗ ██╗██╗ ██╗██╗███╗ ███╗ Z + ██║ ██╔══██╗╚══███╔╝╚██╗ ██╔╝██║ ██║██║████╗ ████║ Z + ██║ ███████║ ███╔╝ ╚████╔╝ ██║ ██║██║██╔████╔██║ z + ██║ ██╔══██║ ███╔╝ ╚██╔╝ ╚██╗ ██╔╝██║██║╚██╔╝██║ z + ███████╗██║ ██║███████╗ ██║ ╚████╔╝ ██║██║ ╚═╝ ██║ + ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═══╝ ╚═╝╚═╝ ╚═╝ + ]] + + dashboard.section.header.val = vim.split(logo, "\n") + -- stylua: ignore + dashboard.section.buttons.val = { + dashboard.button("f", " " .. " Find file", " Telescope find_files "), + dashboard.button("n", " " .. " New file", " ene startinsert "), + dashboard.button("r", " " .. " Recent files", " Telescope oldfiles "), + dashboard.button("g", " " .. " Find text", " Telescope live_grep "), + dashboard.button("c", " " .. " Config", " lua require('lazyvim.util').telescope.config_files()() "), + dashboard.button("s", " " .. " Restore Session", [[ lua require("persistence").load() ]]), + dashboard.button("x", " " .. " Lazy Extras", " LazyExtras "), + dashboard.button("l", "󰒲 " .. " Lazy", " Lazy "), + dashboard.button("q", " " .. " Quit", " qa "), + } + for _, button in ipairs(dashboard.section.buttons.val) do + button.opts.hl = "AlphaButtons" + button.opts.hl_shortcut = "AlphaShortcut" + end + dashboard.section.header.opts.hl = "AlphaHeader" + dashboard.section.buttons.opts.hl = "AlphaButtons" + dashboard.section.footer.opts.hl = "AlphaFooter" + dashboard.opts.layout[1].val = 8 + return dashboard + end, + config = function(_, dashboard) + -- close Lazy and re-open when the dashboard is ready + if vim.o.filetype == "lazy" then + vim.cmd.close() + vim.api.nvim_create_autocmd("User", { + once = true, + pattern = "AlphaReady", + callback = function() + require("lazy").show() + end, + }) + end + + require("alpha").setup(dashboard.opts) + + vim.api.nvim_create_autocmd("User", { + once = true, + pattern = "LazyVimStarted", + callback = function() + local stats = require("lazy").stats() + local ms = (math.floor(stats.startuptime * 100 + 0.5) / 100) + dashboard.section.footer.val = "⚡ Neovim loaded " + .. stats.loaded + .. "/" + .. stats.count + .. " plugins in " + .. ms + .. "ms" + pcall(vim.cmd.AlphaRedraw) + end, + }) + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/ui/edgy.lua b/lua/lazyvim/plugins/extras/ui/edgy.lua new file mode 100644 index 0000000..e251382 --- /dev/null +++ b/lua/lazyvim/plugins/extras/ui/edgy.lua @@ -0,0 +1,174 @@ +return { + -- edgy + { + "folke/edgy.nvim", + event = "VeryLazy", + keys = { + { + "ue", + function() + require("edgy").toggle() + end, + desc = "Edgy Toggle", + }, + -- stylua: ignore + { "uE", function() require("edgy").select() end, desc = "Edgy Select Window" }, + }, + opts = function() + local opts = { + bottom = { + { + ft = "toggleterm", + size = { height = 0.4 }, + filter = function(buf, win) + return vim.api.nvim_win_get_config(win).relative == "" + end, + }, + { + ft = "noice", + size = { height = 0.4 }, + filter = function(buf, win) + return vim.api.nvim_win_get_config(win).relative == "" + end, + }, + { + ft = "lazyterm", + title = "LazyTerm", + size = { height = 0.4 }, + filter = function(buf) + return not vim.b[buf].lazyterm_cmd + end, + }, + "Trouble", + { + ft = "trouble", + filter = function(buf, win) + return vim.api.nvim_win_get_config(win).relative == "" + end, + }, + { ft = "qf", title = "QuickFix" }, + { + ft = "help", + size = { height = 20 }, + -- don't open help files in edgy that we're editing + filter = function(buf) + return vim.bo[buf].buftype == "help" + end, + }, + { title = "Spectre", ft = "spectre_panel", size = { height = 0.4 } }, + { title = "Neotest Output", ft = "neotest-output-panel", size = { height = 15 } }, + }, + left = { + { + title = "Neo-Tree", + ft = "neo-tree", + filter = function(buf) + return vim.b[buf].neo_tree_source == "filesystem" + end, + pinned = true, + open = function() + vim.api.nvim_input("e") + end, + size = { height = 0.5 }, + }, + { title = "Neotest Summary", ft = "neotest-summary" }, + { + title = "Neo-Tree Git", + ft = "neo-tree", + filter = function(buf) + return vim.b[buf].neo_tree_source == "git_status" + end, + pinned = true, + open = "Neotree position=right git_status", + }, + { + title = "Neo-Tree Buffers", + ft = "neo-tree", + filter = function(buf) + return vim.b[buf].neo_tree_source == "buffers" + end, + pinned = true, + open = "Neotree position=top buffers", + }, + "neo-tree", + }, + keys = { + -- increase width + [""] = function(win) + win:resize("width", 2) + end, + -- decrease width + [""] = function(win) + win:resize("width", -2) + end, + -- increase height + [""] = function(win) + win:resize("height", 2) + end, + -- decrease height + [""] = function(win) + win:resize("height", -2) + end, + }, + } + return opts + end, + }, + + -- use edgy's selection window + { + "nvim-telescope/telescope.nvim", + optional = true, + opts = { + defaults = { + get_selection_window = function() + require("edgy").goto_main() + return 0 + end, + }, + }, + }, + + -- prevent neo-tree from opening files in edgy windows + { + "nvim-neo-tree/neo-tree.nvim", + optional = true, + opts = function(_, opts) + opts.open_files_do_not_replace_types = opts.open_files_do_not_replace_types + or { "terminal", "Trouble", "qf", "Outline", "trouble" } + table.insert(opts.open_files_do_not_replace_types, "edgy") + end, + }, + + -- Fix bufferline offsets when edgy is loaded + { + "akinsho/bufferline.nvim", + optional = true, + opts = function() + local Offset = require("bufferline.offset") + if not Offset.edgy then + local get = Offset.get + Offset.get = function() + if package.loaded.edgy then + local layout = require("edgy.config").layout + local ret = { left = "", left_size = 0, right = "", right_size = 0 } + for _, pos in ipairs({ "left", "right" }) do + local sb = layout[pos] + if sb and #sb.wins > 0 then + local title = " Sidebar" .. string.rep(" ", sb.bounds.width - 8) + ret[pos] = "%#EdgyTitle#" .. title .. "%*" .. "%#WinSeparator#│%*" + ret[pos .. "_size"] = sb.bounds.width + end + end + ret.total_size = ret.left_size + ret.right_size + if ret.total_size > 0 then + return ret + end + end + return get() + end + Offset.edgy = true + end + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/ui/mini-animate.lua b/lua/lazyvim/plugins/extras/ui/mini-animate.lua new file mode 100644 index 0000000..dd2f8cd --- /dev/null +++ b/lua/lazyvim/plugins/extras/ui/mini-animate.lua @@ -0,0 +1,37 @@ +return { + -- animations + { + "echasnovski/mini.animate", + event = "VeryLazy", + opts = function() + -- don't use animate when scrolling with the mouse + local mouse_scrolled = false + for _, scroll in ipairs({ "Up", "Down" }) do + local key = "" + vim.keymap.set({ "", "i" }, key, function() + mouse_scrolled = true + return key + end, { expr = true }) + end + + local animate = require("mini.animate") + return { + resize = { + timing = animate.gen_timing.linear({ duration = 100, unit = "total" }), + }, + scroll = { + timing = animate.gen_timing.linear({ duration = 150, unit = "total" }), + subscroll = animate.gen_subscroll.equal({ + predicate = function(total_scroll) + if mouse_scrolled then + mouse_scrolled = false + return false + end + return total_scroll > 1 + end, + }), + }, + } + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/ui/mini-starter.lua b/lua/lazyvim/plugins/extras/ui/mini-starter.lua new file mode 100644 index 0000000..5f5ec9c --- /dev/null +++ b/lua/lazyvim/plugins/extras/ui/mini-starter.lua @@ -0,0 +1,76 @@ +-- start screen +return { + -- disable alpha + { "goolord/alpha-nvim", enabled = false }, + { "nvimdev/dashboard-nvim", enabled = false }, + + -- enable mini.starter + { + "echasnovski/mini.starter", + version = false, -- wait till new 0.7.0 release to put it back on semver + event = "VimEnter", + opts = function() + local logo = table.concat({ + " ██╗ █████╗ ███████╗██╗ ██╗██╗ ██╗██╗███╗ ███╗ Z", + " ██║ ██╔══██╗╚══███╔╝╚██╗ ██╔╝██║ ██║██║████╗ ████║ Z ", + " ██║ ███████║ ███╔╝ ╚████╔╝ ██║ ██║██║██╔████╔██║ z ", + " ██║ ██╔══██║ ███╔╝ ╚██╔╝ ╚██╗ ██╔╝██║██║╚██╔╝██║ z ", + " ███████╗██║ ██║███████╗ ██║ ╚████╔╝ ██║██║ ╚═╝ ██║ ", + " ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═══╝ ╚═╝╚═╝ ╚═╝ ", + }, "\n") + local pad = string.rep(" ", 22) + local new_section = function(name, action, section) + return { name = name, action = action, section = pad .. section } + end + + local starter = require("mini.starter") + --stylua: ignore + local config = { + evaluate_single = true, + header = logo, + items = { + new_section("Find file", "Telescope find_files", "Telescope"), + new_section("Recent files", "Telescope oldfiles", "Telescope"), + new_section("Grep text", "Telescope live_grep", "Telescope"), + new_section("Config", "lua require('lazyvim.util').telescope.config_files()()", "Config"), + new_section("Extras", "LazyExtras", "Config"), + new_section("Lazy", "Lazy", "Config"), + new_section("New file", "ene | startinsert", "Built-in"), + new_section("Quit", "qa", "Built-in"), + new_section("Session restore", [[lua require("persistence").load()]], "Session"), + }, + content_hooks = { + starter.gen_hook.adding_bullet(pad .. "░ ", false), + starter.gen_hook.aligning("center", "center"), + }, + } + return config + end, + config = function(_, config) + -- close Lazy and re-open when starter is ready + if vim.o.filetype == "lazy" then + vim.cmd.close() + vim.api.nvim_create_autocmd("User", { + pattern = "MiniStarterOpened", + callback = function() + require("lazy").show() + end, + }) + end + + local starter = require("mini.starter") + starter.setup(config) + + vim.api.nvim_create_autocmd("User", { + pattern = "LazyVimStarted", + callback = function() + local stats = require("lazy").stats() + local ms = (math.floor(stats.startuptime * 100 + 0.5) / 100) + local pad_footer = string.rep(" ", 8) + starter.config.footer = pad_footer .. "⚡ Neovim loaded " .. stats.count .. " plugins in " .. ms .. "ms" + pcall(starter.refresh) + end, + }) + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/util/dot.lua b/lua/lazyvim/plugins/extras/util/dot.lua new file mode 100644 index 0000000..c25e0f6 --- /dev/null +++ b/lua/lazyvim/plugins/extras/util/dot.lua @@ -0,0 +1,65 @@ +---@type string +local xdg_config = vim.env.XDG_CONFIG_HOME or vim.env.HOME .. "/.config" + +---@param path string +local function have(path) + return vim.loop.fs_stat(xdg_config .. "/" .. path) ~= nil +end + +return { + + -- Add Hyprland Parser + { + "luckasRanarison/tree-sitter-hypr", + enabled = function() + return have("hypr") + end, + event = "BufRead */hypr/*.conf", + build = ":TSUpdate hypr", + config = function() + -- Fix ft detection for hyprland + vim.filetype.add({ + pattern = { [".*/hypr/.*%.conf"] = "hypr" }, + }) + require("nvim-treesitter.parsers").get_parser_configs().hypr = { + install_info = { + url = "https://github.com/luckasRanarison/tree-sitter-hypr", + files = { "src/parser.c" }, + branch = "master", + }, + filetype = "hypr", + } + end, + }, + + -- add some stuff to treesitter + { + "nvim-treesitter/nvim-treesitter", + opts = function(_, opts) + local function add(lang) + if type(opts.ensure_installed) == "table" then + table.insert(opts.ensure_installed, lang) + end + end + + vim.filetype.add({ + extension = { rasi = "rasi" }, + pattern = { + [".*/waybar/config"] = "jsonc", + [".*/mako/config"] = "dosini", + [".*/kitty/*.conf"] = "bash", + }, + }) + + add("git_config") + + if have("fish") then + add("fish") + end + + if have("rofi") or have("wofi") then + add("rasi") + end + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/util/mini-hipatterns.lua b/lua/lazyvim/plugins/extras/util/mini-hipatterns.lua new file mode 100644 index 0000000..2add9c2 --- /dev/null +++ b/lua/lazyvim/plugins/extras/util/mini-hipatterns.lua @@ -0,0 +1,387 @@ +local M = {} + +---@type table +M.hl = {} + +M.plugin = { + "echasnovski/mini.hipatterns", + event = "LazyFile", + opts = function() + local hi = require("mini.hipatterns") + return { + -- custom LazyVim option to enable the tailwind integration + tailwind = { + enabled = true, + ft = { "typescriptreact", "javascriptreact", "css", "javascript", "typescript", "html" }, + -- full: the whole css class will be highlighted + -- compact: only the color will be highlighted + style = "full", + }, + highlighters = { + hex_color = hi.gen_highlighter.hex_color({ priority = 2000 }), + }, + } + end, + config = function(_, opts) + -- backward compatibility + if opts.tailwind == true then + opts.tailwind = { + enabled = true, + ft = { "typescriptreact", "javascriptreact", "css", "javascript", "typescript", "html" }, + style = "full", + } + end + if type(opts.tailwind) == "table" and opts.tailwind.enabled then + -- reset hl groups when colorscheme changes + vim.api.nvim_create_autocmd("ColorScheme", { + callback = function() + M.hl = {} + end, + }) + opts.highlighters.tailwind = { + pattern = function() + if not vim.tbl_contains(opts.tailwind.ft, vim.bo.filetype) then + return + end + if opts.tailwind.style == "full" then + return "%f[%w:-]()[%w:-]+%-[a-z%-]+%-%d+()%f[^%w:-]" + elseif opts.tailwind.style == "compact" then + return "%f[%w:-][%w:-]+%-()[a-z%-]+%-%d+()%f[^%w:-]" + end + end, + group = function(_, _, m) + ---@type string + local match = m.full_match + ---@type string, number + local color, shade = match:match("[%w-]+%-([a-z%-]+)%-(%d+)") + shade = tonumber(shade) + local bg = vim.tbl_get(M.colors, color, shade) + if bg then + local hl = "MiniHipatternsTailwind" .. color .. shade + if not M.hl[hl] then + M.hl[hl] = true + local bg_shade = shade == 500 and 950 or shade < 500 and 900 or 100 + local fg = vim.tbl_get(M.colors, color, bg_shade) + vim.api.nvim_set_hl(0, hl, { bg = "#" .. bg, fg = "#" .. fg }) + end + return hl + end + end, + extmark_opts = { priority = 2000 }, + } + end + require("mini.hipatterns").setup(opts) + end, +} + +M.colors = { + slate = { + [50] = "f8fafc", + [100] = "f1f5f9", + [200] = "e2e8f0", + [300] = "cbd5e1", + [400] = "94a3b8", + [500] = "64748b", + [600] = "475569", + [700] = "334155", + [800] = "1e293b", + [900] = "0f172a", + [950] = "020617", + }, + + gray = { + [50] = "f9fafb", + [100] = "f3f4f6", + [200] = "e5e7eb", + [300] = "d1d5db", + [400] = "9ca3af", + [500] = "6b7280", + [600] = "4b5563", + [700] = "374151", + [800] = "1f2937", + [900] = "111827", + [950] = "030712", + }, + + zinc = { + [50] = "fafafa", + [100] = "f4f4f5", + [200] = "e4e4e7", + [300] = "d4d4d8", + [400] = "a1a1aa", + [500] = "71717a", + [600] = "52525b", + [700] = "3f3f46", + [800] = "27272a", + [900] = "18181b", + [950] = "09090B", + }, + + neutral = { + [50] = "fafafa", + [100] = "f5f5f5", + [200] = "e5e5e5", + [300] = "d4d4d4", + [400] = "a3a3a3", + [500] = "737373", + [600] = "525252", + [700] = "404040", + [800] = "262626", + [900] = "171717", + [950] = "0a0a0a", + }, + + stone = { + [50] = "fafaf9", + [100] = "f5f5f4", + [200] = "e7e5e4", + [300] = "d6d3d1", + [400] = "a8a29e", + [500] = "78716c", + [600] = "57534e", + [700] = "44403c", + [800] = "292524", + [900] = "1c1917", + [950] = "0a0a0a", + }, + + red = { + [50] = "fef2f2", + [100] = "fee2e2", + [200] = "fecaca", + [300] = "fca5a5", + [400] = "f87171", + [500] = "ef4444", + [600] = "dc2626", + [700] = "b91c1c", + [800] = "991b1b", + [900] = "7f1d1d", + [950] = "450a0a", + }, + + orange = { + [50] = "fff7ed", + [100] = "ffedd5", + [200] = "fed7aa", + [300] = "fdba74", + [400] = "fb923c", + [500] = "f97316", + [600] = "ea580c", + [700] = "c2410c", + [800] = "9a3412", + [900] = "7c2d12", + [950] = "431407", + }, + + amber = { + [50] = "fffbeb", + [100] = "fef3c7", + [200] = "fde68a", + [300] = "fcd34d", + [400] = "fbbf24", + [500] = "f59e0b", + [600] = "d97706", + [700] = "b45309", + [800] = "92400e", + [900] = "78350f", + [950] = "451a03", + }, + + yellow = { + [50] = "fefce8", + [100] = "fef9c3", + [200] = "fef08a", + [300] = "fde047", + [400] = "facc15", + [500] = "eab308", + [600] = "ca8a04", + [700] = "a16207", + [800] = "854d0e", + [900] = "713f12", + [950] = "422006", + }, + + lime = { + [50] = "f7fee7", + [100] = "ecfccb", + [200] = "d9f99d", + [300] = "bef264", + [400] = "a3e635", + [500] = "84cc16", + [600] = "65a30d", + [700] = "4d7c0f", + [800] = "3f6212", + [900] = "365314", + [950] = "1a2e05", + }, + + green = { + [50] = "f0fdf4", + [100] = "dcfce7", + [200] = "bbf7d0", + [300] = "86efac", + [400] = "4ade80", + [500] = "22c55e", + [600] = "16a34a", + [700] = "15803d", + [800] = "166534", + [900] = "14532d", + [950] = "052e16", + }, + + emerald = { + [50] = "ecfdf5", + [100] = "d1fae5", + [200] = "a7f3d0", + [300] = "6ee7b7", + [400] = "34d399", + [500] = "10b981", + [600] = "059669", + [700] = "047857", + [800] = "065f46", + [900] = "064e3b", + [950] = "022c22", + }, + + teal = { + [50] = "f0fdfa", + [100] = "ccfbf1", + [200] = "99f6e4", + [300] = "5eead4", + [400] = "2dd4bf", + [500] = "14b8a6", + [600] = "0d9488", + [700] = "0f766e", + [800] = "115e59", + [900] = "134e4a", + [950] = "042f2e", + }, + + cyan = { + [50] = "ecfeff", + [100] = "cffafe", + [200] = "a5f3fc", + [300] = "67e8f9", + [400] = "22d3ee", + [500] = "06b6d4", + [600] = "0891b2", + [700] = "0e7490", + [800] = "155e75", + [900] = "164e63", + [950] = "083344", + }, + + sky = { + [50] = "f0f9ff", + [100] = "e0f2fe", + [200] = "bae6fd", + [300] = "7dd3fc", + [400] = "38bdf8", + [500] = "0ea5e9", + [600] = "0284c7", + [700] = "0369a1", + [800] = "075985", + [900] = "0c4a6e", + [950] = "082f49", + }, + + blue = { + [50] = "eff6ff", + [100] = "dbeafe", + [200] = "bfdbfe", + [300] = "93c5fd", + [400] = "60a5fa", + [500] = "3b82f6", + [600] = "2563eb", + [700] = "1d4ed8", + [800] = "1e40af", + [900] = "1e3a8a", + [950] = "172554", + }, + + indigo = { + [50] = "eef2ff", + [100] = "e0e7ff", + [200] = "c7d2fe", + [300] = "a5b4fc", + [400] = "818cf8", + [500] = "6366f1", + [600] = "4f46e5", + [700] = "4338ca", + [800] = "3730a3", + [900] = "312e81", + [950] = "1e1b4b", + }, + + violet = { + [50] = "f5f3ff", + [100] = "ede9fe", + [200] = "ddd6fe", + [300] = "c4b5fd", + [400] = "a78bfa", + [500] = "8b5cf6", + [600] = "7c3aed", + [700] = "6d28d9", + [800] = "5b21b6", + [900] = "4c1d95", + [950] = "2e1065", + }, + + purple = { + [50] = "faf5ff", + [100] = "f3e8ff", + [200] = "e9d5ff", + [300] = "d8b4fe", + [400] = "c084fc", + [500] = "a855f7", + [600] = "9333ea", + [700] = "7e22ce", + [800] = "6b21a8", + [900] = "581c87", + [950] = "3b0764", + }, + + fuchsia = { + [50] = "fdf4ff", + [100] = "fae8ff", + [200] = "f5d0fe", + [300] = "f0abfc", + [400] = "e879f9", + [500] = "d946ef", + [600] = "c026d3", + [700] = "a21caf", + [800] = "86198f", + [900] = "701a75", + [950] = "4a044e", + }, + + pink = { + [50] = "fdf2f8", + [100] = "fce7f3", + [200] = "fbcfe8", + [300] = "f9a8d4", + [400] = "f472b6", + [500] = "ec4899", + [600] = "db2777", + [700] = "be185d", + [800] = "9d174d", + [900] = "831843", + [950] = "500724", + }, + + rose = { + [50] = "fff1f2", + [100] = "ffe4e6", + [200] = "fecdd3", + [300] = "fda4af", + [400] = "fb7185", + [500] = "f43f5e", + [600] = "e11d48", + [700] = "be123c", + [800] = "9f1239", + [900] = "881337", + [950] = "4c0519", + }, +} + +return M.plugin diff --git a/lua/lazyvim/plugins/extras/util/project.lua b/lua/lazyvim/plugins/extras/util/project.lua new file mode 100644 index 0000000..3936c96 --- /dev/null +++ b/lua/lazyvim/plugins/extras/util/project.lua @@ -0,0 +1,66 @@ +return { + { + "telescope.nvim", + dependencies = { + -- project management + { + "ahmedkhalf/project.nvim", + opts = { + manual_mode = true, + }, + event = "VeryLazy", + config = function(_, opts) + require("project_nvim").setup(opts) + require("lazyvim.util").on_load("telescope.nvim", function() + require("telescope").load_extension("projects") + end) + end, + keys = { + { "fp", "Telescope projects", desc = "Projects" }, + }, + }, + }, + }, + + { + "goolord/alpha-nvim", + optional = true, + opts = function(_, dashboard) + local button = dashboard.button("p", " " .. " Projects", ":Telescope projects ") + button.opts.hl = "AlphaButtons" + button.opts.hl_shortcut = "AlphaShortcut" + table.insert(dashboard.section.buttons.val, 4, button) + end, + }, + { + "echasnovski/mini.starter", + optional = true, + opts = function(_, opts) + local items = { + { + name = "Projects", + action = "Telescope projects", + section = string.rep(" ", 22) .. "Telescope", + }, + } + vim.list_extend(opts.items, items) + end, + }, + { + "nvimdev/dashboard-nvim", + optional = true, + opts = function(_, opts) + local projects = { + action = "Telescope projects", + desc = " Projects", + icon = " ", + key = "p", + } + + projects.desc = projects.desc .. string.rep(" ", 43 - #projects.desc) + projects.key_format = " %s" + + table.insert(opts.config.center, 3, projects) + end, + }, +} diff --git a/lua/lazyvim/plugins/extras/vscode.lua b/lua/lazyvim/plugins/extras/vscode.lua new file mode 100644 index 0000000..6f78fd9 --- /dev/null +++ b/lua/lazyvim/plugins/extras/vscode.lua @@ -0,0 +1,51 @@ +if not vim.g.vscode then + return {} +end + +local enabled = { + "flit.nvim", + "lazy.nvim", + "leap.nvim", + "mini.ai", + "mini.comment", + "mini.pairs", + "mini.surround", + "nvim-treesitter", + "nvim-treesitter-textobjects", + "nvim-ts-context-commentstring", + "vim-repeat", + "LazyVim", +} + +local Config = require("lazy.core.config") +Config.options.checker.enabled = false +Config.options.change_detection.enabled = false +Config.options.defaults.cond = function(plugin) + return vim.tbl_contains(enabled, plugin.name) or plugin.vscode +end + +-- Add some vscode specific keymaps +vim.api.nvim_create_autocmd("User", { + pattern = "LazyVimKeymaps", + callback = function() + vim.keymap.set("n", "", "Find") + vim.keymap.set("n", "/", [[call VSCodeNotify('workbench.action.findInFiles')]]) + vim.keymap.set("n", "ss", [[call VSCodeNotify('workbench.action.gotoSymbol')]]) + end, +}) + +return { + { + "LazyVim/LazyVim", + config = function(_, opts) + opts = opts or {} + -- disable the colorscheme + opts.colorscheme = function() end + require("lazyvim").setup(opts) + end, + }, + { + "nvim-treesitter/nvim-treesitter", + opts = { highlight = { enable = false } }, + }, +} diff --git a/lua/lazyvim/plugins/formatting.lua b/lua/lazyvim/plugins/formatting.lua new file mode 100644 index 0000000..eb2dd1a --- /dev/null +++ b/lua/lazyvim/plugins/formatting.lua @@ -0,0 +1,116 @@ +local Util = require("lazyvim.util") + +local M = {} + +---@param opts ConformOpts +function M.setup(_, opts) + for name, formatter in pairs(opts.formatters or {}) do + if type(formatter) == "table" then + ---@diagnostic disable-next-line: undefined-field + if formatter.extra_args then + ---@diagnostic disable-next-line: undefined-field + formatter.prepend_args = formatter.extra_args + Util.deprecate(("opts.formatters.%s.extra_args"):format(name), ("opts.formatters.%s.prepend_args"):format(name)) + end + end + end + + for _, key in ipairs({ "format_on_save", "format_after_save" }) do + if opts[key] then + Util.warn( + ("Don't set `opts.%s` for `conform.nvim`.\n**LazyVim** will use the conform formatter automatically"):format( + key + ) + ) + ---@diagnostic disable-next-line: no-unknown + opts[key] = nil + end + end + require("conform").setup(opts) +end + +return { + { + "stevearc/conform.nvim", + dependencies = { "mason.nvim" }, + lazy = true, + cmd = "ConformInfo", + keys = { + { + "cF", + function() + require("conform").format({ formatters = { "injected" } }) + end, + mode = { "n", "v" }, + desc = "Format Injected Langs", + }, + }, + init = function() + -- Install the conform formatter on VeryLazy + require("lazyvim.util").on_very_lazy(function() + require("lazyvim.util").format.register({ + name = "conform.nvim", + priority = 100, + primary = true, + format = function(buf) + local plugin = require("lazy.core.config").plugins["conform.nvim"] + local Plugin = require("lazy.core.plugin") + local opts = Plugin.values(plugin, "opts", false) + require("conform").format(Util.merge(opts.format, { bufnr = buf })) + end, + sources = function(buf) + local ret = require("conform").list_formatters(buf) + ---@param v conform.FormatterInfo + return vim.tbl_map(function(v) + return v.name + end, ret) + end, + }) + end) + end, + opts = function() + local plugin = require("lazy.core.config").plugins["conform.nvim"] + if plugin.config ~= M.setup then + Util.error({ + "Don't set `plugin.config` for `conform.nvim`.\n", + "This will break **LazyVim** formatting.\n", + "Please refer to the docs at https://www.lazyvim.org/plugins/formatting", + }, { title = "LazyVim" }) + end + ---@class ConformOpts + local opts = { + -- LazyVim will use these options when formatting with the conform.nvim formatter + format = { + timeout_ms = 3000, + async = false, -- not recommended to change + quiet = false, -- not recommended to change + }, + ---@type table + formatters_by_ft = { + lua = { "stylua" }, + fish = { "fish_indent" }, + sh = { "shfmt" }, + }, + -- The options you set here will be merged with the builtin formatters. + -- You can also define any custom formatters here. + ---@type table + formatters = { + injected = { options = { ignore_errors = true } }, + -- # Example of using dprint only when a dprint.json file is present + -- dprint = { + -- condition = function(ctx) + -- return vim.fs.find({ "dprint.json" }, { path = ctx.filename, upward = true })[1] + -- end, + -- }, + -- + -- # Example of using shfmt with extra args + -- shfmt = { + -- prepend_args = { "-i", "2", "-ci" }, + -- }, + }, + } + return opts + end, + config = M.setup, + }, +} diff --git a/lua/lazyvim/plugins/init.lua b/lua/lazyvim/plugins/init.lua new file mode 100644 index 0000000..8c7b5fa --- /dev/null +++ b/lua/lazyvim/plugins/init.lua @@ -0,0 +1,16 @@ +if vim.fn.has("nvim-0.9.0") == 0 then + vim.api.nvim_echo({ + { "LazyVim requires Neovim >= 0.9.0\n", "ErrorMsg" }, + { "Press any key to exit", "MoreMsg" }, + }, true, {}) + vim.fn.getchar() + vim.cmd([[quit]]) + return {} +end + +require("lazyvim.config").init() + +return { + { "folke/lazy.nvim", version = "*" }, + { "LazyVim/LazyVim", priority = 10000, lazy = false, config = true, cond = true, version = "*" }, +} diff --git a/lua/lazyvim/plugins/linting.lua b/lua/lazyvim/plugins/linting.lua new file mode 100644 index 0000000..2d2527c --- /dev/null +++ b/lua/lazyvim/plugins/linting.lua @@ -0,0 +1,93 @@ +return { + { + "mfussenegger/nvim-lint", + event = "LazyFile", + opts = { + -- Event to trigger linters + events = { "BufWritePost", "BufReadPost", "InsertLeave" }, + linters_by_ft = { + fish = { "fish" }, + -- Use the "*" filetype to run linters on all filetypes. + -- ['*'] = { 'global linter' }, + -- Use the "_" filetype to run linters on filetypes that don't have other linters configured. + -- ['_'] = { 'fallback linter' }, + }, + -- LazyVim extension to easily override linter options + -- or add custom linters. + ---@type table + linters = { + -- -- Example of using selene only when a selene.toml file is present + -- selene = { + -- -- `condition` is another LazyVim extension that allows you to + -- -- dynamically enable/disable linters based on the context. + -- condition = function(ctx) + -- return vim.fs.find({ "selene.toml" }, { path = ctx.filename, upward = true })[1] + -- end, + -- }, + }, + }, + config = function(_, opts) + local Util = require("lazyvim.util") + + local M = {} + + local lint = require("lint") + for name, linter in pairs(opts.linters) do + if type(linter) == "table" and type(lint.linters[name]) == "table" then + lint.linters[name] = vim.tbl_deep_extend("force", lint.linters[name], linter) + else + lint.linters[name] = linter + end + end + lint.linters_by_ft = opts.linters_by_ft + + function M.debounce(ms, fn) + local timer = vim.loop.new_timer() + return function(...) + local argv = { ... } + timer:start(ms, 0, function() + timer:stop() + vim.schedule_wrap(fn)(unpack(argv)) + end) + end + end + + function M.lint() + -- Use nvim-lint's logic first: + -- * checks if linters exist for the full filetype first + -- * otherwise will split filetype by "." and add all those linters + -- * this differs from conform.nvim which only uses the first filetype that has a formatter + local names = lint._resolve_linter_by_ft(vim.bo.filetype) + + -- Add fallback linters. + if #names == 0 then + vim.list_extend(names, lint.linters_by_ft["_"] or {}) + end + + -- Add global linters. + vim.list_extend(names, lint.linters_by_ft["*"] or {}) + + -- Filter out linters that don't exist or don't match the condition. + local ctx = { filename = vim.api.nvim_buf_get_name(0) } + ctx.dirname = vim.fn.fnamemodify(ctx.filename, ":h") + names = vim.tbl_filter(function(name) + local linter = lint.linters[name] + if not linter then + Util.warn("Linter not found: " .. name, { title = "nvim-lint" }) + end + return linter and not (type(linter) == "table" and linter.condition and not linter.condition(ctx)) + end, names) + + -- Run linters. + if #names > 0 then + lint.try_lint(names) + end + end + + vim.api.nvim_create_autocmd(opts.events, { + group = vim.api.nvim_create_augroup("nvim-lint", { clear = true }), + callback = M.debounce(100, M.lint), + }) + end, + }, +} diff --git a/lua/lazyvim/plugins/lsp/init.lua b/lua/lazyvim/plugins/lsp/init.lua new file mode 100644 index 0000000..3b60d14 --- /dev/null +++ b/lua/lazyvim/plugins/lsp/init.lua @@ -0,0 +1,243 @@ +local Util = require("lazyvim.util") + +return { + -- lspconfig + { + "neovim/nvim-lspconfig", + event = "LazyFile", + dependencies = { + { "folke/neoconf.nvim", cmd = "Neoconf", config = false, dependencies = { "nvim-lspconfig" } }, + { "folke/neodev.nvim", opts = {} }, + "mason.nvim", + "williamboman/mason-lspconfig.nvim", + }, + ---@class PluginLspOpts + opts = { + -- options for vim.diagnostic.config() + diagnostics = { + underline = true, + update_in_insert = false, + virtual_text = { + spacing = 4, + source = "if_many", + prefix = "●", + -- this will set set the prefix to a function that returns the diagnostics icon based on the severity + -- this only works on a recent 0.10.0 build. Will be set to "●" when not supported + -- prefix = "icons", + }, + severity_sort = true, + }, + -- Enable this to enable the builtin LSP inlay hints on Neovim >= 0.10.0 + -- Be aware that you also will need to properly configure your LSP server to + -- provide the inlay hints. + inlay_hints = { + enabled = false, + }, + -- add any global capabilities here + capabilities = {}, + -- options for vim.lsp.buf.format + -- `bufnr` and `filter` is handled by the LazyVim formatter, + -- but can be also overridden when specified + format = { + formatting_options = nil, + timeout_ms = nil, + }, + -- LSP Server Settings + ---@type lspconfig.options + servers = { + lua_ls = { + -- mason = false, -- set to false if you don't want this server to be installed with mason + -- Use this to add any additional keymaps + -- for specific lsp servers + ---@type LazyKeysSpec[] + -- keys = {}, + settings = { + Lua = { + workspace = { + checkThirdParty = false, + }, + completion = { + callSnippet = "Replace", + }, + }, + }, + }, + }, + -- you can do any additional lsp server setup here + -- return true if you don't want this server to be setup with lspconfig + ---@type table + setup = { + -- example to setup with typescript.nvim + -- tsserver = function(_, opts) + -- require("typescript").setup({ server = opts }) + -- return true + -- end, + -- Specify * to use this function as a fallback for any server + -- ["*"] = function(server, opts) end, + }, + }, + ---@param opts PluginLspOpts + config = function(_, opts) + if Util.has("neoconf.nvim") then + local plugin = require("lazy.core.config").spec.plugins["neoconf.nvim"] + require("neoconf").setup(require("lazy.core.plugin").values(plugin, "opts", false)) + end + + -- setup autoformat + Util.format.register(Util.lsp.formatter()) + + -- deprectaed options + if opts.autoformat ~= nil then + vim.g.autoformat = opts.autoformat + Util.deprecate("nvim-lspconfig.opts.autoformat", "vim.g.autoformat") + end + + -- setup keymaps + Util.lsp.on_attach(function(client, buffer) + require("lazyvim.plugins.lsp.keymaps").on_attach(client, buffer) + end) + + local register_capability = vim.lsp.handlers["client/registerCapability"] + + vim.lsp.handlers["client/registerCapability"] = function(err, res, ctx) + local ret = register_capability(err, res, ctx) + local client_id = ctx.client_id + ---@type lsp.Client + local client = vim.lsp.get_client_by_id(client_id) + local buffer = vim.api.nvim_get_current_buf() + require("lazyvim.plugins.lsp.keymaps").on_attach(client, buffer) + return ret + end + + -- diagnostics + for name, icon in pairs(require("lazyvim.config").icons.diagnostics) do + name = "DiagnosticSign" .. name + vim.fn.sign_define(name, { text = icon, texthl = name, numhl = "" }) + end + + if opts.inlay_hints.enabled then + Util.lsp.on_attach(function(client, buffer) + if client.supports_method("textDocument/inlayHint") then + Util.toggle.inlay_hints(buffer, true) + end + end) + end + + if type(opts.diagnostics.virtual_text) == "table" and opts.diagnostics.virtual_text.prefix == "icons" then + opts.diagnostics.virtual_text.prefix = vim.fn.has("nvim-0.10.0") == 0 and "●" + or function(diagnostic) + local icons = require("lazyvim.config").icons.diagnostics + for d, icon in pairs(icons) do + if diagnostic.severity == vim.diagnostic.severity[d:upper()] then + return icon + end + end + end + end + + vim.diagnostic.config(vim.deepcopy(opts.diagnostics)) + + local servers = opts.servers + local has_cmp, cmp_nvim_lsp = pcall(require, "cmp_nvim_lsp") + local capabilities = vim.tbl_deep_extend( + "force", + {}, + vim.lsp.protocol.make_client_capabilities(), + has_cmp and cmp_nvim_lsp.default_capabilities() or {}, + opts.capabilities or {} + ) + + local function setup(server) + local server_opts = vim.tbl_deep_extend("force", { + capabilities = vim.deepcopy(capabilities), + }, servers[server] or {}) + + if opts.setup[server] then + if opts.setup[server](server, server_opts) then + return + end + elseif opts.setup["*"] then + if opts.setup["*"](server, server_opts) then + return + end + end + require("lspconfig")[server].setup(server_opts) + end + + -- get all the servers that are available through mason-lspconfig + local have_mason, mlsp = pcall(require, "mason-lspconfig") + local all_mslp_servers = {} + if have_mason then + all_mslp_servers = vim.tbl_keys(require("mason-lspconfig.mappings.server").lspconfig_to_package) + end + + local ensure_installed = {} ---@type string[] + for server, server_opts in pairs(servers) do + if server_opts then + server_opts = server_opts == true and {} or server_opts + -- run manual setup if mason=false or if this is a server that cannot be installed with mason-lspconfig + if server_opts.mason == false or not vim.tbl_contains(all_mslp_servers, server) then + setup(server) + else + ensure_installed[#ensure_installed + 1] = server + end + end + end + + if have_mason then + mlsp.setup({ ensure_installed = ensure_installed, handlers = { setup } }) + end + + if Util.lsp.get_config("denols") and Util.lsp.get_config("tsserver") then + local is_deno = require("lspconfig.util").root_pattern("deno.json", "deno.jsonc") + Util.lsp.disable("tsserver", is_deno) + Util.lsp.disable("denols", function(root_dir) + return not is_deno(root_dir) + end) + end + end, + }, + + -- cmdline tools and lsp servers + { + + "williamboman/mason.nvim", + cmd = "Mason", + keys = { { "cm", "Mason", desc = "Mason" } }, + build = ":MasonUpdate", + opts = { + ensure_installed = { + "stylua", + "shfmt", + -- "flake8", + }, + }, + ---@param opts MasonSettings | {ensure_installed: string[]} + config = function(_, opts) + require("mason").setup(opts) + local mr = require("mason-registry") + mr:on("package:install:success", function() + vim.defer_fn(function() + -- trigger FileType event to possibly load this newly installed LSP server + require("lazy.core.handler.event").trigger({ + event = "FileType", + buf = vim.api.nvim_get_current_buf(), + }) + end, 100) + end) + local function ensure_installed() + for _, tool in ipairs(opts.ensure_installed) do + local p = mr.get_package(tool) + if not p:is_installed() then + p:install() + end + end + end + if mr.refresh then + mr.refresh(ensure_installed) + else + ensure_installed() + end + end, + }, +} diff --git a/lua/lazyvim/plugins/lsp/keymaps.lua b/lua/lazyvim/plugins/lsp/keymaps.lua new file mode 100644 index 0000000..c1046db --- /dev/null +++ b/lua/lazyvim/plugins/lsp/keymaps.lua @@ -0,0 +1,102 @@ +local M = {} + +---@type LazyKeysLspSpec[]|nil +M._keys = nil + +---@alias LazyKeysLspSpec LazyKeysSpec|{has?:string} +---@alias LazyKeysLsp LazyKeys|{has?:string} + +---@return LazyKeysLspSpec[] +function M.get() + if M._keys then + return M._keys + end + -- stylua: ignore + M._keys = { + { "cl", "LspInfo", desc = "Lsp Info" }, + { "gd", function() require("telescope.builtin").lsp_definitions({ reuse_win = true }) end, desc = "Goto Definition", has = "definition" }, + { "gr", "Telescope lsp_references", desc = "References" }, + { "gD", vim.lsp.buf.declaration, desc = "Goto Declaration" }, + { "gI", function() require("telescope.builtin").lsp_implementations({ reuse_win = true }) end, desc = "Goto Implementation" }, + { "gy", function() require("telescope.builtin").lsp_type_definitions({ reuse_win = true }) end, desc = "Goto T[y]pe Definition" }, + { "cK", vim.lsp.buf.hover, desc = "Hover" }, + { "gK", vim.lsp.buf.signature_help, desc = "Signature Help", has = "signatureHelp" }, + { "", vim.lsp.buf.signature_help, mode = "i", desc = "Signature Help", has = "signatureHelp" }, + { "ca", vim.lsp.buf.code_action, desc = "Code Action", mode = { "n", "v" }, has = "codeAction" }, + { + "cA", + function() + vim.lsp.buf.code_action({ + context = { + only = { + "source", + }, + diagnostics = {}, + }, + }) + end, + desc = "Source Action", + has = "codeAction", + } + } + if require("lazyvim.util").has("inc-rename.nvim") then + M._keys[#M._keys + 1] = { + "cr", + function() + local inc_rename = require("inc_rename") + return ":" .. inc_rename.config.cmd_name .. " " .. vim.fn.expand("") + end, + expr = true, + desc = "Rename", + has = "rename", + } + else + M._keys[#M._keys + 1] = { "cr", vim.lsp.buf.rename, desc = "Rename", has = "rename" } + end + return M._keys +end + +---@param method string +function M.has(buffer, method) + method = method:find("/") and method or "textDocument/" .. method + local clients = require("lazyvim.util").lsp.get_clients({ bufnr = buffer }) + for _, client in ipairs(clients) do + if client.supports_method(method) then + return true + end + end + return false +end + +---@return (LazyKeys|{has?:string})[] +function M.resolve(buffer) + local Keys = require("lazy.core.handler.keys") + if not Keys.resolve then + return {} + end + local spec = M.get() + local opts = require("lazyvim.util").opts("nvim-lspconfig") + local clients = require("lazyvim.util").lsp.get_clients({ bufnr = buffer }) + for _, client in ipairs(clients) do + local maps = opts.servers[client.name] and opts.servers[client.name].keys or {} + vim.list_extend(spec, maps) + end + return Keys.resolve(spec) +end + +function M.on_attach(_, buffer) + local Keys = require("lazy.core.handler.keys") + local keymaps = M.resolve(buffer) + + for _, keys in pairs(keymaps) do + if not keys.has or M.has(buffer, keys.has) then + local opts = Keys.opts(keys) + opts.has = nil + opts.silent = opts.silent ~= false + opts.buffer = buffer + vim.keymap.set(keys.mode or "n", keys.lhs, keys.rhs, opts) + end + end +end + +return M diff --git a/lua/lazyvim/plugins/treesitter.lua b/lua/lazyvim/plugins/treesitter.lua new file mode 100644 index 0000000..e66725c --- /dev/null +++ b/lua/lazyvim/plugins/treesitter.lua @@ -0,0 +1,146 @@ +return { + -- Treesitter is a new parser generator tool that we can + -- use in Neovim to power faster and more accurate + -- syntax highlighting. + { + "nvim-treesitter/nvim-treesitter", + version = false, -- last release is way too old and doesn't work on Windows + build = ":TSUpdate", + event = { "LazyFile", "VeryLazy" }, + init = function(plugin) + -- PERF: add nvim-treesitter queries to the rtp and it's custom query predicates early + -- This is needed because a bunch of plugins no longer `require("nvim-treesitter")`, which + -- no longer trigger the **nvim-treeitter** module to be loaded in time. + -- Luckily, the only thins that those plugins need are the custom queries, which we make available + -- during startup. + require("lazy.core.loader").add_to_rtp(plugin) + require("nvim-treesitter.query_predicates") + end, + dependencies = { + { + "nvim-treesitter/nvim-treesitter-textobjects", + config = function() + -- When in diff mode, we want to use the default + -- vim text objects c & C instead of the treesitter ones. + local move = require("nvim-treesitter.textobjects.move") ---@type table + local configs = require("nvim-treesitter.configs") + for name, fn in pairs(move) do + if name:find("goto") == 1 then + move[name] = function(q, ...) + if vim.wo.diff then + local config = configs.get_module("textobjects.move")[name] ---@type table + for key, query in pairs(config or {}) do + if q == query and key:find("[%]%[][cC]") then + vim.cmd("normal! " .. key) + return + end + end + end + return fn(q, ...) + end + end + end + end, + }, + }, + cmd = { "TSUpdateSync", "TSUpdate", "TSInstall" }, + keys = { + { "", desc = "Increment selection" }, + { "", desc = "Decrement selection", mode = "x" }, + }, + ---@type TSConfig + ---@diagnostic disable-next-line: missing-fields + opts = { + highlight = { enable = true }, + indent = { enable = true }, + ensure_installed = { + "bash", + "c", + "diff", + "html", + "javascript", + "jsdoc", + "json", + "jsonc", + "lua", + "luadoc", + "luap", + "markdown", + "markdown_inline", + "python", + "query", + "regex", + "toml", + "tsx", + "typescript", + "vim", + "vimdoc", + "yaml", + }, + incremental_selection = { + enable = true, + keymaps = { + init_selection = "", + node_incremental = "", + scope_incremental = false, + node_decremental = "", + }, + }, + textobjects = { + move = { + enable = true, + goto_next_start = { ["]f"] = "@function.outer", ["]c"] = "@class.outer" }, + goto_next_end = { ["]F"] = "@function.outer", ["]C"] = "@class.outer" }, + goto_previous_start = { ["[f"] = "@function.outer", ["[c"] = "@class.outer" }, + goto_previous_end = { ["[F"] = "@function.outer", ["[C"] = "@class.outer" }, + }, + }, + }, + ---@param opts TSConfig + config = function(_, opts) + if type(opts.ensure_installed) == "table" then + ---@type table + local added = {} + opts.ensure_installed = vim.tbl_filter(function(lang) + if added[lang] then + return false + end + added[lang] = true + return true + end, opts.ensure_installed) + end + require("nvim-treesitter.configs").setup(opts) + end, + }, + + -- Show context of the current function + { + "nvim-treesitter/nvim-treesitter-context", + event = "LazyFile", + enabled = true, + opts = { mode = "cursor", max_lines = 3 }, + keys = { + { + "ut", + function() + local Util = require("lazyvim.util") + local tsc = require("treesitter-context") + tsc.toggle() + if Util.inject.get_upvalue(tsc.toggle, "enabled") then + Util.info("Enabled Treesitter Context", { title = "Option" }) + else + Util.warn("Disabled Treesitter Context", { title = "Option" }) + end + end, + desc = "Toggle Treesitter Context", + }, + }, + }, + + -- Automatically add closing tags for HTML and JSX + { + "windwp/nvim-ts-autotag", + event = "LazyFile", + opts = {}, + }, +} diff --git a/lua/lazyvim/plugins/ui.lua b/lua/lazyvim/plugins/ui.lua new file mode 100644 index 0000000..50efb15 --- /dev/null +++ b/lua/lazyvim/plugins/ui.lua @@ -0,0 +1,413 @@ +local Util = require("lazyvim.util") + +return { + -- Better `vim.notify()` + { + "rcarriga/nvim-notify", + keys = { + { + "un", + function() + require("notify").dismiss({ silent = true, pending = true }) + end, + desc = "Dismiss all Notifications", + }, + }, + opts = { + timeout = 3000, + max_height = function() + return math.floor(vim.o.lines * 0.75) + end, + max_width = function() + return math.floor(vim.o.columns * 0.75) + end, + on_open = function(win) + vim.api.nvim_win_set_config(win, { zindex = 100 }) + end, + }, + init = function() + -- when noice is not enabled, install notify on VeryLazy + if not Util.has("noice.nvim") then + Util.on_very_lazy(function() + vim.notify = require("notify") + end) + end + end, + }, + + -- better vim.ui + { + "stevearc/dressing.nvim", + lazy = true, + init = function() + ---@diagnostic disable-next-line: duplicate-set-field + vim.ui.select = function(...) + require("lazy").load({ plugins = { "dressing.nvim" } }) + return vim.ui.select(...) + end + ---@diagnostic disable-next-line: duplicate-set-field + vim.ui.input = function(...) + require("lazy").load({ plugins = { "dressing.nvim" } }) + return vim.ui.input(...) + end + end, + }, + + -- This is what powers LazyVim's fancy-looking + -- tabs, which include filetype icons and close buttons. + { + "akinsho/bufferline.nvim", + event = "VeryLazy", + keys = { + { "bp", "BufferLineTogglePin", desc = "Toggle pin" }, + { "bP", "BufferLineGroupClose ungrouped", desc = "Delete non-pinned buffers" }, + { "bo", "BufferLineCloseOthers", desc = "Delete other buffers" }, + { "br", "BufferLineCloseRight", desc = "Delete buffers to the right" }, + { "bl", "BufferLineCloseLeft", desc = "Delete buffers to the left" }, + { "", "BufferLineCyclePrev", desc = "Prev buffer" }, + { "", "BufferLineCycleNext", desc = "Next buffer" }, + { "[b", "BufferLineCyclePrev", desc = "Prev buffer" }, + { "]b", "BufferLineCycleNext", desc = "Next buffer" }, + }, + opts = { + options = { + -- stylua: ignore + close_command = function(n) require("mini.bufremove").delete(n, false) end, + -- stylua: ignore + right_mouse_command = function(n) require("mini.bufremove").delete(n, false) end, + diagnostics = "nvim_lsp", + always_show_bufferline = false, + diagnostics_indicator = function(_, _, diag) + local icons = require("lazyvim.config").icons.diagnostics + local ret = (diag.error and icons.Error .. diag.error .. " " or "") + .. (diag.warning and icons.Warn .. diag.warning or "") + return vim.trim(ret) + end, + offsets = { + { + filetype = "neo-tree", + text = "Neo-tree", + highlight = "Directory", + text_align = "left", + }, + }, + }, + }, + config = function(_, opts) + require("bufferline").setup(opts) + -- Fix bufferline when restoring a session + vim.api.nvim_create_autocmd("BufAdd", { + callback = function() + vim.schedule(function() + pcall(nvim_bufferline) + end) + end, + }) + end, + }, + + -- statusline + { + "nvim-lualine/lualine.nvim", + event = "VeryLazy", + init = function() + vim.g.lualine_laststatus = vim.o.laststatus + if vim.fn.argc(-1) > 0 then + -- set an empty statusline till lualine loads + vim.o.statusline = " " + else + -- hide the statusline on the starter page + vim.o.laststatus = 0 + end + end, + opts = function() + -- PERF: we don't need this lualine require madness 🤷 + local lualine_require = require("lualine_require") + lualine_require.require = require + + local icons = require("lazyvim.config").icons + + vim.o.laststatus = vim.g.lualine_laststatus + + return { + options = { + theme = "auto", + globalstatus = true, + disabled_filetypes = { statusline = { "dashboard", "alpha", "starter" } }, + }, + sections = { + lualine_a = { "mode" }, + lualine_b = { "branch" }, + + lualine_c = { + Util.lualine.root_dir(), + { + "diagnostics", + symbols = { + error = icons.diagnostics.Error, + warn = icons.diagnostics.Warn, + info = icons.diagnostics.Info, + hint = icons.diagnostics.Hint, + }, + }, + { "filetype", icon_only = true, separator = "", padding = { left = 1, right = 0 } }, + { Util.lualine.pretty_path() }, + }, + lualine_x = { + -- stylua: ignore + { + function() return require("noice").api.status.command.get() end, + cond = function() return package.loaded["noice"] and require("noice").api.status.command.has() end, + color = Util.ui.fg("Statement"), + }, + -- stylua: ignore + { + function() return require("noice").api.status.mode.get() end, + cond = function() return package.loaded["noice"] and require("noice").api.status.mode.has() end, + color = Util.ui.fg("Constant"), + }, + -- stylua: ignore + { + function() return " " .. require("dap").status() end, + cond = function () return package.loaded["dap"] and require("dap").status() ~= "" end, + color = Util.ui.fg("Debug"), + }, + { + require("lazy.status").updates, + cond = require("lazy.status").has_updates, + color = Util.ui.fg("Special"), + }, + { + "diff", + symbols = { + added = icons.git.added, + modified = icons.git.modified, + removed = icons.git.removed, + }, + source = function() + local gitsigns = vim.b.gitsigns_status_dict + if gitsigns then + return { + added = gitsigns.added, + modified = gitsigns.changed, + removed = gitsigns.removed, + } + end + end, + }, + }, + lualine_y = { + { "progress", separator = " ", padding = { left = 1, right = 0 } }, + { "location", padding = { left = 0, right = 1 } }, + }, + lualine_z = { + function() + return " " .. os.date("%R") + end, + }, + }, + extensions = { "neo-tree", "lazy" }, + } + end, + }, + + -- indent guides for Neovim + { + "lukas-reineke/indent-blankline.nvim", + event = "LazyFile", + opts = { + indent = { + char = "│", + tab_char = "│", + }, + scope = { enabled = false }, + exclude = { + filetypes = { + "help", + "alpha", + "dashboard", + "neo-tree", + "Trouble", + "trouble", + "lazy", + "mason", + "notify", + "toggleterm", + "lazyterm", + }, + }, + }, + main = "ibl", + }, + + -- Active indent guide and indent text objects. When you're browsing + -- code, this highlights the current level of indentation, and animates + -- the highlighting. + { + "echasnovski/mini.indentscope", + version = false, -- wait till new 0.7.0 release to put it back on semver + event = "LazyFile", + opts = { + -- symbol = "▏", + symbol = "│", + options = { try_as_border = true }, + }, + init = function() + vim.api.nvim_create_autocmd("FileType", { + pattern = { + "help", + "alpha", + "dashboard", + "neo-tree", + "Trouble", + "trouble", + "lazy", + "mason", + "notify", + "toggleterm", + "lazyterm", + }, + callback = function() + vim.b.miniindentscope_disable = true + end, + }) + end, + }, + + -- Displays a popup with possible key bindings of the command you started typing + { + "folke/which-key.nvim", + opts = function(_, opts) + if require("lazyvim.util").has("noice.nvim") then + opts.defaults["sn"] = { name = "+noice" } + end + end, + }, + + -- Highly experimental plugin that completely replaces the UI for messages, cmdline and the popupmenu. + { + "folke/noice.nvim", + event = "VeryLazy", + opts = { + lsp = { + override = { + ["vim.lsp.util.convert_input_to_markdown_lines"] = true, + ["vim.lsp.util.stylize_markdown"] = true, + ["cmp.entry.get_documentation"] = true, + }, + }, + routes = { + { + filter = { + event = "msg_show", + any = { + { find = "%d+L, %d+B" }, + { find = "; after #%d+" }, + { find = "; before #%d+" }, + }, + }, + view = "mini", + }, + }, + presets = { + bottom_search = true, + command_palette = true, + long_message_to_split = true, + inc_rename = true, + }, + }, + -- stylua: ignore + keys = { + { "", function() require("noice").redirect(vim.fn.getcmdline()) end, mode = "c", desc = "Redirect Cmdline" }, + { "snl", function() require("noice").cmd("last") end, desc = "Noice Last Message" }, + { "snh", function() require("noice").cmd("history") end, desc = "Noice History" }, + { "sna", function() require("noice").cmd("all") end, desc = "Noice All" }, + { "snd", function() require("noice").cmd("dismiss") end, desc = "Dismiss All" }, + { "", function() if not require("noice.lsp").scroll(4) then return "" end end, silent = true, expr = true, desc = "Scroll forward", mode = {"i", "n", "s"} }, + { "", function() if not require("noice.lsp").scroll(-4) then return "" end end, silent = true, expr = true, desc = "Scroll backward", mode = {"i", "n", "s"}}, + }, + }, + + -- icons + { "nvim-tree/nvim-web-devicons", lazy = true }, + + -- ui components + { "MunifTanjim/nui.nvim", lazy = true }, + + { + "goolord/alpha-nvim", + optional = true, + enabled = function() + require("lazyvim.util").warn({ + "`dashboard.nvim` is now the default LazyVim starter plugin.", + "", + "To keep using `alpha.nvim`, please enable the `lazyvim.plugins.extras.ui.alpha` extra.", + "Or to hide this message, remove the alpha spec from your config.", + }) + return false + end, + }, + { + "nvimdev/dashboard-nvim", + event = "VimEnter", + opts = function() + local logo = [[ + ██╗ █████╗ ███████╗██╗ ██╗██╗ ██╗██╗███╗ ███╗ Z + ██║ ██╔══██╗╚══███╔╝╚██╗ ██╔╝██║ ██║██║████╗ ████║ Z + ██║ ███████║ ███╔╝ ╚████╔╝ ██║ ██║██║██╔████╔██║ z + ██║ ██╔══██║ ███╔╝ ╚██╔╝ ╚██╗ ██╔╝██║██║╚██╔╝██║ z + ███████╗██║ ██║███████╗ ██║ ╚████╔╝ ██║██║ ╚═╝ ██║ + ╚══════╝╚═╝ ╚═╝╚══════╝ ╚═╝ ╚═══╝ ╚═╝╚═╝ ╚═╝ + ]] + + logo = string.rep("\n", 8) .. logo .. "\n\n" + + local opts = { + theme = "doom", + hide = { + -- this is taken care of by lualine + -- enabling this messes up the actual laststatus setting after loading a file + statusline = false, + }, + config = { + header = vim.split(logo, "\n"), + -- stylua: ignore + center = { + { action = "Telescope find_files", desc = " Find file", icon = " ", key = "f" }, + { action = "ene | startinsert", desc = " New file", icon = " ", key = "n" }, + { action = "Telescope oldfiles", desc = " Recent files", icon = " ", key = "r" }, + { action = "Telescope live_grep", desc = " Find text", icon = " ", key = "g" }, + { action = [[lua require("lazyvim.util").telescope.config_files()()]], desc = " Config", icon = " ", key = "c" }, + { action = 'lua require("persistence").load()', desc = " Restore Session", icon = " ", key = "s" }, + { action = "LazyExtras", desc = " Lazy Extras", icon = " ", key = "x" }, + { action = "Lazy", desc = " Lazy", icon = "󰒲 ", key = "l" }, + { action = "qa", desc = " Quit", icon = " ", key = "q" }, + }, + footer = function() + local stats = require("lazy").stats() + local ms = (math.floor(stats.startuptime * 100 + 0.5) / 100) + return { "⚡ Neovim loaded " .. stats.loaded .. "/" .. stats.count .. " plugins in " .. ms .. "ms" } + end, + }, + } + + for _, button in ipairs(opts.config.center) do + button.desc = button.desc .. string.rep(" ", 43 - #button.desc) + button.key_format = " %s" + end + + -- close Lazy and re-open when the dashboard is ready + if vim.o.filetype == "lazy" then + vim.cmd.close() + vim.api.nvim_create_autocmd("User", { + pattern = "DashboardLoaded", + callback = function() + require("lazy").show() + end, + }) + end + + return opts + end, + }, +} diff --git a/lua/lazyvim/plugins/util.lua b/lua/lazyvim/plugins/util.lua new file mode 100644 index 0000000..c387088 --- /dev/null +++ b/lua/lazyvim/plugins/util.lua @@ -0,0 +1,29 @@ +return { + + -- measure startuptime + { + "dstein64/vim-startuptime", + cmd = "StartupTime", + config = function() + vim.g.startuptime_tries = 10 + end, + }, + + -- Session management. This saves your session in the background, + -- keeping track of open buffers, window arrangement, and more. + -- You can restore sessions when returning through the dashboard. + { + "folke/persistence.nvim", + event = "BufReadPre", + opts = { options = vim.opt.sessionoptions:get() }, + -- stylua: ignore + keys = { + { "qs", function() require("persistence").load() end, desc = "Restore Session" }, + { "ql", function() require("persistence").load({ last = true }) end, desc = "Restore Last Session" }, + { "qd", function() require("persistence").stop() end, desc = "Don't Save Current Session" }, + }, + }, + + -- library used by other plugins + { "nvim-lua/plenary.nvim", lazy = true }, +} diff --git a/lua/lazyvim/plugins/xtras.lua b/lua/lazyvim/plugins/xtras.lua new file mode 100644 index 0000000..4eb7380 --- /dev/null +++ b/lua/lazyvim/plugins/xtras.lua @@ -0,0 +1,23 @@ +local Config = require("lazyvim.config") + +-- Some extras need to be loaded before others +local prios = { + ["lazyvim.plugins.extras.editor.aerial"] = 100, + ["lazyvim.plugins.extras.editor.symbols-outline"] = 100, + ["lazyvim.plugins.extras.test.core"] = 1, + ["lazyvim.plugins.extras.dap.core"] = 1, +} + +table.sort(Config.json.data.extras, function(a, b) + local pa = prios[a] or 10 + local pb = prios[b] or 10 + if pa == pb then + return a < b + end + return pa < pb +end) + +---@param extra string +return vim.tbl_map(function(extra) + return { import = extra } +end, Config.json.data.extras) diff --git a/lua/lazyvim/util/extras.lua b/lua/lazyvim/util/extras.lua new file mode 100644 index 0000000..4e2f0bc --- /dev/null +++ b/lua/lazyvim/util/extras.lua @@ -0,0 +1,243 @@ +local Config = require("lazyvim.config") +local Float = require("lazy.view.float") +local LazyConfig = require("lazy.core.config") +local Plugin = require("lazy.core.plugin") +local Text = require("lazy.view.text") +local Util = require("lazyvim.util") + +---@class LazyExtraSource +---@field name string +---@field desc? string +---@field module string + +---@class LazyExtra +---@field name string +---@field source LazyExtraSource +---@field module string +---@field desc? string +---@field enabled boolean +---@field managed boolean +---@field row? number +---@field plugins string[] +---@field optional string[] + +---@class lazyvim.util.extras +local M = {} + +---@type LazyExtraSource[] +M.sources = { + { name = "LazyVim", desc = "LazyVim extras", module = "lazyvim.plugins.extras" }, + { name = "User", desc = "User extras", module = "plugins.extras" }, +} + +M.ns = vim.api.nvim_create_namespace("lazyvim.extras") +---@type string[] +M.state = nil + +---@return LazyExtra[] +function M.get() + M.state = M.state or LazyConfig.spec.modules + local extras = {} ---@type LazyExtra[] + for _, source in ipairs(M.sources) do + local root = Util.find_root(source.module) + if root then + Util.walk(root, function(path, name, type) + if type == "file" and name:match("%.lua$") then + name = path:sub(#root + 2, -5):gsub("/", ".") + local ok, extra = pcall(M.get_extra, source, source.module .. "." .. name) + if ok then + extras[#extras + 1] = extra + end + end + end) + end + end + table.sort(extras, function(a, b) + return a.name < b.name + end) + return extras +end + +---@param modname string +---@param source LazyExtraSource +function M.get_extra(source, modname) + local enabled = vim.tbl_contains(M.state, modname) + local spec = Plugin.Spec.new({ import = modname }, { optional = true }) + local plugins = {} ---@type string[] + local optional = {} ---@type string[] + for _, p in pairs(spec.plugins) do + if p.optional then + optional[#optional + 1] = p.name + else + plugins[#plugins + 1] = p.name + end + end + table.sort(plugins) + table.sort(optional) + + ---@type LazyExtra + return { + source = source, + name = modname:sub(#source.module + 2), + module = modname, + enabled = enabled, + desc = require(modname).desc, + managed = vim.tbl_contains(Config.json.data.extras, modname) or not enabled, + plugins = plugins, + optional = optional, + } +end + +---@class LazyExtraView +---@field float LazyFloat +---@field text Text +---@field extras LazyExtra[] +---@field diag LazyDiagnostic[] +local X = {} + +---@return LazyExtraView +function X.new() + local self = setmetatable({}, { __index = X }) + self.float = Float.new({ title = "LazyVim Extras" }) + self.float:on_key("x", function() + self:toggle() + end, "Toggle extra") + self.diag = {} + self:update() + return self +end + +---@param diag LazyDiagnostic +function X:diagnostic(diag) + diag.row = diag.row or self.text:row() + diag.severity = diag.severity or vim.diagnostic.severity.INFO + table.insert(self.diag, diag) +end + +function X:toggle() + local pos = vim.api.nvim_win_get_cursor(self.float.win) + for _, extra in ipairs(self.extras) do + if extra.row == pos[1] then + if not extra.managed then + Util.error( + "Not managed by LazyExtras. Remove from your config to enable/disable here.", + { title = "LazyExtras" } + ) + return + end + extra.enabled = not extra.enabled + Config.json.data.extras = vim.tbl_filter(function(name) + return name ~= extra.module + end, Config.json.data.extras) + M.state = vim.tbl_filter(function(name) + return name ~= extra.module + end, M.state) + if extra.enabled then + table.insert(Config.json.data.extras, extra.module) + M.state[#M.state + 1] = extra.module + end + table.sort(Config.json.data.extras) + Util.json.save() + Util.info( + "`" + .. extra.name + .. "`" + .. " " + .. (extra.enabled and "**enabled**" or "**disabled**") + .. "\nPlease restart LazyVim to apply the changes.", + { title = "LazyExtras" } + ) + self:update() + return + end + end +end + +function X:update() + self.diag = {} + self.extras = M.get() + self.text = Text.new() + self.text.padding = 2 + self:render() + self.text:trim() + vim.bo[self.float.buf].modifiable = true + self.text:render(self.float.buf) + vim.bo[self.float.buf].modifiable = false + vim.diagnostic.set( + M.ns, + self.float.buf, + ---@param diag LazyDiagnostic + vim.tbl_map(function(diag) + diag.col = 0 + diag.lnum = diag.row - 1 + return diag + end, self.diag), + { signs = false, virtual_text = true } + ) +end + +function X:render() + self.text:nl():nl():append("LazyVim Extras", "LazyH1"):nl():nl() + self.text + :append("This is a list of all enabled/disabled LazyVim extras.", "LazyComment") + :nl() + :append("Each extra shows the required and optional plugins it may install.", "LazyComment") + :nl() + :append("Enable/disable extras with the ", "LazyComment") + :append("", "LazySpecial") + :append(" key", "LazyComment") + :nl() + self:section({ enabled = true, title = "Enabled" }) + self:section({ enabled = false, title = "Disabled" }) +end + +---@param extra LazyExtra +function X:extra(extra) + if not extra.managed then + self:diagnostic({ + message = "Not managed by LazyExtras (config)", + severity = vim.diagnostic.severity.WARN, + }) + end + extra.row = self.text:row() + local hl = extra.managed and "LazySpecial" or "LazyLocal" + if extra.enabled then + self.text:append(" " .. LazyConfig.options.ui.icons.loaded .. " ", hl) + else + self.text:append(" " .. LazyConfig.options.ui.icons.not_loaded .. " ", hl) + end + self.text:append(extra.name) + if extra.source.name ~= "LazyVim" then + self.text:append(" "):append(LazyConfig.options.ui.icons.event .. " " .. extra.source.name, "LazyReasonEvent") + end + for _, plugin in ipairs(extra.plugins) do + self.text:append(" "):append(LazyConfig.options.ui.icons.plugin .. "" .. plugin, "LazyReasonPlugin") + end + for _, plugin in ipairs(extra.optional) do + self.text:append(" "):append(LazyConfig.options.ui.icons.plugin .. "" .. plugin, "LazyReasonRequire") + end + if extra.desc then + self.text:nl():append(" " .. extra.desc, "LazyComment") + end + self.text:nl() +end + +---@param opts {enabled?:boolean, title?:string} +function X:section(opts) + opts = opts or {} + ---@type LazyExtra[] + local extras = vim.tbl_filter(function(extra) + return opts.enabled == nil or extra.enabled == opts.enabled + end, self.extras) + + self.text:nl():append(opts.title .. ":", "LazyH2"):append(" (" .. #extras .. ")", "LazyComment"):nl() + for _, extra in ipairs(extras) do + self:extra(extra) + end +end + +function M.show() + return X.new() +end + +return M diff --git a/lua/lazyvim/util/format.lua b/lua/lazyvim/util/format.lua new file mode 100644 index 0000000..fa97229 --- /dev/null +++ b/lua/lazyvim/util/format.lua @@ -0,0 +1,173 @@ +local Util = require("lazyvim.util") + +---@class lazyvim.util.format +---@overload fun(opts?: {force?:boolean}) +local M = setmetatable({}, { + __call = function(m, ...) + return m.format(...) + end, +}) + +---@class LazyFormatter +---@field name string +---@field primary? boolean +---@field format fun(bufnr:number) +---@field sources fun(bufnr:number):string[] +---@field priority number + +M.formatters = {} ---@type LazyFormatter[] + +---@param formatter LazyFormatter +function M.register(formatter) + M.formatters[#M.formatters + 1] = formatter + table.sort(M.formatters, function(a, b) + return a.priority > b.priority + end) +end + +function M.formatexpr() + if Util.has("conform.nvim") then + return require("conform").formatexpr() + end + return vim.lsp.formatexpr({ timeout_ms = 3000 }) +end + +---@param buf? number +---@return (LazyFormatter|{active:boolean,resolved:string[]})[] +function M.resolve(buf) + buf = buf or vim.api.nvim_get_current_buf() + local have_primary = false + ---@param formatter LazyFormatter + return vim.tbl_map(function(formatter) + local sources = formatter.sources(buf) + local active = #sources > 0 and (not formatter.primary or not have_primary) + have_primary = have_primary or (active and formatter.primary) or false + return setmetatable({ + active = active, + resolved = sources, + }, { __index = formatter }) + end, M.formatters) +end + +---@param buf? number +function M.info(buf) + buf = buf or vim.api.nvim_get_current_buf() + local gaf = vim.g.autoformat == nil or vim.g.autoformat + local baf = vim.b[buf].autoformat + local enabled = M.enabled(buf) + local lines = { + "# Status", + ("- [%s] global **%s**"):format(gaf and "x" or " ", gaf and "enabled" or "disabled"), + ("- [%s] buffer **%s**"):format( + enabled and "x" or " ", + baf == nil and "inherit" or baf and "enabled" or "disabled" + ), + } + local have = false + for _, formatter in ipairs(M.resolve(buf)) do + if #formatter.resolved > 0 then + have = true + lines[#lines + 1] = "\n# " .. formatter.name .. (formatter.active and " ***(active)***" or "") + for _, line in ipairs(formatter.resolved) do + lines[#lines + 1] = ("- [%s] **%s**"):format(formatter.active and "x" or " ", line) + end + end + end + if not have then + lines[#lines + 1] = "\n***No formatters available for this buffer.***" + end + Util[enabled and "info" or "warn"]( + table.concat(lines, "\n"), + { title = "LazyFormat (" .. (enabled and "enabled" or "disabled") .. ")" } + ) +end + +---@param buf? number +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 buf? boolean +function M.toggle(buf) + if buf then + vim.b.autoformat = not M.enabled() + else + vim.g.autoformat = not M.enabled() + vim.b.autoformat = nil + end + M.info() +end + +---@param opts? {force?:boolean, buf?:number} +function M.format(opts) + opts = opts or {} + local buf = opts.buf or vim.api.nvim_get_current_buf() + if not ((opts and opts.force) or M.enabled(buf)) then + return + end + + local done = false + for _, formatter in ipairs(M.resolve(buf)) do + if formatter.active then + done = true + Util.try(function() + return formatter.format(buf) + end, { msg = "Formatter `" .. formatter.name .. "` failed" }) + end + end + + if not done and opts and opts.force then + Util.warn("No formatter available", { title = "LazyVim" }) + end +end + +function M.health() + local Config = require("lazy.core.config") + local has_plugin = Config.spec.plugins["none-ls.nvim"] + local has_extra = vim.tbl_contains(Config.spec.modules, "lazyvim.plugins.extras.lsp.none-ls") + if has_plugin and not has_extra then + Util.warn({ + "`conform.nvim` and `nvim-lint` are now the default formatters and linters in LazyVim.", + "", + "You can use those plugins together with `none-ls.nvim`,", + "but you need to enable the `lazyvim.plugins.extras.lsp.none-ls` extra,", + "for formatting to work correctly.", + "", + "In case you no longer want to use `none-ls.nvim`, just remove the spec from your config.", + }) + end +end + +function M.setup() + M.health() + + -- Autoformat autocmd + vim.api.nvim_create_autocmd("BufWritePre", { + group = vim.api.nvim_create_augroup("LazyFormat", {}), + callback = function(event) + M.format({ buf = event.buf }) + end, + }) + + -- Manual format + vim.api.nvim_create_user_command("LazyFormat", function() + M.format({ force = true }) + end, { desc = "Format selection or buffer" }) + + -- Format info + vim.api.nvim_create_user_command("LazyFormatInfo", function() + M.info() + end, { desc = "Show info about the formatters for the current buffer" }) +end + +return M diff --git a/lua/lazyvim/util/init.lua b/lua/lazyvim/util/init.lua new file mode 100644 index 0000000..24fd0a1 --- /dev/null +++ b/lua/lazyvim/util/init.lua @@ -0,0 +1,171 @@ +local LazyUtil = require("lazy.core.util") + +---@class lazyvim.util: LazyUtilCore +---@field ui lazyvim.util.ui +---@field lsp lazyvim.util.lsp +---@field root lazyvim.util.root +---@field telescope lazyvim.util.telescope +---@field terminal lazyvim.util.terminal +---@field toggle lazyvim.util.toggle +---@field format lazyvim.util.format +---@field plugin lazyvim.util.plugin +---@field extras lazyvim.util.extras +---@field inject lazyvim.util.inject +---@field news lazyvim.util.news +---@field json lazyvim.util.json +---@field lualine lazyvim.util.lualine +local M = {} + +---@type table +local deprecated = { + get_clients = "lsp", + on_attach = "lsp", + on_rename = "lsp", + root_patterns = { "root", "patterns" }, + get_root = { "root", "get" }, + float_term = { "terminal", "open" }, + toggle_diagnostics = { "toggle", "diagnostics" }, + toggle_number = { "toggle", "number" }, + fg = "ui", +} + +setmetatable(M, { + __index = function(t, k) + if LazyUtil[k] then + return LazyUtil[k] + end + local dep = deprecated[k] + if dep then + local mod = type(dep) == "table" and dep[1] or dep + local key = type(dep) == "table" and dep[2] or k + M.deprecate([[require("lazyvim.util").]] .. k, [[require("lazyvim.util").]] .. mod .. "." .. key) + ---@diagnostic disable-next-line: no-unknown + t[mod] = require("lazyvim.util." .. mod) -- load here to prevent loops + return t[mod][key] + end + ---@diagnostic disable-next-line: no-unknown + t[k] = require("lazyvim.util." .. k) + return t[k] + end, +}) + +function M.is_win() + return vim.loop.os_uname().sysname:find("Windows") ~= nil +end + +---@param plugin string +function M.has(plugin) + return require("lazy.core.config").spec.plugins[plugin] ~= nil +end + +---@param fn fun() +function M.on_very_lazy(fn) + vim.api.nvim_create_autocmd("User", { + pattern = "VeryLazy", + callback = function() + fn() + end, + }) +end + +---@param name string +function M.opts(name) + local plugin = require("lazy.core.config").plugins[name] + if not plugin then + return {} + end + local Plugin = require("lazy.core.plugin") + return Plugin.values(plugin, "opts", false) +end + +function M.deprecate(old, new) + M.warn(("`%s` is deprecated. Please use `%s` instead"):format(old, new), { + title = "LazyVim", + once = true, + stacktrace = true, + stacklevel = 6, + }) +end + +-- delay notifications till vim.notify was replaced or after 500ms +function M.lazy_notify() + local notifs = {} + local function temp(...) + table.insert(notifs, vim.F.pack_len(...)) + end + + local orig = vim.notify + vim.notify = temp + + local timer = vim.loop.new_timer() + local check = assert(vim.loop.new_check()) + + local replay = function() + timer:stop() + check:stop() + if vim.notify == temp then + vim.notify = orig -- put back the original notify if needed + end + vim.schedule(function() + ---@diagnostic disable-next-line: no-unknown + for _, notif in ipairs(notifs) do + vim.notify(vim.F.unpack_len(notif)) + end + end) + end + + -- wait till vim.notify has been replaced + check:start(function() + if vim.notify ~= temp then + replay() + end + end) + -- or if it took more than 500ms, then something went wrong + timer:start(500, 0, replay) +end + +---@param name string +---@param fn fun(name:string) +function M.on_load(name, fn) + local Config = require("lazy.core.config") + if Config.plugins[name] and Config.plugins[name]._.loaded then + fn(name) + else + vim.api.nvim_create_autocmd("User", { + pattern = "LazyLoad", + callback = function(event) + if event.data == name then + fn(name) + return true + end + end, + }) + end +end + +-- Wrapper around vim.keymap.set that will +-- not create a keymap if a lazy key handler exists. +-- It will also set `silent` to true by default. +function M.safe_keymap_set(mode, lhs, rhs, opts) + local keys = require("lazy.core.handler").handlers.keys + ---@cast keys LazyKeysHandler + local modes = type(mode) == "string" and { mode } or mode + + ---@param m string + modes = vim.tbl_filter(function(m) + return not (keys.have and keys:have(lhs, m)) + end, modes) + + -- do not create the keymap if a lazy keys handler exists + if #modes > 0 then + opts = opts or {} + opts.silent = opts.silent ~= false + if opts.remap and not vim.g.vscode then + ---@diagnostic disable-next-line: no-unknown + opts.remap = nil + end + vim.keymap.set(modes, lhs, rhs, opts) + end +end + +return M diff --git a/lua/lazyvim/util/inject.lua b/lua/lazyvim/util/inject.lua new file mode 100644 index 0000000..81e0f57 --- /dev/null +++ b/lua/lazyvim/util/inject.lua @@ -0,0 +1,34 @@ +---@class lazyvim.util.inject +local M = {} + +---@generic A: any +---@generic B: any +---@generic C: any +---@generic F: function +---@param fn F|fun(a:A, b:B, c:C) +---@param wrapper fun(a:A, b:B, c:C): boolean? +---@return F +function M.args(fn, wrapper) + return function(...) + if wrapper(...) == false then + return + end + return fn(...) + end +end + +function M.get_upvalue(func, name) + local i = 1 + while true do + local n, v = debug.getupvalue(func, i) + if not n then + break + end + if n == name then + return v + end + i = i + 1 + end +end + +return M diff --git a/lua/lazyvim/util/json.lua b/lua/lazyvim/util/json.lua new file mode 100644 index 0000000..d98d038 --- /dev/null +++ b/lua/lazyvim/util/json.lua @@ -0,0 +1,82 @@ +local Config = require("lazyvim.config") +local Util = require("lazyvim.util") + +---@class lazyvim.util.json +local M = {} + +---@param value any +---@param indent string +local function encode(value, indent) + local t = type(value) + + if t == "string" then + return string.format("%q", value) + elseif t == "number" or t == "boolean" then + return tostring(value) + elseif t == "table" then + local is_list = Util.is_list(value) + local parts = {} + local next_indent = indent .. " " + + if is_list then + ---@diagnostic disable-next-line: no-unknown + for _, v in ipairs(value) do + local e = encode(v, next_indent) + if e then + table.insert(parts, next_indent .. e) + end + end + return "[\n" .. table.concat(parts, ",\n") .. "\n" .. indent .. "]" + else + local keys = vim.tbl_keys(value) + table.sort(keys) + ---@diagnostic disable-next-line: no-unknown + for _, k in ipairs(keys) do + local e = encode(value[k], next_indent) + if e then + table.insert(parts, next_indent .. string.format("%q", k) .. ": " .. e) + end + end + return "{\n" .. table.concat(parts, ",\n") .. "\n" .. indent .. "}" + end + end +end + +function M.encode(value) + return encode(value, "") +end + +function M.save() + Config.json.data.version = Config.json.version + local path = vim.fn.stdpath("config") .. "/lazyvim.json" + local f = io.open(path, "w") + if f then + f:write(Util.json.encode(Config.json.data)) + f:close() + end +end + +function M.migrate() + Util.info("Migrating `lazyvim.json` to version `" .. Config.json.version .. "`") + local json = Config.json + + -- v0 + if not json.data.version then + if json.data.hashes then + ---@diagnostic disable-next-line: no-unknown + json.data.hashes = nil + end + json.data.extras = vim.tbl_map(function(extra) + return "lazyvim.plugins.extras." .. extra + end, json.data.extras or {}) + elseif json.data.version == 1 then + json.data.extras = vim.tbl_map(function(extra) + -- replace double extras module name + return extra:gsub("^lazyvim%.plugins%.extras%.lazyvim%.plugins%.extras%.", "lazyvim.plugins.extras.") + end, json.data.extras or {}) + end + + M.save() +end + +return M diff --git a/lua/lazyvim/util/lsp.lua b/lua/lazyvim/util/lsp.lua new file mode 100644 index 0000000..b34a2af --- /dev/null +++ b/lua/lazyvim/util/lsp.lua @@ -0,0 +1,125 @@ +local Util = require("lazyvim.util") + +---@class lazyvim.util.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 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 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 + +---@param on_attach fun(client, buffer) +function M.on_attach(on_attach) + vim.api.nvim_create_autocmd("LspAttach", { + callback = function(args) + local buffer = args.buf ---@type number + local client = vim.lsp.get_client_by_id(args.data.client_id) + on_attach(client, buffer) + end, + }) +end + +---@param from string +---@param to string +function M.on_rename(from, to) + local clients = M.get_clients() + for _, client in ipairs(clients) do + if client.supports_method("workspace/willRenameFiles") then + ---@diagnostic disable-next-line: invisible + local resp = client.request_sync("workspace/willRenameFiles", { + files = { + { + oldUri = vim.uri_from_fname(from), + newUri = vim.uri_from_fname(to), + }, + }, + }, 1000, 0) + if resp and resp.result ~= nil then + vim.lsp.util.apply_workspace_edit(resp.result, client.offset_encoding) + end + end + end +end + +---@return _.lspconfig.options +function M.get_config(server) + local configs = require("lspconfig.configs") + return rawget(configs, server) +end + +---@param server string +---@param cond fun( root_dir, config): boolean +function M.disable(server, cond) + local util = require("lspconfig.util") + local def = M.get_config(server) + ---@diagnostic disable-next-line: undefined-field + def.document_config.on_new_config = util.add_hook_before(def.document_config.on_new_config, function(config, root_dir) + if cond(root_dir, config) then + config.enabled = false + end + end) +end + +---@param opts? LazyFormatter| {filter?: (string|lsp.Client.filter)} +function M.formatter(opts) + opts = opts or {} + local filter = opts.filter or {} + filter = type(filter) == "string" and { name = filter } or filter + ---@cast filter lsp.Client.filter + ---@type LazyFormatter + local ret = { + name = "LSP", + primary = true, + priority = 1, + format = function(buf) + M.format(Util.merge(filter, { bufnr = buf })) + end, + sources = function(buf) + local clients = M.get_clients(Util.merge(filter, { bufnr = buf })) + ---@param client lsp.Client + local ret = vim.tbl_filter(function(client) + return client.supports_method("textDocument/formatting") + or client.supports_method("textDocument/rangeFormatting") + end, clients) + ---@param client lsp.Client + return vim.tbl_map(function(client) + return client.name + end, ret) + end, + } + return Util.merge(ret, opts) --[[@as LazyFormatter]] +end + +---@alias lsp.Client.format {timeout_ms?: number, format_options?: table} | lsp.Client.filter + +---@param opts? lsp.Client.format +function M.format(opts) + opts = vim.tbl_deep_extend("force", {}, opts or {}, require("lazyvim.util").opts("nvim-lspconfig").format or {}) + local ok, conform = pcall(require, "conform") + -- use conform for formatting with LSP when available, + -- since it has better format diffing + if ok then + opts.formatters = {} + opts.lsp_fallback = true + conform.format(opts) + else + vim.lsp.buf.format(opts) + end +end + +return M diff --git a/lua/lazyvim/util/lualine.lua b/lua/lazyvim/util/lualine.lua new file mode 100644 index 0000000..3e3cdb9 --- /dev/null +++ b/lua/lazyvim/util/lualine.lua @@ -0,0 +1,143 @@ +local Util = require("lazyvim.util") + +---@class lazyvim.util.lualine +local M = {} + +function M.cmp_source(name, icon) + local started = false + local function status() + if not package.loaded["cmp"] then + return + end + for _, s in ipairs(require("cmp").core.sources) do + if s.name == name then + if s.source:is_available() then + started = true + else + return started and "error" or nil + end + if s.status == s.SourceStatus.FETCHING then + return "pending" + end + return "ok" + end + end + end + + local colors = { + ok = Util.ui.fg("Special"), + error = Util.ui.fg("DiagnosticError"), + pending = Util.ui.fg("DiagnosticWarn"), + } + + return { + function() + return icon or require("lazyvim.config").icons.kinds[name:sub(1, 1):upper() .. name:sub(2)] + end, + cond = function() + return status() ~= nil + end, + color = function() + return colors[status()] or colors.ok + end, + } +end + +---@param component any +---@param text string +---@param hl_group? string +---@return string +function M.format(component, text, hl_group) + if not hl_group then + return text + end + ---@type table + component.hl_cache = component.hl_cache or {} + local lualine_hl_group = component.hl_cache[hl_group] + if not lualine_hl_group then + local utils = require("lualine.utils.utils") + lualine_hl_group = component:create_hl({ fg = utils.extract_highlight_colors(hl_group, "fg") }, "LV_" .. hl_group) + component.hl_cache[hl_group] = lualine_hl_group + end + return component:format_hl(lualine_hl_group) .. text .. component:get_default_hl() +end + +---@param opts? {relative: "cwd"|"root", modified_hl: string?} +function M.pretty_path(opts) + opts = vim.tbl_extend("force", { + relative = "cwd", + modified_hl = "Constant", + }, opts or {}) + + return function(self) + local path = vim.fn.expand("%:p") --[[@as string]] + + if path == "" then + return "" + end + local root = Util.root.get({ normalize = true }) + local cwd = Util.root.cwd() + + if opts.relative == "cwd" and path:find(cwd, 1, true) == 1 then + path = path:sub(#cwd + 2) + else + path = path:sub(#root + 2) + end + + local sep = package.config:sub(1, 1) + local parts = vim.split(path, "[\\/]") + if #parts > 3 then + parts = { parts[1], "…", parts[#parts - 1], parts[#parts] } + end + + if opts.modified_hl and vim.bo.modified then + parts[#parts] = M.format(self, parts[#parts], opts.modified_hl) + end + + return table.concat(parts, sep) + end +end + +---@param opts? {cwd:false, subdirectory: true, parent: true, other: true, icon?:string} +function M.root_dir(opts) + opts = vim.tbl_extend("force", { + cwd = false, + subdirectory = true, + parent = true, + other = true, + icon = "󱉭 ", + color = Util.ui.fg("Special"), + }, opts or {}) + + local function get() + local cwd = Util.root.cwd() + local root = Util.root.get({ normalize = true }) + local name = vim.fs.basename(root) + + if root == cwd then + -- root is cwd + return opts.cwd and name + elseif root:find(cwd, 1, true) == 1 then + -- root is subdirectory of cwd + return opts.subdirectory and name + elseif cwd:find(root, 1, true) == 1 then + -- root is parent directory of cwd + return opts.parent and name + else + -- root and cwd are not related + return opts.other and name + end + end + + return { + function() + return (opts.icon and opts.icon .. " ") .. get() + end, + cond = function() + return type(get()) == "string" + end, + color = opts.color, + } +end + +return M diff --git a/lua/lazyvim/util/news.lua b/lua/lazyvim/util/news.lua new file mode 100644 index 0000000..4c21247 --- /dev/null +++ b/lua/lazyvim/util/news.lua @@ -0,0 +1,90 @@ +local Config = require("lazyvim.config") +local Util = require("lazyvim.util") + +---@class lazyvim.util.news +local M = {} + +function M.hash(file) + local stat = vim.loop.fs_stat(file) + if not stat then + return + end + return stat.size .. "" +end + +function M.setup() + vim.schedule(function() + if Config.news.lazyvim then + if not Config.json.data.news["NEWS.md"] then + M.welcome() + end + M.lazyvim(true) + end + if Config.news.neovim then + M.neovim(true) + end + end) +end + +function M.welcome() + Util.info("Welcome to LazyVim!") +end + +function M.changelog() + M.open("CHANGELOG.md", { plugin = "LazyVim" }) +end + +function M.lazyvim(when_changed) + M.open("NEWS.md", { plugin = "LazyVim", when_changed = when_changed }) +end + +function M.neovim(when_changed) + M.open("doc/news.txt", { rtp = true, when_changed = when_changed }) +end + +---@param file string +---@param opts? {plugin?:string, rtp?:boolean, when_changed?:boolean} +function M.open(file, opts) + local ref = file + opts = opts or {} + if opts.plugin then + local plugin = require("lazy.core.config").plugins[opts.plugin] --[[@as LazyPlugin?]] + if not plugin then + return Util.error("plugin not found: " .. opts.plugin) + end + file = plugin.dir .. "/" .. file + elseif opts.rtp then + file = vim.api.nvim_get_runtime_file(file, false)[1] + end + + if not file then + return Util.error("File not found") + end + + if opts.when_changed then + local is_new = not Config.json.data.news[ref] + local hash = M.hash(file) + if hash == Config.json.data.news[ref] then + return + end + Config.json.data.news[ref] = hash + Util.json.save() + -- don't open if file has never been opened + if is_new then + return + end + end + + local float = require("lazy.util").float({ + file = file, + size = { width = 0.6, height = 0.6 }, + }) + vim.opt_local.spell = false + vim.opt_local.wrap = false + vim.opt_local.signcolumn = "yes" + vim.opt_local.statuscolumn = " " + vim.opt_local.conceallevel = 3 + vim.diagnostic.disable(float.buf) +end + +return M diff --git a/lua/lazyvim/util/plugin.lua b/lua/lazyvim/util/plugin.lua new file mode 100644 index 0000000..7ab6913 --- /dev/null +++ b/lua/lazyvim/util/plugin.lua @@ -0,0 +1,156 @@ +local Plugin = require("lazy.core.plugin") +local Util = require("lazyvim.util") + +---@class lazyvim.util.plugin +local M = {} + +M.use_lazy_file = true +M.lazy_file_events = { "BufReadPost", "BufNewFile", "BufWritePre" } + +---@type table +M.deprecated_extras = { + ["lazyvim.plugins.extras.formatting.conform"] = "`conform.nvim` is now the default **LazyVim** formatter.", + ["lazyvim.plugins.extras.linting.nvim-lint"] = "`nvim-lint` is now the default **LazyVim** linter.", + ["lazyvim.plugins.extras.ui.dashboard"] = "`dashboard.nvim` is now the default **LazyVim** starter.", +} + +M.deprecated_modules = { + ["null-ls"] = "lsp.none-ls", + ["nvim-navic.lib"] = "editor.navic", + ["nvim-navic"] = "editor.navic", +} + +---@type table +M.renames = { + ["windwp/nvim-spectre"] = "nvim-pack/nvim-spectre", + ["jose-elias-alvarez/null-ls.nvim"] = "nvimtools/none-ls.nvim", + ["null-ls.nvim"] = "none-ls.nvim", + ["romgrk/nvim-treesitter-context"] = "nvim-treesitter/nvim-treesitter-context", + ["glepnir/dashboard-nvim"] = "nvimdev/dashboard-nvim", +} + +function M.setup() + M.fix_imports() + M.fix_renames() + M.lazy_file() + table.insert(package.loaders, function(module) + if M.deprecated_modules[module] then + Util.warn( + ("`%s` is no longer included by default in **LazyVim**.\nPlease install the `%s` extra if you still want to use it."):format( + module, + M.deprecated_modules[module] + ), + { title = "LazyVim" } + ) + return function() end + end + end) +end + +function M.extra_idx(name) + local Config = require("lazy.core.config") + for i, extra in ipairs(Config.spec.modules) do + if extra == "lazyvim.plugins.extras." .. name then + return i + end + end +end + +-- Properly load file based plugins without blocking the UI +function M.lazy_file() + M.use_lazy_file = M.use_lazy_file and vim.fn.argc(-1) > 0 + + -- Add support for the LazyFile event + local Event = require("lazy.core.handler.event") + + if M.use_lazy_file then + -- We'll handle delayed execution of events ourselves + Event.mappings.LazyFile = { id = "LazyFile", event = "User", pattern = "LazyFile" } + Event.mappings["User LazyFile"] = Event.mappings.LazyFile + else + -- Don't delay execution of LazyFile events, but let lazy know about the mapping + Event.mappings.LazyFile = { id = "LazyFile", event = { "BufReadPost", "BufNewFile", "BufWritePre" } } + Event.mappings["User LazyFile"] = Event.mappings.LazyFile + return + end + + local events = {} ---@type {event: string, buf: number, data?: any}[] + + local done = false + local function load() + if #events == 0 or done then + return + end + done = true + vim.api.nvim_del_augroup_by_name("lazy_file") + + ---@type table + local skips = {} + for _, event in ipairs(events) do + skips[event.event] = skips[event.event] or Event.get_augroups(event.event) + end + + vim.api.nvim_exec_autocmds("User", { pattern = "LazyFile", modeline = false }) + for _, event in ipairs(events) do + if vim.api.nvim_buf_is_valid(event.buf) then + Event.trigger({ + event = event.event, + exclude = skips[event.event], + data = event.data, + buf = event.buf, + }) + if vim.bo[event.buf].filetype then + Event.trigger({ + event = "FileType", + buf = event.buf, + }) + end + end + end + vim.api.nvim_exec_autocmds("CursorMoved", { modeline = false }) + events = {} + end + + -- schedule wrap so that nested autocmds are executed + -- and the UI can continue rendering without blocking + load = vim.schedule_wrap(load) + + vim.api.nvim_create_autocmd(M.lazy_file_events, { + group = vim.api.nvim_create_augroup("lazy_file", { clear = true }), + callback = function(event) + table.insert(events, event) + load() + end, + }) +end + +function M.fix_imports() + Plugin.Spec.import = Util.inject.args(Plugin.Spec.import, function(_, spec) + local dep = M.deprecated_extras[spec and spec.import] + if dep then + dep = dep .. "\n" .. "Please remove the extra to hide this warning." + Util.warn(dep, { title = "LazyVim", once = true, stacktrace = true, stacklevel = 6 }) + return false + end + end) +end + +function M.fix_renames() + Plugin.Spec.add = Util.inject.args(Plugin.Spec.add, function(self, plugin) + if type(plugin) == "table" then + if M.renames[plugin[1]] then + Util.warn( + ("Plugin `%s` was renamed to `%s`.\nPlease update your config for `%s`"):format( + plugin[1], + M.renames[plugin[1]], + self.importing or "LazyVim" + ), + { title = "LazyVim" } + ) + plugin[1] = M.renames[plugin[1]] + end + end + end) +end + +return M diff --git a/lua/lazyvim/util/root.lua b/lua/lazyvim/util/root.lua new file mode 100644 index 0000000..2958197 --- /dev/null +++ b/lua/lazyvim/util/root.lua @@ -0,0 +1,181 @@ +local Util = require("lazyvim.util") + +---@class lazyvim.util.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.loop.cwd() } +end + +function M.detectors.lsp(buf) + local bufpath = M.bufpath(buf) + if not bufpath then + return {} + end + local roots = {} ---@type string[] + for _, client in pairs(Util.lsp.get_clients({ bufnr = buf })) do + -- only check workspace folders, since we're not interested in clients + -- running in single file mode + local workspace = client.config.workspace_folders + for _, ws in pairs(workspace or {}) do + roots[#roots + 1] = vim.uri_to_fname(ws.uri) + end + end + return vim.tbl_filter(function(path) + path = 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) + patterns = type(patterns) == "string" and { patterns } or patterns + local path = M.bufpath(buf) or vim.loop.cwd() + local pattern = vim.fs.find(patterns, { path = path, upward = true })[1] + return pattern and { vim.fs.dirname(pattern) } or {} +end + +function M.bufpath(buf) + return M.realpath(vim.api.nvim_buf_get_name(assert(buf))) +end + +function M.cwd() + return M.realpath(vim.loop.cwd()) or "" +end + +function M.realpath(path) + if path == "" or path == nil then + return nil + end + path = vim.loop.fs_realpath(path) or path + return Util.norm(path) +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 + +function M.info() + local spec = type(vim.g.root_spec) == "table" and vim.g.root_spec or M.spec + + local roots = M.detect({ all = true }) + local lines = {} ---@type string[] + local first = true + for _, root in ipairs(roots) do + for _, path in ipairs(root.paths) do + lines[#lines + 1] = ("- [%s] `%s` **(%s)**"):format( + first and "x" or " ", + path, + type(root.spec) == "table" and table.concat(root.spec, ", ") or root.spec + ) + first = false + end + end + lines[#lines + 1] = "```lua" + lines[#lines + 1] = "vim.g.root_spec = " .. vim.inspect(spec) + lines[#lines + 1] = "```" + require("lazyvim.util").info(lines, { title = "LazyVim Roots" }) + return roots[1] and roots[1].paths[1] or vim.loop.cwd() +end + +---@type table +M.cache = {} + +function M.setup() + vim.api.nvim_create_user_command("LazyRoot", function() + Util.root.info() + end, { desc = "LazyVim roots for the current buffer" }) + + vim.api.nvim_create_autocmd({ "LspAttach", "BufWritePost" }, { + group = vim.api.nvim_create_augroup("lazyvim_root_cache", { clear = true }), + callback = function(event) + M.cache[event.buf] = nil + end, + }) +end + +-- 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} +---@return string +function M.get(opts) + local buf = vim.api.nvim_get_current_buf() + local ret = M.cache[buf] + if not ret then + local roots = M.detect({ all = false }) + ret = roots[1] and roots[1].paths[1] or vim.loop.cwd() + M.cache[buf] = ret + end + if opts and opts.normalize then + return ret + end + return Util.is_win() and ret:gsub("/", "\\") or ret +end + +---@param opts? {hl_last?: string} +function M.pretty_path(opts) + return "" +end + +return M diff --git a/lua/lazyvim/util/telescope.lua b/lua/lazyvim/util/telescope.lua new file mode 100644 index 0000000..1d816b8 --- /dev/null +++ b/lua/lazyvim/util/telescope.lua @@ -0,0 +1,57 @@ +local Util = require("lazyvim.util") + +---@class lazyvim.util.telescope.opts +---@field cwd? string|boolean +---@field show_untracked? boolean + +---@class lazyvim.util.telescope +---@overload fun(builtin:string, opts?:lazyvim.util.telescope.opts) +local M = setmetatable({}, { + __call = function(m, ...) + return m.telescope(...) + end, +}) + +-- this will return a function that calls telescope. +-- cwd will default to lazyvim.util.get_root +-- for `files`, git_files or find_files will be chosen depending on .git +---@param builtin string +---@param opts? lazyvim.util.telescope.opts +function M.telescope(builtin, opts) + local params = { builtin = builtin, opts = opts } + return function() + builtin = params.builtin + opts = params.opts + opts = vim.tbl_deep_extend("force", { cwd = Util.root() }, opts or {}) --[[@as lazyvim.util.telescope.opts]] + if builtin == "files" then + if vim.loop.fs_stat((opts.cwd or vim.loop.cwd()) .. "/.git") then + opts.show_untracked = true + builtin = "git_files" + else + builtin = "find_files" + end + end + if opts.cwd and opts.cwd ~= vim.loop.cwd() then + ---@diagnostic disable-next-line: inject-field + opts.attach_mappings = function(_, map) + map("i", "", function() + local action_state = require("telescope.actions.state") + local line = action_state.get_current_line() + M.telescope( + params.builtin, + vim.tbl_deep_extend("force", {}, params.opts or {}, { cwd = false, default_text = line }) + )() + end) + return true + end + end + + require("telescope.builtin")[builtin](opts) + end +end + +function M.config_files() + return Util.telescope("find_files", { cwd = vim.fn.stdpath("config") }) +end + +return M diff --git a/lua/lazyvim/util/terminal.lua b/lua/lazyvim/util/terminal.lua new file mode 100644 index 0000000..7787615 --- /dev/null +++ b/lua/lazyvim/util/terminal.lua @@ -0,0 +1,55 @@ +---@class lazyvim.util.terminal +---@overload fun(cmd: string|string[], opts: LazyTermOpts): LazyFloat +local M = setmetatable({}, { + __call = function(m, ...) + return m.open(...) + end, +}) + +---@type table +local terminals = {} + +---@class LazyTermOpts: LazyCmdOptions +---@field interactive? boolean +---@field esc_esc? boolean +---@field ctrl_hjkl? boolean + +-- Opens a floating terminal (interactive by default) +---@param cmd? string[]|string +---@param opts? LazyTermOpts +function M.open(cmd, opts) + opts = vim.tbl_deep_extend("force", { + ft = "lazyterm", + size = { width = 0.9, height = 0.9 }, + }, opts or {}, { persistent = true }) --[[@as LazyTermOpts]] + + local termkey = vim.inspect({ cmd = cmd or "shell", cwd = opts.cwd, env = opts.env, count = vim.v.count1 }) + + if terminals[termkey] and terminals[termkey]:buf_valid() then + terminals[termkey]:toggle() + else + terminals[termkey] = require("lazy.util").float_term(cmd, opts) + local buf = terminals[termkey].buf + vim.b[buf].lazyterm_cmd = cmd + if opts.esc_esc == false then + vim.keymap.set("t", "", "", { buffer = buf, nowait = true }) + end + if opts.ctrl_hjkl == false then + vim.keymap.set("t", "", "", { buffer = buf, nowait = true }) + vim.keymap.set("t", "", "", { buffer = buf, nowait = true }) + vim.keymap.set("t", "", "", { buffer = buf, nowait = true }) + vim.keymap.set("t", "", "", { buffer = buf, nowait = true }) + end + + vim.api.nvim_create_autocmd("BufEnter", { + buffer = buf, + callback = function() + vim.cmd.startinsert() + end, + }) + end + + return terminals[termkey] +end + +return M diff --git a/lua/lazyvim/util/toggle.lua b/lua/lazyvim/util/toggle.lua new file mode 100644 index 0000000..db41c82 --- /dev/null +++ b/lua/lazyvim/util/toggle.lua @@ -0,0 +1,76 @@ +local Util = require("lazyvim.util") + +---@class lazyvim.util.toggle +local M = {} + +---@param silent boolean? +---@param values? {[1]:any, [2]:any} +function M.option(option, silent, values) + if values then + if vim.opt_local[option]:get() == values[1] then + ---@diagnostic disable-next-line: no-unknown + vim.opt_local[option] = values[2] + else + ---@diagnostic disable-next-line: no-unknown + vim.opt_local[option] = values[1] + end + return Util.info("Set " .. option .. " to " .. vim.opt_local[option]:get(), { title = "Option" }) + end + ---@diagnostic disable-next-line: no-unknown + vim.opt_local[option] = not vim.opt_local[option]:get() + if not silent then + if vim.opt_local[option]:get() then + Util.info("Enabled " .. option, { title = "Option" }) + else + Util.warn("Disabled " .. option, { title = "Option" }) + end + end +end + +local nu = { number = true, relativenumber = true } +function M.number() + if vim.opt_local.number:get() or vim.opt_local.relativenumber:get() then + nu = { number = vim.opt_local.number:get(), relativenumber = vim.opt_local.relativenumber:get() } + vim.opt_local.number = false + vim.opt_local.relativenumber = false + Util.warn("Disabled line numbers", { title = "Option" }) + else + vim.opt_local.number = nu.number + vim.opt_local.relativenumber = nu.relativenumber + Util.info("Enabled line numbers", { title = "Option" }) + end +end + +local enabled = true +function M.diagnostics() + enabled = not enabled + if enabled then + vim.diagnostic.enable() + Util.info("Enabled diagnostics", { title = "Diagnostics" }) + else + vim.diagnostic.disable() + Util.warn("Disabled diagnostics", { title = "Diagnostics" }) + end +end + +---@param buf? number +---@param value? boolean +function M.inlay_hints(buf, value) + local ih = vim.lsp.buf.inlay_hint or vim.lsp.inlay_hint + if type(ih) == "function" then + ih(buf, value) + elseif type(ih) == "table" and ih.enable then + if value == nil then + value = not ih.is_enabled(buf) + end + ih.enable(buf, value) + end +end + +setmetatable(M, { + __call = function(m, ...) + return m.option(...) + end, +}) + +return M diff --git a/lua/lazyvim/util/ui.lua b/lua/lazyvim/util/ui.lua new file mode 100644 index 0000000..ce8b478 --- /dev/null +++ b/lua/lazyvim/util/ui.lua @@ -0,0 +1,189 @@ +---@class lazyvim.util.ui +local M = {} + +---@alias Sign {name:string, text:string, texthl:string, priority:number} + +-- Returns a list of regular and extmark signs sorted by priority (low to high) +---@return Sign[] +---@param buf number +---@param lnum number +function M.get_signs(buf, lnum) + -- Get regular signs + ---@type Sign[] + local signs = {} + + if vim.fn.has("nvim-0.10") == 0 then + -- Only needed for Neovim <0.10 + -- Newer versions include legacy signs in nvim_buf_get_extmarks + for _, sign in ipairs(vim.fn.sign_getplaced(buf, { group = "*", lnum = lnum })[1].signs) do + local ret = vim.fn.sign_getdefined(sign.name)[1] --[[@as Sign]] + if ret then + ret.priority = sign.priority + signs[#signs + 1] = ret + end + end + end + + -- Get extmark signs + local extmarks = vim.api.nvim_buf_get_extmarks( + buf, + -1, + { lnum - 1, 0 }, + { lnum - 1, -1 }, + { details = true, type = "sign" } + ) + for _, extmark in pairs(extmarks) do + signs[#signs + 1] = { + name = extmark[4].sign_hl_group or "", + text = extmark[4].sign_text, + texthl = extmark[4].sign_hl_group, + priority = extmark[4].priority, + } + end + + -- Sort by priority + table.sort(signs, function(a, b) + return (a.priority or 0) < (b.priority or 0) + end) + + return signs +end + +---@return Sign? +---@param buf number +---@param lnum number +function M.get_mark(buf, lnum) + local marks = vim.fn.getmarklist(buf) + vim.list_extend(marks, vim.fn.getmarklist()) + for _, mark in ipairs(marks) do + if mark.pos[1] == buf and mark.pos[2] == lnum and mark.mark:match("[a-zA-Z]") then + return { text = mark.mark:sub(2), texthl = "DiagnosticHint" } + end + end +end + +---@param sign? Sign +---@param len? number +function M.icon(sign, len) + sign = sign or {} + len = len or 2 + local text = vim.fn.strcharpart(sign.text or "", 0, len) ---@type string + text = text .. string.rep(" ", len - vim.fn.strchars(text)) + return sign.texthl and ("%#" .. sign.texthl .. "#" .. text .. "%*") or text +end + +function M.foldtext() + local ok = pcall(vim.treesitter.get_parser, vim.api.nvim_get_current_buf()) + local ret = ok and vim.treesitter.foldtext and vim.treesitter.foldtext() + if not ret or type(ret) == "string" then + ret = { { vim.api.nvim_buf_get_lines(0, vim.v.lnum - 1, vim.v.lnum, false)[1], {} } } + end + table.insert(ret, { " " .. require("lazyvim.config").icons.misc.dots }) + + if not vim.treesitter.foldtext then + return table.concat( + vim.tbl_map(function(line) + return line[1] + end, ret), + " " + ) + end + return ret +end + +function M.statuscolumn() + local win = vim.g.statusline_winid + local buf = vim.api.nvim_win_get_buf(win) + local is_file = vim.bo[buf].buftype == "" + local show_signs = vim.wo[win].signcolumn ~= "no" + + local components = { "", "", "" } -- left, middle, right + + if show_signs then + ---@type Sign?,Sign?,Sign? + local left, right, fold + for _, s in ipairs(M.get_signs(buf, vim.v.lnum)) do + if s.name and s.name:find("GitSign") then + right = s + else + left = s + end + end + if vim.v.virtnum ~= 0 then + left = nil + end + vim.api.nvim_win_call(win, function() + if vim.fn.foldclosed(vim.v.lnum) >= 0 then + fold = { text = vim.opt.fillchars:get().foldclose or "", texthl = "Folded" } + end + end) + -- Left: mark or non-git sign + components[1] = M.icon(M.get_mark(buf, vim.v.lnum) or left) + -- Right: fold icon or git sign (only if file) + components[3] = is_file and M.icon(fold or right) or "" + end + + -- Numbers in Neovim are weird + -- They show when either number or relativenumber is true + local is_num = vim.wo[win].number + local is_relnum = vim.wo[win].relativenumber + if (is_num or is_relnum) and vim.v.virtnum == 0 then + if vim.v.relnum == 0 then + components[2] = is_num and "%l" or "%r" -- the current line + else + components[2] = is_relnum and "%r" or "%l" -- other lines + end + components[2] = "%=" .. components[2] .. " " -- right align + end + + return table.concat(components, "") +end + +function M.fg(name) + ---@type {foreground?:number}? + ---@diagnostic disable-next-line: deprecated + local hl = vim.api.nvim_get_hl and vim.api.nvim_get_hl(0, { name = name }) or vim.api.nvim_get_hl_by_name(name, true) + ---@diagnostic disable-next-line: undefined-field + local fg = hl and (hl.fg or hl.foreground) + return fg and { fg = string.format("#%06x", fg) } or nil +end + +M.skip_foldexpr = {} ---@type table +local skip_check = assert(vim.loop.new_check()) + +function M.foldexpr() + local buf = vim.api.nvim_get_current_buf() + + -- still in the same tick and no parser + if M.skip_foldexpr[buf] then + return "0" + end + + -- don't use treesitter folds for non-file buffers + if vim.bo[buf].buftype ~= "" then + return "0" + end + + -- as long as we don't have a filetype, don't bother + -- checking if treesitter is available (it won't) + if vim.bo[buf].filetype == "" then + return "0" + end + + local ok = pcall(vim.treesitter.get_parser, buf) + + if ok then + return vim.treesitter.foldexpr() + end + + -- no parser available, so mark it as skip + -- in the next tick, all skip marks will be reset + M.skip_foldexpr[buf] = true + skip_check:start(function() + M.skip_foldexpr = {} + skip_check:stop() + end) + return "0" +end + +return M diff --git a/lua/plex/config/keymaps.lua b/lua/plex/config/keymaps.lua index a13dd49..08df334 100644 --- a/lua/plex/config/keymaps.lua +++ b/lua/plex/config/keymaps.lua @@ -32,8 +32,9 @@ map('n', 'l', 'Lazy', { desc = 'Open Lazy UI' }) -- Move faster between lines -- See vim-smoothie -map({ 'n', 'x' }, 'K', "") -map({ 'n', 'x' }, 'J', "") +map({ 'n', 'x', 'v' }, 'K', "K") -- K normally looks a keyword up +map({ 'n', 'x', 'v' }, 'K', "") +map({ 'n', 'x', 'v' }, 'J', "") -- Easier line-wise movement map({'n', 'v'}, 'H', '') diff --git a/lua/plex/plugins/editor.lua b/lua/plex/plugins/editor.lua index a0d392a..48c1b75 100644 --- a/lua/plex/plugins/editor.lua +++ b/lua/plex/plugins/editor.lua @@ -392,33 +392,6 @@ return { }, }, - ----------------------------------------------------------------------------- - { - 'simrat39/symbols-outline.nvim', - cmd = { 'SymbolsOutline', 'SymbolsOutlineOpen' }, - keys = { - { 'o', 'SymbolsOutline', desc = 'Symbols Outline' }, - }, - opts = { - width = 30, - autofold_depth = 0, - keymaps = { - hover_symbol = 'K', - toggle_preview = 'p', - }, - }, - init = function() - vim.api.nvim_create_autocmd('FileType', { - group = vim.api.nvim_create_augroup('plex_outline', {}), - pattern = 'Outline', - callback = function() - vim.opt_local.winhighlight = 'CursorLine:WildMenu' - vim.opt_local.signcolumn = 'auto' - end, - }) - end, - }, - ----------------------------------------------------------------------------- { 's1n7ax/nvim-window-picker', diff --git a/lua/plex/plugins/extras/formatting/prettier.lua b/lua/plex/plugins/extras/formatting/prettier.lua index 5b3871d..dd805ff 100644 --- a/lua/plex/plugins/extras/formatting/prettier.lua +++ b/lua/plex/plugins/extras/formatting/prettier.lua @@ -33,15 +33,7 @@ return { require("formatter.filetypes.any").remove_trailing_whitespace, }, text = { require("formatter.defaults.prettierd") }, - rust = { - rustfmt = function() - return { - exe = "rustfmt", - args = { "--emit=std ut" }, - stdin = true, - } - end, - }, + rust = { require("formatter.filetypes.rust").rustfmt }, cpp = { require("formatter.filetypes.cpp").clangformat }, css = { require("formatter.filetypes.css").prettierd }, html = { require("formatter.filetypes.html").prettierd }, diff --git a/lua/plex/plugins/extras/ui/minimap.lua b/lua/plex/plugins/extras/ui/minimap.lua index 076c33f..d860397 100644 --- a/lua/plex/plugins/extras/ui/minimap.lua +++ b/lua/plex/plugins/extras/ui/minimap.lua @@ -1,18 +1,49 @@ return { { - 'echasnovski/mini.map', + -- TODO: switch for a better plugin, some chars are not customizable and + -- don't render correctly (unicode ? signs) + "echasnovski/mini.map", keys = { - { 'mn', 'lua MiniMap.toggle()', desc = 'Mini map' }, + { "mn", "lua MiniMap.toggle()", desc = "Mini map" }, }, opts = function() - local minimap = require('mini.map') + local minimap = require("mini.map") return { integrations = { minimap.gen_integration.diagnostic(), minimap.gen_integration.builtin_search(), minimap.gen_integration.gitsigns(), }, - window = { winblend = 50 }, + symbols = { + -- Encode symbols. See `:h MiniMap.config` for specification and + -- `:h MiniMap.gen_encode_symbols` for pre-built ones. + -- Default: solid blocks with 3x2 resolution. + encode = nil, + + -- Scrollbar parts for view and line. Use empty string to disable any. + scroll_line = "█", + scroll_view = "┃", + }, + -- Window options + window = { + -- Whether window is focusable in normal way (with `wincmd` or mouse) + focusable = true, + + -- Side to stick ('left' or 'right') + side = "right", + + -- Whether to show count of multiple integration highlights + show_integration_count = true, + + -- Total width + width = 10, + + -- Value of 'winblend' option + winblend = 25, + + -- Z-index + zindex = 10, + }, } end, }, diff --git a/lua/plex/plugins/lsp/init.lua b/lua/plex/plugins/lsp/init.lua index ad0d31b..48b4954 100644 --- a/lua/plex/plugins/lsp/init.lua +++ b/lua/plex/plugins/lsp/init.lua @@ -60,7 +60,7 @@ return { }, -- Enable this to show formatters used in a notification -- Useful for debugging formatter issues - format_notify = false, + format_notify = true, -- LSP Server Settings ---@type lspconfig.options ---@diagnostic disable: missing-fields @@ -99,6 +99,7 @@ return { end -- Setup autoformat require('plex.plugins.lsp.format').setup(opts) + -- Setup formatting, keymaps and highlights. local lsp_on_attach = require('plex.lib.utils').on_attach ---@param client lsp.Client diff --git a/lua/plex/plugins/lsp/keymaps.lua b/lua/plex/plugins/lsp/keymaps.lua index 8dc4ce7..c1046db 100644 --- a/lua/plex/plugins/lsp/keymaps.lua +++ b/lua/plex/plugins/lsp/keymaps.lua @@ -1,179 +1,102 @@ --- LSP: Key-maps --- https://github.com/rafi/vim-config - local M = {} ----@type PluginLspKeys +---@type LazyKeysLspSpec[]|nil M._keys = nil ----@return (LazyKeys|{has?:string})[] +---@alias LazyKeysLspSpec LazyKeysSpec|{has?:string} +---@alias LazyKeysLsp LazyKeys|{has?:string} + +---@return LazyKeysLspSpec[] function M.get() - if M._keys then - return M._keys - end - local format = function() - require('plex.plugins.lsp.format').format({ force = true }) - end - - ---@class PluginLspKeys - -- stylua: ignore - M._keys = { - { 'gD', vim.lsp.buf.declaration, desc = 'Goto Declaration', has = 'declaration' }, - { 'gd', vim.lsp.buf.definition, desc = 'Goto Definition', has = 'definition' }, - { 'gr', vim.lsp.buf.references, desc = 'References', has = 'references' }, - { 'gy', vim.lsp.buf.type_definition, desc = 'Goto Type Definition', has = 'typeDefinition' }, - { 'gi', vim.lsp.buf.implementation, desc = 'Goto Implementation', has = 'implementation' }, - { 'gK', vim.lsp.buf.signature_help, desc = 'Signature Help', has = 'signatureHelp' }, - { 'h', vim.lsp.buf.signature_help, mode = 'i', desc = 'Signature Help', has = 'signatureHelp' }, - { ']d', M.diagnostic_goto(true), desc = 'Next Diagnostic' }, - { '[d', M.diagnostic_goto(false), desc = 'Prev Diagnostic' }, - { ']e', M.diagnostic_goto(true, 'ERROR'), desc = 'Next Error' }, - { '[e', M.diagnostic_goto(false, 'ERROR'), desc = 'Prev Error' }, - - { ',wa', vim.lsp.buf.add_workspace_folder, desc = 'Show Workspace Folders' }, - { ',wr', vim.lsp.buf.remove_workspace_folder, desc = 'Remove Workspace Folder' }, - { ',wl', 'lua =vim.lsp.buf.list_workspace_folders()', desc = 'List Workspace Folders' }, - - { 'K', function() - -- Show hover documentation or folded lines. - local winid = require('plex.lib.utils').has('nvim-ufo') - and require('ufo').peekFoldedLinesUnderCursor() or nil - if not winid then - vim.lsp.buf.hover() - end - end }, - - { 'ud', function() M.diagnostic_toggle(false) end, desc = 'Disable Diagnostics' }, - { 'uD', function() M.diagnostic_toggle(true) end, desc = 'Disable All Diagnostics' }, - - { 'cl', 'LspInfo' }, - { 'cf', format, desc = 'Format Document', has = 'formatting' }, - { 'cf', format, mode = 'x', desc = 'Format Range' }, -- , has = 'rangeFormatting' - { 'cr', vim.lsp.buf.rename, desc = 'Rename', has = 'rename' }, - { 'ce', vim.diagnostic.open_float, desc = 'Open diagnostics' }, - { 'ca', vim.lsp.buf.code_action, mode = { 'n', 'x' }, has = 'codeAction', desc = 'Code Action' }, - { 'cA', function() - vim.lsp.buf.code_action({ - context = { - only = { 'source' }, - diagnostics = {}, - }, - }) - end, desc = 'Source Action', has = 'codeAction' }, - } - return M._keys + if M._keys then + return M._keys + end + -- stylua: ignore + M._keys = { + { "cl", "LspInfo", desc = "Lsp Info" }, + { "gd", function() require("telescope.builtin").lsp_definitions({ reuse_win = true }) end, desc = "Goto Definition", has = "definition" }, + { "gr", "Telescope lsp_references", desc = "References" }, + { "gD", vim.lsp.buf.declaration, desc = "Goto Declaration" }, + { "gI", function() require("telescope.builtin").lsp_implementations({ reuse_win = true }) end, desc = "Goto Implementation" }, + { "gy", function() require("telescope.builtin").lsp_type_definitions({ reuse_win = true }) end, desc = "Goto T[y]pe Definition" }, + { "cK", vim.lsp.buf.hover, desc = "Hover" }, + { "gK", vim.lsp.buf.signature_help, desc = "Signature Help", has = "signatureHelp" }, + { "", vim.lsp.buf.signature_help, mode = "i", desc = "Signature Help", has = "signatureHelp" }, + { "ca", vim.lsp.buf.code_action, desc = "Code Action", mode = { "n", "v" }, has = "codeAction" }, + { + "cA", + function() + vim.lsp.buf.code_action({ + context = { + only = { + "source", + }, + diagnostics = {}, + }, + }) + end, + desc = "Source Action", + has = "codeAction", + } + } + if require("lazyvim.util").has("inc-rename.nvim") then + M._keys[#M._keys + 1] = { + "cr", + function() + local inc_rename = require("inc_rename") + return ":" .. inc_rename.config.cmd_name .. " " .. vim.fn.expand("") + end, + expr = true, + desc = "Rename", + has = "rename", + } + else + M._keys[#M._keys + 1] = { "cr", vim.lsp.buf.rename, desc = "Rename", has = "rename" } + end + return M._keys end ---@param method string function M.has(buffer, method) - method = method:find('/') and method or 'textDocument/' .. method - local clients - if vim.lsp.get_clients ~= nil then - clients = vim.lsp.get_clients({ bufnr = buffer }) - else - ---@diagnostic disable-next-line: deprecated - clients = vim.lsp.get_active_clients({ bufnr = buffer }) - end - for _, client in ipairs(clients) do - if client.supports_method(method) then - return true - end - end - return false + method = method:find("/") and method or "textDocument/" .. method + local clients = require("lazyvim.util").lsp.get_clients({ bufnr = buffer }) + for _, client in ipairs(clients) do + if client.supports_method(method) then + return true + end + end + return false end ----@param buffer integer +---@return (LazyKeys|{has?:string})[] function M.resolve(buffer) - local Keys = require('lazy.core.handler.keys') - local keymaps = {} ---@type table - - local function add(keymap) - local keys = Keys.parse(keymap) - if keys[2] == false then - keymaps[keys.id] = nil - else - keymaps[keys.id] = keys - end - end - for _, keymap in ipairs(M.get()) do - add(keymap) - end - - local opts = require('plex.lib.utils').opts('nvim-lspconfig') - local clients - if vim.lsp.get_clients ~= nil then - clients = vim.lsp.get_clients({ bufnr = buffer }) - else - ---@diagnostic disable-next-line: deprecated - clients = vim.lsp.get_active_clients({ bufnr = buffer }) - end - for _, client in ipairs(clients) do - local maps = opts.servers[client.name] and opts.servers[client.name].keys - or {} - for _, keymap in ipairs(maps) do - add(keymap) - end - end - return keymaps + local Keys = require("lazy.core.handler.keys") + if not Keys.resolve then + return {} + end + local spec = M.get() + local opts = require("lazyvim.util").opts("nvim-lspconfig") + local clients = require("lazyvim.util").lsp.get_clients({ bufnr = buffer }) + for _, client in ipairs(clients) do + local maps = opts.servers[client.name] and opts.servers[client.name].keys or {} + vim.list_extend(spec, maps) + end + return Keys.resolve(spec) end ----@param client lsp.Client ----@param buffer integer -function M.on_attach(client, buffer) - local Keys = require('lazy.core.handler.keys') - local keymaps = M.resolve(buffer) +function M.on_attach(_, buffer) + local Keys = require("lazy.core.handler.keys") + local keymaps = M.resolve(buffer) - -- FIXME: This part causes weird errors and IDK what it does. - -- for _, keys in pairs(keymaps) do - -- if not keys.has or M.has(buffer, keys.has) then - -- local opts = Keys.opts(keys) - -- ---@diagnostic disable-next-line: no-unknown - -- opts.has = nil - -- opts.silent = opts.silent ~= false - -- opts.buffer = buffer - -- vim.keymap.set(keys.mode or 'n', keys[1], keys[2], opts) - -- end - -- end -end - --- Toggle diagnostics locally (false) or globally (true). ----@param global boolean -function M.diagnostic_toggle(global) - local bufnr, cmd, msg, state - if global then - bufnr = nil - state = vim.g.diagnostics_disabled - vim.g.diagnostics_disabled = not state - else - bufnr = 0 - if vim.fn.has('nvim-0.9') == 1 then - state = vim.diagnostic.is_disabled(bufnr) - else - state = vim.b.diagnostics_disabled - vim.b.diagnostics_disabled = not state - end - end - - cmd = state and 'enable' or 'disable' - msg = cmd:gsub('^%l', string.upper) .. 'd diagnostics' - if global then - msg = msg .. ' globally' - end - vim.notify(msg) - vim.schedule(function() - vim.diagnostic[cmd](bufnr) - end) -end - ----@param next boolean ----@param severity string|nil ----@return fun() -function M.diagnostic_goto(next, severity) - local go = next and vim.diagnostic.goto_next or vim.diagnostic.goto_prev - local severity_int = severity and vim.diagnostic.severity[severity] or nil - return function() - go({ severity = severity_int }) - end + for _, keys in pairs(keymaps) do + if not keys.has or M.has(buffer, keys.has) then + local opts = Keys.opts(keys) + opts.has = nil + opts.silent = opts.silent ~= false + opts.buffer = buffer + vim.keymap.set(keys.mode or "n", keys.lhs, keys.rhs, opts) + end + end end return M diff --git a/spell/en.utf-8.add b/spell/en.utf-8.add index 42696c2..313c823 100644 --- a/spell/en.utf-8.add +++ b/spell/en.utf-8.add @@ -223,3 +223,15 @@ UNE Electionary UNE's recuse +hacky +config +Yubi +submodules +Curve25519 +gpg +devops +lazygit +IDEs +hypervisors +QEMU +virt