From fc535b66881baceb73ada929d941c590deac5138 Mon Sep 17 00:00:00 2001 From: Rootiest Date: Mon, 24 Aug 2026 21:58:24 -0400 Subject: [PATCH] feat(mini.pairs): smart Markdown code-fence expansion on 3rd backtick Typing a 3rd backtick after mini.pairs' `` pair now expands into a fenced code block instead of pairing into 4 backticks: the cursor lands right after the opener (ready for a language id), and backspacing the fence back out removes the empty closing fence too. Also includes an already-in-progress .gitignore expansion (AGENTS/ and agent-generated docs/ subdirs) and a plugins.lua tabs-to-spaces reformat that were sitting uncommitted in the working tree. --- .gitignore | 13 + README.md | 4 +- lua/plugins.lua | 969 ++++++++++++++++++++++++++---------------------- 3 files changed, 533 insertions(+), 453 deletions(-) diff --git a/.gitignore b/.gitignore index 83bf9d4..c60062f 100644 --- a/.gitignore +++ b/.gitignore @@ -175,3 +175,16 @@ Thumbs.db .[Rr][Ee][Mm][Ee][Mm][Bb][Ee][Rr] # ────────────────────────────────────────────────────────────── + +# ──────────────── Added by agents-init ────────────────── +# agents-init --agents +AGENTS/ +# ──────────────────────────────────────────────────────── + +# ──────────────── Added by agents-init ────────────────── +# agents-init --plugins +docs/superpowers +docs/plans +docs/specs +docs/devlogs +# ──────────────────────────────────────────────────────── diff --git a/README.md b/README.md index 161635c..c16913a 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ A modern, modular, and high-performance Neovim configuration built from scratch - **AI-Powered**: Native integration with **GitHub Copilot** — inline completions via **blink-copilot** and multi-line refactoring via **Sidekick.nvim** Next Edit Suggestions (NES). - **User-Centric QoL**: Hybrid line numbers, autosave-on-edit, and seamless system clipboard integration. - **Resilient & Portable**: Intelligent terminal title management (Kitty + Fallback), automatic project root detection, and machine-local override support. -- **Lean & Readable**: ~850 lines of Lua code (excluding comments and blanks). +- **Lean & Readable**: ~980 lines of Lua code (excluding comments and blanks). ## 📁 Architecture @@ -42,7 +42,7 @@ The configuration is strictly modular: - **Focusline**: Keeps the active line at a configurable screen position (30%) during scrolling motions. - **Mini.ai**: Better text objects (including `g` for entire buffer). - **Mini.surround**: Surround text objects (add/delete/change). -- **Mini.pairs**: Auto-close brackets and quotes. +- **Mini.pairs**: Auto-close brackets and quotes. In Markdown, a 3rd backtick expands `` `` `` into a fenced code block (cursor after the opener, ready for a language id) instead of pairing into 4 backticks; backspacing the fence back out removes the closing fence too. - **Persistence**: Session management. - **Which-key**: Interactive keybinding documentation. - **Gitsigns**: In-buffer git indicators and line highlights. diff --git a/lua/plugins.lua b/lua/plugins.lua index 1c783da..81dcb61 100644 --- a/lua/plugins.lua +++ b/lua/plugins.lua @@ -13,7 +13,7 @@ vim.pack.add({ { src = "https://github.com/catppuccin/nvim", name = "catppuccin" -- Catppuccin Config Config.plugins.catppuccin = { - flavour = "mocha", + flavour = "mocha", } vim.cmd.colorscheme("catppuccin") @@ -24,89 +24,89 @@ vim.pack.add({ { src = "https://github.com/folke/snacks.nvim", name = "snacks" } -- Snacks Config -- Store opts in the registry first. Config.plugins.snacks = { - bigfile = { enabled = true }, - dashboard = { - enabled = true, - sections = { - { section = "header" }, - { section = "keys", gap = 1, padding = 1 }, - }, - preset = { - header = require("const").header, - }, - }, - explorer = { enabled = true }, - indent = { enabled = true }, - input = { enabled = true }, - picker = { - enabled = true, - matcher = { - fuzzy = true, - smartcase = true, - ignorecase = true, - sort_empty = false, - filename_bonus = true, - file_pos = true, - cwd_bonus = true, - frecency = true, - history_bonus = true, - }, - win = { - input = { - keys = { - [""] = { "flash", mode = { "n", "i" } }, - ["s"] = { "flash" }, - [""] = { "sidekick_send", mode = { "n", "i" } }, - }, - }, - }, - actions = { - flash = function(picker) - 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 ~= "snacks_picker_list" - end, - }, - }, - action = function(match) - local idx = picker.list:row2idx(match.pos[1]) - picker.list:_move(idx, true, true) - end, - }) - end, - sidekick_send = function(...) - return require("sidekick.cli.picker.snacks").send(...) - end, - }, - }, - notifier = { enabled = true }, - quickfile = { enabled = true }, - scope = { enabled = true }, - scratch = { enabled = true }, - scroll = { enabled = true }, - statuscolumn = { enabled = true }, - terminal = { - enabled = true, - win = { - keys = { - toggle = { - "", - function(self) - self:hide() - end, - mode = "t", - desc = "Toggle Terminal", - }, - }, - }, - }, - words = { enabled = true }, - zen = { enabled = true }, + bigfile = { enabled = true }, + dashboard = { + enabled = true, + sections = { + { section = "header" }, + { section = "keys", gap = 1, padding = 1 }, + }, + preset = { + header = require("const").header, + }, + }, + explorer = { enabled = true }, + indent = { enabled = true }, + input = { enabled = true }, + picker = { + enabled = true, + matcher = { + fuzzy = true, + smartcase = true, + ignorecase = true, + sort_empty = false, + filename_bonus = true, + file_pos = true, + cwd_bonus = true, + frecency = true, + history_bonus = true, + }, + win = { + input = { + keys = { + [""] = { "flash", mode = { "n", "i" } }, + ["s"] = { "flash" }, + [""] = { "sidekick_send", mode = { "n", "i" } }, + }, + }, + }, + actions = { + flash = function(picker) + 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 ~= "snacks_picker_list" + end, + }, + }, + action = function(match) + local idx = picker.list:row2idx(match.pos[1]) + picker.list:_move(idx, true, true) + end, + }) + end, + sidekick_send = function(...) + return require("sidekick.cli.picker.snacks").send(...) + end, + }, + }, + notifier = { enabled = true }, + quickfile = { enabled = true }, + scope = { enabled = true }, + scratch = { enabled = true }, + scroll = { enabled = true }, + statuscolumn = { enabled = true }, + terminal = { + enabled = true, + win = { + keys = { + toggle = { + "", + function(self) + self:hide() + end, + mode = "t", + desc = "Toggle Terminal", + }, + }, + }, + }, + words = { enabled = true }, + zen = { enabled = true }, } -- Setup Snacks with the registered opts. @@ -118,9 +118,9 @@ vim.pack.add({ { src = "https://github.com/folke/persistence.nvim", name = "pers -- Persistence Config Config.plugins.persistence = { - dir = vim.fn.stdpath("state") .. "/sessions/", - need = 1, - branch = true, + dir = vim.fn.stdpath("state") .. "/sessions/", + need = 1, + branch = true, } require("persistence").setup(Config.plugins.persistence) @@ -128,405 +128,472 @@ require("persistence").setup(Config.plugins.persistence) local lazyload = require("lazyload") lazyload.on_vim_enter(function() - -- Gitsigns.nvim - vim.pack.add({ { src = "https://github.com/lewis6991/gitsigns.nvim", name = "gitsigns" } }) - require("gitsigns").setup({ - numhl = false, - signcolumn = true, - current_line_blame = true, -- Required for the statusline variable to update - current_line_blame_opts = { - virt_text = false, -- Disable virtual text as we'll use the statusline - }, - }) + -- Gitsigns.nvim + vim.pack.add({ { src = "https://github.com/lewis6991/gitsigns.nvim", name = "gitsigns" } }) + require("gitsigns").setup({ + numhl = false, + signcolumn = true, + current_line_blame = true, -- Required for the statusline variable to update + current_line_blame_opts = { + virt_text = false, -- Disable virtual text as we'll use the statusline + }, + }) - -- Treesitter Context - vim.pack.add({ - { src = "https://github.com/nvim-treesitter/nvim-treesitter-context", name = "nvim-treesitter-context" }, - }) - require("treesitter-context").setup({ - max_lines = 3, - trim_scope = "outer", - }) + -- Treesitter Context + vim.pack.add({ + { src = "https://github.com/nvim-treesitter/nvim-treesitter-context", name = "nvim-treesitter-context" }, + }) + require("treesitter-context").setup({ + max_lines = 3, + trim_scope = "outer", + }) - -- Grug-far.nvim - vim.pack.add({ { src = "https://github.com/MagicDuck/grug-far.nvim", name = "grug-far" } }) - require("grug-far").setup() + -- Grug-far.nvim + vim.pack.add({ { src = "https://github.com/MagicDuck/grug-far.nvim", name = "grug-far" } }) + require("grug-far").setup() - -- Flash.nvim - vim.pack.add({ { src = "https://github.com/folke/flash.nvim", name = "flash" } }) - require("flash").setup({ - modes = { char = { keys = {} } }, - keys = {}, - }) + -- Flash.nvim + vim.pack.add({ { src = "https://github.com/folke/flash.nvim", name = "flash" } }) + require("flash").setup({ + modes = { char = { keys = {} } }, + keys = {}, + }) - -- Leap.nvim - vim.pack.add({ { src = "https://git.disroot.org/andyg/leap.nvim", name = "leap" } }) - vim.api.nvim_set_hl(0, "LeapBackdrop", { link = "Comment" }) + -- Leap.nvim + vim.pack.add({ { src = "https://git.disroot.org/andyg/leap.nvim", name = "leap" } }) + vim.api.nvim_set_hl(0, "LeapBackdrop", { link = "Comment" }) - -- Focusline.nvim - vim.pack.add({ - { src = "https://github.com/ABDsheikho/focusline.nvim" }, - }) + -- Focusline.nvim + vim.pack.add({ + { src = "https://github.com/ABDsheikho/focusline.nvim" }, + }) - require("focusline").setup({ - -- focus_target can be a line number (e.g., 15), or a ratio (e.g., 0.25, 1 / 4, "25%"). - focus_target = "30%", -- try it with 30% - -- which motion to associate focusline with. - with_motion = { - "zz", - "z,", - "\x04", -- Ctrl+D - "\x15", -- Ctrl+U - }, - }) + require("focusline").setup({ + -- focus_target can be a line number (e.g., 15), or a ratio (e.g., 0.25, 1 / 4, "25%"). + focus_target = "30%", -- try it with 30% + -- which motion to associate focusline with. + with_motion = { + "zz", + "z,", + "\x04", -- Ctrl+D + "\x15", -- Ctrl+U + }, + }) - -- Mini.surround - vim.pack.add({ { src = "https://github.com/echasnovski/mini.surround", name = "mini.surround" } }) - require("mini.surround").setup() + -- Mini.surround + vim.pack.add({ { src = "https://github.com/echasnovski/mini.surround", name = "mini.surround" } }) + require("mini.surround").setup() - -- Mini.pairs - vim.pack.add({ { src = "https://github.com/echasnovski/mini.pairs", name = "mini.pairs" } }) - require("mini.pairs").setup() + -- Mini.pairs + vim.pack.add({ { src = "https://github.com/echasnovski/mini.pairs", name = "mini.pairs" } }) + require("mini.pairs").setup() - -- Gx.nvim - vim.pack.add({ { src = "https://github.com/chrishrb/gx.nvim", name = "gx" } }) - ---@diagnostic disable-next-line: missing-fields - require("gx").setup({ - handlers = { - plugin = true, - github = true, - package_json = true, - search = true, - }, - }) + -- Smart backtick handling for Markdown: typing a 3rd backtick after an + -- existing `` `` `` pair expands into a fenced code block instead of + -- pairing into ```` ` ```` (4 backticks). + local function attach_markdown_smart_backtick(buf) + vim.keymap.set("i", "`", function() + local row, col = unpack(vim.api.nvim_win_get_cursor(0)) + local line = vim.api.nvim_get_current_line() + local before, after = line:sub(1, col), line:sub(col + 1) + local disabled = vim.g.minipairs_disable == true or vim.b.minipairs_disable == true - -- Comment-box.nvim - vim.pack.add({ { src = "https://github.com/LudoPinelli/comment-box.nvim", name = "comment-box" } }) - require("comment-box").setup() + if not disabled and before:sub(-2) == "``" and after:sub(1, 1) ~= "`" then + local prefix = before:sub(1, -3) + local indent = prefix:match("^%s*") + vim.api.nvim_set_current_line(prefix .. "```") + vim.api.nvim_buf_set_lines(0, row, row, false, { indent .. "```" .. after }) + vim.api.nvim_win_set_cursor(0, { row, #prefix + 3 }) + return + end - -- Undotree - vim.g.undotree_ShortIndicators = 0 - vim.g.undotree_SplitWidth = 32 - vim.g.undotree_SetFocusWhenToggle = 1 - vim.g.undotree_TreeNodeShape = "" - vim.g.undotree_TreeVertShape = "" - vim.g.undotree_TreeSplitShape = "" - vim.g.undotree_TreeReturnShape = "" - vim.g.undotree_HelpLine = 0 - vim.g.undotree_DiffAutoOpen = 0 - vim.pack.add({ { src = "https://github.com/mbbill/undotree", name = "undotree" } }) + local keys = MiniPairs.closeopen("``", "^[^\\]") + vim.api.nvim_feedkeys(keys, "in", false) + end, { buffer = buf, desc = "Smart Markdown backtick / code fence" }) - -- Conform.nvim - vim.pack.add({ { src = "https://github.com/stevearc/conform.nvim", name = "conform" } }) - Config.plugins.conform = { - formatters_by_ft = { - lua = { "stylua" }, - c = { "clang-format" }, - cpp = { "clang-format" }, - rust = { "rustfmt" }, - python = { "ruff_format" }, - fish = { "fish_indent" }, - sh = { "shfmt" }, - bash = { "shfmt" }, - }, - format_on_save = function(bufnr) - -- Disable with a global or buffer-local variable - if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then - return - end - return { timeout_ms = 500, lsp_format = "fallback" } - end, - } - require("conform").setup(Config.plugins.conform) + -- Deleting the opening fence's last backtick also removes the untouched + -- closing fence line it created, so undoing a fence is a clean backspace. + vim.keymap.set("i", "", function() + local row, col = unpack(vim.api.nvim_win_get_cursor(0)) + local line = vim.api.nvim_get_current_line() + local before, after = line:sub(1, col), line:sub(col + 1) + local disabled = vim.g.minipairs_disable == true or vim.b.minipairs_disable == true - -- Which-key.nvim - vim.pack.add({ { src = "https://github.com/folke/which-key.nvim", name = "which-key" } }) - require("which-key").setup() + if not disabled and before:sub(-1) == "`" and before:sub(-2, -2) ~= "`" then + local prefix = before:sub(1, -2) + local indent = prefix:match("^%s*") + local next_line = vim.api.nvim_buf_get_lines(0, row, row + 1, false)[1] + if next_line == indent .. "```" then + vim.api.nvim_buf_set_lines(0, row, row + 1, false, {}) + vim.api.nvim_set_current_line(prefix .. after) + vim.api.nvim_win_set_cursor(0, { row, #prefix }) + return + end + end - -- Lazydev.nvim (Neovim Lua type definitions for lua_ls) - vim.pack.add({ { src = "https://github.com/folke/lazydev.nvim", name = "lazydev" } }) - require("lazydev").setup({ - library = { - { path = "${3rd}/luv/library", words = { "vim%.uv" } }, - { path = "snacks.nvim", words = { "Snacks" } }, - }, - }) + local keys = MiniPairs.bs() + vim.api.nvim_feedkeys(keys, "in", false) + end, { buffer = buf, desc = "Smart Markdown fence backspace" }) + end - -- LSP Support - vim.pack.add({ { src = "https://github.com/williamboman/mason.nvim", name = "mason" } }) - vim.pack.add({ { src = "https://github.com/williamboman/mason-lspconfig.nvim", name = "mason-lspconfig" } }) - vim.pack.add({ { src = "https://github.com/neovim/nvim-lspconfig", name = "nvim-lspconfig" } }) + vim.api.nvim_create_autocmd("FileType", { + group = vim.api.nvim_create_augroup("markdown_smart_backtick", { clear = true }), + pattern = "markdown", + desc = "Expand `` ` `` pair into a fenced code block on 3rd backtick", + callback = function(args) + attach_markdown_smart_backtick(args.buf) + end, + }) - require("mason").setup() + -- Catch buffers that already had their FileType event fire before this + -- deferred setup ran (e.g. `nvim file.md` opened directly at startup). + for _, buf in ipairs(vim.api.nvim_list_bufs()) do + if vim.bo[buf].filetype == "markdown" then + attach_markdown_smart_backtick(buf) + end + end - -- Completion (Blink.cmp) - -- We check if the binary exists or if cargo is available to build it. - -- If neither, we skip loading to avoid errors. - local blink_path = vim.fn.stdpath("data") .. "/site/pack/core/opt/blink.cmp" - local has_blink_bin = - -- Check v2 location (most likely where it is now) - vim.fn.filereadable(blink_path .. "/lua/blink/cmp/lib/libblink_cmp_fuzzy.so") == 1 - -- Check v1/Cargo location (fallback) - or vim.fn.filereadable(blink_path .. "/target/release/libblink_cmp_fuzzy.so") == 1 - -- Check macOS/Windows extensions - or vim.fn.filereadable(blink_path .. "/target/release/libblink_cmp_fuzzy.dylib") == 1 - or vim.fn.filereadable(blink_path .. "/target/release/libblink_cmp_fuzzy.dll") == 1 + -- Gx.nvim + vim.pack.add({ { src = "https://github.com/chrishrb/gx.nvim", name = "gx" } }) + ---@diagnostic disable-next-line: missing-fields + require("gx").setup({ + handlers = { + plugin = true, + github = true, + package_json = true, + search = true, + }, + }) - local has_cargo = vim.fn.executable("cargo") == 1 + -- Comment-box.nvim + vim.pack.add({ { src = "https://github.com/LudoPinelli/comment-box.nvim", name = "comment-box" } }) + require("comment-box").setup() - if has_blink_bin or has_cargo then - -- 1. Add blink.lib first as it's a dependency for blink.cmp - vim.pack.add({ { src = "https://github.com/saghen/blink.lib", name = "blink.lib" } }) + -- Undotree + vim.g.undotree_ShortIndicators = 0 + vim.g.undotree_SplitWidth = 32 + vim.g.undotree_SetFocusWhenToggle = 1 + vim.g.undotree_TreeNodeShape = "" + vim.g.undotree_TreeVertShape = "" + vim.g.undotree_TreeSplitShape = "" + vim.g.undotree_TreeReturnShape = "" + vim.g.undotree_HelpLine = 0 + vim.g.undotree_DiffAutoOpen = 0 + vim.pack.add({ { src = "https://github.com/mbbill/undotree", name = "undotree" } }) - -- 2. Add blink.cmp and other sources - vim.pack.add({ { src = "https://github.com/saghen/blink.cmp", name = "blink.cmp" } }) - vim.pack.add({ { src = "https://github.com/rafamadriz/friendly-snippets", name = "friendly-snippets" } }) - vim.pack.add({ { src = "https://github.com/fang2hou/blink-copilot", name = "blink-copilot" } }) + -- Conform.nvim + vim.pack.add({ { src = "https://github.com/stevearc/conform.nvim", name = "conform" } }) + Config.plugins.conform = { + formatters_by_ft = { + lua = { "stylua" }, + c = { "clang-format" }, + cpp = { "clang-format" }, + rust = { "rustfmt" }, + python = { "ruff_format" }, + fish = { "fish_indent" }, + sh = { "shfmt" }, + bash = { "shfmt" }, + }, + format_on_save = function(bufnr) + -- Disable with a global or buffer-local variable + if vim.g.disable_autoformat or vim.b[bufnr].disable_autoformat then + return + end + return { timeout_ms = 500, lsp_format = "fallback" } + end, + } + require("conform").setup(Config.plugins.conform) - -- 3. Blink.cmp setup - require("blink.cmp").setup({ - keymap = { - preset = "default", - [""] = { - "snippet_forward", - function() - return require("sidekick").nes_jump_or_apply() - end, - function() - return vim.lsp.inline_completion.get() - end, - "fallback", - }, - }, - appearance = { - use_nvim_cmp_as_default = true, - nerd_font_variant = "mono", - kind_icons = { Copilot = "" }, - }, - sources = { - default = { "lsp", "path", "snippets", "buffer", "copilot" }, - providers = { - copilot = { - name = "copilot", - module = "blink-copilot", - kind = "lsp", - server_name = "copilot", - score_offset = 100, - async = true, - opts = { - max_completions = 3, - max_attempts = 4, - }, - }, - }, - }, - signature = { enabled = true }, - }) - else - vim.notify("blink.cmp: binary not found and cargo not installed. Completion disabled.", vim.log.levels.WARN) - end + -- Which-key.nvim + vim.pack.add({ { src = "https://github.com/folke/which-key.nvim", name = "which-key" } }) + require("which-key").setup() - -- Autocmd to build blink.cmp after plugin installation or update - vim.api.nvim_create_autocmd("User", { - pattern = "PackChanged", -- This triggers after vim.pack.update() finishes - callback = function() - -- Check if blink is actually installed before trying to build - local status, blink = pcall(require, "blink.cmp") - if status then - vim.notify("Blink.cmp: Building native library...", vim.log.levels.INFO) - ---@diagnostic disable-next-line: undefined-field - blink.build():wait(60000) - vim.notify("Blink.cmp: Build complete.", vim.log.levels.INFO) - end - end, - }) + -- Lazydev.nvim (Neovim Lua type definitions for lua_ls) + vim.pack.add({ { src = "https://github.com/folke/lazydev.nvim", name = "lazydev" } }) + require("lazydev").setup({ + library = { + { path = "${3rd}/luv/library", words = { "vim%.uv" } }, + { path = "snacks.nvim", words = { "Snacks" } }, + }, + }) - local lspconfig = require("lspconfig") - local capabilities = (has_blink_bin or has_cargo) and require("blink.cmp").get_lsp_capabilities() or nil + -- LSP Support + vim.pack.add({ { src = "https://github.com/williamboman/mason.nvim", name = "mason" } }) + vim.pack.add({ { src = "https://github.com/williamboman/mason-lspconfig.nvim", name = "mason-lspconfig" } }) + vim.pack.add({ { src = "https://github.com/neovim/nvim-lspconfig", name = "nvim-lspconfig" } }) - vim.lsp.config("rust_analyzer", { - settings = { - ["rust-analyzer"] = { - check = { - command = "clippy", - }, - }, - }, - }) + require("mason").setup() - require("mason-lspconfig").setup({ - ensure_installed = { - "lua_ls", - "clangd", - "rust_analyzer", - "pyright", - "fish_lsp", - "bashls", - "copilot", - }, - handlers = { - -- Default handler - function(server_name) - lspconfig[server_name].setup({ capabilities = capabilities }) - end, - -- Specific overrides - ["lua_ls"] = function() - lspconfig.lua_ls.setup({ - capabilities = capabilities, - settings = { - Lua = { - diagnostics = { globals = { "Config" } }, - }, - }, - }) - end, - ["rust_analyzer"] = function() - lspconfig.rust_analyzer.setup({ - capabilities = capabilities, - settings = { - ["rust-analyzer"] = { - check = { - command = "clippy", - }, - }, - }, - }) - end, - }, - }) + -- Completion (Blink.cmp) + -- We check if the binary exists or if cargo is available to build it. + -- If neither, we skip loading to avoid errors. + local blink_path = vim.fn.stdpath("data") .. "/site/pack/core/opt/blink.cmp" + local has_blink_bin = + -- Check v2 location (most likely where it is now) + vim.fn.filereadable(blink_path .. "/lua/blink/cmp/lib/libblink_cmp_fuzzy.so") == 1 + -- Check v1/Cargo location (fallback) + or vim.fn.filereadable(blink_path .. "/target/release/libblink_cmp_fuzzy.so") == 1 + -- Check macOS/Windows extensions + or vim.fn.filereadable(blink_path .. "/target/release/libblink_cmp_fuzzy.dylib") == 1 + or vim.fn.filereadable(blink_path .. "/target/release/libblink_cmp_fuzzy.dll") == 1 - -- Enable native inline completion (ghost text in insert mode) - vim.lsp.inline_completion.enable() + local has_cargo = vim.fn.executable("cargo") == 1 - -- Mini.ai - vim.pack.add({ { src = "https://github.com/echasnovski/mini.ai", name = "mini.ai" } }) + if has_blink_bin or has_cargo then + -- 1. Add blink.lib first as it's a dependency for blink.cmp + vim.pack.add({ { src = "https://github.com/saghen/blink.lib", name = "blink.lib" } }) - Config.plugins.mini_ai = { - custom_textobjects = { - g = function() - local n_lines = vim.api.nvim_buf_line_count(0) - return { - from = { line = 1, col = 1 }, - ---@diagnostic disable-next-line: undefined-field - to = { line = n_lines, col = math.max(vim.fn.getline(n_lines):len(), 1) }, - } - end, - }, - } - require("mini.ai").setup(Config.plugins.mini_ai) + -- 2. Add blink.cmp and other sources + vim.pack.add({ { src = "https://github.com/saghen/blink.cmp", name = "blink.cmp" } }) + vim.pack.add({ { src = "https://github.com/rafamadriz/friendly-snippets", name = "friendly-snippets" } }) + vim.pack.add({ { src = "https://github.com/fang2hou/blink-copilot", name = "blink-copilot" } }) - -- Icons - vim.pack.add({ { src = "https://github.com/nvim-tree/nvim-web-devicons", name = "nvim-web-devicons" } }) + -- 3. Blink.cmp setup + local cmp = require('blink.cmp') + cmp.build():wait(60000) + cmp.setup({ + keymap = { + preset = "default", + [""] = { + "snippet_forward", + function() + return require("sidekick").nes_jump_or_apply() + end, + function() + return vim.lsp.inline_completion.get() + end, + "fallback", + }, + }, + appearance = { + use_nvim_cmp_as_default = true, + nerd_font_variant = "mono", + kind_icons = { Copilot = "" }, + }, + sources = { + default = { "lsp", "path", "snippets", "buffer", "copilot" }, + providers = { + copilot = { + name = "copilot", + module = "blink-copilot", + kind = "lsp", + server_name = "copilot", + score_offset = 100, + async = true, + opts = { + max_completions = 3, + max_attempts = 4, + }, + }, + }, + }, + signature = { enabled = true }, + }) + else + vim.notify("blink.cmp: binary not found and cargo not installed. Completion disabled.", vim.log.levels.WARN) + end - -- Lualine - vim.pack.add({ { src = "https://github.com/nvim-lualine/lualine.nvim", name = "lualine" } }) - require("lualine").setup({ - options = { - theme = "auto", - globalstatus = true, - }, - sections = { - lualine_b = { "branch", "diff", "diagnostics" }, - lualine_c = { - { "filename" }, - { - function() - return vim.b.gitsigns_blame_line or "" - end, - cond = function() - return vim.b.gitsigns_blame_line ~= nil and vim.b.gitsigns_blame_line ~= "" - end, - }, - }, - }, - }) + -- Autocmd to build blink.cmp after plugin installation or update + vim.api.nvim_create_autocmd("User", { + pattern = "PackChanged", -- This triggers after vim.pack.update() finishes + callback = function() + -- Check if blink is actually installed before trying to build + local status, blink = pcall(require, "blink.cmp") + if status then + vim.notify("Blink.cmp: Building native library...", vim.log.levels.INFO) + ---@diagnostic disable-next-line: undefined-field + blink.build():wait(60000) + vim.notify("Blink.cmp: Build complete.", vim.log.levels.INFO) + end + end, + }) - -- Inc-rename.nvim - vim.pack.add({ { src = "https://github.com/smjonas/inc-rename.nvim", name = "inc-rename" } }) - require("inc_rename").setup({ - -- the name of the command - cmd_name = "IncRename", - -- the highlight group used for highlighting the identifier's new name - hl_group = "Substitute", - -- whether an empty new name should be previewed; if false the command preview will be cancelled instead - preview_empty_name = false, - -- whether to display a `Renamed m instances in n files` message after a rename operation - show_message = true, - -- whether to save the "IncRename" command in the commandline history (set to false to prevent issues with - -- navigating to older entries that may arise due to the behavior of command preview) - save_in_cmdline_history = true, - -- the type of the external input buffer to use (currently supports "dressing" or "snacks") - input_buffer_type = nil, - -- callback to run after renaming, receives the result table (from LSP handler) as an argument - post_hook = nil, - }) + local lspconfig = require("lspconfig") + local capabilities = (has_blink_bin or has_cargo) and require("blink.cmp").get_lsp_capabilities() or nil - -- Qalc - vim.pack.add({ { src = "https://github.com/Apeiros-46B/qalc.nvim", name = "qalc" } }) + vim.lsp.config("rust_analyzer", { + settings = { + ["rust-analyzer"] = { + check = { + command = "clippy", + }, + }, + }, + }) - -- Noice dependencies - vim.pack.add({ { src = "https://github.com/MunifTanjim/nui.nvim", name = "nui" } }) + require("mason-lspconfig").setup({ + ensure_installed = { + "lua_ls", + "clangd", + "rust_analyzer", + "pyright", + "fish_lsp", + "bashls", + "copilot", + }, + handlers = { + -- Default handler + function(server_name) + lspconfig[server_name].setup({ capabilities = capabilities }) + end, + -- Specific overrides + ["lua_ls"] = function() + lspconfig.lua_ls.setup({ + capabilities = capabilities, + settings = { + Lua = { + diagnostics = { globals = { "Config" } }, + }, + }, + }) + end, + ["rust_analyzer"] = function() + lspconfig.rust_analyzer.setup({ + capabilities = capabilities, + settings = { + ["rust-analyzer"] = { + check = { + command = "clippy", + }, + }, + }, + }) + end, + }, + }) - -- Noice.nvim - vim.pack.add({ { src = "https://github.com/folke/noice.nvim", name = "noice" } }) - require("noice").setup({ - 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.set_autocmds"] = true, - ["vim.ui.codelens.display_inline"] = true, - }, - }, - -- 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 = true, -- enables an input dialog for inc-rename.nvim - lsp_doc_border = false, -- add a border to hover docs and signature help - }, - routes = { - { - filter = { event = "lsp", kind = "progress", find = "Loading workspace" }, - opts = { skip = true }, - }, - { - filter = { event = "lsp", kind = "progress", find = "^Diagnosing" }, - opts = { skip = true }, - }, - { - filter = { event = "lsp", kind = "progress", find = "semantic tokens" }, - opts = { skip = true }, - }, - }, - }) + -- Enable native inline completion (ghost text in insert mode) + vim.lsp.inline_completion.enable() - -- Kitty Scrollback - vim.pack.add({ { src = "https://github.com/mikesmithgh/kitty-scrollback.nvim", name = "kitty-scrollback" } }) - Config.plugins.kitty_scrollback = {} - require("kitty-scrollback").setup(Config.plugins.kitty_scrollback) + -- Mini.ai + vim.pack.add({ { src = "https://github.com/echasnovski/mini.ai", name = "mini.ai" } }) - -- Haunt.nvim - vim.pack.add({ { src = "https://github.com/TheNoeTrevino/haunt.nvim", name = "haunt" } }) - Config.plugins.haunt = { - picker = "snacks", - } - require("haunt").setup(Config.plugins.haunt) + Config.plugins.mini_ai = { + custom_textobjects = { + g = function() + local n_lines = vim.api.nvim_buf_line_count(0) + return { + from = { line = 1, col = 1 }, + ---@diagnostic disable-next-line: undefined-field + to = { line = n_lines, col = math.max(vim.fn.getline(n_lines):len(), 1) }, + } + end, + }, + } + require("mini.ai").setup(Config.plugins.mini_ai) - -- Sidekick.nvim - vim.pack.add({ { src = "https://github.com/folke/sidekick.nvim", name = "sidekick" } }) - Config.plugins.sidekick = { - cli = { - prompts = { - haunt_all = function() - return require("haunt.sidekick").get_locations() - end, - haunt_buffer = function() - return require("haunt.sidekick").get_locations({ current_buffer = true }) - end, - }, - }, - } - require("sidekick").setup(Config.plugins.sidekick) + -- Icons + vim.pack.add({ { src = "https://github.com/nvim-tree/nvim-web-devicons", name = "nvim-web-devicons" } }) + + -- Lualine + vim.pack.add({ { src = "https://github.com/nvim-lualine/lualine.nvim", name = "lualine" } }) + require("lualine").setup({ + options = { + theme = "auto", + globalstatus = true, + }, + sections = { + lualine_b = { "branch", "diff", "diagnostics" }, + lualine_c = { + { "filename" }, + { + function() + return vim.b.gitsigns_blame_line or "" + end, + cond = function() + return vim.b.gitsigns_blame_line ~= nil and vim.b.gitsigns_blame_line ~= "" + end, + }, + }, + }, + }) + + -- Inc-rename.nvim + vim.pack.add({ { src = "https://github.com/smjonas/inc-rename.nvim", name = "inc-rename" } }) + require("inc_rename").setup({ + -- the name of the command + cmd_name = "IncRename", + -- the highlight group used for highlighting the identifier's new name + hl_group = "Substitute", + -- whether an empty new name should be previewed; if false the command preview will be cancelled instead + preview_empty_name = false, + -- whether to display a `Renamed m instances in n files` message after a rename operation + show_message = true, + -- whether to save the "IncRename" command in the commandline history (set to false to prevent issues with + -- navigating to older entries that may arise due to the behavior of command preview) + save_in_cmdline_history = true, + -- the type of the external input buffer to use (currently supports "dressing" or "snacks") + input_buffer_type = nil, + -- callback to run after renaming, receives the result table (from LSP handler) as an argument + post_hook = nil, + }) + + -- Qalc + vim.pack.add({ { src = "https://github.com/Apeiros-46B/qalc.nvim", name = "qalc" } }) + + -- Noice dependencies + vim.pack.add({ { src = "https://github.com/MunifTanjim/nui.nvim", name = "nui" } }) + + -- Noice.nvim + vim.pack.add({ { src = "https://github.com/folke/noice.nvim", name = "noice" } }) + require("noice").setup({ + 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.set_autocmds"] = true, + ["vim.ui.codelens.display_inline"] = true, + }, + }, + -- 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 = true, -- enables an input dialog for inc-rename.nvim + lsp_doc_border = false, -- add a border to hover docs and signature help + }, + routes = { + { + filter = { event = "lsp", kind = "progress", find = "Loading workspace" }, + opts = { skip = true }, + }, + { + filter = { event = "lsp", kind = "progress", find = "^Diagnosing" }, + opts = { skip = true }, + }, + { + filter = { event = "lsp", kind = "progress", find = "semantic tokens" }, + opts = { skip = true }, + }, + }, + }) + + -- Kitty Scrollback + vim.pack.add({ { src = "https://github.com/mikesmithgh/kitty-scrollback.nvim", name = "kitty-scrollback" } }) + Config.plugins.kitty_scrollback = {} + require("kitty-scrollback").setup(Config.plugins.kitty_scrollback) + + -- Haunt.nvim + vim.pack.add({ { src = "https://github.com/TheNoeTrevino/haunt.nvim", name = "haunt" } }) + Config.plugins.haunt = { + picker = "snacks", + } + require("haunt").setup(Config.plugins.haunt) + + -- Sidekick.nvim + vim.pack.add({ { src = "https://github.com/folke/sidekick.nvim", name = "sidekick" } }) + Config.plugins.sidekick = { + cli = { + prompts = { + haunt_all = function() + return require("haunt.sidekick").get_locations() + end, + haunt_buffer = function() + return require("haunt.sidekick").get_locations({ current_buffer = true }) + end, + }, + }, + } + require("sidekick").setup(Config.plugins.sidekick) end) -- 2.54.0