From fe15b33bff6a6cec887b6cce915082449cc9f719 Mon Sep 17 00:00:00 2001 From: Cian Hughes Date: Wed, 22 Jan 2025 13:05:14 +0000 Subject: [PATCH] Moved neovim config to its own repo --- ftplugin/elixir.lua | 2 + ftplugin/go.lua | 2 + ftplugin/nix.lua | 2 + ftplugin/rust.lua | 1 + init.lua | 31 ++++ lua/config/icons.lua | 32 ++++ lua/config/init.lua | 92 ++++++++++ lua/config/keys.lua | 367 ++++++++++++++++++++++++++++++++++++++ lua/keybindings.lua | 19 ++ lua/plugins/extras.lua | 49 +++++ lua/plugins/lsp.lua | 236 ++++++++++++++++++++++++ lua/plugins/mini.lua | 130 ++++++++++++++ lua/plugins/snacks.lua | 14 ++ lua/plugins/telescope.lua | 74 ++++++++ lua/plugins/ui.lua | 152 ++++++++++++++++ lua/plugins/utils.lua | 266 +++++++++++++++++++++++++++ 16 files changed, 1469 insertions(+) create mode 100644 ftplugin/elixir.lua create mode 100644 ftplugin/go.lua create mode 100644 ftplugin/nix.lua create mode 100644 ftplugin/rust.lua create mode 100644 init.lua create mode 100644 lua/config/icons.lua create mode 100644 lua/config/init.lua create mode 100644 lua/config/keys.lua create mode 100644 lua/keybindings.lua create mode 100644 lua/plugins/extras.lua create mode 100644 lua/plugins/lsp.lua create mode 100644 lua/plugins/mini.lua create mode 100644 lua/plugins/snacks.lua create mode 100644 lua/plugins/telescope.lua create mode 100644 lua/plugins/ui.lua create mode 100644 lua/plugins/utils.lua diff --git a/ftplugin/elixir.lua b/ftplugin/elixir.lua new file mode 100644 index 0000000..1b5bef9 --- /dev/null +++ b/ftplugin/elixir.lua @@ -0,0 +1,2 @@ +vim.bo.tabstop = 2 +vim.bo.shiftwidth = 2 diff --git a/ftplugin/go.lua b/ftplugin/go.lua new file mode 100644 index 0000000..1b5bef9 --- /dev/null +++ b/ftplugin/go.lua @@ -0,0 +1,2 @@ +vim.bo.tabstop = 2 +vim.bo.shiftwidth = 2 diff --git a/ftplugin/nix.lua b/ftplugin/nix.lua new file mode 100644 index 0000000..1b5bef9 --- /dev/null +++ b/ftplugin/nix.lua @@ -0,0 +1,2 @@ +vim.bo.tabstop = 2 +vim.bo.shiftwidth = 2 diff --git a/ftplugin/rust.lua b/ftplugin/rust.lua new file mode 100644 index 0000000..40b1976 --- /dev/null +++ b/ftplugin/rust.lua @@ -0,0 +1 @@ +vim.keymap.set("i", "'", "'", { buffer = 0 }) diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..154a6b1 --- /dev/null +++ b/init.lua @@ -0,0 +1,31 @@ +-- Enable experimental fast lua module loader +vim.loader.enable() + +require("config") +require("keybindings") + +-- Then, i want to load lazy as my plugin manager and have it load my plugins +local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim" +if not vim.loop.fs_stat(lazypath) then + vim.fn.system({ + "git", + "clone", + "--filter=blob:none", + "https://github.com/folke/lazy.nvim.git", + "--branch=stable", -- latest stable release + lazypath, + }) +end + +vim.opt.rtp:prepend(lazypath) + +require("lazy").setup("plugins", opts) + +-- Next, set up custom LSPs +local lspconfig = require("lspconfig") +lspconfig.nushell.setup({}) +lspconfig.prolog_ls.setup({ root_dir = lspconfig.util.root_pattern(".git") }) +lspconfig.nixd.setup({}) +lspconfig.mojo.setup({}) + +require("nvim-web-devicons").refresh() -- This fixes screwiness with the devicon colors diff --git a/lua/config/icons.lua b/lua/config/icons.lua new file mode 100644 index 0000000..5bb40d2 --- /dev/null +++ b/lua/config/icons.lua @@ -0,0 +1,32 @@ +return { + Ada = { + icon = "", + color = "#b89843", + cterm_color = "137", + name = "Ada", + }, + Fortran = { + icon = "󱈚", + color = "#734f96", + cterm_color = "96", + name = "Fortran", + }, + Lisp = { + icon = "", + color = "#c40904", + cterm_color = "160", + name = "Lisp", + }, + Prolog = { + icon = "", + color = "#e4b854", + cterm_color = "179", + name = "Prolog", + }, + Scallop = { + icon = "", + color = "#ffa6c9", + cterm_color = "225", + name = "Scallop", + }, +} diff --git a/lua/config/init.lua b/lua/config/init.lua new file mode 100644 index 0000000..a1a32ab --- /dev/null +++ b/lua/config/init.lua @@ -0,0 +1,92 @@ +-- First, i want to configure my vanilla nvim settings +vim.wo.number = true -- Line numbers +vim.wo.relativenumber = true +vim.opt.wrap = false +vim.opt.mouse = "a" -- enable mouse mode for window resizing +vim.opt.clipboard = "unnamedplus" -- share system and nvim clipboard +vim.g.have_nerd_font = true +vim.opt.undofile = true -- Save undo history +-- Add custom commentstring definitions +vim.api.nvim_create_autocmd("FileType", { + pattern = "nix,flake", + command = "setlocal commentstring=#\\ %s", +}) +vim.api.nvim_create_autocmd("FileType", { + pattern = "scallop", + command = "setlocal commentstring=//%s", +}) +-- Add custom file types +vim.filetype.add({ + extension = { + scl = "scallop", + prolog = "prolog", + pro = "prolog", + nu = "nu", + mojo = "mojo", + }, +}) + +vim.api.nvim_create_autocmd("LspAttach", { + callback = function(args) + local bufnr = args.buf + local client = vim.lsp.get_client_by_id(args.data.client_id) + if client.server_capabilities.completionProvider then + vim.bo[bufnr].omnifunc = "v:lua.vim.lsp.omnifunc" + end + if client.server_capabilities.definitionProvider then + vim.bo[bufnr].tagfunc = "v:lua.vim.lsp.tagfunc" + end + end, +}) + +-- Case-insensitive searching UNLESS \C or capital in search +vim.opt.ignorecase = true +vim.opt.smartcase = true +vim.opt.signcolumn = "yes" -- Keep signcolumn on by default +-- Decrease update time and timeout between key commands +vim.opt.updatetime = 250 +vim.opt.timeoutlen = 300 +-- Configure how new splits should be opened +vim.opt.splitright = true +vim.opt.splitbelow = true +-- Render markers so we don't mistakenly put tabs (and other whitespace) where we dont want them +vim.opt.list = true +vim.opt.listchars = { tab = "» ", trail = "·", nbsp = "␣" } +-- Also, while we're at it: let's set the tab widths +vim.opt.tabstop = 4 +vim.opt.softtabstop = 4 +vim.opt.shiftwidth = 4 +vim.opt.smartindent = true +vim.opt.expandtab = true +vim.opt.smarttab = true +vim.opt.inccommand = "split" -- Preview substitutions live, as you type! +vim.opt.cursorline = true -- Show which line your cursor is on +vim.opt.scrolloff = 4 -- Minimal number of screen lines to keep above and below the cursor. +-- Set highlight on search, but clear on pressing in normal mode +vim.opt.hlsearch = true +vim.keymap.set("n", "", "nohlsearch") + +-- Highlight when yanking (copying) text +vim.api.nvim_create_autocmd("TextYankPost", { + desc = "Highlight when yanking (copying) text", + group = vim.api.nvim_create_augroup("kickstart-highlight-yank", { clear = true }), + callback = function() + vim.highlight.on_yank() + end, +}) + +-- Diagnostics config +vim.diagnostic.config({ + float = { + focusable = false, + style = "minimal", + border = "rounded", + source = "always", + header = "", + prefix = "", + }, + signs = true, + underline = true, + update_in_insert = true, + severity_sort = false, +}) diff --git a/lua/config/keys.lua b/lua/config/keys.lua new file mode 100644 index 0000000..6c0cd23 --- /dev/null +++ b/lua/config/keys.lua @@ -0,0 +1,367 @@ +return { + groups = { + { "s", group = "[S]earch" }, + { "c", group = "[C]ode" }, + { "d", group = "[D]iagnostics" }, + { "g", group = "[G]enerate" }, + { "r", group = "[R]ename" }, + { "w", group = "[W]orkspace" }, + { "t", group = "[T]ree" }, + { "l", group = "[L]azyGit" }, + { "o", group = "[O]verseer" }, + { "h", group = "[H]arpoon" }, + { "x", group = "[X] Trouble" }, + { "|", group = "[|] Copilot" }, + }, + copilot = { + { "||", "Copilot toggle", desc = "[||] Toggle Copilot", mode = "n" }, + { + "|t", + "Copilot suggestion toggle_auto_trigger", + desc = "[|] [T]oggle inline suggestions", + mode = "n", + }, + { "|p", "Copilot panel", desc = "[|] [P]anel", mode = "n" }, + }, + harpoon = { + { + "ha", + function() + require("harpoon"):list():add() + end, + desc = "[H]arpoon [A]dd file", + mode = "n", + }, + { + "hq", + function() + local harpoon = require("harpoon") + harpoon.ui:toggle_quick_menu(harpoon:list()) + end, + desc = "[H]arpoon [Q]uick menu", + mode = "n", + }, + { + "", + function() + require("harpoon"):list():select(1) + end, + desc = "Harpoon file 1", + mode = "n", + }, + { + "", + function() + require("harpoon"):list():select(2) + end, + desc = "Harpoon file 2", + mode = "n", + }, + { + "", + function() + require("harpoon"):list():select(3) + end, + desc = "Harpoon file 3", + mode = "n", + }, + { + "", + function() + require("harpoon"):list():select(4) + end, + desc = "Harpoon file 4", + mode = "n", + }, + }, + lazygit = { + { + "lg", + function() + vim.api.nvim_command("lua Snacks.lazygit()") + end, + desc = "[L]azy[G]it", + mode = "n", + }, + { + "ll", + function() + vim.api.nvim_command("lua Snacks.lazygit.log()") + end, + desc = "[L]azyGit [L]og", + mode = "n", + }, + { + "lf", + function() + vim.api.nvim_command("lua Snacks.lazygit.log_file()") + end, + desc = "[L]azyGit [F]ile Log", + mode = "n", + }, + }, + neogen = { + { + "gd", + ":lua require('neogen').generate()", + desc = "[G]enerate [D]ocumentation", + mode = "n", + }, + }, + neotree = { + { + "tt", + function() + vim.api.nvim_command("Neotree toggle") + end, + desc = "[T]ree [T]oggle", + mode = "n", + }, + { + "ts", + function() + vim.api.nvim_command("Neotree show") + end, + desc = "[T]ree [S]how", + mode = "n", + }, + { + "tc", + function() + vim.api.nvim_command("Neotree close") + end, + desc = "[T]ree [C]lose", + mode = "n", + }, + { + "tf", + function() + vim.api.nvim_command("Neotree focus") + end, + desc = "[T]ree [F]ocus", + mode = "n", + }, + { + "tg", + function() + vim.api.nvim_command("Neotree git_status") + end, + desc = "[T]ree [G]it Status", + mode = "n", + }, + { + "tb", + function() + vim.api.nvim_command("Neotree buffers") + end, + desc = "[T]ree [B]uffers", + mode = "n", + }, + }, + oil = { + { "te", vim.cmd.Oil, desc = "[T]ree [E]dit", mode = "n" }, + }, + overseer = { + { "ob", vim.cmd.OverseerBuild, desc = "[O]verseer [B]uild", mode = "n" }, + { "oc", vim.cmd.OverseerRunCmd, desc = "[O]verseer Run [C]ommand", mode = "n" }, + { "or", vim.cmd.OverseerRun, desc = "[O]verseer [R]un", mode = "n" }, + { "ot", vim.cmd.OverseerToggle, desc = "[O]verseer [T]oggle", mode = "n" }, + }, + precognition = { + { + "p", + function() + if require("precognition").toggle() then + vim.notify("Precognition ON") + else + vim.notify("Precognition OFF") + end + end, + desc = "[P]recognition", + mode = "n", + }, + }, + telescope = function(telescope_builtin) + return { + { + "sh", + telescope_builtin.help_tags, + desc = "[S]earch [H]elp", + mode = "n", + }, + { + "sk", + telescope_builtin.keymaps, + desc = "[S]earch [K]eymaps", + mode = "n", + }, + { + "sf", + telescope_builtin.find_files, + desc = "[S]earch [F]iles", + mode = "n", + }, + { + "sb", + telescope_builtin.find_files, + desc = "[S]earch file [B]rowser", + mode = "n", + }, + { + "ss", + telescope_builtin.builtin, + desc = "[S]earch [S]elect Telescope", + mode = "n", + }, + { + "sw", + telescope_builtin.grep_string, + desc = "[S]earch current [W]ord", + mode = "n", + }, + { + "sg", + telescope_builtin.live_grep, + desc = "[S]earch by [G]rep", + mode = "n", + }, + { + "sd", + telescope_builtin.diagnostics, + desc = "[S]earch [D]iagnostics", + mode = "n", + }, + { + "sr", + telescope_builtin.resume, + desc = "[S]earch [R]esume", + mode = "n", + }, + { + "s.", + telescope_builtin.oldfiles, + desc = '[S]earch Recent Files ("." for repeat)', + mode = "n", + }, + { + "", + telescope_builtin.buffers, + desc = "[ ] Find existing buffers", + mode = "n", + }, + -- Slightly advanced example of overriding default behavior and theme + { + "/", + function() + -- You can pass additional configuration to telescope to change theme, layout, etc. + telescope_builtin.current_buffer_fuzzy_find( + require("telescope.themes").get_dropdown({ + winblend = 10, + previewer = false, + }) + ) + end, + desc = "[/] Fuzzily search in current buffer", + mode = "n", + }, + -- Also possible to pass additional configuration options. + -- See `:help telescope.builtin.live_grep()` for information about particular keys + { + "s/", + function() + telescope_builtin.live_grep({ + grep_open_files = true, + prompt_title = "Live Grep in Open Files", + }) + end, + desc = "[S]earch [/] in Open Files", + mode = "n", + }, + -- Shortcut for searching your neovim configuration files + { + "sn", + function() + telescope_builtin.find_files({ cwd = vim.fn.stdpath("config") }) + end, + desc = "[S]earch [N]eovim files", + mode = "n", + }, + } + end, + todo_comments = { + { + "]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", "Trouble todo toggle", desc = "Todo" }, + { + "xT", + "Trouble todo toggle filter = {tag = {TODO,FIX,FIXME}}", + desc = "Todo/Fix/Fixme", + }, + { "st", "TodoTelescope", desc = "Todo" }, + { + "sT", + "TodoTelescope keywords=TODO,FIX,FIXME", + desc = "Todo/Fix/Fixme", + }, + }, + trouble = { + { "xx", "Trouble diagnostics toggle", desc = "Diagnostics" }, + { + "xX", + "Trouble diagnostics toggle filter.buf=0", + desc = "Buffer Diagnostics", + }, + { "cs", "Trouble symbols toggle", desc = "Symbols" }, + { + "cS", + "Trouble lsp toggle", + desc = "LSP references/definitions/... (Trouble)", + }, + { "xL", "Trouble loclist toggle", desc = "Location List" }, + { "xQ", "Trouble qflist toggle", desc = "Quickfix List" }, + { + "[q", + function() + if require("trouble").is_open() then + require("trouble").prev({ 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 + if err then + vim.notify(err, vim.log.levels.ERROR) + else + vim.notify("An error occured but returned nil!", vim.log.levels.ERROR) + end + end + end + end, + desc = "Next Trouble/Quickfix Item", + }, + }, +} diff --git a/lua/keybindings.lua b/lua/keybindings.lua new file mode 100644 index 0000000..8c33a17 --- /dev/null +++ b/lua/keybindings.lua @@ -0,0 +1,19 @@ +vim.keymap.set("n", "", "h", { noremap = true, silent = true }) +vim.keymap.set("n", "", "j", { noremap = true, silent = true }) +vim.keymap.set("n", "", "k", { noremap = true, silent = true }) +vim.keymap.set("n", "", "l", { noremap = true, silent = true }) +vim.keymap.set("n", "", "h", { noremap = true, silent = true }) +vim.keymap.set("n", "", "j", { noremap = true, silent = true }) +vim.keymap.set("n", "", "k", { noremap = true, silent = true }) +vim.keymap.set("n", "", "l", { noremap = true, silent = true }) +vim.keymap.set("n", "", "+", { noremap = true, silent = true }) +vim.keymap.set("n", "", "-", { noremap = true, silent = true }) +vim.keymap.set("n", "", ">", { noremap = true, silent = true }) +vim.keymap.set("n", "", "<", { noremap = true, silent = true }) +vim.keymap.set("n", "", "s", { noremap = true, silent = true }) +-- This keymap isn't ideal but its the best i can figure out right now +vim.keymap.set("n", "", "x", { noremap = true, silent = true }) +vim.keymap.set("n", "", ":q", { noremap = true, silent = true }) +-- Non standard key mappings are here +vim.keymap.set("n", "d", vim.diagnostic.open_float, { desc = "[D]iagnostics" }) +vim.keymap.set("n", "f", vim.lsp.buf.format, { desc = "[F]ormat" }) diff --git a/lua/plugins/extras.lua b/lua/plugins/extras.lua new file mode 100644 index 0000000..2732e71 --- /dev/null +++ b/lua/plugins/extras.lua @@ -0,0 +1,49 @@ +return { -- Non programming quality of life utilities go here + { -- Definitely need to add a plugin for quickly making notes in obsidian + "epwalsh/obsidian.nvim", + version = "*", + cmd = "Obsidian", + event = "VeryLazy", + cond = function() + return vim.fn.executable("obsidian") == 1 + end, + dependencies = { + "nvim-lua/plenary.nvim", + "nvim-telescope/telescope.nvim", + "nvim-treesitter/nvim-treesitter", + }, + opts = { + workspaces = { + { + name = "work", + path = "~/Documents/Work_Notes/", + }, + }, + completion = { + nvim_cmp = true, + min_chars = 2, + }, + }, + }, + { + "MeanderingProgrammer/render-markdown.nvim", + opts = {}, + ft = "markdown", + dependencies = { + "nvim-treesitter/nvim-treesitter", + "echasnovski/mini.nvim", + "nvim-tree/nvim-web-devicons", + }, + ---@module 'render-markdown' + ---@type render.md.UserConfig + }, + { -- A cheatsheet will always be useful until im a bit more familiar with vim + "sudormrfbin/cheatsheet.nvim", + event = "VeryLazy", + dependencies = { + "nvim-telescope/telescope.nvim", + "nvim-lua/popup.nvim", + "nvim-lua/plenary.nvim", + }, + }, +} diff --git a/lua/plugins/lsp.lua b/lua/plugins/lsp.lua new file mode 100644 index 0000000..467cd4e --- /dev/null +++ b/lua/plugins/lsp.lua @@ -0,0 +1,236 @@ +return { -- LSP Config should be a standalone function, hence this module + { -- LSP Configuration & Plugins + "neovim/nvim-lspconfig", + dependencies = { + -- Automatically install LSPs and related tools to stdpath for neovim + "williamboman/mason.nvim", + "williamboman/mason-lspconfig.nvim", + "WhoIsSethDaniel/mason-tool-installer.nvim", + + -- Useful status updates for LSP. + -- NOTE: `opts = {}` is the same as calling `require('fidget').setup({})` + { "j-hui/fidget.nvim", opts = {} }, + + -- `neodev` configures Lua LSP for your Neovim config, runtime and plugins + -- used for completion, annotations and signatures of Neovim apis + { "folke/neodev.nvim", opts = {} }, + }, + config = function() + -- Brief Aside: **What is LSP?** + -- + -- LSP is an acronym you've probably heard, but might not understand what it is. + -- + -- LSP stands for Language Server Protocol. It's a protocol that helps editors + -- and language tooling communicate in a standardized fashion. + -- + -- In general, you have a "server" which is some tool built to understand a particular + -- language (such as `gopls`, `lua_ls`, `rust_analyzer`, etc). These Language Servers + -- (sometimes called LSP servers, but that's kind of like ATM Machine) are standalone + -- processes that communicate with some "client" - in this case, Neovim! + -- + -- LSP provides Neovim with features like: + -- - Go to definition + -- - Find references + -- - Autocompletion + -- - Symbol Search + -- - and more! + -- + -- Thus, Language Servers are external tools that must be installed separately from + -- Neovim. This is where `mason` and related plugins come into play. + -- + -- If you're wondering about lsp vs treesitter, you can check out the wonderfully + -- and elegantly composed help section, `:help lsp-vs-treesitter` + + -- This function gets run when an LSP attaches to a particular buffer. + -- That is to say, every time a new file is opened that is associated with + -- an lsp (for example, opening `main.rs` is associated with `rust_analyzer`) this + -- function will be executed to configure the current buffer + + require("mason").setup({ + PATH = "append", + }) + + vim.api.nvim_create_autocmd("LspAttach", { + group = vim.api.nvim_create_augroup("kickstart-lsp-attach", { clear = true }), + callback = function(event) + -- NOTE: Remember that lua is a real programming language, and as such it is possible + -- to define small helper and utility functions so you don't have to repeat yourself + -- many times. + -- + -- In this case, we create a function that lets us more easily define mappings specific + -- for LSP related items. It sets the mode, buffer and description for us each time. + local map = function(keys, func, desc) + vim.keymap.set( + "n", + keys, + func, + { buffer = event.buf, desc = "LSP: " .. desc } + ) + end + + -- Jump to the definition of the word under your cursor. + -- This is where a variable was first declared, or where a function is defined, etc. + -- To jump back, press . + map("gd", require("telescope.builtin").lsp_definitions, "[G]oto [D]efinition") + + -- Find references for the word under your cursor. + map("gr", require("telescope.builtin").lsp_references, "[G]oto [R]eferences") + + -- Jump to the implementation of the word under your cursor. + -- Useful when your language has ways of declaring types without an actual implementation. + map( + "gI", + require("telescope.builtin").lsp_implementations, + "[G]oto [I]mplementation" + ) + + -- Jump to the type of the word under your cursor. + -- Useful when you're not sure what type a variable is and you want to see + -- the definition of its *type*, not where it was *defined*. + map( + "D", + require("telescope.builtin").lsp_type_definitions, + "Type [D]efinition" + ) + + -- Fuzzy find all the symbols in your current document. + -- Symbols are things like variables, functions, types, etc. + map( + "gs", + require("telescope.builtin").lsp_document_symbols, + "[G]oto [S]ymbols" + ) + + -- Fuzzy find all the symbols in your current workspace + -- Similar to document symbols, except searches over your whole project. + map( + "ws", + require("telescope.builtin").lsp_dynamic_workspace_symbols, + "[W]orkspace [S]ymbols" + ) + + -- Rename the variable under your cursor + -- Most Language Servers support renaming across files, etc. + map("rn", vim.lsp.buf.rename, "[R]e[n]ame") + + -- Execute a code action, usually your cursor needs to be on top of an error + -- or a suggestion from your LSP for this to activate. + map("ca", vim.lsp.buf.code_action, "[C]ode [A]ction") + + -- Opens a popup that displays documentation about the word under your cursor + -- See `:help K` for why this keymap + map("K", vim.lsp.buf.hover, "Hover Documentation") + + -- WARN: This is not Goto Definition, this is Goto Declaration. + -- For example, in C this would take you to the header + map("gD", vim.lsp.buf.declaration, "[G]oto [D]eclaration") + + -- The following two autocommands are used to highlight references of the + -- word under your cursor when your cursor rests there for a little while. + -- See `:help CursorHold` for information about when this is executed + -- + -- When you move your cursor, the highlights will be cleared (the second autocommand). + local client = vim.lsp.get_client_by_id(event.data.client_id) + if client and client.server_capabilities.documentHighlightProvider then + vim.api.nvim_create_autocmd({ "CursorHold", "CursorHoldI" }, { + buffer = event.buf, + callback = vim.lsp.buf.document_highlight, + }) + + vim.api.nvim_create_autocmd({ "CursorMoved", "CursorMovedI" }, { + buffer = event.buf, + callback = vim.lsp.buf.clear_references, + }) + end + end, + }) + + -- LSP servers and clients are able to communicate to each other what features they support. + -- By default, Neovim doesn't support everything that is in the LSP Specification. + -- When you add nvim-cmp, luasnip, etc. Neovim now has *more* capabilities. + -- So, we create new capabilities with nvim cmp, and then broadcast that to the servers. + local capabilities = vim.lsp.protocol.make_client_capabilities() + capabilities = vim.tbl_deep_extend( + "force", + capabilities, + require("cmp_nvim_lsp").default_capabilities() + ) + + -- Enable the following language servers + -- Feel free to add/remove any LSPs that you want here. They will automatically be installed. + -- + -- Add any additional override configuration in the following tables. Available keys are: + -- - cmd (table): Override the default command used to start the server + -- - filetypes (table): Override the default list of associated filetypes for the server + -- - capabilities (table): Override fields in capabilities. Can be used to disable certain LSP features. + -- - settings (table): Override the default settings passed when initializing the server. + -- For example, to see the options for `lua_ls`, you could go to: https://luals.github.io/wiki/settings/ + local servers = { + -- clangd = {}, + -- gopls = {}, + -- pyright = {}, + -- rust_analyzer = {}, + -- ... etc. See `:help lspconfig-all` for a list of all the pre-configured LSPs + -- + -- Some languages (like typescript) have entire language plugins that can be useful: + -- https://github.com/pmizio/typescript-tools.nvim + -- + -- But for many setups, the LSP (`tsserver`) will work just fine + -- tsserver = {}, + -- + + lua_ls = { + -- cmd = {...}, + -- filetypes { ...}, + -- capabilities = {}, + settings = { + Lua = { + completion = { + callSnippet = "Replace", + }, + -- You can toggle below to ignore Lua_LS's noisy `missing-fields` warnings + -- diagnostics = { disable = { 'missing-fields' } }, + }, + }, + }, + } + + -- Ensure the servers and tools above are installed + -- To check the current status of installed tools and/or manually install + -- other tools, you can run + -- :Mason + -- + -- You can press `g?` for help in this menu + require("mason").setup() + + -- You can add other tools here that you want Mason to install + -- for you, so that they are available from within Neovim. + local ensure_installed = vim.tbl_keys(servers or {}) + -- vim.list_extend(ensure_installed, { + -- "stylua", -- Used to format lua code + -- }) + require("mason-tool-installer").setup({ ensure_installed = ensure_installed }) + + require("mason-lspconfig").setup({ + handlers = { + function(server_name) + local server = servers[server_name] or {} + -- This handles overriding only values explicitly passed + -- by the server configuration above. Useful when disabling + -- certain features of an LSP (for example, turning off formatting for tsserver) + server.capabilities = vim.tbl_deep_extend( + "force", + {}, + capabilities, + server.capabilities or {} + ) + require("lspconfig")[server_name].setup(server) + end, + }, + }) + end, + }, + -- Finally: add language and filetype specific plugins + { "fladson/vim-kitty", ft = "kitty" }, + { "scallop-lang/vim-scallop", ft = "scallop" }, +} diff --git a/lua/plugins/mini.lua b/lua/plugins/mini.lua new file mode 100644 index 0000000..447aa7e --- /dev/null +++ b/lua/plugins/mini.lua @@ -0,0 +1,130 @@ +return { -- Mini is so varied it's hard to categorise. So i dumped my mini installs here + { -- Collection of various small independent plugins/modules + "echasnovski/mini.nvim", + config = function() + -- Better Around/Inside textobjects + -- + -- Examples: + -- - va) - [V]isually select [A]round [)]paren + -- - yinq - [Y]ank [I]nside [N]ext [']quote + -- - ci' - [C]hange [I]nside [']quote + require("mini.ai").setup({ n_lines = 500 }) + + -- Add/delete/replace surroundings (brackets, quotes, etc.) + -- + -- - saiw) - [S]urround [A]dd [I]nner [W]ord [)]Paren + -- - sd' - [S]urround [D]elete [']quotes + -- - sr)' - [S]urround [R]eplace [)] ['] + require("mini.surround").setup() + + -- Setup of mini.notify + local notify = require("mini.notify") + notify.setup() + vim.notify = notify.make_notify({ + ERROR = { duration = 5000 }, + WARN = { duration = 4000 }, + INFO = { duration = 3000 }, + }) + + -- Some other mini.nvim plugins that look useful to me + require("mini.clue").setup() + require("mini.visits").setup() + require("mini.sessions").setup() + require("mini.pairs").setup({ mappings = { ["`"] = false } }) + require("mini.comment").setup() + require("mini.splitjoin").setup() + require("mini.trailspace").setup() + + -- My custom mini.starter config + local starter_items = { + { + action = "Telescope file_browser", + name = "Tree", + section = "Telescope", + }, + { + action = "Telescope live_grep", + name = "Live grep", + section = "Telescope", + }, + { + action = "Telescope find_files", + name = "File grep", + section = "Telescope", + }, + { + action = "Telescope command_history", + name = "Command history", + section = "Telescope", + }, + { + action = "Telescope help_tags", + name = "Help tags", + section = "Telescope", + }, + { + name = "Log", + action = [[lua Snacks.lazygit.log()]], + section = "Git", + }, + { + name = "Lazygit", + action = [[lua Snacks.lazygit()]], + section = "Git", + }, + { + name = "Browser", + action = function() + local handle = io.popen("git remote show") + if handle == nil then + vim.notify("Failed to find remote", vim.log.levels.ERROR) + return + end + local result = handle:read("*a") + handle:close() + local remote = vim.split(result, "\n")[1] + handle = io.popen("git remote get-url " .. remote) + if handle == nil then + vim.notify("Failed to get url for " .. remote, vim.log.levels.ERROR) + return + end + local url = handle:read("*a") + handle:close() + handle = io.popen("xdg-open " .. url) + if handle == nil then + vim.notify("Failed to open " .. url, vim.log.levels.ERROR) + return + end + result = handle:read("*a") + handle:close() + end, + section = "Git", + }, + { + name = "Harpoon Quickmenu", + action = [[lua require("harpoon").ui:toggle_quick_menu(require("harpoon"):list())]], + section = "Misc", + }, + } + require("mini.starter").setup({ + header = "⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣠⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\ +⠀⠀⠀⠀⠀⠀⢀⣴⣾⣿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\ +⠀⠀⠀⠀⢀⣴⠿⢟⣛⣩⣤⣶⣶⣶⣿⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\ +⠀⠀⢀⣴⣿⠿⠸⣿⣿⣿⣿⣿⣿⡿⢿⣿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀\ +⠀⢠⠞⠉⠀⠀⠀⣿⠋⠻⣿⣿⣿⠀⣦⣿⠏⠀⠀⠀⢀⣀⣀⣀⣀⣀⠀⠀\ +⢠⠏⠀⠀⠀⠀⠀⠻⣤⣷⣿⣿⣿⣶⢟⣁⣒⣒⡋⠉⠉⠁⠀⠀⠀⠈⠉⡧\ +⢻⡀⠀⠀⠀⠀⠀⣀⡤⠌⢙⣛⣛⣵⣿⣿⡛⠛⠿⠃⠀⠀⠀⠀⠀⢀⡜⠁\ +⠀⠉⠙⠒⠒⠛⠉⠁⠀⠸⠛⠉⠉⣿⣿⣿⣿⣦⣄⠀⠀⠀⢀⣠⠞⠁⠀⠀\ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣿⣿⣿⡿⣿⣿⣷⡄⠞⠋⠀⠀⠀⠀⠀\ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⣿⣿⣿⣷⡻⣿⣿⣧⠀⠀⠀⠀⠀⠀⠀\ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢨⣑⡙⠻⠿⠿⠈⠙⣿⣧⠀⠀⠀⠀⠀⠀\ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⣷⡀⠀⠀⠀⠀⢹⣿⣆⠀⠀⠀⠀⠀\ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣿⡇⠀⠀⠀⠀⠸⣿⣿⡄⠀⠀⠀⠀\ +⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠁⠀⠀⠀⠀⠀⡿⣿⣿⠀⠀⠀⠀\ +⠀⠀⠀⠀⠀⠀⠀ ⠀⠀⠀⠀⠀⠀⠀⠈⠙⠀⠀⠀⠀⠀", + items = starter_items, + footer = "", + }) + end, + }, +} diff --git a/lua/plugins/snacks.lua b/lua/plugins/snacks.lua new file mode 100644 index 0000000..558bb68 --- /dev/null +++ b/lua/plugins/snacks.lua @@ -0,0 +1,14 @@ +return { + { + "folke/snacks.nvim", + priority = 1000, + lazy = false, + ---@type snacks.Config + opts = { + bigfile = { enabled = true }, + lazygit = { enabled = true }, + quickfile = { enabled = true }, + }, + keys = require("config.keys").lazygit, + }, +} diff --git a/lua/plugins/telescope.lua b/lua/plugins/telescope.lua new file mode 100644 index 0000000..e5a43ed --- /dev/null +++ b/lua/plugins/telescope.lua @@ -0,0 +1,74 @@ +return { -- Telescope is so core to nvim that it gets its own module + { -- Fuzzy Finder (files, lsp, etc) + "nvim-telescope/telescope.nvim", + event = "VimEnter", + branch = "0.1.x", + dependencies = { + "nvim-lua/plenary.nvim", + { -- If encountering errors, see telescope-fzf-native README for install instructions + "nvim-telescope/telescope-fzf-native.nvim", + + -- `build` is used to run some command when the plugin is installed/updated. + -- This is only run then, not every time Neovim starts up. + build = "make", + + -- `cond` is a condition used to determine whether this plugin should be + -- installed and loaded. + cond = function() + return vim.fn.executable("make") == 1 + end, + }, + { "nvim-telescope/telescope-ui-select.nvim" }, + -- Useful for getting pretty icons, but requires a Nerd Font. + { "nvim-tree/nvim-web-devicons", enabled = vim.g.have_nerd_font }, + -- A really nice filetree browser built on telescope + { "nvim-telescope/telescope-file-browser.nvim" }, + }, + config = function() + -- Telescope is a fuzzy finder that comes with a lot of different things that + -- it can fuzzy find! It's more than just a "file finder", it can search + -- many different aspects of Neovim, your workspace, LSP, and more! + -- + -- The easiest way to use telescope, is to start by doing something like: + -- :Telescope help_tags + -- + -- After running this command, a window will open up and you're able to + -- type in the prompt window. You'll see a list of help_tags options and + -- a corresponding preview of the help. + -- + -- Two important keymaps to use while in telescope are: + -- - Insert mode: + -- - Normal mode: ? + -- + -- This opens a window that shows you all of the keymaps for the current + -- telescope picker. This is really useful to discover what Telescope can + -- do as well as how to actually do it! + + -- [[ Configure Telescope ]] + -- See `:help telescope` and `:help telescope.setup()` + require("telescope").setup({ + -- You can put your default mappings / updates / etc. in here + -- All the info you're looking for is in `:help telescope.setup()` + -- + -- defaults = { + -- mappings = { + -- i = { [''] = 'to_fuzzy_refine' }, + -- }, + -- }, + -- pickers = {} + extensions = { + ["ui-select"] = { + require("telescope.themes").get_dropdown(), + }, + }, + }) + + -- Enable telescope extensions, if they are installed + pcall(require("telescope").load_extension, "fzf") + pcall(require("telescope").load_extension, "ui-select") + pcall(require("telescope").load_extension, "file-browser") + end, + -- See `:help telescope.builtin` + keys = require("config.keys").telescope(require("telescope.builtin")), + }, +} diff --git a/lua/plugins/ui.lua b/lua/plugins/ui.lua new file mode 100644 index 0000000..b2f75a1 --- /dev/null +++ b/lua/plugins/ui.lua @@ -0,0 +1,152 @@ +return { -- UI components and other visual elements are declared here + { -- Theme + "folke/tokyonight.nvim", + lazy = false, + priority = 1000, + init = function() + vim.cmd.colorscheme("tokyonight-night") + end, + }, + { "MunifTanjim/nui.nvim", lazy = true }, + { -- Useful plugin to show you pending keybinds. + "folke/which-key.nvim", + event = "VimEnter", + config = function() + local wk = require("which-key") + local groups = require("config.keys").groups + wk.add(groups) + end, + }, + { + "nvim-tree/nvim-web-devicons", + config = function() + local icons = require("config.icons") + require("nvim-web-devicons").setup({ + color_icons = true, + override_by_extension = { + ["scl"] = icons.Scallop, + ["prolog"] = icons.Prolog, + ["pro"] = icons.Prolog, + ["lisp"] = icons.Lisp, + ["lsp"] = icons.Lisp, + ["asd"] = icons.Lisp, + ["f"] = icons.Fortran, + ["f77"] = icons.Fortran, + ["f90"] = icons.Fortran, + ["f18"] = icons.Fortran, + ["adb"] = icons.Ada, + ["ads"] = icons.Ada, + }, + }) + end, + }, + { -- A file explorer, because i'm not used to the vim workflow yet + "nvim-neo-tree/neo-tree.nvim", + branch = "v3.x", + event = "VimEnter", + dependencies = { + "nvim-lua/plenary.nvim", + "nvim-tree/nvim-web-devicons", -- not strictly required, but recommended + "MunifTanjim/nui.nvim", + }, + keys = require("config.keys").neotree, + }, + { + "s1n7ax/nvim-window-picker", + name = "window-picker", + event = "VeryLazy", + version = "2.*", + config = function() + require("window-picker").setup() + end, + }, + { -- Adds git related signs to the gutter, as well as utilities for managing changes + "mhinz/vim-signify", + event = "VimEnter", + config = function() + -- defer config for 5ms. Old vim plugins can be janky in neovim + vim.defer_fn(function() + vim.g.signify_sign_show_count = 0 + vim.g.signify_sign_add = "┃" + vim.g.signify_sign_change = "┃" + vim.g.signify_sign_delete = "_" + vim.g.signify_sign_delete_first_line = "‾" + vim.g.signify_sign_change_delete = "~" + vim.cmd.highlight({ "SignifySignAdd", "guifg=#449dab" }) + vim.cmd.highlight({ "SignifySignChange", "guifg=#6183bb" }) + vim.cmd.highlight({ "SignifySignDelete", "guifg=#914c54" }) + vim.cmd.highlight({ "link", "SignifySignDeleteFirstLine", "SignifySignDelete" }) + vim.cmd.highlight({ "link", "SignifySignChangeDelete", "SignifySignChange" }) + end, 5) + end, + }, + -- Modular, configurable status bar + { + "nvim-lualine/lualine.nvim", + dependencies = { "nvim-tree/nvim-web-devicons" }, + config = function() + local hl_color = require("tokyonight").load({ style = "night" }).orange + vim.cmd.highlight({ "LualineHarpoonActive", "guifg=" .. hl_color }) + + require("lualine").setup({ + options = { + component_separators = { left = "", right = "" }, + section_separators = { left = "", right = "" }, + }, + sections = { + lualine_c = { + { + "harpoon2", + icon = "󰛢", + indicators = { "A", "S", "D", "F" }, + active_indicators = { + "%#LualineHarpoonActive#A%*", + "%#LualineHarpoonActive#S%*", + "%#LualineHarpoonActive#D%*", + "%#LualineHarpoonActive#F%*", + }, + _separator = "∙", + no_harpoon = "Harpoon not loaded", + }, + "filename", + }, + lualine_x = { + { "copilot", show_colors = true }, + "encoding", + "fileformat", + "filetype", + }, + }, + extensions = { + "fugitive", + "fzf", + "lazy", + "mason", + "neo-tree", + "oil", + "overseer", + "quickfix", + }, + }) + end, + }, + { + "letieu/harpoon-lualine", + dependencies = { + { + "ThePrimeagen/harpoon", + branch = "harpoon2", + }, + }, + }, + { + "AndreM222/copilot-lualine", + event = "VeryLazy", + dependencies = { "zbirenbaum/copilot.lua" }, + }, + -- Assistant for refreshers on vim motions + { + "tris203/precognition.nvim", + keys = require("config.keys").precognition, + }, +} diff --git a/lua/plugins/utils.lua b/lua/plugins/utils.lua new file mode 100644 index 0000000..30c065b --- /dev/null +++ b/lua/plugins/utils.lua @@ -0,0 +1,266 @@ +return { -- General programming utilities go here + -- Tools for configuration and plugin development + { "folke/neoconf.nvim", cmd = "Neoconf" }, + { + "folke/neodev.nvim", + opts = { + override = function(root_dir, library) + if + root_dir:find( + os.getenv("XDG_CONFIG_HOME") + .. "/nix/home-manager/core/dotfiles/dot_config/nvim/", + 1, + true + ) == 1 + then + library.enabled = true + library.plugins = true + end + end, + }, + }, + -- Privilege escalation plugin + { "lambdalisue/suda.vim", event = "VeryLazy" }, + { + "folke/trouble.nvim", + cmd = { "Trouble" }, + opts = { + modes = { + lsp = { + win = { position = "right" }, + }, + }, + }, + keys = require("config.keys").trouble, + }, + { + "folke/todo-comments.nvim", + cmd = { "TodoTrouble", "TodoTelescope" }, + event = "VimEnter", + dependencies = { "nvim-lua/plenary.nvim" }, + keys = require("config.keys").todo_comments, + }, + "tpope/vim-fugitive", -- Also want to add fugitive, since it's apparently a great git plugin + "jlfwong/vim-mercenary", -- Mercenary is the mercurial equivalent of fugitive + { -- Oil is a very nice buffer-based filetree editor + "stevearc/oil.nvim", + event = "VeryLazy", + opts = {}, + dependencies = { "nvim-tree/nvim-web-devicons" }, + keys = require("config.keys").oil, + }, + { -- Harpoon, because i keep losing track of my markers + "theprimeagen/harpoon", + branch = "harpoon2", + dependencies = { "nvim-lua/plenary.nvim" }, + config = function() + require("harpoon"):setup({}) + end, + keys = require("config.keys").harpoon, + }, + -- Snippets + "SirVer/ultisnips", + "honza/vim-snippets", + "rafamadriz/friendly-snippets", + { -- Package and devenv plugins + "danymat/neogen", + event = "VimEnter", + config = function() + require("neogen").setup({ + enabled = true, + snippet_engine = "luasnip", + languages = { + python = { + template = { + annotation_convention = "google_docstrings", + }, + }, + }, + }) + end, + keys = require("config.keys").neogen, + }, + { -- Autoformat + "stevearc/conform.nvim", + event = "VimEnter", + opts = { + notify_on_error = false, + format_on_save = { + timeout_ms = 500, + lsp_fallback = true, + }, + formatters_by_ft = { + lua = { "stylua" }, + nix = { "alejandra" }, + -- Conform can also run multiple formatters sequentially + -- python = { "isort", "black" }, + -- + -- You can use a sub-list to tell conform to run *until* a formatter + -- is found. + -- javascript = { { "prettierd", "prettier" } }, + }, + }, + }, + { -- Autocompletion + "hrsh7th/nvim-cmp", + event = "InsertEnter", + dependencies = { + -- Snippet Engine & its associated nvim-cmp source + { + "L3MON4D3/LuaSnip", + build = (function() + -- Build Step is needed for regex support in snippets + -- This step is not supported in many windows environments + -- Remove the below condition to re-enable on windows + if vim.fn.has("win32") == 1 or vim.fn.executable("make") == 0 then + return + end + return "make install_jsregexp" + end)(), + }, + "saadparwaiz1/cmp_luasnip", + + -- Adds other completion capabilities. + -- nvim-cmp does not ship with all sources by default. They are split + -- into multiple repos for maintenance purposes. + "hrsh7th/cmp-nvim-lsp", + "hrsh7th/cmp-path", + + -- If you want to add a bunch of pre-configured snippets, + -- you can use this plugin to help you. It even has snippets + -- for various frameworks/libraries/etc. but you will have to + -- set up the ones that are useful for you. + -- 'rafamadriz/friendly-snippets', + }, + config = function() + -- See `:help cmp` + local cmp = require("cmp") + local luasnip = require("luasnip") + luasnip.config.setup({}) + + cmp.setup({ + snippet = { + expand = function(args) + luasnip.lsp_expand(args.body) + end, + }, + completion = { completeopt = "menu,menuone,noinsert" }, + + -- For an understanding of why these mappings were + -- chosen, you will need to read `:help ins-completion` + -- + -- No, but seriously. Please read `:help ins-completion`, it is really good! + mapping = cmp.mapping.preset.insert({ + -- Select the [n]ext item + [""] = cmp.mapping.select_next_item(), + -- Select the [p]revious item + [""] = cmp.mapping.select_prev_item(), + + -- Accept ([y]es) the completion. + -- This will auto-import if your LSP supports it. + -- This will expand snippets if the LSP sent a snippet. + [""] = cmp.mapping.confirm({ select = true }), + + -- Manually trigger a completion from nvim-cmp. + -- Generally you don't need this, because nvim-cmp will display + -- completions whenever it has completion options available. + [""] = cmp.mapping.complete({}), + + -- Think of as moving to the right of your snippet expansion. + -- So if you have a snippet that's like: + -- function $name($args) + -- $body + -- end + -- + -- will move you to the right of each of the expansion locations. + -- is similar, except moving you backwards. + [""] = cmp.mapping(function() + if luasnip.expand_or_locally_jumpable() then + luasnip.expand_or_jump() + end + end, { "i", "s" }), + [""] = cmp.mapping(function() + if luasnip.locally_jumpable(-1) then + luasnip.jump(-1) + end + end, { "i", "s" }), + }), + sources = { + { name = "nvim_lsp" }, + { name = "luasnip" }, + { name = "path" }, + }, + }) + end, + }, + { -- Highlight, edit, and navigate code + "nvim-treesitter/nvim-treesitter", + build = ":TSUpdate", + opts = { + ensure_installed = { "bash", "c", "html", "lua", "markdown", "vim", "vimdoc" }, + auto_install = true, + highlight = { enable = true }, + indent = { enable = true }, + }, + config = function(_, opts) + ---@diagnostic disable-next-line: missing-fields + require("nvim-treesitter.configs").setup(opts) + end, + dependencies = { + { "nushell/tree-sitter-nu", build = ":TSUpdate nu" }, + }, + }, + { -- Undo tree + "mbbill/undotree", + event = "VeryLazy", + opts = {}, + config = function() + vim.keymap.set("n", "U", vim.cmd.UndotreeToggle, { desc = "[U]ndotree" }) + end, + }, + { -- Add Overseer as a task running tool + "stevearc/overseer.nvim", + event = "VeryLazy", + opts = {}, + config = function() + require("overseer").setup() + end, + keys = require("config.keys").overseer, + }, + { -- A plugin to integrate tests is helpful, so i'm adding neotest + "nvim-neotest/neotest", + event = "VeryLazy", + dependencies = { + "nvim-neotest/nvim-nio", + "nvim-lua/plenary.nvim", + "antoinemadec/FixCursorHold.nvim", + "nvim-treesitter/nvim-treesitter", + }, + }, + -- Github copilot, cos its pretty handy + { + "zbirenbaum/copilot.lua", + cmd = "Copilot", + event = "InsertEnter", + config = function() + require("copilot").setup({ + panel = { + auto_refresh = true, + layout = { + position = "right", + }, + }, + suggestion = { + keymap = { + accept = "", + accept_word = "", + accept_line = "", + }, + }, + }) + end, + keys = require("config.keys").copilot, + }, + -- Rust tools like inlay hints are absolutely essential + { "simrat39/rust-tools.nvim", ft = "rust" }, +}