feat: improve portability and add Grug-far QoL improvements

- Refactor terminal title logic in lua/options.lua with Kitty detection and OSC 2 fallback.
- Set mapleader and maplocalleader explicitly at the top of lua/options.lua.
- Add <leader>sr mapping for Grug-far search and replace in lua/keymaps.lua.
- Add autocommand to close Grug-far window with 'q'.
- Update README.md with system dependencies (Debian/Arch) and new keymaps.
- Highlight portability features (terminal titles, root detection) in README.md.
This commit is contained in:
2026-04-22 12:30:11 -04:00
parent 8d6103ec07
commit 0058de2a77
3 changed files with 404 additions and 112 deletions
+44 -17
View File
@@ -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 |
| :--- | :--- |
| `<leader><space>` | Smart Find Files (Snacks) |
| `<leader>e` | File Explorer |
| `<leader>ff` | Find Files |
| `<leader>sg` | Project-wide Grep |
| `gd` / `gr` | Go to Definition / References |
| `<leader>e` | File Explorer (Snacks) |
| `<leader>sr` | Search and Replace (Grug-far) |
| `<leader>gg` | Open Lazygit |
| `<leader>qs` | Restore Last Session (Persistence) |
| `<leader>cf` | Format Buffer (Conform) |
| `<leader>uu` | Toggle Undo Tree |
| `<leader>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.
+252 -95
View File
@@ -20,17 +20,17 @@
-- along with this program. If not, see <https://www.gnu.org/licenses/>.
-- 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", "<leader>sr", function()
require("grug-far").open({
prefills = {
search = vim.fn.expand("<cword>"),
},
})
end, { desc = "Search and Replace" })
-- Snacks Keymaps
-- Top Pickers & Explorer
vim.keymap.set("n", "<leader><space>", function() Snacks.picker.smart() end, { desc = "Smart Find Files" })
vim.keymap.set("n", "<leader>,", function() Snacks.picker.buffers() end, { desc = "Buffers" })
vim.keymap.set("n", "<leader>/", function() Snacks.picker.grep() end, { desc = "Grep" })
vim.keymap.set("n", "<leader>:", function() Snacks.picker.command_history() end, { desc = "Command History" })
vim.keymap.set("n", "<leader>n", function() Snacks.picker.notifications() end, { desc = "Notification History" })
vim.keymap.set("n", "<leader>e", function() Snacks.explorer() end, { desc = "File Explorer" })
vim.keymap.set("n", "<leader><space>", function()
Snacks.picker.smart()
end, { desc = "Smart Find Files" })
vim.keymap.set("n", "<leader>,", function()
Snacks.picker.buffers()
end, { desc = "Buffers" })
vim.keymap.set("n", "<leader>/", function()
Snacks.picker.grep()
end, { desc = "Grep" })
vim.keymap.set("n", "<leader>:", function()
Snacks.picker.command_history()
end, { desc = "Command History" })
vim.keymap.set("n", "<leader>n", function()
Snacks.picker.notifications()
end, { desc = "Notification History" })
vim.keymap.set("n", "<leader>e", function()
Snacks.explorer()
end, { desc = "File Explorer" })
-- Find
vim.keymap.set("n", "<leader>fb", function() Snacks.picker.buffers() end, { desc = "Buffers" })
vim.keymap.set("n", "<leader>fc", function() Snacks.picker.files({ cwd = vim.fn.stdpath("config") }) end, { desc = "Find Config File" })
vim.keymap.set("n", "<leader>ff", function() Snacks.picker.files() end, { desc = "Find Files" })
vim.keymap.set("n", "<leader>fg", function() Snacks.picker.git_files() end, { desc = "Find Git Files" })
vim.keymap.set("n", "<leader>fp", function() Snacks.picker.projects() end, { desc = "Projects" })
vim.keymap.set("n", "<leader>fr", function() Snacks.picker.recent() end, { desc = "Recent" })
vim.keymap.set("n", "<leader>fb", function()
Snacks.picker.buffers()
end, { desc = "Buffers" })
vim.keymap.set("n", "<leader>fc", function()
Snacks.picker.files({ cwd = vim.fn.stdpath("config") })
end, { desc = "Find Config File" })
vim.keymap.set("n", "<leader>ff", function()
Snacks.picker.files()
end, { desc = "Find Files" })
vim.keymap.set("n", "<leader>fg", function()
Snacks.picker.git_files()
end, { desc = "Find Git Files" })
vim.keymap.set("n", "<leader>fp", function()
Snacks.picker.projects()
end, { desc = "Projects" })
vim.keymap.set("n", "<leader>fr", function()
Snacks.picker.recent()
end, { desc = "Recent" })
-- Git
vim.keymap.set("n", "<leader>gb", function() Snacks.picker.git_branches() end, { desc = "Git Branches" })
vim.keymap.set("n", "<leader>gl", function() Snacks.picker.git_log() end, { desc = "Git Log" })
vim.keymap.set("n", "<leader>gL", function() Snacks.picker.git_log_line() end, { desc = "Git Log Line" })
vim.keymap.set("n", "<leader>gs", function() Snacks.picker.git_status() end, { desc = "Git Status" })
vim.keymap.set("n", "<leader>gS", function() Snacks.picker.git_stash() end, { desc = "Git Stash" })
vim.keymap.set("n", "<leader>gd", function() Snacks.picker.git_diff() end, { desc = "Git Diff (Hunks)" })
vim.keymap.set("n", "<leader>gf", function() Snacks.picker.git_log_file() end, { desc = "Git Log File" })
vim.keymap.set("n", "<leader>gb", function()
Snacks.picker.git_branches()
end, { desc = "Git Branches" })
vim.keymap.set("n", "<leader>gl", function()
Snacks.picker.git_log()
end, { desc = "Git Log" })
vim.keymap.set("n", "<leader>gL", function()
Snacks.picker.git_log_line()
end, { desc = "Git Log Line" })
vim.keymap.set("n", "<leader>gs", function()
Snacks.picker.git_status()
end, { desc = "Git Status" })
vim.keymap.set("n", "<leader>gS", function()
Snacks.picker.git_stash()
end, { desc = "Git Stash" })
vim.keymap.set("n", "<leader>gd", function()
Snacks.picker.git_diff()
end, { desc = "Git Diff (Hunks)" })
vim.keymap.set("n", "<leader>gf", function()
Snacks.picker.git_log_file()
end, { desc = "Git Log File" })
-- GH
vim.keymap.set("n", "<leader>gi", function() Snacks.picker.gh_issue() end, { desc = "GitHub Issues (open)" })
vim.keymap.set("n", "<leader>gI", function() Snacks.picker.gh_issue({ state = "all" }) end, { desc = "GitHub Issues (all)" })
vim.keymap.set("n", "<leader>gp", function() Snacks.picker.gh_pr() end, { desc = "GitHub Pull Requests (open)" })
vim.keymap.set("n", "<leader>gP", function() Snacks.picker.gh_pr({ state = "all" }) end, { desc = "GitHub Pull Requests (all)" })
vim.keymap.set("n", "<leader>gi", function()
Snacks.picker.gh_issue()
end, { desc = "GitHub Issues (open)" })
vim.keymap.set("n", "<leader>gI", function()
Snacks.picker.gh_issue({ state = "all" })
end, { desc = "GitHub Issues (all)" })
vim.keymap.set("n", "<leader>gp", function()
Snacks.picker.gh_pr()
end, { desc = "GitHub Pull Requests (open)" })
vim.keymap.set("n", "<leader>gP", function()
Snacks.picker.gh_pr({ state = "all" })
end, { desc = "GitHub Pull Requests (all)" })
-- Grep
vim.keymap.set("n", "<leader>sb", function() Snacks.picker.lines() end, { desc = "Buffer Lines" })
vim.keymap.set("n", "<leader>sB", function() Snacks.picker.grep_buffers() end, { desc = "Grep Open Buffers" })
vim.keymap.set("n", "<leader>sg", function() Snacks.picker.grep() end, { desc = "Grep" })
vim.keymap.set({ "n", "x" }, "<leader>sw", function() Snacks.picker.grep_word() end, { desc = "Visual selection or word" })
vim.keymap.set("n", "<leader>sb", function()
Snacks.picker.lines()
end, { desc = "Buffer Lines" })
vim.keymap.set("n", "<leader>sB", function()
Snacks.picker.grep_buffers()
end, { desc = "Grep Open Buffers" })
vim.keymap.set("n", "<leader>sg", function()
Snacks.picker.grep()
end, { desc = "Grep" })
vim.keymap.set({ "n", "x" }, "<leader>sw", function()
Snacks.picker.grep_word()
end, { desc = "Visual selection or word" })
-- Search
vim.keymap.set("n", '<leader>s"', function() Snacks.picker.registers() end, { desc = "Registers" })
vim.keymap.set("n", '<leader>s/', function() Snacks.picker.search_history() end, { desc = "Search History" })
vim.keymap.set("n", "<leader>sa", function() Snacks.picker.autocmds() end, { desc = "Autocmds" })
vim.keymap.set("n", "<leader>sc", function() Snacks.picker.command_history() end, { desc = "Command History" })
vim.keymap.set("n", "<leader>sC", function() Snacks.picker.commands() end, { desc = "Commands" })
vim.keymap.set("n", "<leader>sd", function() Snacks.picker.diagnostics() end, { desc = "Diagnostics" })
vim.keymap.set("n", "<leader>sD", function() Snacks.picker.diagnostics_buffer() end, { desc = "Buffer Diagnostics" })
vim.keymap.set("n", "<leader>sh", function() Snacks.picker.help() end, { desc = "Help Pages" })
vim.keymap.set("n", "<leader>sH", function() Snacks.picker.highlights() end, { desc = "Highlights" })
vim.keymap.set("n", "<leader>si", function() Snacks.picker.icons() end, { desc = "Icons" })
vim.keymap.set("n", "<leader>sj", function() Snacks.picker.jumps() end, { desc = "Jumps" })
vim.keymap.set("n", "<leader>sk", function() Snacks.picker.keymaps() end, { desc = "Keymaps" })
vim.keymap.set("n", "<leader>sl", function() Snacks.picker.loclist() end, { desc = "Location List" })
vim.keymap.set("n", "<leader>sm", function() Snacks.picker.marks() end, { desc = "Marks" })
vim.keymap.set("n", "<leader>sM", function() Snacks.picker.man() end, { desc = "Man Pages" })
vim.keymap.set("n", "<leader>sp", function() Snacks.picker.lazy() end, { desc = "Search for Plugin Spec" })
vim.keymap.set("n", "<leader>sq", function() Snacks.picker.qflist() end, { desc = "Quickfix List" })
vim.keymap.set("n", "<leader>sR", function() Snacks.picker.resume() end, { desc = "Resume" })
vim.keymap.set("n", "<leader>su", function() Snacks.picker.undo() end, { desc = "Undo History" })
vim.keymap.set("n", "<leader>uC", function() Snacks.picker.colorschemes() end, { desc = "Colorschemes" })
vim.keymap.set("n", '<leader>s"', function()
Snacks.picker.registers()
end, { desc = "Registers" })
vim.keymap.set("n", "<leader>s/", function()
Snacks.picker.search_history()
end, { desc = "Search History" })
vim.keymap.set("n", "<leader>sa", function()
Snacks.picker.autocmds()
end, { desc = "Autocmds" })
vim.keymap.set("n", "<leader>sc", function()
Snacks.picker.command_history()
end, { desc = "Command History" })
vim.keymap.set("n", "<leader>sC", function()
Snacks.picker.commands()
end, { desc = "Commands" })
vim.keymap.set("n", "<leader>sd", function()
Snacks.picker.diagnostics()
end, { desc = "Diagnostics" })
vim.keymap.set("n", "<leader>sD", function()
Snacks.picker.diagnostics_buffer()
end, { desc = "Buffer Diagnostics" })
vim.keymap.set("n", "<leader>sh", function()
Snacks.picker.help()
end, { desc = "Help Pages" })
vim.keymap.set("n", "<leader>sH", function()
Snacks.picker.highlights()
end, { desc = "Highlights" })
vim.keymap.set("n", "<leader>si", function()
Snacks.picker.icons()
end, { desc = "Icons" })
vim.keymap.set("n", "<leader>sj", function()
Snacks.picker.jumps()
end, { desc = "Jumps" })
vim.keymap.set("n", "<leader>sk", function()
Snacks.picker.keymaps()
end, { desc = "Keymaps" })
vim.keymap.set("n", "<leader>sl", function()
Snacks.picker.loclist()
end, { desc = "Location List" })
vim.keymap.set("n", "<leader>sm", function()
Snacks.picker.marks()
end, { desc = "Marks" })
vim.keymap.set("n", "<leader>sM", function()
Snacks.picker.man()
end, { desc = "Man Pages" })
vim.keymap.set("n", "<leader>sp", function()
Snacks.picker.lazy()
end, { desc = "Search for Plugin Spec" })
vim.keymap.set("n", "<leader>sq", function()
Snacks.picker.qflist()
end, { desc = "Quickfix List" })
vim.keymap.set("n", "<leader>sR", function()
Snacks.picker.resume()
end, { desc = "Resume" })
vim.keymap.set("n", "<leader>su", function()
Snacks.picker.undo()
end, { desc = "Undo History" })
vim.keymap.set("n", "<leader>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", "<leader>ss", function() Snacks.picker.lsp_symbols() end, { desc = "LSP Symbols" })
vim.keymap.set("n", "<leader>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", "<leader>ss", function()
Snacks.picker.lsp_symbols()
end, { desc = "LSP Symbols" })
vim.keymap.set("n", "<leader>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", "<leader>ca", vim.lsp.buf.code_action, { desc = "Code Action" })
vim.keymap.set("n", "<leader>cr", function()
return ":" .. require("inc_rename").config.cmd_name .. " " .. vim.fn.expand("<cword>")
return ":" .. require("inc_rename").config.cmd_name .. " " .. vim.fn.expand("<cword>")
end, { expr = true, desc = "Rename Symbol" })
-- Other
vim.keymap.set("n", "<leader>sq", "<cmd>nohlsearch<cr>", { desc = "Clear Search Highlights" })
vim.keymap.set("n", "<leader>z", function() Snacks.zen() end, { desc = "Toggle Zen Mode" })
vim.keymap.set("n", "<leader>Z", function() Snacks.zen.zoom() end, { desc = "Toggle Zoom" })
vim.keymap.set("n", "<leader>.", function() Snacks.scratch() end, { desc = "Toggle Scratch Buffer" })
vim.keymap.set("n", "<leader>S", function() Snacks.scratch.select() end, { desc = "Select Scratch Buffer" })
vim.keymap.set("n", "<leader>n", function() Snacks.notifier.show_history() end, { desc = "Notification History" })
vim.keymap.set("n", "<leader>bd", function() Snacks.bufdelete() end, { desc = "Delete Buffer" })
vim.keymap.set("n", "<leader>cR", function() Snacks.rename.rename_file() end, { desc = "Rename File" })
vim.keymap.set({ "n", "v" }, "<leader>gB", function() Snacks.gitbrowse() end, { desc = "Git Browse" })
vim.keymap.set("n", "<leader>gg", function() Snacks.lazygit() end, { desc = "Lazygit" })
vim.keymap.set("n", "<leader>un", function() Snacks.notifier.hide() end, { desc = "Dismiss All Notifications" })
vim.keymap.set({ "n", "t" }, "<c-/>", function() Snacks.terminal() end, { desc = "Toggle Terminal" })
vim.keymap.set({ "n", "t" }, "<c-_>", 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", "<leader>z", function()
Snacks.zen()
end, { desc = "Toggle Zen Mode" })
vim.keymap.set("n", "<leader>Z", function()
Snacks.zen.zoom()
end, { desc = "Toggle Zoom" })
vim.keymap.set("n", "<leader>.", function()
Snacks.scratch()
end, { desc = "Toggle Scratch Buffer" })
vim.keymap.set("n", "<leader>S", function()
Snacks.scratch.select()
end, { desc = "Select Scratch Buffer" })
vim.keymap.set("n", "<leader>n", function()
Snacks.notifier.show_history()
end, { desc = "Notification History" })
vim.keymap.set("n", "<leader>bd", function()
Snacks.bufdelete()
end, { desc = "Delete Buffer" })
vim.keymap.set("n", "<leader>cR", function()
Snacks.rename.rename_file()
end, { desc = "Rename File" })
vim.keymap.set({ "n", "v" }, "<leader>gB", function()
Snacks.gitbrowse()
end, { desc = "Git Browse" })
vim.keymap.set("n", "<leader>gg", function()
Snacks.lazygit()
end, { desc = "Lazygit" })
vim.keymap.set("n", "<leader>un", function()
Snacks.notifier.hide()
end, { desc = "Dismiss All Notifications" })
vim.keymap.set({ "n", "t" }, "<c-/>", function()
Snacks.terminal()
end, { desc = "Toggle Terminal" })
vim.keymap.set({ "n", "t" }, "<c-_>", 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", "<leader>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' }, '<CR>', '<Plug>(leap)')
vim.keymap.set('n', 'S', '<Plug>(leap-from-window)')
vim.keymap.set({ "n", "x", "o" }, "<CR>", "<Plug>(leap)")
vim.keymap.set("n", "S", "<Plug>(leap-from-window)")
----------------------------------------------------------
-- Conform Keymaps
vim.keymap.set({ "n", "v" }, "<leader>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", "<leader>uu", "<cmd>UndotreeToggle<cr>", { desc = "Toggle Un
----------------------------------------------------------
-- Persistence Keymaps
vim.keymap.set("n", "<leader>qs", function() require("persistence").load() end, { desc = "Restore Session" })
vim.keymap.set("n", "<leader>qS", function() require("persistence").select() end, { desc = "Select Session" })
vim.keymap.set("n", "<leader>ql", function() require("persistence").load({ last = true }) end, { desc = "Restore Last Session" })
vim.keymap.set("n", "<leader>qd", function() require("persistence").stop() end, { desc = "Don't Save Current Session" })
vim.keymap.set("n", "<leader>qs", function()
require("persistence").load()
end, { desc = "Restore Session" })
vim.keymap.set("n", "<leader>qS", function()
require("persistence").select()
end, { desc = "Select Session" })
vim.keymap.set("n", "<leader>ql", function()
require("persistence").load({ last = true })
end, { desc = "Restore Last Session" })
vim.keymap.set("n", "<leader>qd", function()
require("persistence").stop()
end, { desc = "Don't Save Current Session" })
+108
View File
@@ -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", "<cmd>close<cr>", { 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,
})