diff --git a/README.md b/README.md index 93c9f12..8832284 100644 --- a/README.md +++ b/README.md @@ -13,6 +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. ## 📁 Architecture @@ -20,20 +21,43 @@ The configuration is strictly modular: - `init.lua`: Entry point and global state initialization. - `lua/lazyload.lua`: Logic for async and phased plugin loading. -- `lua/options.lua`: Global Vim settings and non-plugin autocommands. +- `lua/options.lua`: Global Vim settings, root management, and terminal title logic. - `lua/plugins.lua`: Plugin declarations and detailed registry-based configurations. - `lua/keymaps.lua`: Centralized user-facing keybindings. -- `lua/const.lua`: Stores constant values for reference in the configuration. +- `lua/const.lua`: Stores constant values (like dashboard headers) for the configuration. + +## 🛠️ System Dependencies + +To ensure all features (pickers, formatters, and LSPs) work correctly, the following packages are required: + +### Essential Tools +- `git`, `curl`, `unzip`, `build-essential` (or `base-devel`) +- `ripgrep` (Grep support) +- `fd` (Fast file finding) +- `fzf` (Fuzzy finder fallback) +- `lazygit` (Git TUI) +- `gh` (GitHub CLI integration) +- `xclip` / `xsel` (X11) or `wl-copy` (Wayland) for clipboard sync. + +### Runtime Environments +- `Node.js` & `npm` (Copilot and various LSPs) +- `Python3` & `pip` (Python LSPs) +- `Cargo` (Rust toolchain, required for building `blink.cmp`) + +### Installation Commands + +**Debian / Ubuntu:** +```bash +sudo apt install git ripgrep fd-find fzf lazygit gh xclip nodejs npm build-essential curl unzip +``` + +**Arch Linux:** +```bash +sudo pacman -S git ripgrep fd fzf lazygit github-cli xclip nodejs npm base-devel curl unzip +``` ## 🚀 Getting Started -### Prerequisites - -- **Neovim 0.10+** -- **Nerd Font** (Mono variant recommended for optimal UI alignment) -- **Cargo** (Required for building `blink.cmp` fuzzy matching library) -- **Node.js** (Required for various LSPs and Copilot) - ### Installation ```bash @@ -44,24 +68,27 @@ nvim ### Post-Install 1. **Build Blink**: If completion isn't working, run `cargo build --release` inside `~/.local/share/nvim/site/pack/core/opt/blink.cmp`. -2. **LSP Servers**: Run `:Mason` to monitor the installation of Language Servers (Lua, C, Rust, Python, etc.). -3. **Copilot**: Run `:LspCopilotSignIn` to authenticate with GitHub. +2. **LSP Servers**: Run `:Mason` to monitor the installation of Language Servers. +3. **Copilot**: Run `:LspCopilotSignIn` to authenticate. ## ⌨️ Key Features & Mappings | Key | Description | | :--- | :--- | | `` | Smart Find Files (Snacks) | -| `e` | File Explorer | -| `ff` | Find Files | -| `sg` | Project-wide Grep | -| `gd` / `gr` | Go to Definition / References | +| `e` | File Explorer (Snacks) | +| `sr` | Search and Replace (Grug-far) | +| `gg` | Open Lazygit | +| `qs` | Restore Last Session (Persistence) | +| `cf` | Format Buffer (Conform) | +| `uu` | Toggle Undo Tree | +| `z` | Toggle Zen Mode | +| `gd` / `gr` | Goto Definition / References | | `K` | Hover Documentation | | `s` / `S` | Leap Motion (Normal/Window) | -| `ys` / `ds` | Mini.surround (Add/Delete) | +| `ys` / `ds` / `cs` | Surround (Add/Delete/Change) | | `:Q` | Forced Write-All and Quit | ## 📜 License Distributed under the **GPLv3 or later** License. See `LICENSE` for more information. - diff --git a/lua/keymaps.lua b/lua/keymaps.lua index 447d043..c268b12 100644 --- a/lua/keymaps.lua +++ b/lua/keymaps.lua @@ -20,17 +20,17 @@ -- along with this program. If not, see . -- System Clipboard Integration --- This makes 'y' and 'p' (and all other yank/paste operations) +-- This makes 'y' and 'p' (and all other yank/paste operations) -- use the system clipboard by default. -vim.opt.clipboard = 'unnamedplus' +vim.opt.clipboard = "unnamedplus" ---------------------------------------------------------- -- Redirect 'delete' and 'change' operations to the 'd' register -- This keeps your system clipboard (unnamedplus) clean -local delete_keys = { 'd', 'D', 'c', 'C', 'x', 'X' } +local delete_keys = { "d", "D", "c", "C", "x", "X" } for _, key in ipairs(delete_keys) do - vim.keymap.set({ 'n', 'v' }, key, '"d' .. key, { noremap = true, silent = true }) + vim.keymap.set({ "n", "v" }, key, '"d' .. key, { noremap = true, silent = true }) end -- If you ever need to paste what you just deleted: @@ -40,130 +40,279 @@ end -- Quit and Save All with :Q -- Replaces safety-checked :xa with a forced write-all-and-quit. -vim.api.nvim_create_user_command('Q', 'xa!', { desc = 'Write all and quit (forced)' }) +vim.api.nvim_create_user_command("Q", "xa!", { desc = "Write all and quit (forced)" }) ---------------------------------------------------------- +-- Search and Replace (Grug-far) +vim.keymap.set("n", "sr", function() + require("grug-far").open({ + prefills = { + search = vim.fn.expand(""), + }, + }) +end, { desc = "Search and Replace" }) + -- Snacks Keymaps -- Top Pickers & Explorer -vim.keymap.set("n", "", function() Snacks.picker.smart() end, { desc = "Smart Find Files" }) -vim.keymap.set("n", ",", function() Snacks.picker.buffers() end, { desc = "Buffers" }) -vim.keymap.set("n", "/", function() Snacks.picker.grep() end, { desc = "Grep" }) -vim.keymap.set("n", ":", function() Snacks.picker.command_history() end, { desc = "Command History" }) -vim.keymap.set("n", "n", function() Snacks.picker.notifications() end, { desc = "Notification History" }) -vim.keymap.set("n", "e", function() Snacks.explorer() end, { desc = "File Explorer" }) +vim.keymap.set("n", "", function() + Snacks.picker.smart() +end, { desc = "Smart Find Files" }) +vim.keymap.set("n", ",", function() + Snacks.picker.buffers() +end, { desc = "Buffers" }) +vim.keymap.set("n", "/", function() + Snacks.picker.grep() +end, { desc = "Grep" }) +vim.keymap.set("n", ":", function() + Snacks.picker.command_history() +end, { desc = "Command History" }) +vim.keymap.set("n", "n", function() + Snacks.picker.notifications() +end, { desc = "Notification History" }) +vim.keymap.set("n", "e", function() + Snacks.explorer() +end, { desc = "File Explorer" }) -- Find -vim.keymap.set("n", "fb", function() Snacks.picker.buffers() end, { desc = "Buffers" }) -vim.keymap.set("n", "fc", function() Snacks.picker.files({ cwd = vim.fn.stdpath("config") }) end, { desc = "Find Config File" }) -vim.keymap.set("n", "ff", function() Snacks.picker.files() end, { desc = "Find Files" }) -vim.keymap.set("n", "fg", function() Snacks.picker.git_files() end, { desc = "Find Git Files" }) -vim.keymap.set("n", "fp", function() Snacks.picker.projects() end, { desc = "Projects" }) -vim.keymap.set("n", "fr", function() Snacks.picker.recent() end, { desc = "Recent" }) +vim.keymap.set("n", "fb", function() + Snacks.picker.buffers() +end, { desc = "Buffers" }) +vim.keymap.set("n", "fc", function() + Snacks.picker.files({ cwd = vim.fn.stdpath("config") }) +end, { desc = "Find Config File" }) +vim.keymap.set("n", "ff", function() + Snacks.picker.files() +end, { desc = "Find Files" }) +vim.keymap.set("n", "fg", function() + Snacks.picker.git_files() +end, { desc = "Find Git Files" }) +vim.keymap.set("n", "fp", function() + Snacks.picker.projects() +end, { desc = "Projects" }) +vim.keymap.set("n", "fr", function() + Snacks.picker.recent() +end, { desc = "Recent" }) -- Git -vim.keymap.set("n", "gb", function() Snacks.picker.git_branches() end, { desc = "Git Branches" }) -vim.keymap.set("n", "gl", function() Snacks.picker.git_log() end, { desc = "Git Log" }) -vim.keymap.set("n", "gL", function() Snacks.picker.git_log_line() end, { desc = "Git Log Line" }) -vim.keymap.set("n", "gs", function() Snacks.picker.git_status() end, { desc = "Git Status" }) -vim.keymap.set("n", "gS", function() Snacks.picker.git_stash() end, { desc = "Git Stash" }) -vim.keymap.set("n", "gd", function() Snacks.picker.git_diff() end, { desc = "Git Diff (Hunks)" }) -vim.keymap.set("n", "gf", function() Snacks.picker.git_log_file() end, { desc = "Git Log File" }) +vim.keymap.set("n", "gb", function() + Snacks.picker.git_branches() +end, { desc = "Git Branches" }) +vim.keymap.set("n", "gl", function() + Snacks.picker.git_log() +end, { desc = "Git Log" }) +vim.keymap.set("n", "gL", function() + Snacks.picker.git_log_line() +end, { desc = "Git Log Line" }) +vim.keymap.set("n", "gs", function() + Snacks.picker.git_status() +end, { desc = "Git Status" }) +vim.keymap.set("n", "gS", function() + Snacks.picker.git_stash() +end, { desc = "Git Stash" }) +vim.keymap.set("n", "gd", function() + Snacks.picker.git_diff() +end, { desc = "Git Diff (Hunks)" }) +vim.keymap.set("n", "gf", function() + Snacks.picker.git_log_file() +end, { desc = "Git Log File" }) -- GH -vim.keymap.set("n", "gi", function() Snacks.picker.gh_issue() end, { desc = "GitHub Issues (open)" }) -vim.keymap.set("n", "gI", function() Snacks.picker.gh_issue({ state = "all" }) end, { desc = "GitHub Issues (all)" }) -vim.keymap.set("n", "gp", function() Snacks.picker.gh_pr() end, { desc = "GitHub Pull Requests (open)" }) -vim.keymap.set("n", "gP", function() Snacks.picker.gh_pr({ state = "all" }) end, { desc = "GitHub Pull Requests (all)" }) +vim.keymap.set("n", "gi", function() + Snacks.picker.gh_issue() +end, { desc = "GitHub Issues (open)" }) +vim.keymap.set("n", "gI", function() + Snacks.picker.gh_issue({ state = "all" }) +end, { desc = "GitHub Issues (all)" }) +vim.keymap.set("n", "gp", function() + Snacks.picker.gh_pr() +end, { desc = "GitHub Pull Requests (open)" }) +vim.keymap.set("n", "gP", function() + Snacks.picker.gh_pr({ state = "all" }) +end, { desc = "GitHub Pull Requests (all)" }) -- Grep -vim.keymap.set("n", "sb", function() Snacks.picker.lines() end, { desc = "Buffer Lines" }) -vim.keymap.set("n", "sB", function() Snacks.picker.grep_buffers() end, { desc = "Grep Open Buffers" }) -vim.keymap.set("n", "sg", function() Snacks.picker.grep() end, { desc = "Grep" }) -vim.keymap.set({ "n", "x" }, "sw", function() Snacks.picker.grep_word() end, { desc = "Visual selection or word" }) +vim.keymap.set("n", "sb", function() + Snacks.picker.lines() +end, { desc = "Buffer Lines" }) +vim.keymap.set("n", "sB", function() + Snacks.picker.grep_buffers() +end, { desc = "Grep Open Buffers" }) +vim.keymap.set("n", "sg", function() + Snacks.picker.grep() +end, { desc = "Grep" }) +vim.keymap.set({ "n", "x" }, "sw", function() + Snacks.picker.grep_word() +end, { desc = "Visual selection or word" }) -- Search -vim.keymap.set("n", 's"', function() Snacks.picker.registers() end, { desc = "Registers" }) -vim.keymap.set("n", 's/', function() Snacks.picker.search_history() end, { desc = "Search History" }) -vim.keymap.set("n", "sa", function() Snacks.picker.autocmds() end, { desc = "Autocmds" }) -vim.keymap.set("n", "sc", function() Snacks.picker.command_history() end, { desc = "Command History" }) -vim.keymap.set("n", "sC", function() Snacks.picker.commands() end, { desc = "Commands" }) -vim.keymap.set("n", "sd", function() Snacks.picker.diagnostics() end, { desc = "Diagnostics" }) -vim.keymap.set("n", "sD", function() Snacks.picker.diagnostics_buffer() end, { desc = "Buffer Diagnostics" }) -vim.keymap.set("n", "sh", function() Snacks.picker.help() end, { desc = "Help Pages" }) -vim.keymap.set("n", "sH", function() Snacks.picker.highlights() end, { desc = "Highlights" }) -vim.keymap.set("n", "si", function() Snacks.picker.icons() end, { desc = "Icons" }) -vim.keymap.set("n", "sj", function() Snacks.picker.jumps() end, { desc = "Jumps" }) -vim.keymap.set("n", "sk", function() Snacks.picker.keymaps() end, { desc = "Keymaps" }) -vim.keymap.set("n", "sl", function() Snacks.picker.loclist() end, { desc = "Location List" }) -vim.keymap.set("n", "sm", function() Snacks.picker.marks() end, { desc = "Marks" }) -vim.keymap.set("n", "sM", function() Snacks.picker.man() end, { desc = "Man Pages" }) -vim.keymap.set("n", "sp", function() Snacks.picker.lazy() end, { desc = "Search for Plugin Spec" }) -vim.keymap.set("n", "sq", function() Snacks.picker.qflist() end, { desc = "Quickfix List" }) -vim.keymap.set("n", "sR", function() Snacks.picker.resume() end, { desc = "Resume" }) -vim.keymap.set("n", "su", function() Snacks.picker.undo() end, { desc = "Undo History" }) -vim.keymap.set("n", "uC", function() Snacks.picker.colorschemes() end, { desc = "Colorschemes" }) +vim.keymap.set("n", 's"', function() + Snacks.picker.registers() +end, { desc = "Registers" }) +vim.keymap.set("n", "s/", function() + Snacks.picker.search_history() +end, { desc = "Search History" }) +vim.keymap.set("n", "sa", function() + Snacks.picker.autocmds() +end, { desc = "Autocmds" }) +vim.keymap.set("n", "sc", function() + Snacks.picker.command_history() +end, { desc = "Command History" }) +vim.keymap.set("n", "sC", function() + Snacks.picker.commands() +end, { desc = "Commands" }) +vim.keymap.set("n", "sd", function() + Snacks.picker.diagnostics() +end, { desc = "Diagnostics" }) +vim.keymap.set("n", "sD", function() + Snacks.picker.diagnostics_buffer() +end, { desc = "Buffer Diagnostics" }) +vim.keymap.set("n", "sh", function() + Snacks.picker.help() +end, { desc = "Help Pages" }) +vim.keymap.set("n", "sH", function() + Snacks.picker.highlights() +end, { desc = "Highlights" }) +vim.keymap.set("n", "si", function() + Snacks.picker.icons() +end, { desc = "Icons" }) +vim.keymap.set("n", "sj", function() + Snacks.picker.jumps() +end, { desc = "Jumps" }) +vim.keymap.set("n", "sk", function() + Snacks.picker.keymaps() +end, { desc = "Keymaps" }) +vim.keymap.set("n", "sl", function() + Snacks.picker.loclist() +end, { desc = "Location List" }) +vim.keymap.set("n", "sm", function() + Snacks.picker.marks() +end, { desc = "Marks" }) +vim.keymap.set("n", "sM", function() + Snacks.picker.man() +end, { desc = "Man Pages" }) +vim.keymap.set("n", "sp", function() + Snacks.picker.lazy() +end, { desc = "Search for Plugin Spec" }) +vim.keymap.set("n", "sq", function() + Snacks.picker.qflist() +end, { desc = "Quickfix List" }) +vim.keymap.set("n", "sR", function() + Snacks.picker.resume() +end, { desc = "Resume" }) +vim.keymap.set("n", "su", function() + Snacks.picker.undo() +end, { desc = "Undo History" }) +vim.keymap.set("n", "uC", function() + Snacks.picker.colorschemes() +end, { desc = "Colorschemes" }) -- LSP -vim.keymap.set("n", "gd", function() Snacks.picker.lsp_definitions() end, { desc = "Goto Definition" }) -vim.keymap.set("n", "gD", function() Snacks.picker.lsp_declarations() end, { desc = "Goto Declaration" }) -vim.keymap.set("n", "gr", function() Snacks.picker.lsp_references() end, { nowait = true, desc = "References" }) -vim.keymap.set("n", "gI", function() Snacks.picker.lsp_implementations() end, { desc = "Goto Implementation" }) -vim.keymap.set("n", "gy", function() Snacks.picker.lsp_type_definitions() end, { desc = "Goto T[y]pe Definition" }) -vim.keymap.set("n", "gai", function() Snacks.picker.lsp_incoming_calls() end, { desc = "C[a]lls Incoming" }) -vim.keymap.set("n", "gao", function() Snacks.picker.lsp_outgoing_calls() end, { desc = "C[a]lls Outgoing" }) -vim.keymap.set("n", "ss", function() Snacks.picker.lsp_symbols() end, { desc = "LSP Symbols" }) -vim.keymap.set("n", "sS", function() Snacks.picker.lsp_workspace_symbols() end, { desc = "LSP Workspace Symbols" }) +vim.keymap.set("n", "gd", function() + Snacks.picker.lsp_definitions() +end, { desc = "Goto Definition" }) +vim.keymap.set("n", "gD", function() + Snacks.picker.lsp_declarations() +end, { desc = "Goto Declaration" }) +vim.keymap.set("n", "gr", function() + Snacks.picker.lsp_references() +end, { nowait = true, desc = "References" }) +vim.keymap.set("n", "gI", function() + Snacks.picker.lsp_implementations() +end, { desc = "Goto Implementation" }) +vim.keymap.set("n", "gy", function() + Snacks.picker.lsp_type_definitions() +end, { desc = "Goto T[y]pe Definition" }) +vim.keymap.set("n", "gai", function() + Snacks.picker.lsp_incoming_calls() +end, { desc = "C[a]lls Incoming" }) +vim.keymap.set("n", "gao", function() + Snacks.picker.lsp_outgoing_calls() +end, { desc = "C[a]lls Outgoing" }) +vim.keymap.set("n", "ss", function() + Snacks.picker.lsp_symbols() +end, { desc = "LSP Symbols" }) +vim.keymap.set("n", "sS", function() + Snacks.picker.lsp_workspace_symbols() +end, { desc = "LSP Workspace Symbols" }) -- Standard LSP vim.keymap.set("n", "K", vim.lsp.buf.hover, { desc = "Hover Documentation" }) vim.keymap.set("n", "ca", vim.lsp.buf.code_action, { desc = "Code Action" }) vim.keymap.set("n", "cr", function() - return ":" .. require("inc_rename").config.cmd_name .. " " .. vim.fn.expand("") + return ":" .. require("inc_rename").config.cmd_name .. " " .. vim.fn.expand("") end, { expr = true, desc = "Rename Symbol" }) -- Other vim.keymap.set("n", "sq", "nohlsearch", { desc = "Clear Search Highlights" }) -vim.keymap.set("n", "z", function() Snacks.zen() end, { desc = "Toggle Zen Mode" }) -vim.keymap.set("n", "Z", function() Snacks.zen.zoom() end, { desc = "Toggle Zoom" }) -vim.keymap.set("n", ".", function() Snacks.scratch() end, { desc = "Toggle Scratch Buffer" }) -vim.keymap.set("n", "S", function() Snacks.scratch.select() end, { desc = "Select Scratch Buffer" }) -vim.keymap.set("n", "n", function() Snacks.notifier.show_history() end, { desc = "Notification History" }) -vim.keymap.set("n", "bd", function() Snacks.bufdelete() end, { desc = "Delete Buffer" }) -vim.keymap.set("n", "cR", function() Snacks.rename.rename_file() end, { desc = "Rename File" }) -vim.keymap.set({ "n", "v" }, "gB", function() Snacks.gitbrowse() end, { desc = "Git Browse" }) -vim.keymap.set("n", "gg", function() Snacks.lazygit() end, { desc = "Lazygit" }) -vim.keymap.set("n", "un", function() Snacks.notifier.hide() end, { desc = "Dismiss All Notifications" }) -vim.keymap.set({ "n", "t" }, "", function() Snacks.terminal() end, { desc = "Toggle Terminal" }) -vim.keymap.set({ "n", "t" }, "", function() Snacks.terminal() end, { desc = "which_key_ignore" }) -vim.keymap.set({ "n", "t" }, "]]", function() Snacks.words.jump(vim.v.count1) end, { desc = "Next Reference" }) -vim.keymap.set({ "n", "t" }, "[[", function() Snacks.words.jump(-vim.v.count1) end, { desc = "Prev Reference" }) +vim.keymap.set("n", "z", function() + Snacks.zen() +end, { desc = "Toggle Zen Mode" }) +vim.keymap.set("n", "Z", function() + Snacks.zen.zoom() +end, { desc = "Toggle Zoom" }) +vim.keymap.set("n", ".", function() + Snacks.scratch() +end, { desc = "Toggle Scratch Buffer" }) +vim.keymap.set("n", "S", function() + Snacks.scratch.select() +end, { desc = "Select Scratch Buffer" }) +vim.keymap.set("n", "n", function() + Snacks.notifier.show_history() +end, { desc = "Notification History" }) +vim.keymap.set("n", "bd", function() + Snacks.bufdelete() +end, { desc = "Delete Buffer" }) +vim.keymap.set("n", "cR", function() + Snacks.rename.rename_file() +end, { desc = "Rename File" }) +vim.keymap.set({ "n", "v" }, "gB", function() + Snacks.gitbrowse() +end, { desc = "Git Browse" }) +vim.keymap.set("n", "gg", function() + Snacks.lazygit() +end, { desc = "Lazygit" }) +vim.keymap.set("n", "un", function() + Snacks.notifier.hide() +end, { desc = "Dismiss All Notifications" }) +vim.keymap.set({ "n", "t" }, "", function() + Snacks.terminal() +end, { desc = "Toggle Terminal" }) +vim.keymap.set({ "n", "t" }, "", function() + Snacks.terminal() +end, { desc = "which_key_ignore" }) +vim.keymap.set({ "n", "t" }, "]]", function() + Snacks.words.jump(vim.v.count1) +end, { desc = "Next Reference" }) +vim.keymap.set({ "n", "t" }, "[[", function() + Snacks.words.jump(-vim.v.count1) +end, { desc = "Prev Reference" }) vim.keymap.set("n", "N", function() - Snacks.win({ - file = vim.api.nvim_get_runtime_file("doc/news.txt", false)[1], - width = 0.6, - height = 0.6, - wo = { - spell = false, - wrap = false, - signcolumn = "yes", - statuscolumn = " ", - conceallevel = 3, - }, - }) + Snacks.win({ + file = vim.api.nvim_get_runtime_file("doc/news.txt", false)[1], + width = 0.6, + height = 0.6, + wo = { + spell = false, + wrap = false, + signcolumn = "yes", + statuscolumn = " ", + conceallevel = 3, + }, + }) end, { desc = "Neovim News" }) ---------------------------------------------------------- -- Leap Keymaps -vim.keymap.set({ 'n', 'x', 'o' }, '', '(leap)') -vim.keymap.set('n', 'S', '(leap-from-window)') +vim.keymap.set({ "n", "x", "o" }, "", "(leap)") +vim.keymap.set("n", "S", "(leap-from-window)") ---------------------------------------------------------- -- Conform Keymaps vim.keymap.set({ "n", "v" }, "cf", function() - require("conform").format({ lsp_format = "fallback" }) + require("conform").format({ lsp_format = "fallback" }) end, { desc = "Format Buffer" }) ---------------------------------------------------------- @@ -174,7 +323,15 @@ vim.keymap.set("n", "uu", "UndotreeToggle", { desc = "Toggle Un ---------------------------------------------------------- -- Persistence Keymaps -vim.keymap.set("n", "qs", function() require("persistence").load() end, { desc = "Restore Session" }) -vim.keymap.set("n", "qS", function() require("persistence").select() end, { desc = "Select Session" }) -vim.keymap.set("n", "ql", function() require("persistence").load({ last = true }) end, { desc = "Restore Last Session" }) -vim.keymap.set("n", "qd", function() require("persistence").stop() end, { desc = "Don't Save Current Session" }) +vim.keymap.set("n", "qs", function() + require("persistence").load() +end, { desc = "Restore Session" }) +vim.keymap.set("n", "qS", function() + require("persistence").select() +end, { desc = "Select Session" }) +vim.keymap.set("n", "ql", function() + require("persistence").load({ last = true }) +end, { desc = "Restore Last Session" }) +vim.keymap.set("n", "qd", function() + require("persistence").stop() +end, { desc = "Don't Save Current Session" }) diff --git a/lua/options.lua b/lua/options.lua index 45a0eb0..2a00f18 100644 --- a/lua/options.lua +++ b/lua/options.lua @@ -4,6 +4,10 @@ └────────────────────────────────────────────────────────────────┘ --]] +-- 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 @@ -66,3 +70,107 @@ vim.api.nvim_create_user_command("Format", function(args) end require("conform").format({ async = true, lsp_format = "fallback", range = range }) end, { range = true }) + +--[[ + ┌────────────────────────────────────────────────────────────────┐ + │ 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 }), + callback = function() + local path = vim.api.nvim_buf_get_name(0) + if path == "" then + return + end + + -- Skip special buffers, but allow directory buffers (for explorer support) + if vim.bo.buftype ~= "" and vim.bo.filetype ~= "snacks_explorer_tree" then + return + end + + -- Get the directory (handle both files and directory paths) + local dir = vim.fn.isdirectory(path) == 1 and path or vim.fn.fnamemodify(path, ":p:h") + + -- Find the project root using common markers, fallback to the directory itself + local root = vim.fs.root(dir, { ".git", "lua", "package.json", "go.mod", "Cargo.toml", "Makefile" }) or dir + + -- Change directory if it's different and valid + if root and vim.fn.isdirectory(root) == 1 and root ~= vim.fn.getcwd() then + vim.fn.chdir(root) + end + end, +}) + +-- Grug-far QoL: Close with 'q' +vim.api.nvim_create_autocmd("FileType", { + group = vim.api.nvim_create_augroup("grug_far_q_to_close", { clear = true }), + pattern = "grug-far", + callback = function(args) + vim.keymap.set("n", "q", "close", { buffer = args.buf, silent = true, desc = "Close Grug-far" }) + end, +}) + +--[[ + ┌────────────────────────────────────────────────────────────────┐ + │ 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" + +-- 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 + if is_kitty then + io.stdout:write("\27]2;" .. title .. "\7") + -- Fallback: Use Neovim's built-in title management for other terminals + -- (Standard OSC 2 sequences are supported by most, but opt.title is safer) + elseif os.getenv("TERM") ~= "linux" then + vim.opt.title = true + vim.opt.titlestring = title + end +end + +-- Create the Autocommand Group +local title_group = vim.api.nvim_create_augroup("TerminalTitle", { clear = true }) + +vim.api.nvim_create_autocmd({ "BufEnter", "BufWinEnter" }, { + group = title_group, + callback = function() + local buftype = vim.bo.buftype + local filetype = vim.bo.filetype + local filename = vim.fn.expand("%:t") + + -- 1. Specifically catch Snacks Terminal or standard Terminals + if filetype == "snacks_terminal" or buftype == "terminal" then + set_terminal_title("NVIM: Terminal") + + -- 2. Handle normal files (buftype is empty) + elseif buftype == "" and filename ~= "" then + set_terminal_title("NVIM: " .. filename) + + -- 3. Handle the empty start screen + elseif filename == "" and buftype == "" then + set_terminal_title("Neovim") + end + end, +}) + +-- Reset the title back to the shell name when you quit Neovim +vim.api.nvim_create_autocmd("VimLeave", { + group = title_group, + callback = function() + -- On leave, direct stdout is more reliable than setting an option + if is_kitty or os.getenv("TERM") ~= "linux" then + io.stdout:write("\27]2;" .. shell_name .. "\7") + end + end, +})