diff --git a/README.md b/README.md index 78287d7..9f62ecc 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ A modern, modular, and high-performance Neovim configuration built from scratch - **High Performance**: Featuring **blink.cmp** (Rust-based completion) and optimized **Snacks.nvim** components. - **AI-Powered**: Native integration with the **GitHub Copilot Language Server**. - **User-Centric QoL**: Hybrid line numbers, autosave-on-edit, and seamless system clipboard integration. -- **Resilient & Portable**: Intelligent terminal title management (Kitty + Fallback) and automatic project root detection. +- **Resilient & Portable**: Intelligent terminal title management (Kitty + Fallback), automatic project root detection, and machine-local override support. - **Lean & Readable**: ~800 lines of Lua code (excluding comments and blanks). ## 📁 Architecture @@ -88,6 +88,11 @@ nvim | `K` | Hover Documentation | | `s` / `S` | Leap Motion (Normal/Window) | | `ys` / `ds` / `cs` | Surround (Add/Delete/Change) | +| `gx` / `gX` | Open URL under cursor (Gx.nvim) | +| `cbb` | Create Centered Comment Box | +| `cbl` | Create Centered Comment Line | +| `cbd` | Delete Comment Box/Line | +| `cbk` | Browse Box Style Catalog | | `:Q` | Forced Write-All and Quit | ## 📜 License diff --git a/init.lua b/init.lua index c5cd48e..3e69fcc 100644 --- a/init.lua +++ b/init.lua @@ -19,18 +19,23 @@ -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . --- Set leader keys before loading any plugins -vim.g.mapleader = " " -vim.g.maplocalleader = " " - -- Initialize global registry _G.Config = { - plugins = {}, - called = {}, + plugins = {}, + called = {}, } -- Load core modules -require('lazyload') -require('options') -require('plugins') -require('keymaps') +require("lazyload") -- Plugin lazy-loading module +require("options") -- Configuration options +require("plugins") -- Plugin specifications and setup +require("keymaps") -- Key mappings + +-- Source machine-local and secret overrides (silently ignored if absent) +local user_dots = vim.fn.expand("~/.config/.user-dots/nvim/") +for _, file in ipairs({ "secrets.lua", "local.lua" }) do + local path = user_dots .. file + if vim.fn.filereadable(path) == 1 then + dofile(path) + end +end diff --git a/lua/keymaps.lua b/lua/keymaps.lua index c268b12..aeb3426 100644 --- a/lua/keymaps.lua +++ b/lua/keymaps.lua @@ -322,6 +322,14 @@ vim.keymap.set("n", "uu", "UndotreeToggle", { desc = "Toggle Un ---------------------------------------------------------- +-- Comment Box Keymaps +vim.keymap.set({ "n", "v" }, "cbb", "CBccbox", { desc = "Centered Box" }) +vim.keymap.set({ "n", "v" }, "cbl", "CBcline", { desc = "Centered Line" }) +vim.keymap.set({ "n", "v" }, "cbd", "CBd", { desc = "Delete Box/Line" }) +vim.keymap.set("n", "cbk", "CBcatalog", { desc = "Box Style Catalog" }) + +---------------------------------------------------------- + -- Persistence Keymaps vim.keymap.set("n", "qs", function() require("persistence").load() diff --git a/lua/options.lua b/lua/options.lua index 2a00f18..8d15222 100644 --- a/lua/options.lua +++ b/lua/options.lua @@ -4,10 +4,6 @@ └────────────────────────────────────────────────────────────────┘ --]] --- Set leader keys before any other configuration -vim.g.mapleader = " " -vim.g.maplocalleader = "\\" - -- Copyright (C) 2026 rootiest -- -- This program is free software: you can redistribute it and/or modify @@ -23,6 +19,32 @@ vim.g.maplocalleader = "\\" -- You should have received a copy of the GNU General Public License -- along with this program. If not, see . +-- ╭─────────────────────────────────────────────────────────╮ +-- │ Basic Options │ +-- ╰─────────────────────────────────────────────────────────╯ + +-- Set leader keys before any other configuration +vim.g.mapleader = " " +vim.g.maplocalleader = "\\" + +-- Indentation +vim.opt.tabstop = 2 -- Tab width in spaces +vim.opt.shiftwidth = 2 -- Indent width for >> and auto-indent +vim.opt.expandtab = true -- Use spaces instead of tabs + +-- Performance and UI defaults +vim.opt.updatetime = 200 -- Faster completion and CursorHold events +vim.opt.autowrite = true -- Enable auto write +vim.opt.number = true -- Show line numbers +vim.opt.relativenumber = true -- Relative line numbers + +-- ╭─────────────────────────────────────────────────────────╮ +-- │ Sessions and Saving │ +-- ╰─────────────────────────────────────────────────────────╯ + +-- Persistent undo across sessions +vim.opt.undofile = true + -- Autowrite/Autosave -- This ensures changes are saved on every buffer change or when leaving insert mode. vim.api.nvim_create_autocmd({ "InsertLeave", "TextChanged" }, { @@ -36,29 +58,26 @@ vim.api.nvim_create_autocmd({ "InsertLeave", "TextChanged" }, { end, }) --- Performance and UI defaults -vim.opt.updatetime = 200 -- Faster completion and CursorHold events -vim.opt.autowrite = true -- Enable auto write -vim.opt.number = true -- Show line numbers -vim.opt.relativenumber = true -- Relative line numbers +-- Automatically reload files changed outside of Neovim +vim.opt.autoread = true --- Persistent undo across sessions -vim.opt.undofile = true - --- Indentation -vim.opt.tabstop = 2 -- Tab width in spaces -vim.opt.shiftwidth = 2 -- Indent width for >> and auto-indent -vim.opt.expandtab = true -- Use spaces instead of tabs - --- FormatOnSave -vim.api.nvim_create_autocmd("BufWritePre", { +-- Auto-reload Triggers +-- Reload when focus is gained or when the cursor is idle +vim.api.nvim_create_autocmd({ "FocusGained", "CursorHold" }, { + group = vim.api.nvim_create_augroup("autoread_on_focus", { clear = true }), pattern = "*", - callback = function(args) - require("conform").format({ bufnr = args.buf }) + callback = function() + -- Only if the buffer is not modified, to avoid losing unsaved changes + if vim.bo.modified == false then + vim.cmd("checktime") + end end, }) --- Execute Format +-- Format Function +-- Formats the entire buffer by default, +-- but if a range is provided (e.g., via visual selection), +-- it formats only that range. vim.api.nvim_create_user_command("Format", function(args) local range = nil if args.count ~= -1 then @@ -71,17 +90,38 @@ vim.api.nvim_create_user_command("Format", function(args) require("conform").format({ async = true, lsp_format = "fallback", range = range }) end, { range = true }) ---[[ - ┌────────────────────────────────────────────────────────────────┐ - │ Root Management │ - └────────────────────────────────────────────────────────────────┘ ---]] +-- FormatOnSave +-- Formats the buffer before saving. +-- This is a common practice to ensure code is consistently formatted. +vim.api.nvim_create_autocmd("BufWritePre", { + pattern = "*", + callback = function() + -- Call Format function + vim.api.nvim_command("Format") + end, +}) + +-- Return to last-known cursor position when reopening files +vim.api.nvim_create_autocmd("BufReadPost", { + desc = "Jump to last known cursor position on open", + pattern = "*", + callback = function() + local last_pos = vim.fn.line("'\"") + if last_pos > 1 and last_pos <= vim.fn.line("$") then + vim.cmd('normal! g`\"') + end + end, +}) + +-- ╭─────────────────────────────────────────────────────────╮ +-- │ Root Management │ +-- ╰─────────────────────────────────────────────────────────╯ -- Automatically change the working directory to the project root. -- This ensures that plugins like Snacks.picker and GrugFar work -- relative to the file or project you are currently editing. vim.api.nvim_create_autocmd("BufEnter", { - group = vim.api.nvim_create_augroup("rootiest_auto_cd", { clear = true }), + group = vim.api.nvim_create_augroup("buffer_auto_cd", { clear = true }), callback = function() local path = vim.api.nvim_buf_get_name(0) if path == "" then @@ -115,17 +155,18 @@ vim.api.nvim_create_autocmd("FileType", { end, }) ---[[ - ┌────────────────────────────────────────────────────────────────┐ - │ Shell Interaction │ - └────────────────────────────────────────────────────────────────┘ ---]] +-- ╭─────────────────────────────────────────────────────────╮ +-- │ Shell Interaction │ +-- ╰─────────────────────────────────────────────────────────╯ -- Detect terminal environment local is_kitty = os.getenv("KITTY_PID") ~= nil local current_shell = os.getenv("SHELL") or "/bin/sh" local shell_name = current_shell:match("([^/]+)$") or "sh" +-- Terminal Title Management +local title_group = vim.api.nvim_create_augroup("TerminalTitle", { clear = true }) + -- Helper function to update the terminal title local function set_terminal_title(title) -- If in Kitty, we use the direct escape sequence as it's reliable @@ -139,9 +180,7 @@ local function set_terminal_title(title) end end --- Create the Autocommand Group -local title_group = vim.api.nvim_create_augroup("TerminalTitle", { clear = true }) - +-- Terminal title updates on buffer enter and window enter vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, { group = title_group, callback = function() diff --git a/lua/plugins.lua b/lua/plugins.lua index abe6d22..bf77842 100644 --- a/lua/plugins.lua +++ b/lua/plugins.lua @@ -102,7 +102,14 @@ Config.plugins.snacks = { enabled = true, win = { keys = { - toggle = { "", function(self) self:hide() end, mode = "t", desc = "Toggle Terminal" }, + toggle = { + "", + function(self) + self:hide() + end, + mode = "t", + desc = "Toggle Terminal", + }, }, }, }, @@ -164,6 +171,23 @@ lazyload.on_vim_enter(function() 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" }, + }) + + 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() @@ -172,6 +196,22 @@ lazyload.on_vim_enter(function() 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, + }, + }) + + -- Comment-box.nvim + vim.pack.add({ { src = "https://github.com/LudoPinelli/comment-box.nvim", name = "comment-box" } }) + require("comment-box").setup() + -- Undotree vim.g.undotree_ShortIndicators = 0 vim.g.undotree_SplitWidth = 32 @@ -215,6 +255,7 @@ lazyload.on_vim_enter(function() 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" } }, }, }) @@ -230,25 +271,33 @@ lazyload.on_vim_enter(function() -- 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 = vim.fn.filereadable(blink_path .. "/target/release/libblink_cmp_fuzzy.so") == 1 + 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 + local has_cargo = vim.fn.executable("cargo") == 1 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" } }) + + -- 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" } }) - -- Blink.cmp setup + -- 3. Blink.cmp setup require("blink.cmp").setup({ keymap = { preset = "default" }, appearance = { use_nvim_cmp_as_default = true, nerd_font_variant = "mono", - kind_icons = { - Copilot = "", - }, + kind_icons = { Copilot = "" }, }, sources = { default = { "lsp", "path", "snippets", "buffer", "copilot" }, @@ -273,6 +322,21 @@ lazyload.on_vim_enter(function() vim.notify("blink.cmp: binary not found and cargo not installed. Completion disabled.", vim.log.levels.WARN) 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, + }) + local lspconfig = require("lspconfig") local capabilities = (has_blink_bin or has_cargo) and require("blink.cmp").get_lsp_capabilities() or nil @@ -314,6 +378,7 @@ lazyload.on_vim_enter(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, @@ -349,7 +414,26 @@ lazyload.on_vim_enter(function() -- Inc-rename.nvim vim.pack.add({ { src = "https://github.com/smjonas/inc-rename.nvim", name = "inc-rename" } }) - require("inc_rename").setup() + 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" } }) @@ -374,4 +458,9 @@ lazyload.on_vim_enter(function() lsp_doc_border = false, -- add a border to hover docs and signature help }, }) + + -- 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) end) diff --git a/nvim-pack-lock.json b/nvim-pack-lock.json index 43d3f75..09c6a81 100644 --- a/nvim-pack-lock.json +++ b/nvim-pack-lock.json @@ -5,21 +5,37 @@ "src": "https://github.com/fang2hou/blink-copilot" }, "blink.cmp": { - "rev": "c573a15a62bd0bfd4006ee0849b24f5404395500", + "rev": "80f5dd3f11049f1c3a87557718d4e55556a0e3f5", "src": "https://github.com/saghen/blink.cmp" }, + "blink.lib": { + "rev": "f29d8bac6549bc1e7d699c83f680823d7def98bd", + "src": "https://github.com/saghen/blink.lib" + }, "catppuccin": { "rev": "426dbebe06b5c69fd846ceb17b42e12f890aedf1", "src": "https://github.com/catppuccin/nvim" }, + "comment-box": { + "rev": "06bb771690bc9df0763d14769b779062d8f12bc5", + "src": "https://github.com/LudoPinelli/comment-box.nvim" + }, "conform": { - "rev": "086a40dc7ed8242c03be9f47fbcee68699cc2395", + "rev": "dca1a190aa85f9065979ef35802fb77131911106", "src": "https://github.com/stevearc/conform.nvim" }, + "cord.nvim": { + "rev": "0be090969e978f7442870ef227e09eff7bf3480d", + "src": "https://github.com/vyfor/cord.nvim" + }, "flash": { "rev": "fcea7ff883235d9024dc41e638f164a450c14ca2", "src": "https://github.com/folke/flash.nvim" }, + "focusline.nvim": { + "rev": "037a5c3e7849f2b9ad65732733d43c1136d72601", + "src": "https://github.com/ABDsheikho/focusline.nvim" + }, "friendly-snippets": { "rev": "6cd7280adead7f586db6fccbd15d2cac7e2188b9", "src": "https://github.com/rafamadriz/friendly-snippets" @@ -29,35 +45,43 @@ "src": "https://github.com/f-person/git-blame.nvim" }, "gitsigns": { - "rev": "8d82c240f190fc33723d48c308ccc1ed8baad69d", + "rev": "dd3f588bacbeb041be6facf1742e42097f62165d", "src": "https://github.com/lewis6991/gitsigns.nvim" }, "grug-far": { - "rev": "21604255d0e8f9968322f61f2b6c09e5efe1285a", + "rev": "21790e59dd0109a92a70cb874dd002af186314f5", "src": "https://github.com/MagicDuck/grug-far.nvim" }, + "gx": { + "rev": "ba9c408fc0130fc4548760c3933a81b58fc50de8", + "src": "https://github.com/chrishrb/gx.nvim" + }, "inc-rename": { "rev": "0074b551a17338ccdcd299bd86687cc651bcb33d", "src": "https://github.com/smjonas/inc-rename.nvim" }, + "kitty-scrollback": { + "rev": "27a34ad53f188758fe202eb08f0063bce572dde4", + "src": "https://github.com/mikesmithgh/kitty-scrollback.nvim" + }, "lazydev": { "rev": "ff2cbcba459b637ec3fd165a2be59b7bbaeedf0d", "src": "https://github.com/folke/lazydev.nvim" }, "leap": { - "rev": "b960d5038c5c505c52e56a54490f9bbb1f0e6ef6", + "rev": "156c71aabb68d520c6269db4103c8e5580e3ea67", "src": "https://git.disroot.org/andyg/leap.nvim" }, "lualine": { - "rev": "a905eeebc4e63fdc48b5135d3bf8aea5618fb21c", + "rev": "131a558e13f9f28b15cd235557150ccb23f89286", "src": "https://github.com/nvim-lualine/lualine.nvim" }, "mason": { - "rev": "b03fb0f20bc1d43daf558cda981a2be22e73ac42", + "rev": "cb8445f8ce85d957416c106b780efd51c6298f89", "src": "https://github.com/williamboman/mason.nvim" }, "mason-lspconfig": { - "rev": "0a3b42c3e503df87aef6d6513e13148381495c3a", + "rev": "0c2823e0418f3d9230ff8b201c976e84de1cb401", "src": "https://github.com/williamboman/mason-lspconfig.nvim" }, "mini.ai": { @@ -81,7 +105,7 @@ "src": "https://github.com/MunifTanjim/nui.nvim" }, "nvim-lspconfig": { - "rev": "4b7fbaa239c5db6b36f424a4521ca9f1a401be33", + "rev": "31026a13eefb20681124706a79fc1df6bf11ab27", "src": "https://github.com/neovim/nvim-lspconfig" }, "nvim-treesitter": { @@ -93,13 +117,17 @@ "src": "https://github.com/nvim-treesitter/nvim-treesitter-context" }, "nvim-web-devicons": { - "rev": "c72328a5494b4502947a022fe69c0c47e53b6aa6", + "rev": "4fc505ac7bd7692824a142e96e5f529c133862f8", "src": "https://github.com/nvim-tree/nvim-web-devicons" }, "persistence": { "rev": "b20b2a7887bd39c1a356980b45e03250f3dce49c", "src": "https://github.com/folke/persistence.nvim" }, + "qalc": { + "rev": "33198ba0533d6a514f9a48cb472e40407c2ea9f6", + "src": "https://github.com/Apeiros-46B/qalc.nvim" + }, "snacks": { "rev": "ad9ede6a9cddf16cedbd31b8932d6dcdee9b716e", "src": "https://github.com/folke/snacks.nvim"