From 13cc065ec014b86369dfe4ad60d1a43fa06fd251 Mon Sep 17 00:00:00 2001 From: Igor Soarez Date: Thu, 17 Sep 2026 19:45:23 +0100 Subject: dotfiles Shell, editor and terminal configuration, the tools that go with them, and szecret, an age-based secret manager whose store lives elsewhere. History restarts here. What came before was not written to be read by anyone else, and carried machine names, addresses and an encrypted secret store; it is kept privately instead. What is left is meant to be safe on any machine, including one whose owner is not me: see CLAUDE.md for the rule it is written to. --- config/nvim/lua/plugins/colorschemes.lua | 23 +++ config/nvim/lua/plugins/completion.lua | 159 +++++++++++++++ config/nvim/lua/plugins/editing.lua | 127 ++++++++++++ config/nvim/lua/plugins/editor.lua | 29 +++ config/nvim/lua/plugins/fzf.lua | 148 ++++++++++++++ config/nvim/lua/plugins/git.lua | 63 ++++++ config/nvim/lua/plugins/lsp.lua | 281 ++++++++++++++++++++++++++ config/nvim/lua/plugins/reload.lua | 28 +++ config/nvim/lua/plugins/testing.lua | 41 ++++ config/nvim/lua/plugins/ui.lua | 335 +++++++++++++++++++++++++++++++ config/nvim/lua/plugins/which-key.lua | 30 +++ 11 files changed, 1264 insertions(+) create mode 100644 config/nvim/lua/plugins/colorschemes.lua create mode 100644 config/nvim/lua/plugins/completion.lua create mode 100644 config/nvim/lua/plugins/editing.lua create mode 100644 config/nvim/lua/plugins/editor.lua create mode 100644 config/nvim/lua/plugins/fzf.lua create mode 100644 config/nvim/lua/plugins/git.lua create mode 100644 config/nvim/lua/plugins/lsp.lua create mode 100644 config/nvim/lua/plugins/reload.lua create mode 100644 config/nvim/lua/plugins/testing.lua create mode 100644 config/nvim/lua/plugins/ui.lua create mode 100644 config/nvim/lua/plugins/which-key.lua (limited to 'config/nvim/lua/plugins') diff --git a/config/nvim/lua/plugins/colorschemes.lua b/config/nvim/lua/plugins/colorschemes.lua new file mode 100644 index 0000000..005403d --- /dev/null +++ b/config/nvim/lua/plugins/colorschemes.lua @@ -0,0 +1,23 @@ +local plugins = { + { "sonph/onehalf", rtp = "vim/" }, + { "altercation/vim-colors-solarized" }, + { "nlknguyen/papercolor-theme" }, + { "tanvirtin/monokai.nvim" }, + { "tomasr/molokai" }, + { "cohlin/vim-colorschemes" }, + { "reedes/vim-colors-pencil" }, + { "ayu-theme/ayu-vim" }, + { "overcache/NeoSolarized" }, + { "cormacrelf/vim-colors-github" }, + { "rafi/awesome-vim-colorschemes" }, +} + +-- These are lazy-loaded: cfg-watch (lua/plugins/reload.lua) lists them as +-- dependencies, so they are loaded right before ~/.local/share/nvim/colors.lua +-- runs `:colorscheme`. Keeping them lazy keeps them off the startup path while +-- still letting the dynamic light/dark theme switcher reach any of them. +for _, plugin in ipairs(plugins) do + plugin.lazy = true +end + +return plugins diff --git a/config/nvim/lua/plugins/completion.lua b/config/nvim/lua/plugins/completion.lua new file mode 100644 index 0000000..fa037cd --- /dev/null +++ b/config/nvim/lua/plugins/completion.lua @@ -0,0 +1,159 @@ +local plugins = { + -- Autocompletion and Snippets + { + "Saghen/blink.cmp", + version = "1.*", -- Use a stable release version + main = "blink.cmp", + opts = { + keymap = { -- https://cmp.saghen.dev/configuration/keymap.html + preset = "default", + [""] = { "accept", "fallback" }, + }, + sources = { + -- Enable completion from lazydev, LSP, filesystem paths, snippets, buffer + -- (and optionally supermaven) + -- default = { "lazydev", "lsp", "path", "snippets", "buffer", "supermaven" }, + default = { "lazydev", "lsp", "path", "snippets", "buffer" }, + providers = { + -- Neovim Lua API / plugin spec completions (folke/lazydev.nvim) + lazydev = { + name = "LazyDev", + module = "lazydev.integrations.blink", + score_offset = 100, -- show lazydev items above LSP + }, + buffer = { + -- Optional: Configure buffer-specific settings + -- max_async_buffer_size = 200000, -- 200KB default + -- use_cache = true, -- Cache words for better performance + }, + -- lsp = { fallbacks = {} }, -- always show the buffer source + supermaven = { + enabled = false, + name = "supermaven", + module = "blink-cmp-supermaven", + async = true, + + }, + }, + }, + completion = { + -- Enable ghost text to show completions inline + ghost_text = { + -- enabled = true, + enabled = false, + }, + -- Configure the completion menu + menu = { + auto_show = true, -- Automatically show the menu + }, + list = { + selection = { + preselect = true, + auto_insert = false, + }, + }, + -- Avoid unnecessary requests for performance + trigger = { + -- prefetch_on_insert = true, -- When true, will prefetch the completion items when entering insert mode + show_in_snippet = false, -- When false, will not show the completion window automatically when in a snippet + show_on_backspace = false, -- When true, will show completion window after backspacing + show_on_backspace_in_keyword = false, -- When true, will show completion window after backspacing into a keyword + show_on_backspace_after_accept = false, -- When true, will show the completion window after accepting a completion and then backspacing into a keyword + show_on_backspace_after_insert_enter = false, -- When true, will show the completion window after entering insert mode and backspacing into keyword + show_on_keyword = false, -- When true, will show the completion window after typing any of alphanumerics, `-` or `_` + show_on_trigger_character = false, -- When true, will show the completion window after typing a trigger character + show_on_insert = false, -- When true, will show the completion window after entering insert mode + -- LSPs can indicate when to show the completion window via trigger characters + -- however, some LSPs (i.e. tsserver) return characters that would essentially + -- always show the window. We block these by default. + -- show_on_blocked_trigger_characters = { " ", "\n", "\t" }, + -- You can also block per filetype with a function: + -- show_on_blocked_trigger_characters = function(ctx) + -- if vim.bo.filetype == 'markdown' then return { ' ', '\n', '\t', '.', '/', '(', '[' } end + -- return { ' ', '\n', '\t' } + -- end, + -- When both this and show_on_trigger_character are true, will show the completion window + -- when the cursor comes after a trigger character after accepting an item + show_on_accept_on_trigger_character = false, + -- When both this and show_on_trigger_character are true, will show the completion window + -- when the cursor comes after a trigger character when entering insert mode + show_on_insert_on_trigger_character = false, + -- List of trigger characters (on top of `show_on_blocked_trigger_characters`) that won't trigger + -- the completion window when the cursor comes after a trigger character when + -- entering insert mode/accepting an item + -- show_on_x_blocked_trigger_characters = { "'", '"', "(" }, + -- or a function, similar to show_on_blocked_trigger_character + }, + }, + signature = { + enabled = true, + window = { + show_documentation = false, + }, + }, + -- Disable blink.cmp for command-line mode to let native wildmenu work + cmdline = { + enabled = false, + }, + }, + }, + + -- Supermaven was acquired (and killed) by Cursor + { + "supermaven-inc/supermaven-nvim", + event = "InsertEnter", + opts = { + keymaps = { + accept_suggestion = "", + clear_suggestion = "", + accept_word = "", + }, + ignore_filetypes = {}, + disable_inline_completion = false, + disable_keymaps = false, + condition = function() + return false + end, + log_level = "info", + }, + config = function(_, opts) + require("supermaven-nvim").setup(opts) + -- Clear supermaven ghost text when entering command-line mode + -- to prevent interference with wildmenu completion + vim.api.nvim_create_autocmd("CmdlineEnter", { + callback = function() + local ok, preview = pcall(require, "supermaven-nvim.completion_preview") + if ok and preview.on_dispose_inlay then + preview.on_dispose_inlay() + end + end, + }) + end, + }, + { + "huijiro/blink-cmp-supermaven", + }, + + -- Lua LSP completion + types for the Neovim API and lazy plugin specs. + -- Replaces the old lua_ls `globals` hack for editing this config. + { + "folke/lazydev.nvim", + ft = "lua", + opts = { + library = { + -- Load luvit types when the `vim.uv` word is found + { path = "${3rd}/luv/library", words = { "vim%.uv" } }, + -- Types for snacks.nvim (used throughout this config) + { path = "snacks.nvim", words = { "Snacks" } }, + }, + }, + }, +} + +for _, plugin in ipairs(plugins) do + if not plugin.priority then + plugin.priority = 2000 + end +end + +return plugins diff --git a/config/nvim/lua/plugins/editing.lua b/config/nvim/lua/plugins/editing.lua new file mode 100644 index 0000000..af7a867 --- /dev/null +++ b/config/nvim/lua/plugins/editing.lua @@ -0,0 +1,127 @@ +local plugins = { + { "godlygeek/tabular" }, -- text filtering and alignment + { + "Wansmer/treesj", -- splitting/joining blocks of code + dependencies = { "nvim-treesitter/nvim-treesitter" }, + config = function() + require("treesj").setup({ + -- use_default_keymaps = false, + }) + vim.keymap.set("n", "jj", require("treesj").join) + vim.keymap.set("n", "js", require("treesj").split) + vim.keymap.set("n", "jm", require("treesj").toggle) + vim.keymap.set("n", "jM", function() + require("treesj").toggle({ split = { recursive = true } }) + end) + end, + }, + { "tpope/vim-repeat" }, -- enable plugins to repeat actions + { "tpope/vim-sleuth" }, -- heuristically set buffer options + -- Add/delete/replace surrounding pairs: sa (add), sd (delete), sr (replace). + -- Replaces tpope/vim-surround; consolidates onto mini.* with built-in + -- dot-repeat. NOTE: the `s` prefix shadows native `s` (substitute) — use `cl`. + { + "nvim-mini/mini.surround", + event = "VeryLazy", + opts = {}, + }, + -- Auto close/rename HTML/JSX/TSX/Astro tags + { + "windwp/nvim-ts-autotag", + ft = { + "html", "xml", "markdown", "svelte", "vue", + "javascript", "javascriptreact", "typescript", "typescriptreact", "astro", + }, + opts = {}, + }, + -- Context-aware commentstring (e.g. {/* */} inside JSX, not //) + { + "folke/ts-comments.nvim", + event = "VeryLazy", + opts = {}, + }, + -- Extended text objects: af/if (function), ac/ic (class), + -- ao/io (block/conditional/loop), plus args/quotes/brackets. + -- Takes over the old fold if/af mappings. + { + "nvim-mini/mini.ai", + event = "VeryLazy", + dependencies = { + "nvim-treesitter/nvim-treesitter-textobjects", + }, + opts = function() + local ai = require("mini.ai") + return { + n_lines = 500, + custom_textobjects = { + f = ai.gen_spec.treesitter({ a = "@function.outer", i = "@function.inner" }), + c = ai.gen_spec.treesitter({ a = "@class.outer", i = "@class.inner" }), + o = ai.gen_spec.treesitter({ + a = { "@block.outer", "@conditional.outer", "@loop.outer" }, + i = { "@block.inner", "@conditional.inner", "@loop.inner" }, + }), + }, + } + end, + }, + -- Treesitter textobjects (main): move between and swap functions/classes/args, + -- and make ; , (and f F t T) repeat the last move. + { + "nvim-treesitter/nvim-treesitter-textobjects", + branch = "main", + event = "VeryLazy", + config = function() + require("nvim-treesitter-textobjects").setup({ + move = { set_jumps = true }, -- record moves in the jumplist + }) + + local move = require("nvim-treesitter-textobjects.move") + local swap = require("nvim-treesitter-textobjects.swap") + local rep = require("nvim-treesitter-textobjects.repeatable_move") + + -- Repeat the last textobject move with ; and , ... + vim.keymap.set({ "n", "x", "o" }, ";", rep.repeat_last_move) + vim.keymap.set({ "n", "x", "o" }, ",", rep.repeat_last_move_opposite) + -- ... and upgrade f/F/t/T to repeat with the same ; , + vim.keymap.set({ "n", "x", "o" }, "f", rep.builtin_f_expr, { expr = true }) + vim.keymap.set({ "n", "x", "o" }, "F", rep.builtin_F_expr, { expr = true }) + vim.keymap.set({ "n", "x", "o" }, "t", rep.builtin_t_expr, { expr = true }) + vim.keymap.set({ "n", "x", "o" }, "T", rep.builtin_T_expr, { expr = true }) + + -- Jump between functions / classes ( ]] [[ are taken by quickfix ). + -- Note: ]c / [c shadow diff-mode change navigation; swap to ]C/[C if + -- you'd rather keep that. + local jumps = { + ["]f"] = { move.goto_next_start, "@function.outer", "Next function start" }, + ["[f"] = { move.goto_previous_start, "@function.outer", "Prev function start" }, + ["]F"] = { move.goto_next_end, "@function.outer", "Next function end" }, + ["[F"] = { move.goto_previous_end, "@function.outer", "Prev function end" }, + ["]c"] = { move.goto_next_start, "@class.outer", "Next class start" }, + ["[c"] = { move.goto_previous_start, "@class.outer", "Prev class start" }, + } + for lhs, m in pairs(jumps) do + vim.keymap.set({ "n", "x", "o" }, lhs, function() + m[1](m[2], "textobjects") + end, { desc = m[3] }) + end + + -- Swap the argument under the cursor with the next / previous one + vim.keymap.set("n", "sa", function() + swap.swap_next("@parameter.inner") + end, { desc = "Swap argument with next" }) + vim.keymap.set("n", "sA", function() + swap.swap_previous("@parameter.inner") + end, { desc = "Swap argument with previous" }) + end, + }, + -- Comment.nvim and nvim-ts-context-commentstring removed + -- Neovim 0.10+ has built-in gc/gcc commenting with native treesitter commentstring support +} + +for _, plugin in ipairs(plugins) do + if not plugin.priority then + plugin.priority = 2000 + end +end + +return plugins diff --git a/config/nvim/lua/plugins/editor.lua b/config/nvim/lua/plugins/editor.lua new file mode 100644 index 0000000..4e1cb4d --- /dev/null +++ b/config/nvim/lua/plugins/editor.lua @@ -0,0 +1,29 @@ +return { + -- Highlight and navigate TODO / FIXME / HACK / BUG comments + { + "folke/todo-comments.nvim", + event = "VeryLazy", + dependencies = { "nvim-lua/plenary.nvim" }, + opts = {}, + 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" }, + { "qt", "TodoTrouble", desc = "Todo (Trouble)" }, + }, + }, + -- Pretty, navigable lists for diagnostics, references, symbols, quickfix. + -- Lives under q ("lists") to avoid the x buffer-close prefix. + { + "folke/trouble.nvim", + cmd = "Trouble", + dependencies = { "nvim-tree/nvim-web-devicons" }, + opts = {}, + keys = { + { "qd", "Trouble diagnostics toggle", desc = "Diagnostics (Trouble)" }, + { "qb", "Trouble diagnostics toggle filter.buf=0", desc = "Buffer diagnostics (Trouble)" }, + { "qs", "Trouble symbols toggle", desc = "Symbols (Trouble)" }, + { "ql", "Trouble lsp toggle", desc = "LSP defs/refs (Trouble)" }, + { "qq", "Trouble qflist toggle", desc = "Quickfix list (Trouble)" }, + }, + }, +} diff --git a/config/nvim/lua/plugins/fzf.lua b/config/nvim/lua/plugins/fzf.lua new file mode 100644 index 0000000..8ef0104 --- /dev/null +++ b/config/nvim/lua/plugins/fzf.lua @@ -0,0 +1,148 @@ +return { + { + "ibhagwan/fzf-lua", + dependencies = { "nvim-tree/nvim-web-devicons" }, + config = function() + local fzf = require("fzf-lua") + fzf.setup({ + defaults = { + file_icons = true, + }, + grep = { + rg_opts = table.concat({ + "--color=never", + "--no-heading", + "--with-filename", + "--line-number", + "--column", + "--smart-case", + "--glob=!.git/**", + "--glob=!package-lock.json", + "--glob=!lazy-lock.json", + "--glob=!node_modules/**", + }, " "), + }, + keymap = { + builtin = { + [""] = "toggle-help", + }, + fzf = { + ["ctrl-q"] = "select-all+accept", + }, + }, + }) + + -- General pickers + vim.keymap.set("n", "f", fzf.resume, { desc = "Resume search" }) + vim.keymap.set("n", "", fzf.files, { desc = "Find file" }) + vim.keymap.set("n", "a", fzf.live_grep, { desc = "Find text" }) + vim.keymap.set("n", "\\b", fzf.buffers, { desc = "Find buffer" }) + vim.keymap.set("n", "\\h", fzf.help_tags, { desc = "Find help" }) + vim.keymap.set("n", "\\k", fzf.keymaps, { desc = "Find keymaps" }) + vim.keymap.set("n", "t", fzf.colorschemes, { desc = "Select colorscheme" }) + + -- Git + vim.keymap.set("n", "\\g", fzf.git_status, { desc = "Find changed file" }) + + -- LSP pickers + vim.keymap.set("n", "\\r", function() + fzf.lsp_references({ includeDeclaration = false }) + end, { desc = "Find references" }) + vim.keymap.set("n", "\\c", fzf.lsp_incoming_calls, { desc = "Find incoming calls" }) + vim.keymap.set("n", "\\o", fzf.lsp_outgoing_calls, { desc = "Find outgoing calls" }) + vim.keymap.set("n", "\\d", fzf.lsp_definitions, { desc = "Find definitions" }) + vim.keymap.set("n", "\\t", fzf.lsp_typedefs, { desc = "Find type definitions" }) + vim.keymap.set("n", "\\i", fzf.lsp_implementations, { desc = "Find implementations" }) + vim.keymap.set("n", "\\s", fzf.lsp_document_symbols, { desc = "Find document symbols" }) + vim.keymap.set("n", "\\S", fzf.lsp_live_workspace_symbols, { desc = "Find workspace symbols" }) + + -- Search nvim config + vim.keymap.set("n", "\\C", function() + fzf.live_grep({ + cwd = vim.fn.expand("~/.config/nvim"), + rg_opts = "--follow " .. table.concat({ + "--color=never", + "--no-heading", + "--with-filename", + "--line-number", + "--column", + "--smart-case", + }, " "), + }) + end, { desc = "Search config" }) + + -- Custom directory picker + local function reset_lsp_workspace_folders() + local clients = vim.lsp.get_clients() + if #clients == 0 then + vim.notify("No active LSP clients found", vim.log.levels.WARN) + return + end + for _, client in ipairs(clients) do + local current_folders = client.workspace_folders or {} + for _, folder in ipairs(current_folders) do + vim.lsp.buf.remove_workspace_folder(folder.name) + end + vim.lsp.buf.add_workspace_folder(vim.fn.getcwd()) + vim.notify( + string.format("Reset workspace folders for %s to: %s", client.name, vim.fn.getcwd()), + vim.log.levels.INFO + ) + end + end + + local function build_find_dir_cmd() + local home = vim.fn.expand("~") + local homeSrc = vim.fn.expand("~/src") + local sense = "/Volumes/Sense" + local cmd = "find" + local search_paths = {} + local excluded_dirs = { + "*/cloud-copy*", + "*/node_modules*", + "*/.venv*", + "*/venv*", + } + if vim.fn.isdirectory(sense) == 1 then + table.insert(search_paths, sense) + table.insert(excluded_dirs, homeSrc) + else + table.insert(search_paths, homeSrc) + end + table.insert(search_paths, vim.fn.expand("~/.local/share/nvim")) + + local args = { cmd } + for _, path in ipairs(search_paths) do + table.insert(args, path) + end + table.insert(args, "-type") + table.insert(args, "d") + table.insert(args, "-maxdepth") + table.insert(args, "4") + for _, dir in ipairs(excluded_dirs) do + table.insert(args, "-not") + table.insert(args, "-path") + table.insert(args, dir) + end + return table.concat(args, " ") + end + + vim.keymap.set("n", "p", function() + fzf.fzf_exec(build_find_dir_cmd(), { + prompt = "Change Directory> ", + actions = { + ["default"] = function(selected) + local dir = selected[1] + vim.cmd("cd " .. vim.fn.fnameescape(dir)) + vim.opt.titlestring = vim.fn.fnamemodify(dir, ":t") + vim.opt.title = true + reset_lsp_workspace_folders() + vim.cmd("LspRestart") + print("Changed directory to: " .. dir) + end, + }, + }) + end, { desc = "Change working directory" }) + end, + }, +} diff --git a/config/nvim/lua/plugins/git.lua b/config/nvim/lua/plugins/git.lua new file mode 100644 index 0000000..ad120f4 --- /dev/null +++ b/config/nvim/lua/plugins/git.lua @@ -0,0 +1,63 @@ +return { + { + "lewis6991/gitsigns.nvim", + config = function() + -- Git hunk navigation + local gs = require("gitsigns") + gs.setup({ + signs_staged_enable = true, + signcolumn = true, -- Toggle with `:Gitsigns toggle_signs` + numhl = true, -- Toggle with `:Gitsigns toggle_numhl` + linehl = false, -- Toggle with `:Gitsigns toggle_linehl` + word_diff = false, -- Toggle with `:Gitsigns toggle_word_diff` + current_line_blame_opts = { + virt_text_pos = 'right_align', -- 'eol' | 'overlay' | 'right_align' + }, + -- current_line_blame = true, + -- current_line_blame_formatter = ', - ', + }) + vim.keymap.set("n", "[g", gs.prev_hunk, { desc = "Goto previous git hunk" }) + vim.keymap.set("n", "]g", gs.next_hunk, { desc = "Goto next git hunk" }) + + -- vim.keymap.set("n", "cg", gitsigns.preview_hunk, { desc = "Preview git hunk" }) + vim.keymap.set("n", "gd", function() gs.preview_hunk_inline() end, { desc = "Preview hunk inline" }) + vim.keymap.set("n", "gD", function() gs.preview_hunk() end, { desc = "Preview hunk popup" }) + vim.keymap.set("n", "gf", function() gs.diffthis() end, { desc = "Diff file" }) + vim.keymap.set("n", "gr", function() gs.reset_hunk() end, { desc = "Revert hunk" }) + vim.keymap.set("n", "gs", function() gs.stage_hunk() end, { desc = "Stage hunk" }) + vim.keymap.set("n", "ga", function() gs.stage_buffer() end, { desc = "Stage buffer" }) + vim.keymap.set("n", "gB", function() gs.toggle_current_line_blame() end, { desc = "Toggle current line blame (virtual text)" }) + vim.keymap.set("n", "gb", function() gs.blame() end, { desc = "Open blame" }) + + -- Select hunks as a text object. + vim.keymap.set({'o', 'x'}, 'ih', 'Gitsigns select_hunk') + + -- Open current file/line (or visual selection) on the git remote + vim.keymap.set({ "n", "x" }, "go", function() Snacks.gitbrowse() end, { desc = "Open in remote (browser)" }) + end, + }, + -- Lazy-loaded: kept only for its `:Git ` escape hatch and :Gblame, + -- since neogit + diffview + gitsigns cover day-to-day staging/diff/blame. + { + "tpope/vim-fugitive", + cmd = { "Git", "G", "Gdiffsplit", "Gread", "Gwrite", "Gedit", "Gblame", "Gclog" }, + }, + { + "NeogitOrg/neogit", + lazy = true, + dependencies = { + "nvim-lua/plenary.nvim", -- required + "sindrets/diffview.nvim", -- optional - Diff integration + + -- Only one of these is needed. + "ibhagwan/fzf-lua", -- optional + -- "nvim-telescope/telescope.nvim", -- optional + -- "nvim-mini/mini.pick", -- optional + -- "folke/snacks.nvim", -- optional + }, + cmd = "Neogit", + keys = { + { "gg", "Neogit", desc = "Show Neogit UI" } + } + }, +} diff --git a/config/nvim/lua/plugins/lsp.lua b/config/nvim/lua/plugins/lsp.lua new file mode 100644 index 0000000..c3853b3 --- /dev/null +++ b/config/nvim/lua/plugins/lsp.lua @@ -0,0 +1,281 @@ +-- LSP attach handler using LspAttach autocmd (NeoVim 0.11+ recommended pattern) +vim.api.nvim_create_autocmd('LspAttach', { + callback = function(args) + local client = vim.lsp.get_client_by_id(args.data.client_id) + local bufnr = args.buf + + -- Enable completion triggered by + vim.bo[bufnr].omnifunc = "v:lua.vim.lsp.omnifunc" + + vim.keymap.set("n", "K", function() vim.lsp.buf.hover() end, { buffer = bufnr, desc = "Hover info" }) + vim.keymap.set("n", "", function() vim.lsp.buf.signature_help() end, { buffer = bufnr, desc = "Signature help" }) + vim.keymap.set("n", "L", function() vim.lsp.buf.format({ async = true }) end, { buffer = bufnr, desc = "LSP format" }) + + -- TypeScript/JavaScript specific: organize imports + if client and client.name == "ts_ls" then + vim.keymap.set("n", "gro", function() + client:exec_cmd({ + command = "_typescript.organizeImports", + arguments = { vim.api.nvim_buf_get_name(0) }, + title = "", + }) + end, { buffer = bufnr, desc = "Organize imports" }) + end + end, +}) + +local function configure_lsp() -- Setup LSP configurations using vim.lsp.config (NeoVim 0.11+) + -- Completion capabilities from blink.cmp (extends Neovim's defaults with the + -- snippet/resolve/tag support that the hand-rolled table used to set). + local capabilities = require("blink.cmp").get_lsp_capabilities() + + -- Global config for all LSP servers + vim.lsp.config('*', { + capabilities = capabilities, + }) + + -- Configure lua-language-server + vim.lsp.config('lua_ls', { + settings = { + Lua = { + diagnostics = { + -- `vim`/`require` are covered by lazydev.nvim + LuaLS defaults. + -- `hs` is the Hammerspoon global, which lazydev doesn't know about. + globals = { + "hs", + }, + disable = { + "unused-local", + }, + }, + }, + }, + }) + + -- Configure bash-language-server + vim.lsp.config('bashls', { + settings = { + bashIde = { + enableExplainshellEndpoint = false, + explainshellEndpoint = "", + globPattern = "*@(.sh|.bash|.zsh|bash*|zsh*)", + shfmt = { + path = "shfmt", + args = { "-i", "2", "-ci", "-sr" }, + }, + }, + }, + }) + + -- Configure typescript-language-server + vim.lsp.config('ts_ls', {}) + + -- Configure astro-language-server + vim.lsp.config('astro', {}) + + -- Configure gopls (Go language server) + vim.lsp.config('gopls', { + settings = { + gopls = { + analyses = { + unusedparams = true, + }, + staticcheck = true, + gofumpt = true, + }, + }, + }) + + -- Configure pyright (Python language server) + vim.lsp.config('pyright', { + settings = { + python = { + analysis = { + autoSearchPaths = true, + useLibraryCodeForTypes = true, + diagnosticMode = "workspace", + }, + }, + }, + }) + + -- Configure jdtls (Java language server) + vim.lsp.config('jdtls', {}) + + -- Enable all configured servers + -- Note: This doesn't force installation, it just enables them if installed + vim.lsp.enable({ 'lua_ls', 'bashls', 'ts_ls', 'astro', 'gopls', 'pyright', 'jdtls' }) + + -- ESLint LSP is temporarily disabled (causes errors with call hierarchy) + -- To re-enable, remove the line below and add 'eslint' to vim.lsp.enable() + vim.lsp.enable('eslint', false) +end + +return { + { + "nvim-treesitter/nvim-treesitter", + branch = "main", -- Use main branch for Neovim 0.12+ compatibility + build = ":TSUpdate", + config = function() + -- Add nvim-treesitter's runtime directory to runtimepath for queries (folds, indents, etc.) + local ts_runtime = vim.fn.stdpath("data") .. "/lazy/nvim-treesitter/runtime" + if vim.fn.isdirectory(ts_runtime) == 1 then + vim.opt.runtimepath:prepend(ts_runtime) + end + + -- The `main` branch has no `ensure_installed` option, so we install any + -- missing parsers ourselves. Add/remove languages here as needed. + local TS = require("nvim-treesitter") + local ensure_installed = { + "astro", "go", "html", "javascript", "lua", "python", "tsx", "typescript", + } + local installed = TS.get_installed("parsers") + local to_install = vim.tbl_filter(function(lang) + return not vim.tbl_contains(installed, lang) + end, ensure_installed) + if #to_install > 0 then + TS.install(to_install, { summary = true }) + end + + -- On the `main` branch, treesitter highlighting must be started per + -- buffer (it is NOT automatic). Start it whenever a parser is available. + vim.api.nvim_create_autocmd("FileType", { + group = vim.api.nvim_create_augroup("sz_treesitter_start", { clear = true }), + callback = function(args) + pcall(vim.treesitter.start, args.buf) + end, + }) + + -- Show all available code actions (including organize imports) + vim.keymap.set( + "n", + "ca", + vim.lsp.buf.code_action, + { noremap = true, silent = true, desc = "Show code actions" } + ) + vim.keymap.set("v", "", vim.lsp.buf.code_action, + { noremap = true, silent = true, desc = "Show code actions" }) + end, + }, + { + "williamboman/mason.nvim", + main = "mason", + opts = { + registries = { + "github:mason-org/mason-registry", + "github:Crashdummyy/mason-registry", -- Roslyn LSP + }, + }, + }, + { + "williamboman/mason-lspconfig.nvim", + main = "mason-lspconfig", + opts = { + -- Only ensure core language servers are installed on ALL machines + -- Other servers will be auto-installed when you open a file of that type + ensure_installed = { "lua_ls", "bashls" }, + }, + }, + { + "WhoIsSethDaniel/mason-tool-installer.nvim", + dependencies = { "williamboman/mason.nvim" }, + config = function() + require("mason-tool-installer").setup({ + -- Core tools - always installed + ensure_installed = { + "stylua", -- Lua formatting + "shfmt", -- Bash formatting + "prettier", -- JS/TS/Astro formatting + }, + -- Optional tools - install with: :MasonToolsInstall + -- Or add them here when working on those projects + -- "black", -- Python formatting + -- "isort", -- Python imports sorting + -- "gofumpt", -- Go formatting (stricter than gofmt) + -- "goimports", -- Go imports formatting + -- "csharpier", -- C# formatting + -- "netcoredbg", -- C#/.NET debugging + -- "google-java-format", -- Java formatting + }) + end, + }, + { + "neovim/nvim-lspconfig", + config = configure_lsp, + }, + { + "seblj/roslyn.nvim", + ft = "cs", + dependencies = { + "nvim-lua/plenary.nvim", + }, + opts = { + filewatching = "roslyn", + -- Disable Razor extension (only needed for Blazor) + razor = { + enabled = false, + }, + config = { + settings = { + ["csharp|inlay_hints"] = { + csharp_enable_inlay_hints_for_implicit_object_creation = true, + csharp_enable_inlay_hints_for_implicit_variable_types = true, + csharp_enable_inlay_hints_for_lambda_parameter_types = true, + csharp_enable_inlay_hints_for_types = true, + dotnet_enable_inlay_hints_for_indexer_parameters = true, + dotnet_enable_inlay_hints_for_literal_parameters = true, + dotnet_enable_inlay_hints_for_object_creation_parameters = true, + dotnet_enable_inlay_hints_for_other_parameters = true, + dotnet_enable_inlay_hints_for_parameters = true, + dotnet_suppress_inlay_hints_for_parameters_that_differ_only_by_suffix = true, + dotnet_suppress_inlay_hints_for_parameters_that_match_argument_name = true, + dotnet_suppress_inlay_hints_for_parameters_that_match_method_intent = true, + }, + ["csharp|completion"] = { + dotnet_show_completion_items_from_unimported_namespaces = true, + dotnet_show_name_completion_suggestions = true, + }, + ["csharp|code_lens"] = { + dotnet_enable_references_code_lens = true, + dotnet_enable_tests_code_lens = true, + }, + }, + }, + }, + }, + { + "stevearc/conform.nvim", + event = { "BufReadPre", "BufNewFile" }, + config = function() + local conform = require("conform") + conform.setup({ + formatters_by_ft = { + astro = { "prettier" }, + bash = { "shfmt" }, + cs = { "csharpier" }, + css = { "prettier" }, + go = { "goimports", "gofumpt" }, -- Run goimports then gofumpt + html = { "prettier" }, + java = { "google-java-format" }, + javascript = { "prettier" }, + javascriptreact = { "prettier" }, + json = { "prettier" }, + lua = { "stylua" }, + markdown = { "prettier" }, + python = { "isort", "black" }, -- Run isort then black + typescript = { "prettier" }, + typescriptreact = { "prettier" }, + }, + -- Set to `true` to format on save, or provide a function for conditional formatting + -- format_on_save = { + -- timeout_ms = 500, + -- lsp_fallback = true, -- Use LSP formatting as a fallback if no external formatter is found + -- }, + }) + + vim.keymap.set({ "n", "v" }, "l", function() + conform.format({ async = true, lsp_format = "fallback" }) + end, { desc = "Format file or range" }) + end, + }, +} diff --git a/config/nvim/lua/plugins/reload.lua b/config/nvim/lua/plugins/reload.lua new file mode 100644 index 0000000..6bd1366 --- /dev/null +++ b/config/nvim/lua/plugins/reload.lua @@ -0,0 +1,28 @@ +return { + { + dir = vim.fn.stdpath("config") .. "/local-plugins/cfg-watch", + name = "cfg-watch", + event = "VeryLazy", -- fires after init.lua finishes and UI is ready + -- Colorscheme plugins (defined in lua/plugins/colorschemes.lua as lazy=true) + -- are listed here so they load before load_colors() runs `:colorscheme`. + dependencies = { + { "rktjmp/fwatch.nvim" }, + "sonph/onehalf", + "altercation/vim-colors-solarized", + "nlknguyen/papercolor-theme", + "tanvirtin/monokai.nvim", + "tomasr/molokai", + "cohlin/vim-colorschemes", + "reedes/vim-colors-pencil", + "ayu-theme/ayu-vim", + "overcache/NeoSolarized", + "cormacrelf/vim-colors-github", + "rafi/awesome-vim-colorschemes", + }, + config = function() + local lc = require("cfg_watch") + lc.load_colors() + lc.load_config("local") + end, + }, +} diff --git a/config/nvim/lua/plugins/testing.lua b/config/nvim/lua/plugins/testing.lua new file mode 100644 index 0000000..c402bb1 --- /dev/null +++ b/config/nvim/lua/plugins/testing.lua @@ -0,0 +1,41 @@ +return { + { + "nvim-neotest/neotest", + dependencies = { + "nvim-neotest/nvim-nio", + "nvim-lua/plenary.nvim", + "nvim-treesitter/nvim-treesitter", + "nvim-neotest/neotest-jest", + -- "marilari88/neotest-vitest", + }, + cmd = { "Neotest" }, + keys = { + { "ta", function() require("neotest").run.run(vim.fn.getcwd()) end, desc = "Run all tests" }, + { "tf", function() require("neotest").run.run(vim.fn.expand("%")) end, desc = "Run tests in file" }, + { "tn", function() require("neotest").run.run() end, desc = "Run nearest test" }, + { "to", function() require("neotest").output.open({ enter = true }) end, desc = "Show test output" }, + { "tS", function() require("neotest").summary.toggle() end, desc = "Toggle test summary" }, + }, + config = function() + require("neotest").setup({ + adapters = { + require("neotest-jest")({ + jestCommand = "npm test --", + jestArguments = function(defaultArguments, context) + return defaultArguments + end, + -- jestConfigFile = "custom.jest.config.ts", + -- env = { CI = true }, + cwd = function(path) + return vim.fn.getcwd() + end, + filter_dir = function(name, rel_path, root) + return name ~= "node_modules" and name ~= "dist" and name ~= "build" + end, + }), + -- require("neotest-vitest"), + }, + }) + end, + }, +} diff --git a/config/nvim/lua/plugins/ui.lua b/config/nvim/lua/plugins/ui.lua new file mode 100644 index 0000000..d035789 --- /dev/null +++ b/config/nvim/lua/plugins/ui.lua @@ -0,0 +1,335 @@ +return { + { + "folke/snacks.nvim", + priority = 1000, + lazy = false, + ---@type snacks.Config + opts = { + indent = { + enabled = true, + animate = { + enabled = false, + }, + }, + -- Disable expensive features on very large / minified files + bigfile = { enabled = true }, + -- Render a file before plugins fully load, for faster opening + quickfile = { enabled = true }, + -- LSP reference highlighting under the cursor (CursorHold-based); + -- replaces the hand-rolled CursorMoved autocmd in init.lua + words = { enabled = true }, + -- Treesitter scope detection: ii/ai indent text-objects + [i/]i jumps + scope = { enabled = true }, + -- Unified gutter (signs+number+fold+git). The statuscolumn option + -- itself is set in init.lua. + statuscolumn = { enabled = true }, + }, + keys = { + { "it", function() + if Snacks.indent.enabled then + Snacks.indent.disable() + else + Snacks.indent.enable() + end + end, desc = "Toggle indent guides" }, + }, + }, + { + "nvim-lualine/lualine.nvim", + config = function() + local enable_web_dev_icons = vim.g.enable_web_dev_icons ~= 0 + local lualine_custom_theme = require("lualine.themes.auto") + lualine_custom_theme.normal.c.bg = "none" + require("lualine").setup({ + options = { + icons_enabled = enable_web_dev_icons, + theme = lualine_custom_theme, + component_separators = "|", + section_separators = { left = "", right = "" }, + disabled_filetypes = {}, + always_divide_middle = true, + globalstatus = true, + }, + sections = { + lualine_a = { "mode" }, + lualine_b = { "branch", "diff", "diagnostics" }, + lualine_c = { "filename" }, + lualine_x = { "encoding", "fileformat", "filetype" }, + lualine_y = { + function() + return vim.fn.fnamemodify(vim.fn.getcwd(), ":t") + end, + }, + lualine_z = { "progress", "location" }, + }, + inactive_sections = { + lualine_a = {}, + lualine_b = {}, + lualine_c = { "filename" }, + lualine_x = { "location" }, + lualine_y = {}, + lualine_z = {}, + }, + tabline = {}, + extensions = {}, + }) + end, + }, + { "nvim-tree/nvim-web-devicons" }, + { "nvim-mini/mini.icons", version = false }, + { + "j-hui/fidget.nvim", + main = "fidget", + opts = {}, + }, + { + "romgrk/barbar.nvim", + version = "^1.0.0", + dependencies = { + "nvim-tree/nvim-web-devicons", -- for file icons + "lewis6991/gitsigns.nvim", -- optional for git status + }, + init = function() + vim.g.barbar_auto_setup = false + end, + config = function() + require("barbar").setup({ + -- Enable/disable animations + animation = true, + -- Enable/disable auto-hiding the tab bar when there is a single buffer + auto_hide = true, + -- Enable/disable current/total tabpages indicator (top right corner) + tabpages = true, + -- Enables/disable clickable tabs + clickable = true, + -- Configure icons on the bufferline + icons = { + -- Configure the base icons on the bufferline + buffer_index = false, + buffer_number = false, + button = "", -- Empty string disables close button + -- Enables / disables diagnostic symbols + diagnostics = { + [vim.diagnostic.severity.ERROR] = { enabled = true }, + [vim.diagnostic.severity.WARN] = { enabled = true }, + [vim.diagnostic.severity.INFO] = { enabled = false }, + [vim.diagnostic.severity.HINT] = { enabled = false }, + }, + gitsigns = { + added = { enabled = true, icon = "+" }, + changed = { enabled = true, icon = "~" }, + deleted = { enabled = true, icon = "-" }, + }, + filetype = { + -- Sets the icon's highlight group. + -- If false, will use nvim-web-devicons colors + custom_colors = false, + -- Requires `nvim-web-devicons` if `true` + enabled = false, + }, + separator = { left = "▎", right = "" }, + -- Configure the icons on the bufferline when modified + modified = { button = "" }, + -- Configure the icons on the bufferline based on the visibility of a buffer + pinned = { button = "📌" }, + -- Configure the icons on the bufferline for various kinds of tabs + preset = "default", + -- Configure the icons on the bufferline for alternate layout + alternate = { filetype = { enabled = false } }, + -- Configure the icons on the bufferline when the buffer is currently active + current = { buffer_index = false }, + -- Configure the icons on the bufferline for inactive tabs + -- inactive = { button = "×" }, + inactive = {}, + -- Configure the icons on the bufferline for visible tabs + visible = { modified = { buffer_number = false } }, + }, + -- If true, new buffers will be inserted at the start/end of the list. + -- Default is to insert after current buffer. + insert_at_end = false, + insert_at_start = false, + -- Sets the maximum padding width with which to surround each tab + maximum_padding = 1, + -- Sets the minimum padding width with which to surround each tab + minimum_padding = 1, + -- Sets the maximum buffer name length + maximum_length = 30, + -- Sets the minimum buffer name length + minimum_length = 0, + -- If set, the letters for each buffer in buffer-pick mode will be + -- assigned based on their name. Otherwise or in case all letters are + -- already assigned, the behavior is to assign letters in order of + -- usability (see order below) + semantic_letters = true, + -- Sets the name of unnamed buffers. By default format is "[Buffer X]" + -- where X is the buffer number. But only a static string is accepted here. + no_name_title = nil, + -- Set the filetypes which barbar will offset itself for + sidebar_filetypes = { + NvimTree = { text = "Files", align = "left" }, + }, + }) + + -- Buffer navigation with + number + for i = 1, 8 do + vim.keymap.set("n", "" .. i, ":BufferGoto " .. i .. "") + end + vim.keymap.set("n", "9", ":BufferLast", { desc = "Go to last buffer" }) + vim.keymap.set("n", "]", ":BufferNext") + vim.keymap.set("n", "[", ":BufferPrevious") + -- vim.keymap.set("n", "", ":BufferMovePrevious") + -- vim.keymap.set("n", ">", ":BufferMoveNext") + -- vim.keymap.set("n", "", ":BufferPin") + vim.keymap.set("n", "x", ":BufferClose", { desc = "Close buffer" }) + vim.keymap.set("n", "X", ":BufferCloseAllButCurrent", { desc = "Close other buffers" }) + end, + }, + { "fweep/vim-zsh-path-completion" }, + { + "nvim-tree/nvim-tree.lua", + cmd = { "NvimTreeToggle", "NvimTreeFindFile" }, + keys = { + { "/", ":NvimTreeToggle .", desc = "Toggle file tree" }, + { "?", ":NvimTreeFindFile", desc = "Find current file in tree" }, + }, + config = function() + vim.g.enable_web_dev_icons = true + local enable_web_dev_icons = vim.g.enable_web_dev_icons ~= 0 + require("nvim-tree").setup({ + actions = { + open_file = { + window_picker = { + enable = false, + }, + }, + }, + renderer = { + highlight_git = true, + root_folder_label = false, -- Set to false to hide the root folder name, or a function to customize display + icons = { + show = { + file = enable_web_dev_icons, + folder = enable_web_dev_icons, + folder_arrow = enable_web_dev_icons, + git = false, + }, + }, + -- special_files = {}, + }, + view = { + -- signcolumn = 'no', + }, + }) + end, + }, + { + "folke/noice.nvim", + event = "VeryLazy", + dependencies = { + -- if you lazy-load any plugin below, make sure to add proper `module="..."` entries + "MunifTanjim/nui.nvim", + -- OPTIONAL: + -- `nvim-notify` is only needed, if you want to use the notification view. + -- If not available, we use `mini` as the fallback + "rcarriga/nvim-notify", + }, + config = function() + require("notify").setup({ + background_colour = "#000000", + }) + + local noice = require("noice") + noice.setup({ + messages = { + view = "mini", + view_error = "mini", -- view for errors + view_warn = "mini", -- view for warnings + view_search = false, + }, + lsp = { + -- override markdown rendering so that **cmp** and other plugins use **Treesitter** + override = { + ["vim.lsp.util.convert_input_to_markdown_lines"] = true, + ["vim.lsp.util.stylize_markdown"] = true, + ["cmp.entry.get_documentation"] = true, -- requires hrsh7th/nvim-cmp + }, + }, + -- you can enable a preset for easier configuration + presets = { + -- bottom_search = true, -- use a classic bottom cmdline for search + command_palette = true, -- position the cmdline and popupmenu together + long_message_to_split = true, -- long messages will be sent to a split + inc_rename = false, -- enables an input dialog for inc-rename.nvim + lsp_doc_border = false, -- add a border to hover docs and signature help + }, + routes = { + -- buffer path → mini view, kept visible for 10s. + -- Must come first so it wins (and stops) before the generic + -- notify route below; route opts.timeout sets autohide. + { + view = "cmdline_popup", + filter = { + event = "notify", + cond = function(m) + return m.opts ~= nil and m.opts.buffer_path == true + end, + }, + opts = { timeout = 10000 }, + }, + + { view = "mini", filter = { event = "notify", find = "^Register" } }, + + -- 1) all normal Vim messages → mini + -- { filter = { event = "msg_show" }, view = "mini" }, + -- 2) mode/recording messages → mini + -- { filter = { event = "msg_showmode" }, view = "mini" }, + -- 3) anything sent via vim.notify() → mini + { filter = { event = "notify" }, view = "mini" }, + -- 4) LSP progress to mini (leave hover/signature in their own views) + -- { filter = { event = "lsp", kind = "progress" }, view = "mini" }, + }, + }) + + vim.api.nvim_create_autocmd("RecordingEnter", { + group = vim.api.nvim_create_augroup("NoiceMacroNotification", { clear = true }), + callback = function() + _MACRO_RECORDING_STATUS = true + noice.notify(string.format("Register: %s", vim.fn.reg_recording()), vim.log.levels.INFO, { + title = "Recording macro", + view = "mini", + keep = function() + return _MACRO_RECORDING_STATUS + end, + }) + end, + }) + vim.api.nvim_create_autocmd("RecordingLeave", { + group = vim.api.nvim_create_augroup("NoiceMacroNotificationDismiss", { clear = true }), + callback = function() + _MACRO_RECORDING_STATUS = false + noice.notify(string.format("Register: %s", vim.fn.reg_recording()), vim.log.levels.INFO, { + title = "Saved macro", + view = "mini", + timeout = 2000, + }) + end, + }) + + -- setup :Noice dismiss + vim.keymap.set("n", "nd", ":Noice dismiss", { desc = "Dismiss notifications" }) + + -- : show full buffer path. Emit a notify (instead of the native + -- msg_show) so noice's msg_show dedup can't drop repeated presses, and + -- tag it so the route below sends it to the mini view with a long timeout. + vim.keymap.set("n", "", function() + local path = vim.fn.expand("%:p") + if path == "" then + path = "[No Name]" + end + vim.notify(path, vim.log.levels.INFO, { buffer_path = true }) + end, { desc = "Show full buffer path" }) + end, + }, + -- Inlay hints are toggled natively via dh (see init.lua); the + -- MysticalDevil/inlay-hints.nvim plugin is no longer needed on 0.10+. +} diff --git a/config/nvim/lua/plugins/which-key.lua b/config/nvim/lua/plugins/which-key.lua new file mode 100644 index 0000000..8462cbf --- /dev/null +++ b/config/nvim/lua/plugins/which-key.lua @@ -0,0 +1,30 @@ +return { + { + "folke/which-key.nvim", + event = "VeryLazy", + keys = { + { + "@", + function() + local which_key = require("which-key") + which_key.show({ global = false }) + end, + desc = "Buffer Local Keymaps (which-key)", + }, + }, + config = function() + local wk = require("which-key") + wk.add({ + { "c", group = "Code actions" }, + { "g", group = "Git actions" }, + { "d", group = "Diagnostics" }, + { "t", group = "Tests" }, + { ";", group = "Terminal" }, + { "n", group = "Run" }, + { "j", group = "Split/Join" }, + { "q", group = "Lists (Trouble)" }, + { "s", group = "Swap args" }, + }) + end, + }, +} -- cgit v1.2.3