Nvim additions and fixes

This commit is contained in:
alejo committed 2026-08-28 01:20:47 -03:00
1 parent cbd460d470
commit bd598f5f0e
2 files changed
+765 -34

No files matched your search

+60 -14
View File
@@ -4,6 +4,25 @@ A single-script Neovim configuration built around C/C++ development (CMake + cla
Supports **Fedora, Ubuntu, Debian, and Arch Linux** (and their common derivatives — Pop!_OS, Linux Mint, Manjaro, EndeavourOS, etc. — via `/etc/os-release`'s `ID_LIKE`) — the script detects which one you're on and uses the right package manager and package names automatically. Supports **Fedora, Ubuntu, Debian, and Arch Linux** (and their common derivatives — Pop!_OS, Linux Mint, Manjaro, EndeavourOS, etc. — via `/etc/os-release`'s `ID_LIKE`) — the script detects which one you're on and uses the right package manager and package names automatically.
## Contents
- [Installing](#installing)
- [What gets installed](#what-gets-installed)
- [Editor basics](#editor-basics)
- [Look & feel](#look--feel)
- [Buffers & tabs](#buffers--tabs-bufferline)
- [Text search (Telescope)](#text-search-telescope)
- [File explorer (nvim-tree)](#file-explorer-nvim-tree)
- [LSP & code intelligence](#lsp--code-intelligence)
- [Autocompletion & snippets](#autocompletion--snippets)
- [Documentation generation](#documentation-generation)
- [Debugging (nvim-dap)](#debugging-nvim-dap)
- [Terminal](#terminal)
- [Git](#git)
- [Hex viewer](#hex-viewer)
- [Formatting and linting](#formatting-and-linting)
- [Known quirks](#known-quirks)
## Installing ## Installing
```bash ```bash
@@ -16,9 +35,9 @@ chmod +x setup_neovim.sh
The script: The script:
1. Detects your distro and installs system packages via the right manager (`dnf`/`apt`/`pacman`), plus two Nerd Fonts (JetBrainsMono, Cascadia Code) for icon glyphs. 1. Detects your distro and installs system packages via the right manager (`dnf`/`apt`/`pacman`), plus two Nerd Fonts (JetBrainsMono, Cascadia Code) for icon glyphs.
2. Wipes and rewrites `~/.config/nvim`. 2. Wipes and rewrites `~/.config/nvim`.
3. Writes a personal `~/.clang-format` fallback (see [Formatting](#formatting)). 3. Writes personal `~/.clang-format`/`~/.clang-tidy` fallbacks (see [Formatting and linting](#formatting-and-linting)).
First launch will take a minute or two: `lazy.nvim` bootstraps itself and installs all plugins, then Mason installs the language servers and `codelldb`. First launch takes a minute or two: `lazy.nvim` bootstraps itself and installs all plugins, then Mason installs the language servers and `codelldb`.
### Distro-specific notes ### Distro-specific notes
@@ -40,9 +59,11 @@ Package names vary by distro (see the script for the exact list per package mana
- Relative + absolute line numbers, system clipboard integration (`unnamedplus`), persistent undo, smart-case search, always-on sign column. - Relative + absolute line numbers, system clipboard integration (`unnamedplus`), persistent undo, smart-case search, always-on sign column.
- Tab width: **4**, using real tab characters — not expanded to spaces. - Tab width: **4**, using real tab characters — not expanded to spaces.
- `c`/`cpp`/`javascript`/`typescript` indentation uses Neovim's own built-in per-filetype indent logic (`cindent` for c/cpp, the bundled JS/TS indent scripts for the rest) rather than treesitter's indent module — treesitter's indent is known to be unreliable for these specifically (inconsistent brace placement, phantom extra indent on nested blocks), more so since the `nvim-treesitter` branch this config uses is frozen upstream (see [Known quirks](#known-quirks)). `css`/`json` are also curly-brace languages that can hit the same issue — worth disabling there too if it comes up.
- Comments don't auto-continue onto a new line when the current line has real code on it — pressing Enter or `o`/`O` there starts a plain new line. On a comment-*only* line, Enter continues it with the same marker (`//`, `#`, or `--`); pressing Enter a second time with nothing typed breaks out instead of continuing again, clearing that line — same convention Vim itself has always used for this.
- Bracket/quote pairs auto-close as you type: `()`, `{}`, `[]`, `""`, `''`.
- True color, rounded borders on floating windows (hover, LspInfo, Mason, etc. — Neovim 0.11+), styled window separators. - True color, rounded borders on floating windows (hover, LspInfo, Mason, etc. — Neovim 0.11+), styled window separators.
- OSC 52 clipboard support, so yank/paste works correctly over SSH. - OSC 52 clipboard support, so yank/paste works correctly over SSH.
- `c`/`cpp`/`javascript`/`typescript` indentation uses Neovim's own built-in per-filetype indent logic (`cindent` for c/cpp, the bundled JS/TS indent scripts for the rest) rather than treesitter's indent module — treesitter's indent is known to be unreliable for these specifically (inconsistent brace placement, phantom extra indent on nested blocks), more so since the `nvim-treesitter` branch this config uses is frozen upstream (see [Known quirks](#known-quirks)). `css`/`json` are also curly-brace languages that can hit the same issue — worth disabling there too if it comes up.
### General keymaps ### General keymaps
@@ -52,6 +73,8 @@ Package names vary by distro (see the script for the exact list per package mana
| `<leader>p` | Normal | Force inline paste (paste as characters at cursor, ignoring line-wise register type) | | `<leader>p` | Normal | Force inline paste (paste as characters at cursor, ignoring line-wise register type) |
| `p` / `P` | Visual | Paste over selection without clobbering the unnamed register | | `p` / `P` | Visual | Paste over selection without clobbering the unnamed register |
| `J` / `K` | Visual | Move selected lines down / up | | `J` / `K` | Visual | Move selected lines down / up |
| `<A-j>` / `<A-k>` | Normal/Insert/Visual | Move current line (or selection) down / up |
| `<leader>cb` | Normal | Clear the current line's content, leaving it blank in place (doesn't delete the line itself) |
| `<C-s>` | Normal/Visual/Insert | Save file | | `<C-s>` | Normal/Visual/Insert | Save file |
| `<C-h/j/k/l>` | Normal | Move to left/lower/upper/right window | | `<C-h/j/k/l>` | Normal | Move to left/lower/upper/right window |
| `<leader>sv` / `<leader>sh` | Normal | Split vertically / horizontally | | `<leader>sv` / `<leader>sh` | Normal | Split vertically / horizontally |
@@ -65,7 +88,7 @@ Package names vary by distro (see the script for the exact list per package mana
- **Statusline**: `lualine`, themed to match, shows mode, git branch, diagnostics, filename, encoding/filetype, progress, and cursor location. - **Statusline**: `lualine`, themed to match, shows mode, git branch, diagnostics, filename, encoding/filetype, progress, and cursor location.
- **Indent guides**: `indent-blankline` — colored guide per indent level, plus the current lexical scope (the block your cursor is inside) is highlighted with its own guide color. The underline that used to mark the scope's start/end line is disabled (`show_start`/`show_end = false`) — just the colored guide remains. - **Indent guides**: `indent-blankline` — colored guide per indent level, plus the current lexical scope (the block your cursor is inside) is highlighted with its own guide color. The underline that used to mark the scope's start/end line is disabled (`show_start`/`show_end = false`) — just the colored guide remains.
- **Inline color previews**: `nvim-colorizer` shows hex/rgb color values with their actual color as a background. - **Inline color previews**: `nvim-colorizer` shows hex/rgb color values with their actual color as a background.
- **Notifications**: `nvim-notify` replaces the default notification popups with floating, styled ones. - **Notifications**: `nvim-notify` replaces the default notification popups with floating, styled ones, positioned bottom-right.
- **Prompts**: `dressing.nvim` upgrades the rename prompt, breakpoint-condition prompt, and DAP path prompts to floating dialogs instead of plain command-line input. - **Prompts**: `dressing.nvim` upgrades the rename prompt, breakpoint-condition prompt, and DAP path prompts to floating dialogs instead of plain command-line input.
## Buffers & tabs (`bufferline`) ## Buffers & tabs (`bufferline`)
@@ -76,7 +99,7 @@ Each buffer tab shows its ordinal position number. Pinned buffers are always kep
|---|---| |---|---|
| `<S-h>` / `<S-l>` | Previous / next buffer (follows the order shown on screen, including any reordering) | | `<S-h>` / `<S-l>` | Previous / next buffer (follows the order shown on screen, including any reordering) |
| `<A-1>` … `<A-9>` / `<F1>`–`<F9>` | Jump directly to the buffer at that position. `<F5>` and `<F10>` each pull double duty with the debugger (see [Debugging](#debugging-nvim-dap)) — they act as buffer jumps at rest, and hand themselves over to DAP for the duration of an active debug session. | | `<A-1>` … `<A-9>` / `<F1>`–`<F9>` | Jump directly to the buffer at that position. `<F5>` and `<F10>` each pull double duty with the debugger (see [Debugging](#debugging-nvim-dap)) — they act as buffer jumps at rest, and hand themselves over to DAP for the duration of an active debug session. |
| `<A-0>` / `<F10>` | Jump to the last buffer. | | `<A-0>` / `<F10>` | Jump to the last buffer |
| `<A-,>` / `<A-.>` | Move current buffer left / right (blocked from crossing into the pinned block — wraps to the other end instead) | | `<A-,>` / `<A-.>` | Move current buffer left / right (blocked from crossing into the pinned block — wraps to the other end instead) |
| `<leader>bp` | Toggle pin on current buffer | | `<leader>bp` | Toggle pin on current buffer |
| `<leader>bx` | Close every **unpinned** buffer with **no unsaved changes** (pinned buffers and anything modified are left alone) | | `<leader>bx` | Close every **unpinned** buffer with **no unsaved changes** (pinned buffers and anything modified are left alone) |
@@ -84,7 +107,7 @@ Each buffer tab shows its ordinal position number. Pinned buffers are always kep
Setting an explicit `<F1>` mapping here also overrides Neovim's built-in `<F1>`-opens-help default — any explicit keymap for a key always takes precedence over Neovim's default for it. Setting an explicit `<F1>` mapping here also overrides Neovim's built-in `<F1>`-opens-help default — any explicit keymap for a key always takes precedence over Neovim's default for it.
## File & text search (`telescope`) ## Text search (`Telescope`)
| Key | Action | | Key | Action |
|---|---| |---|---|
@@ -93,7 +116,18 @@ Setting an explicit `<F1>` mapping here also overrides Neovim's built-in `<F1>`-
| `<leader>fb` | List open buffers | | `<leader>fb` | List open buffers |
| `<leader>fh` | Search help tags | | `<leader>fh` | Search help tags |
| `<leader>fr` | Recent files | | `<leader>fr` | Recent files |
| `<leader>e` | Toggle the file explorer sidebar (`nvim-tree`, right side) |
## File explorer (`nvim-tree`)
`<leader>e` toggles the file explorer sidebar (right side). On top of nvim-tree's own defaults (`c`/`x`/`p` to copy/cut/paste, `m` to mark, `d` to delete, etc.), three custom commands:
| Key | Action |
|---|---|
| `gP` | Paste, resolving every naming **collision** in that paste with one `pattern/replacement` (Vim regex — same as `:s///`) instead of nvim-tree's normal one-at-a-time overwrite/rename prompt per conflicting file. Plain `p` is untouched. Only confirmed reliable for a **single** conflicting file at a time — see [Known quirks](#known-quirks). |
| `gM` | Copy every **marked** file/folder (mark with `m`, shown as a star) into the directory under the cursor, applying that same `pattern/replacement` regex to **every** name unconditionally — not just on collision. Doesn't touch nvim-tree's clipboard at all; reads the mark list directly and shells out to `cp -r` to do the copying itself. Marks aren't auto-cleared afterward — toggle them off individually with `m` when done. |
| `gC` | Clear the copy/cut clipboard outright. Useful if it's holding a stale entry (a file since renamed, moved, or deleted), which otherwise pastes as `ENOENT: no such file or directory` until cleared and re-copied fresh. |
`gP` and `gM` both fail safely rather than silently: a stale mark (pointing at a since-deleted path) is skipped and cleaned up automatically instead of attempted, and a rename that would produce a no-op copy-onto-itself is skipped with a clear reason instead of surfacing a raw `cp` error.
## LSP & code intelligence ## LSP & code intelligence
@@ -105,11 +139,11 @@ Configured servers: **clangd** (C/C++), **gopls** (Go), **omnisharp** (C#), **ts
| `gD` | Go to declaration. Same `#include`-awareness and fallback as `gd`. | | `gD` | Go to declaration. Same `#include`-awareness and fallback as `gd`. |
| `gr` | Find all references, in a fuzzy picker with preview. | | `gr` | Find all references, in a fuzzy picker with preview. |
| `K` | Hover documentation (renders Doxygen comments if present). | | `K` | Hover documentation (renders Doxygen comments if present). |
| `<leader>lr` | Rename symbol (prompts for new name, safe against double-firing). | | `<leader>lr` | Rename symbol (prompts for new name, safe against double-firing) |
| `<leader>la` | Code actions. | | `<leader>la` | Code actions |
| `<leader>lf` | Format file (via clangd/LSP formatting — see [Formatting](#formatting) for the style used). | | `<leader>lf` | Format file (via clangd/LSP formatting — see [Formatting and linting](#formatting-and-linting) for the style used) |
| `<leader>li` | Show LSP client info. | | `<leader>li` | Show LSP client info |
| `<leader>cd` | Generate a Doxygen-style comment block (`@brief`/`@param`/`@return`) above the function under the cursor — see [Documentation generation](#documentation-generation). | | `<leader>cd` | Generate a Doxygen-style comment block — see [Documentation generation](#documentation-generation) |
## Autocompletion & snippets ## Autocompletion & snippets
@@ -161,11 +195,23 @@ Neovim's embedded terminal launches **fish** instead of your login shell, purely
`gitsigns` shows added/changed/removed lines in the sign column against the current git state. `gitsigns` shows added/changed/removed lines in the sign column against the current git state.
## Formatting ## Hex viewer
`~/.clang-format` is written on every run (LLVM-based, `BreakBeforeBraces: Attach` for K&R-style braces, 4-wide real tabs via `UseTab: Always`/`TabWidth: 4` to match the editor, 100-column limit). `clang-format` walks upward from the file being formatted looking for a `.clang-format`; since `$HOME` sits above every project, this acts as a personal default for any project that doesn't ship its own `.clang-format` — a project's own file always takes precedence. `<leader>h` toggles the current buffer between normal and hex-dump view (backed by `xxd`), rather than showing both simultaneously. That's deliberate: the hex dump is just ordinary buffer text under the hood, so editing it gets normal Neovim undo/redo for free, and nothing writes to disk until an explicit `:w` — same as any other file. A plugin doing a true live simultaneous hex+ASCII view was tried first, but it manages bytes with its own logic outside normal buffer editing, which is exactly why it had no undo at all.
The mapping also makes sure the buffer is actually loaded in binary mode before toggling (re-reading it with `++bin` if not) — opening a real binary file without that causes Neovim to misinterpret raw bytes as text before `xxd` even runs, surfacing as a `CONVERSION ERROR` instead of a clean hex dump.
## Formatting and linting
`~/.clang-format` and `~/.clang-tidy` are both written on every run, implementing a specific C/C++ coding style guide (naming conventions, Allman braces, real tabs, include ordering, and a curated set of modernize/cppcoreguidelines/bugprone clang-tidy checks — see the comments in each file for exactly which style guide section a given setting maps to). Both tools walk upward from the file being checked looking for their config file; since `$HOME` sits above every project, these act as your personal default for any project that doesn't ship its own `.clang-format`/`.clang-tidy` — a project's own file always takes precedence.
`<leader>lf` (LSP format) only consumes `.clang-format` — clangd's formatting is pure clang-format and has nothing to do with clang-tidy. `.clang-tidy` is picked up separately and automatically by clangd for live diagnostics, since clangd's clang-tidy integration is on by default.
## Known quirks ## Known quirks
- **`nvim-treesitter` is pinned to its `master` branch**, not the current upstream default (`main`). `main` is an incompatible rewrite that removed the `nvim-treesitter.configs` API this config (and most existing configs/plugins) are built on. `master` is kept frozen upstream specifically for this kind of backward compatibility. - **`nvim-treesitter` is pinned to its `master` branch**, not the current upstream default (`main`). `main` is an incompatible rewrite that removed the `nvim-treesitter.configs` API this config (and most existing configs/plugins) are built on. `master` is kept frozen upstream specifically for this kind of backward compatibility.
- A **Neovim 0.12 compatibility shim** is included for `master`-branch treesitter: 0.12 changed query predicate/directive results to sometimes be a list of nodes instead of a single node, which `master`'s legacy predicate handlers don't expect — this caused a `"Decoration provider ... attempt to call method 'range'"` error (most visible on markdown files). The shim normalizes results back to a single node before those handlers run. Purely cosmetic when unpatched (highlighting still worked) but noisy. - A **Neovim 0.12 compatibility shim** is included for `master`-branch treesitter: 0.12 changed query predicate/directive results to sometimes be a list of nodes instead of a single node, which `master`'s legacy predicate handlers don't expect — this caused a `"Decoration provider ... attempt to call method 'range'"` error (most visible on markdown files). The shim normalizes results back to a single node before those handlers run. Purely cosmetic when unpatched (highlighting still worked) but noisy.
- **`nvim-cmp` absorbs the `<CR>` mapping used for smart comment continuation** as its own fallback, rather than it being called directly by Neovim. `cmp.setup()` runs after `init.lua`'s top-level code, and per its own keymap-composition system, it wraps whatever `<CR>` mapping already existed as the function it calls when its completion menu isn't visible, instead of simply discarding it. Worth knowing if you ever add or change an insert-mode `<CR>` mapping here: it will run through cmp's fallback chain rather than being invoked directly, which also means direct buffer edits from inside it (e.g. `nvim_set_current_line()`) fail with `E565` — express any change as keys to return instead.
- **`gP`'s regex-on-paste only handles single-file conflicts reliably.** nvim-tree has a separate, differently-shaped dialog specifically for multi-file conflicts within one paste ("N file(s) already exist" / Rename (suffix) / Overwrite all / Skip all), which `gP` doesn't detect. `gM` (copying marked files with an unconditional regex) was built as the more reliable tool for renaming several files at once.
- **`gM`'s stale-mark cleanup depends on `api.marks.toggle`**, a function name inferred from `m`'s own behavior rather than confirmed in nvim-tree's documentation directly, so it's wrapped defensively — if it's wrong on a given nvim-tree version, a stale mark just won't auto-clear (still requires manually pressing `m` on it), without otherwise breaking `gM`.
- **Smart comment continuation (`<CR>`) only recognizes `//`, `#`, and `--`** (C-family/JS/TS/Go, Python/bash/YAML/CMake, and Lua/SQL respectively) rather than being fully comment-syntax-agnostic — it computes the marker to repeat directly rather than deferring to Neovim's own `'comments'`-option-driven logic, which would cover more comment styles but came with its own timing problems (see the `<CR>`/cmp entry above).
+705 -20
View File
@@ -203,6 +203,14 @@ vim.opt.splitbelow = true
vim.opt.termguicolors = true vim.opt.termguicolors = true
vim.opt.hlsearch = true vim.opt.hlsearch = true
vim.opt.laststatus = 3 -- global statusline (for lualine) vim.opt.laststatus = 3 -- global statusline (for lualine)
-- Drops the "N lines, M bytes written" message after :w. Mainly
-- motivated by hex.nvim's save flow: it runs an xxd filter (which
-- prints its own "N lines filtered" message) as part of writing, and
-- the write's own message stacking on top of that is what triggers
-- Vim's "Press ENTER to continue" prompt after every hex-mode save.
-- Removing this message means there's only one left, which doesn't
-- need the prompt.
vim.opt.shortmess:append("W")
-- Styled window borders between splits (nvim-tree, terminal, etc.) -- Styled window borders between splits (nvim-tree, terminal, etc.)
vim.opt.fillchars = { vim.opt.fillchars = {
@@ -229,6 +237,27 @@ if vim.fn.has("nvim-0.11") == 1 then
vim.o.winborder = "rounded" vim.o.winborder = "rounded"
end end
-- Don't auto-continue comments (// , # , etc.) onto a new line when
-- pressing Enter in insert mode or o/O in normal mode -- EXCEPT when
-- the current line is comment-ONLY (nothing but whitespace + the
-- comment itself, no real code before it), where continuing onto the
-- next line with the same comment marker is actually convenient
-- (writing a multi-line comment block) rather than the annoying case
-- (writing code that happens to have a trailing comment, then hitting
-- Enter to write MORE code and getting an unwanted comment prefix
-- inserted). Many filetypes' own ftplugins (e.g. c.vim) set
-- 'formatoptions' with these flags as part of their own FileType
-- handling -- this runs on the same event, registered afterward, so
-- it reliably strips them back off regardless of what a given
-- filetype's ftplugin set; the <CR> mapping further below re-adds 'r'
-- for just the one keypress when the comment-only condition applies.
vim.api.nvim_create_autocmd("FileType", {
pattern = "*",
callback = function()
vim.opt_local.formatoptions:remove({ "c", "r", "o" })
end,
})
-- SSH Clipboard Support (OSC 52) -- SSH Clipboard Support (OSC 52)
vim.g.clipboard = { vim.g.clipboard = {
name = 'OSC 52', name = 'OSC 52',
@@ -249,6 +278,128 @@ vim.g.clipboard = {
-- Clear search highlight on pressing Esc in normal mode -- Clear search highlight on pressing Esc in normal mode
vim.keymap.set('n', '<Esc>', '<cmd>nohlsearch<CR>') vim.keymap.set('n', '<Esc>', '<cmd>nohlsearch<CR>')
-- Is the cursor's current line comment-ONLY -- i.e. its first
-- non-blank character falls inside a treesitter comment node, meaning
-- there's no real code before the comment on this line? Using
-- treesitter's `comment` node type rather than per-filetype regex
-- (// vs # vs -- etc.) since virtually every grammar names it the
-- same way, so this works generically across languages rather than
-- needing a pattern maintained per filetype. Queries a single point
-- (the first non-blank column) rather than a range spanning the whole
-- line, since a range query only succeeds if it fits entirely inside
-- one node -- comment nodes don't always extend to the exact byte
-- offset a plain string length points at (trailing whitespace,
-- multi-byte characters), and a point query avoids depending on that.
local function cursor_line_is_comment_only()
local ok, parser = pcall(vim.treesitter.get_parser, 0)
if not ok or not parser then
return false
end
local line = vim.api.nvim_get_current_line()
local first_col = line:find("%S")
if not first_col then
return false
end
local row = vim.api.nvim_win_get_cursor(0)[1] - 1
local ok2, tree = pcall(function() return parser:parse()[1] end)
if not ok2 or not tree then
return false
end
local col = first_col - 1
local node = tree:root():named_descendant_for_range(row, col, row, col + 1)
while node do
if node:type():match("comment") then
return true
end
node = node:parent()
end
return false
end
-- <CR> in insert mode: on a comment-only line, repeats the line's
-- comment marker (and a single following space, if one was there) on
-- the new line -- twice in a row with nothing typed in between breaks
-- out instead of continuing again, matching Vim's own long-standing
-- convention for this.
--
-- This used to reuse Neovim's native comment-leader logic instead, by
-- temporarily appending 'r' to formatoptions around the <CR>
-- keypress and reverting it via vim.schedule() afterward -- that
-- never actually worked: there's no guarantee the scheduled revert
-- runs AFTER Neovim's own logic gets to read formatoptions during
-- that same <CR>'s processing, and apparently it ran first every
-- time, removing 'r' before it could take effect. Computing the
-- prefix directly and returning it as part of the same keystring
-- sidesteps that ordering question entirely -- nothing here depends
-- on when anything else happens to run.
--
-- Covers //, #, and -- (C-family/JS/TS/Go, Python/bash/YAML/CMake,
-- and Lua/SQL respectively) rather than being fully comment-syntax-
-- agnostic like the detection above -- that's the trade-off for not
-- depending on formatoptions/'comments' timing.
--
-- NOTE: this mapping ends up invoked through nvim-cmp's OWN <CR>
-- fallback chain, not called directly by Neovim -- cmp.setup() runs
-- after this file's top-level code and, per its own keymap-
-- composition system (cmp/utils/keymap.lua), absorbs whatever <CR>
-- mapping already existed as the function it calls when its
-- completion menu isn't visible, rather than simply discarding it.
-- That has two consequences: (1) it still runs under a textlock, so
-- direct buffer edits like nvim_set_current_line() throw E565 --
-- fixed below by returning keys instead; (2) it runs on literally
-- every <CR> press everywhere, including while navigating a LuaSnip
-- snippet session, which this was never meant to touch at all --
-- guarded against below by bailing out to a plain <CR> whenever one
-- is active for the buffer.
vim.keymap.set('i', '<CR>', function()
local ok_ls, luasnip = pcall(require, "luasnip")
if ok_ls and luasnip.session.current_nodes[vim.api.nvim_get_current_buf()] then
return '<CR>'
end
local line = vim.api.nvim_get_current_line()
-- Current line is JUST a marker with nothing typed after it (i.e.
-- the previous <CR> continued the comment and nothing was added
-- since) -- break out: clear the line and start fresh, rather than
-- continuing yet again. Without this, pressing Enter repeatedly
-- with nothing typed in between just continues forever.
--
-- <Esc>S (leave insert mode, then substitute-line) rather than
-- calling nvim_set_current_line() directly -- direct buffer edits
-- aren't allowed from inside this callback (see the textlock note
-- above), so the change has to be expressed as keys to feed
-- instead, same as the <CR> continuation itself already is.
if line:match("^%s*//%s?$") or line:match("^%s*#%s?$") or line:match("^%s*%-%-%s?$") then
return '<Esc>S'
end
if cursor_line_is_comment_only() then
-- Only the marker itself gets typed onto the new line -- NOT the
-- current line's own leading whitespace. The active indent logic
-- (cindent, etc.) already indents the new line to match on its
-- own; re-typing the captured leading whitespace on top of that
-- is what compounded further right on every single Enter before.
local prefix = line:match("^%s*(//%s?)")
or line:match("^%s*(#%s?)")
or line:match("^%s*(%-%-%s?)")
if prefix then
return '<CR>' .. prefix
end
end
return '<CR>'
end, { expr = true, desc = "Smart comment-continuing newline" })
-- Clears the current line's content, leaving it as a blank line in
-- place rather than deleting the line itself (which would shift
-- everything below it up, like dd does). Motivating case: undoing an
-- auto-continued comment from the mapping above when you didn't
-- actually want it, without backspacing it out character by
-- character.
vim.keymap.set('n', '<leader>cb', function()
vim.api.nvim_set_current_line("")
end, { desc = "Clear Line (Blank)" })
-- Forced Inline Paste (The behavior you liked) -- Forced Inline Paste (The behavior you liked)
-- This forces the pasted text to be treated as characters at your exact cursor position -- This forces the pasted text to be treated as characters at your exact cursor position
vim.keymap.set('n', '<leader>p', function() vim.keymap.set('n', '<leader>p', function()
@@ -264,6 +415,15 @@ vim.keymap.set("x", "P", '"_dP')
vim.keymap.set("v", "J", ":m '>+1<CR>gv=gv", { desc = "Move line down" }) vim.keymap.set("v", "J", ":m '>+1<CR>gv=gv", { desc = "Move line down" })
vim.keymap.set("v", "K", ":m '<-2<CR>gv=gv", { desc = "Move line up" }) vim.keymap.set("v", "K", ":m '<-2<CR>gv=gv", { desc = "Move line up" })
-- Move current line/selection up and down with Alt+j/k, across
-- normal, insert, and visual mode.
vim.keymap.set('n', '<A-j>', ':m .+1<CR>==', { desc = "Move line down" })
vim.keymap.set('n', '<A-k>', ':m .-2<CR>==', { desc = "Move line up" })
vim.keymap.set('i', '<A-j>', '<Esc>:m .+1<CR>==gi', { desc = "Move line down" })
vim.keymap.set('i', '<A-k>', '<Esc>:m .-2<CR>==gi', { desc = "Move line up" })
vim.keymap.set('v', '<A-j>', ":m '>+1<CR>gv=gv", { desc = "Move selection down" })
vim.keymap.set('v', '<A-k>', ":m '<-2<CR>gv=gv", { desc = "Move selection up" })
-- Easy Save -- Easy Save
vim.keymap.set({'n', 'v', 'i'}, '<C-s>', '<cmd>w<CR><Esc>', { desc = "Save File" }) vim.keymap.set({'n', 'v', 'i'}, '<C-s>', '<cmd>w<CR><Esc>', { desc = "Save File" })
@@ -539,6 +699,206 @@ return {
side = "right", side = "right",
width = 30, width = 30,
}, },
-- Routes the paste overwrite/rename CHOICE through
-- vim.ui.select tagged with kind = "nvimtree_overwrite_rename"
-- (nvim-tree's own documented hook for this), which the gP
-- batch-paste below intercepts.
select_prompts = true,
-- Keep nvim-tree's own default mappings and add gP on top of
-- them, via the officially documented on_attach recipe --
-- rather than the TreeAttachedPost event this used before,
-- whose handler is passed the bufnr directly as a plain
-- number (confirmed from nvim-tree's own docs), not a table
-- with a .buf field -- indexing that number is what threw
-- "attempt to index local 'data' (a number value)".
on_attach = function(bufnr)
local api = require("nvim-tree.api")
-- The helper that installs the defaults has been named
-- differently across versions (api.config.mappings.
-- default_on_attach vs the older api.map.on_attach.default)
-- -- try both rather than assume one, so this doesn't
-- silently lose every default mapping on a version where
-- the other name is the real one.
if not pcall(api.config.mappings.default_on_attach, bufnr) then
pcall(api.map.on_attach.default, bufnr)
end
-- gP: paste, resolving every naming collision in this one
-- paste with a single :s///-style pattern/replacement
--
-- CAVEAT: this assumes a single-file-style overwrite/rename
-- choice per conflict. nvim-tree turns out to have a
-- SEPARATE, differently-shaped dialog specifically for
-- multi-file conflicts in one paste ("N file(s) already
-- exist" / Rename (suffix) / Overwrite all / Skip all),
-- which this doesn't detect or intercept -- so gP is only
-- confirmed reliable for a single conflicting file. For
-- "always rename every file, conflict or not" -- which is
-- what was actually being asked for -- gM below is the
-- better fit: it doesn't touch nvim-tree's paste/conflict
-- system at all, just copies marked files directly.
-- instead of nvim-tree's normal one-at-a-time
-- overwrite/rename prompt per conflicting file. Plain `p`
-- (bound above by default_on_attach) is untouched -- this
-- is a deliberately separate mapping, not a replacement.
--
-- nvim-tree doesn't expose a way to enumerate the clipboard
-- or pre-scan for collisions before pasting (only
-- print/clear it), and conflicts are resolved one at a time
-- internally as the paste runs -- there's no "here's the
-- whole batch" moment to hook into directly. This gets the
-- same practical result a different way: ask ONCE upfront
-- for the pattern, then answer nvim-tree's own conflict
-- prompts programmatically for the duration of that paste
-- using vim.fn.substitute() (real Vim regex, same engine
-- :s/// itself uses) against the filename nvim-tree
-- pre-fills as its default.
--
-- vim.ui.select/vim.ui.input are wrapped HERE, at call time
-- inside the keymap function, rather than once when this
-- plugin's config runs. nvim-tree loads eagerly at startup,
-- but dressing.nvim (which ALSO wraps these same two
-- functions, to prettify them) loads lazily on VeryLazy,
-- afterward -- wrapping once at config time would capture
-- the plain pre-dressing versions, which then get silently
-- overwritten (discarding this wrapper entirely) once
-- dressing finishes loading. Wrapping fresh on every call
-- instead always captures whatever's actually active.
vim.keymap.set('n', 'gP', function()
vim.ui.input({ prompt = "Rename pattern/replacement (Vim regex, e.g. foo/bar): " }, function(input)
if not input or input == "" then
api.fs.paste()
return
end
local pattern, replacement = input:match("^(.-)/(.*)$")
if not pattern then
vim.notify("Expected pattern/replacement, e.g. foo/bar", vim.log.levels.WARN)
return
end
local real_select = vim.ui.select
local real_input = vim.ui.input
vim.ui.select = function(items, opts, on_choice)
if opts and opts.kind == "nvimtree_overwrite_rename" then
for i, item in ipairs(items) do
if tostring(item):lower():match("rename") then
on_choice(item, i)
return
end
end
end
real_select(items, opts, on_choice)
end
vim.ui.input = function(input_opts, on_confirm)
if input_opts and input_opts.default then
local ok, new_name = pcall(vim.fn.substitute, input_opts.default, pattern, replacement, "")
on_confirm(ok and new_name ~= "" and new_name or input_opts.default)
return
end
real_input(input_opts, on_confirm)
end
-- Restored via a short defer rather than immediately
-- after api.fs.paste() returns, since it's not
-- confirmed whether paste() resolves every prompt
-- synchronously before returning to caller or defers
-- some of them -- restoring immediately risked only
-- patching the FIRST conflict in a multi-file paste and
-- silently falling back to normal prompts for the rest.
-- If that's what happens, this assumption is the next
-- thing to revisit.
api.fs.paste()
vim.defer_fn(function()
vim.ui.select = real_select
vim.ui.input = real_input
end, 2000)
end)
end, { desc = "nvim-tree: Paste (Batch Rename Regex)", buffer = bufnr, silent = true })
-- Clears the copy/cut clipboard outright. Useful if it's
-- holding a stale entry -- a file that's since been
-- renamed, moved, or deleted -- which pastes as "ENOENT: no
-- such file or directory" until cleared and re-copied fresh.
vim.keymap.set('n', 'gC', api.fs.clear_clipboard, { desc = "nvim-tree: Clear Clipboard", buffer = bufnr, silent = true })
-- gM: copy every MARKED file/folder (toggle a mark with m,
-- shown as a star) into the directory under the cursor,
-- applying a single :s///-style pattern/replacement to
-- EVERY name unconditionally -- not just on collision. This
-- is deliberately separate from nvim-tree's own copy/paste
-- clipboard (c/x/p/gP above) entirely: it reads the marked
-- list via api.marks.list() (a real, documented, stable
-- API -- unlike the clipboard, which has no equivalent way
-- to enumerate its contents) and shells out to `cp -r` to
-- do the actual copying itself, the same approach used in
-- nvim-tree's own official custom-copy recipe. Marks are
-- left as-is afterward (not auto-cleared), matching how the
-- regular clipboard also isn't cleared after a paste --
-- toggle them off individually with m if you're done with
-- them.
vim.keymap.set('n', 'gM', function()
local marks = api.marks.list()
if not marks or #marks == 0 then
vim.notify("No marked files (mark with 'm' first)", vim.log.levels.WARN)
return
end
local cursor_node = api.tree.get_node_under_cursor()
if not cursor_node then
return
end
local dest_dir = cursor_node.type == "directory" and cursor_node.absolute_path
or vim.fn.fnamemodify(cursor_node.absolute_path, ":h")
vim.ui.input({ prompt = "Rename pattern/replacement (Vim regex, e.g. Foo/Bar): " }, function(input)
if not input or input == "" then
return
end
local pattern, replacement = input:match("^(.-)/(.*)$")
if not pattern then
vim.notify("Expected pattern/replacement, e.g. Foo/Bar", vim.log.levels.WARN)
return
end
for _, node in ipairs(marks) do
if not vim.loop.fs_stat(node.absolute_path) then
-- Marks persist by path in nvim-tree's own state and
-- are NOT cleared when the underlying file/folder is
-- deleted, so a stale mark can point at a path that
-- no longer exists (e.g. you marked it, then deleted
-- it). Clean up the stale mark itself here instead
-- of attempting (and failing) a copy from it.
pcall(api.marks.toggle, node)
vim.notify("Skipped stale mark (no longer exists): " .. node.absolute_path, vim.log.levels.WARN)
else
local base = vim.fn.fnamemodify(node.absolute_path, ":t")
local ok, new_name = pcall(vim.fn.substitute, base, pattern, replacement, "")
if not ok or new_name == "" then
new_name = base
end
local dest_path = dest_dir .. "/" .. new_name
if dest_path == node.absolute_path then
-- The pattern didn't match this particular name
-- (so it came back unchanged) and the destination
-- is the same directory the file's already in --
-- that's a copy onto itself, which cp correctly
-- refuses. Skip it up front with a clearer reason
-- instead of surfacing cp's raw "same file" error.
vim.notify("Skipped " .. base .. ": pattern didn't match, and destination is the same as source", vim.log.levels.WARN)
else
local result = vim.fn.system({ "cp", "-r", node.absolute_path, dest_path })
if vim.v.shell_error ~= 0 then
vim.notify("Copy failed for " .. base .. ": " .. result, vim.log.levels.ERROR)
end
end
end
end
api.tree.reload()
end)
end, { desc = "nvim-tree: Copy Marked (Regex Rename)", buffer = bufnr, silent = true })
end,
}) })
vim.keymap.set('n', '<leader>e', ':NvimTreeToggle<CR>', { silent = true, desc = "Toggle File Explorer" }) vim.keymap.set('n', '<leader>e', ':NvimTreeToggle<CR>', { silent = true, desc = "Toggle File Explorer" })
end, end,
@@ -759,6 +1119,40 @@ return {
end, end,
}, },
-- Hex file viewer/editor. Toggles between normal and hex-dump view
-- (backed by xxd) rather than showing both simultaneously -- that
-- trade-off is deliberate: since the hex dump is just ordinary,
-- plain buffer text under the hood, editing it gets Neovim's normal
-- undo/redo for free, and nothing writes to disk until an explicit
-- :w, same as any other file. The only plugin found that does a
-- true live simultaneous hex+ASCII view (hexview.nvim, used here
-- previously) manages bytes with its own custom logic instead of
-- going through normal buffer text editing, which is exactly why it
-- has no undo at all -- not a gap that plugin could patch, an
-- architectural trade-off against the same thing being fixed here.
{
"RaafatTurki/hex.nvim",
config = function()
require("hex").setup()
vim.keymap.set('n', '<leader>h', function()
if not vim.bo.binary then
-- Hex editing depends on the buffer actually being loaded
-- as binary. Otherwise Neovim applies normal text encoding/
-- line-ending handling to the raw bytes first (NUL bytes
-- included, which any real binary is full of) -- that
-- misinterpretation is what produces "CONVERSION ERROR"
-- rather than a clean hex dump, and it happens BEFORE xxd
-- ever runs. Re-reading with ++bin loads the file correctly
-- from scratch instead of operating on an already-misread
-- buffer. Forced (!) since there's nothing worth preserving
-- from a buffer that was misread as text in the first place.
vim.cmd('edit! ++bin %')
end
vim.cmd('HexToggle')
end, { desc = "Toggle Hex View" })
end,
},
-- Nicer notifications (replaces the default vim.notify popups) -- Nicer notifications (replaces the default vim.notify popups)
{ {
"rcarriga/nvim-notify", "rcarriga/nvim-notify",
@@ -766,7 +1160,15 @@ return {
require("notify").setup({ require("notify").setup({
background_colour = "#000000", background_colour = "#000000",
timeout = 3000, timeout = 3000,
render = "compact", -- Stack upward from the bottom instead of down from the top
-- (nvim-notify anchors to the right by default either way),
-- so notifications land bottom-right instead of top-right.
top_down = false,
-- Switched from "compact": that renderer has a documented
-- history of text overflow/wrapping bugs, and is the prime
-- suspect for the garbled/duplicated text seen in some
-- NvimTree error notifications. Not confirmed as the actual
-- cause, but worth trying alongside the position change.
}) })
vim.notify = require("notify") vim.notify = require("notify")
end, end,
@@ -1027,6 +1429,33 @@ return {
end, end,
}, },
-- Auto-pair brackets/quotes: (), {}, [], "", '' -- nvim-autopairs'
-- own defaults. <> was tried here too but removed: it collided too
-- much with < and > as comparison operators to be worth keeping.
{
"windwp/nvim-autopairs",
dependencies = { "hrsh7th/nvim-cmp" },
config = function()
local autopairs = require("nvim-autopairs")
-- map_cr defaults to true, which makes nvim-autopairs install
-- its OWN insert-mode <CR> mapping (for expanding bracket pairs
-- onto their own indented line). That's set during plugin
-- loading, which runs after init.lua's own top-level code --
-- where the comment-continuation <CR> mapping lives -- so it
-- was silently overwriting that mapping entirely regardless of
-- what it did internally. Disabled here to free up <CR> for
-- that mapping; the trade-off is losing autopairs' own
-- brace-expands-on-Enter behavior, which was never explicitly
-- asked for.
autopairs.setup({ map_cr = false })
-- Integrates with nvim-cmp so accepting a completion (e.g. a
-- function call) doesn't end up with doubled-up parens.
local cmp_autopairs = require("nvim-autopairs.completion.cmp")
require("cmp").event:on("confirm_done", cmp_autopairs.on_confirm_done())
end,
},
-- ========================================================= -- =========================================================
-- DAP: In-editor C/C++ debugging (breakpoints, step, watch) -- DAP: In-editor C/C++ debugging (breakpoints, step, watch)
-- Uses codelldb (installed via Mason) as the debug adapter. -- Uses codelldb (installed via Mason) as the debug adapter.
@@ -1312,30 +1741,286 @@ dap.configurations.c = vim.list_extend(dap.configurations.c or {}, configuration
return configurations return configurations
EOF EOF
# --- ~/.clang-format --- # --- ~/.clang-format and ~/.clang-tidy ---
# Personal fallback clang-format style. clang-format walks UP from the # Personal fallback style, implementing a specific C/C++ coding style
# file being formatted looking for a .clang-format file; $HOME sits # guide (naming conventions, brace style, include ordering, etc. --
# above every project you'll open, so this becomes your default style # see the comments in each file below for exactly which guide section
# for any project that doesn't ship its own .clang-format (a project's # each setting maps to). Both tools walk UP from the file being
# own file, being closer to the file, always takes precedence over # checked/formatted looking for their respective config file; $HOME
# this one). BreakBeforeBraces: Attach keeps braces on the same line # sits above every project you'll open, so these become your default
# as the declaration (K&R), instead of Allman-style braces on their # for any project that doesn't ship its own .clang-format/.clang-tidy
# own line. # (a project's own file, being closer to the file, always takes
# precedence over these).
# #
# NOTE: written unconditionally (not "if missing") every run, same as # NOTE: this REPLACES the earlier simpler .clang-format (LLVM +
# the rest of this config -- an earlier version only wrote this if the # Attach/K&R braces) with a full style-guide implementation -- brace
# file didn't already exist, which meant a stray pre-existing # style is intentionally flipped back to Allman here as part of that,
# ~/.clang-format (from some other tool, or leftover from before this # since this is a deliberate, complete style guide rather than the
# script managed it) would silently block the fix forever. # earlier ad-hoc preference.
#
# <leader>lf (LSP format) only consumes .clang-format -- clangd's
# formatting is pure clang-format, unrelated to clang-tidy. The
# .clang-tidy file is picked up separately and automatically by
# clangd for live diagnostics (clang-tidy integration is on by
# default in clangd), not by <leader>lf itself.
#
# Written unconditionally (not "if missing") every run, same as the
# rest of this config -- so a stray pre-existing file from some other
# tool can't silently block these from taking effect.
cat << 'EOF' > "$HOME/.clang-format" cat << 'EOF' > "$HOME/.clang-format"
BasedOnStyle: LLVM # .clang-format
BreakBeforeBraces: Attach # Enforces the formatting rules from the C/C++ Coding Style Guide (Part I: C++ Style).
IndentWidth: 4 # Applies equally to Part II C API files per §15 (Indentation and Braces) — same
ColumnLimit: 100 # tabs/Allman/line-length rules, just different naming (naming can't be enforced
# by clang-format; see .clang-tidy and TOOLING.md).
Language: Cpp
Standard: Latest
# --- Indentation (style guide §2) ---
UseTab: Always UseTab: Always
TabWidth: 4 TabWidth: 4
IndentWidth: 4
ContinuationIndentWidth: 4
IndentCaseLabels: true
IndentPPDirectives: None
IndentExternBlock: Indent
# Access specifiers (public:/private:) sit unindented, flush with the class
# keyword — only members are indented one level, matching every example in §1/§6.
AccessModifierOffset: -4
# Namespace bodies are indented like any other block (style guide §Namespaces).
NamespaceIndentation: All
# --- Braces: Allman style everywhere (style guide §3) ---
BreakBeforeBraces: Allman
Cpp11BracedListStyle: true
# InlineOnly (not Empty or None): out-of-class function definitions always
# get full Allman expansion regardless of length, same as before -- this
# only additionally allows a function *defined inside a class body* to
# stay a compact one-liner if it's short enough to fit. That's specifically
# for the trivial-one-liner exception to "avoid inline member definitions"
# in Class Member Organization (style guide §6) -- a plain getter like
# `int getValue() const { return m_value; }` stays inline-and-compact;
# anything defined out-of-class still always gets the full brace expansion.
AllowShortFunctionsOnASingleLine: InlineOnly
AllowShortIfStatementsOnASingleLine: Never
AllowShortLoopsOnASingleLine: false
AllowShortBlocksOnASingleLine: Never
AllowShortEnumsOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
# No trailing "// namespace X" / "// class X" comments on closing braces
# (style guide §Namespaces / §6).
FixNamespaceComments: false
# --- Pointers and references attach to the type (style guide §4) ---
PointerAlignment: Left
ReferenceAlignment: Pointer
DerivePointerAlignment: false
# --- Line length (style guide §8) ---
ColumnLimit: 100
# --- Spacing (style guide §5) ---
SpaceBeforeParens: ControlStatements
SpacesInParentheses: false
SpacesInSquareBrackets: false
SpaceAfterCStyleCast: false
SpaceBeforeAssignmentOperators: true
SpacesInAngles: false
# --- Include ordering (style guide §7: main header, C headers, C++ std,
# third-party, project headers) ---
SortIncludes: true
SortUsingDeclarations: true
IncludeBlocks: Regroup
IncludeIsMainRegex: '(Test)?$'
IncludeCategories:
# 1. Corresponding header is auto-detected and always sorted first.
# 2. C system headers wrapped for C++ (<cstdio>, <cstdint>, ...).
- Regex: '^<(cassert|cctype|cerrno|cfenv|cfloat|cinttypes|climits|clocale|cmath|csetjmp|csignal|cstdarg|cstddef|cstdint|cstdio|cstdlib|cstring|ctime|cuchar|cwchar|cwctype)>$'
Priority: 1
CaseSensitive: true
# 3. C++ standard library headers (no dot, no slash — <vector>, <memory>, ...).
- Regex: '^<[a-z_]+>$'
Priority: 2
CaseSensitive: true
# 4. Third-party / external library headers (<fmt/format.h>, <boost/...>, ...).
- Regex: '^<.*>$'
Priority: 3
CaseSensitive: true
# 5. Project headers, quoted ("NetworkManager.hpp", ...).
- Regex: '^".*"$'
Priority: 4
CaseSensitive: true
# --- Misc ---
AlignTrailingComments: true
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
# Parameters/arguments: never one-per-line (style guide §8). When a
# signature/call doesn't fit on one line, keep everything together on a
# single continuation line if it fits there (AllowAll...OnNextLine), and
# fall back to packing multiple per line (BinPack...) rather than ever
# breaking to one per line. This can't force a genuinely long signature
# to fit within ColumnLimit without wrapping at all -- doing that would
# require ColumnLimit: 0 project-wide, which would also stop comments and
# ordinary expressions from ever wrapping. See §8 for the full rule.
#
# COMPATIBILITY NOTE: clang-format 20 changed BinPackParameters (and the
# equivalent for call arguments) from a boolean to an enum, and later
# versions deprecated it again in favor of a nested PackParameters/
# PackArguments option. The boolean form below is correct for clang-format
# through 19 (still the most common version in practice, e.g. Ubuntu
# 24.04 LTS ships 18). On clang-format 20 or newer, replace `true` with
# `BinPack` on both lines if you find these are being silently ignored.
BinPackArguments: true
BinPackParameters: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
BreakConstructorInitializers: BeforeColon
ConstructorInitializerIndentWidth: 4
PenaltyReturnTypeOnItsOwnLine: 1000
EOF EOF
echo "Wrote ~/.clang-format (Attach braces, real tabs)."
cat << 'EOF' > "$HOME/.clang-tidy"
# .clang-tidy
# Enforces naming conventions (style guide §1) and a subset of the Language
# Feature Do's and Don'ts (style guide §10) for C++ code. Place at the repo
# root for Part I (C++) files. For Part II (C API) files, use the
# directory-scoped override in c-api/.clang-tidy instead — clang-tidy picks
# the closest .clang-tidy file up the directory tree, so nesting one there
# gives that subtree different naming rules without affecting the rest of
# the project. See TOOLING.md for what this file can't check.
#
# readability-redundant-inline-specifier is a narrow, complementary aid for
# the "avoid inline member definitions" rule in §6 (Class Member
# Organization): it only catches an explicit `inline` keyword that's
# already redundant (e.g. on a function defined in the class body, which
# is implicitly inline regardless). It does not catch — and nothing in
# this file catches — an inline definition that omits the keyword
# entirely, which is the actual, common case the style guide rule is
# about; that one stays a code-review judgment call. See TOOLING.md.
Checks: >
-*,
readability-identifier-naming,
readability-redundant-inline-specifier,
modernize-use-nullptr,
modernize-use-override,
modernize-use-equals-default,
modernize-use-equals-delete,
modernize-loop-convert,
modernize-concat-nested-namespaces,
modernize-use-default-member-init,
modernize-avoid-c-arrays,
modernize-use-nodiscard,
cppcoreguidelines-special-member-functions,
cppcoreguidelines-owning-memory,
cppcoreguidelines-no-malloc,
cppcoreguidelines-pro-type-member-init,
cppcoreguidelines-pro-type-cstyle-cast,
cppcoreguidelines-macro-usage,
performance-unnecessary-value-param,
performance-unnecessary-copy-initialization,
bugprone-exception-escape,
bugprone-use-after-move
WarningsAsErrors: ''
HeaderFilterRegex: '.*\.hpp$'
FormatStyle: file
CheckOptions:
# --- Naming Conventions — style guide §1 ---
- key: readability-identifier-naming.NamespaceCase
value: lower_case
- key: readability-identifier-naming.ClassCase
value: CamelCase
- key: readability-identifier-naming.StructCase
value: CamelCase
- key: readability-identifier-naming.EnumCase
value: CamelCase
- key: readability-identifier-naming.EnumConstantCase
value: CamelCase
- key: readability-identifier-naming.FunctionCase
value: camelBack
- key: readability-identifier-naming.MethodCase
value: camelBack
- key: readability-identifier-naming.VariableCase
value: camelBack
- key: readability-identifier-naming.LocalVariableCase
value: camelBack
- key: readability-identifier-naming.ParameterCase
value: camelBack
# A plain local `const` (not `static`, not `constexpr`) stays under the
# Local Variables rule (camelCase) per the style guide's own note that
# only *global/static* const/constexpr values are "constants" for this
# rule's purposes -- without this, it silently inherits the general
# ConstantCase (PascalCase) fallback below instead, which would flag
# totally ordinary code like `const std::string name = ...;` inside a
# function. StaticConstantCase/GlobalConstantCase/ConstexprVariableCase
# below are unaffected and still require PascalCase, matching the guide.
- key: readability-identifier-naming.LocalConstantCase
value: camelBack
# Private/protected member variables: camelCase with an m_ prefix.
- key: readability-identifier-naming.PrivateMemberCase
value: camelBack
- key: readability-identifier-naming.PrivateMemberPrefix
value: m_
- key: readability-identifier-naming.ProtectedMemberCase
value: camelBack
- key: readability-identifier-naming.ProtectedMemberPrefix
value: m_
# Public members (e.g. plain data structs) — camelCase, no prefix.
- key: readability-identifier-naming.PublicMemberCase
value: camelBack
# Static class data members — camelCase with an s_ prefix (distinct from
# instance members above). "ClassMember" is clang-tidy's category for
# static (non-const) data members specifically.
- key: readability-identifier-naming.ClassMemberCase
value: camelBack
- key: readability-identifier-naming.ClassMemberPrefix
value: s_
# Global (namespace/file-scope, mutable) variables — camelCase with a g_
# prefix, to flag genuinely global mutable state at every use site.
- key: readability-identifier-naming.GlobalVariableCase
value: camelBack
- key: readability-identifier-naming.GlobalVariablePrefix
value: g_
# Static variables — function-local `static` or file-scope `static` —
# camelCase with an s_ prefix, for the same reason as globals above.
- key: readability-identifier-naming.StaticVariableCase
value: camelBack
- key: readability-identifier-naming.StaticVariablePrefix
value: s_
# Constants and enum values — PascalCase (style guide §1 table).
- key: readability-identifier-naming.ConstantCase
value: CamelCase
- key: readability-identifier-naming.GlobalConstantCase
value: CamelCase
- key: readability-identifier-naming.StaticConstantCase
value: CamelCase
- key: readability-identifier-naming.ClassConstantCase
value: CamelCase
- key: readability-identifier-naming.ConstexprVariableCase
value: CamelCase
# Template parameters — PascalCase (e.g. ValueType).
- key: readability-identifier-naming.TemplateParameterCase
value: CamelCase
- key: readability-identifier-naming.TypeTemplateParameterCase
value: CamelCase
# Type aliases / typedefs — PascalCase, matching class naming.
- key: readability-identifier-naming.TypeAliasCase
value: CamelCase
- key: readability-identifier-naming.TypedefCase
value: CamelCase
# Macros — ALL_CAPS_SNAKE_CASE (the one deliberate exception in §1).
- key: readability-identifier-naming.MacroDefinitionCase
value: UPPER_CASE
# --- cppcoreguidelines-owning-memory — style guide §10, Memory and Ownership ---
# Flags raw new/delete and legacy C allocators used for ownership instead of
# std::unique_ptr / std::shared_ptr.
- key: cppcoreguidelines-owning-memory.LegacyResourceProducers
value: 'malloc;calloc;realloc;strdup;strndup'
- key: cppcoreguidelines-owning-memory.LegacyResourceConsumers
value: 'free;realloc'
# --- cppcoreguidelines-macro-usage — style guide §10, prefer constexpr over #define ---
# Allow include-guard-style and project-prefixed macros; flag everything else
# that could instead be a constexpr constant.
- key: cppcoreguidelines-macro-usage.AllowedRegexp
value: '^[A-Z0-9_]+_H(PP)?$'
- key: cppcoreguidelines-macro-usage.CheckCapsOnly
value: false
EOF
echo "Wrote ~/.clang-format and ~/.clang-tidy (Allman braces, real tabs)."
echo "=========================================" echo "========================================="
echo " Setup Complete!" echo " Setup Complete!"