Fast · Minimal · Embedded-first
A modern Neovim configuration for embedded development (C/C++ · Rust · Python),
technical writing (LaTeX · Markdown · prose), and AI-assisted coding.
🌐 中文文档 · English
- Blazing-fast startup — everything is lazy-loaded; the embedded debug stack and AI tools cost zero until you ask for them.
- Full LSP for four languages — clangd, pyright + ruff, rust-analyzer, lua-language-server: go-to-definition, references, rename, code actions.
- One-key ARM debug —
<leader>ddflashes and debugs Cortex-M targets over OpenOCD + GDB, with automatic MCU detection. - Writing toolkit — live Markdown rendering, VimTeX + Synctex forward/inverse search, spell-check and soft wrap for prose.
- AI, built in but optional — blink.cmp completion and Claude Code in a terminal; both behind explicit toggles, with agent stats (
/cost,/status) for free. - Git in the gutter — inline hunks, per-hunk stage/reset, buffer diff review, line blame.
| Purpose | Tools |
|---|---|
| Base | Neovim ≥ 0.12 · git |
| C/C++ LSP | clangd (auto-installed by Mason); a compile_commands.json for full project semantics — see guide |
| Embedded debug | arm-none-eabi-gdb or gdb-multiarch · openocd · ARM toolchain (gcc-arm-none-eabi) |
| LaTeX | TeX Live (provides latexmk) · zathura + zathura-pdf-poppler for PDF with forward/inverse search |
clangd note: if clangd reports
'iostream' file not foundon C++ files, see the C/C++ guide.
git clone https://github.com/<your-name>/<repo>.git ~/.config/nvim
nvim # lazy.nvim bootstraps itself, then installs all plugins
:Mason # first run installs LSP servers (clangd / pyright / ruff)Plugins update automatically at startup (lazy.nvim checker); run :Lazy for manual management.
Leader is Space (shown as ␣), localleader is , (LaTeX).
| Key | Action |
|---|---|
q |
Enter preview mode — read-only pager, see Preview Mode |
Q |
Record macro (original q, followed by a register name) |
Z |
Clear search highlight |
R |
Replace all search matches — prompts :%s//, type new/g⏎ (all) or new/gc⏎ (confirm each) |
S |
Prefix for quick insert chords — see S-Prefix Chords |
viwp / visual p |
Paste over a word — visual p is remapped to preserve the register, so repeated viwp replaces multiple targets with the same yank |
H / L |
Jump to first / last non-blank character of the line |
_ / g_ |
Jump to top / bottom of the screen |
<C-a> |
Select all |
U |
Redo |
gcc / gc |
Toggle line / range comment |
gbc / gb |
Toggle block comment |
<A-e> |
Autopairs fast-wrap (wrap a word with a delimiter) |
ys{text-object}{char} · ds{char} · cs{old}{new} |
Surround: add / delete / change a pair |
visual S |
Surround the visual selection |
| Key | Action |
|---|---|
s |
Flash jump — type a label to jump anywhere (normal / visual / operator-pending: ds-style jumps work as motions) |
f / F / t / T |
Native char navigation (flash's char mode disabled) |
<C-u> / <C-d> |
Smooth scroll half page |
<C-b> / <C-f> |
Smooth scroll full page |
<C-h/j/k/l> |
Move between windows |
<A-h/j/k/l> |
Resize windows |
␣w{hjkl} |
Swap window positions |
␣e |
Toggle file tree |
<C-i> / ␣th |
Toggle terminal / horizontal terminal |
| Key | Action |
|---|---|
j / k / e / y |
Scroll one line |
J / K |
Scroll ten lines (count multiplies: 3J = 30) |
d / u |
Scroll half page (smooth) |
f / b |
Scroll full page (smooth) |
h / l |
Scroll horizontally |
Esc |
Exit preview |
Absolute line numbers and read-only (edits raise E21). Scroll mappings are mounted only while the mode is active — outside preview every key keeps its native meaning (d = delete, u = undo, y = yank). Preview also auto-exits when leaving the buffer (switching to neo-tree, terminal, or another file).
| Key | Action |
|---|---|
SJ / SK |
Insert blank lines below/above current line |
SS |
Smart line split |
SN |
Split line after cursor |
| Key | Action |
|---|---|
/ |
Fuzzy filter (live) — the way to navigate large directories |
f |
Filter on submit |
N / n |
Open in horizontal / vertical split |
zM / zR |
Collapse / expand all |
. / u |
Set temporary root / navigate up |
R / q |
Refresh / close |
| Key | Action |
|---|---|
gd / gr / gy / gI |
Definition / references / type definition / implementations (Telescope) |
K / gl |
Hover documentation / line diagnostics |
␣ds / ␣ws |
Document / workspace symbols |
␣xx |
All workspace diagnostics |
␣rn / ␣ca |
Rename symbol / code actions |
| Key | Action |
|---|---|
,ll / ,lv |
Compile (latexmk) / view PDF with forward search |
,lt / ,le |
Table of contents / errors |
,lk / ,lc / ,lq / ,lx |
Stop / clean / log / reload |
| Key | Action |
|---|---|
␣db / ␣dB |
Toggle / conditional breakpoint |
<F5> / <F10> / <F11> / <F12> |
Continue / step over / step into / step out |
␣dr |
DAP REPL |
␣dl / ␣dt |
Run to cursor / terminate |
␣dd / :ArmDebug |
One-key ARM flash & debug (OpenOCD + GDB + DAP UI) |
␣td |
Toggle embedded features (off ⇒ ␣dd/:ArmDebug refuse; software debugging unaffected) |
Debug sessions open the DAP UI panel (variables · scopes · call stack · breakpoints) automatically; virtual text shows variable values inline. Adapters: codelldb (C/C++/Rust), debugpy (Python), arm_gdb (embedded).
| Key | Action |
|---|---|
]c / [c |
Next / previous hunk |
␣hs / ␣hr |
Stage / reset current hunk |
␣hp |
Preview hunk |
␣hd |
Review: diff entire buffer vs index |
␣hb |
Blame current line |
| Key | Action |
|---|---|
␣tc |
Toggle completion (blink.cmp) |
␣ac / ␣aC |
Toggle Claude Code terminal / resume latest session |
~/.config/nvim
├── init.lua # entry: lazy → keymaps → options → diagnostics → preview → lsp
├── PERFORMANCE.md # performance assessment report
├── lua/
│ ├── config/
│ │ ├── lazy.lua # lazy.nvim bootstrap, leader keys
│ │ ├── keymaps.lua # global keymaps + S-prefix chords + embedded switch
│ │ ├── options.lua # editor options, autocmds
│ │ ├── diagnostics.lua # one-sign-per-line (worst severity) handler
│ │ ├── preview.lua # preview mode (read-only pager)
│ │ └── lsp.lua # LSP keymaps (telescope + floats)
│ └── plugins/ # one spec file per domain
│ ├── lsp.lua # ALL LSP configs + conform formatters (single file by design)
│ ├── dap.lua # DAP: generic debugging + ARM flash & debug (lazy)
│ ├── latex.lua · markdown.lua · blink.lua · claude.lua · flash.lua · gitsigns.lua
│ └── … # UI / editing plugins
└── after/ftplugin/ # per-filetype settings
├── tex.lua # spell, soft wrap, conceallevel=2
├── markdown.lua · text.lua # spell, soft wrap
| Plugin | Purpose | Config |
|---|---|---|
| lazy.nvim | Plugin manager | lua/config/lazy.lua |
| vscode.nvim | Dark colorscheme | colorscheme.lua |
| telescope.nvim (master) | Fuzzy finder & LSP pickers | telescope.lua |
| neo-tree.nvim | File explorer | neo-tree.lua |
| toggleterm.nvim | Terminal | toggleterm.lua |
| smart-splits.nvim | Window movement & resizing | smart-splits.lua |
| flash.nvim | Label jump (s) |
flash.lua |
| nvim-surround | Surround pairs | nvim-surround.lua |
| Comment.nvim | Comments | comment.lua |
| nvim-autopairs | Auto pairs & fast-wrap | autopairs.lua |
| gitsigns.nvim | Git hunks in the gutter | gitsigns.lua |
| mason.nvim | LSP server installer | mason.lua |
| nvim-lspconfig | LSP setup (clangd / pyright / ruff / lua_ls) | lsp.lua |
| conform.nvim | Format on save | lsp.lua |
| rustaceanvim | Rust LSP + clippy on save | rust.lua |
| nvim-treesitter (main) | Parser installs (12 parsers); highlighting via nvim core | treesitter.lua |
| rainbow-delimiters.nvim | Rainbow brackets | rainbow.lua |
| indent-blankline.nvim | Indent guides | indent-blankline.lua |
| neoscroll.nvim | Smooth scrolling | neoscroll.lua |
| nvim-dap + dap-ui + virtual-text | Debugging: codelldb / debugpy / ARM flash & debug | dap.lua |
| render-markdown.nvim | Live Markdown rendering | markdown.lua |
| vimtex (v2.18) | LaTeX compile / view / Synctex | latex.lua |
| blink.cmp (v1) | Completion (LSP / path / buffer) | blink.lua |
| claude-code.nvim | Claude Code in a terminal | claude.lua |
clangd provides semantic analysis only if it knows your build flags — that's what compile_commands.json is for. Without it, cross-file symbols fail with cascading errors (the "errors everywhere" symptom).
Generate it once per project:
| Build system | How |
|---|---|
| CMake | cmake -B build -DCMAKE_EXPORT_COMPILE_COMMANDS=ON (the file is produced at configure time — no compile needed; symlink build/compile_commands.json to the project root if needed) |
| Makefile / anything | bear -- make (sudo apt install bear) |
| Quick fallback | Put a compile_flags.txt in the project root (one flag per line, e.g. -I./inc) |
'iostream' file not found? This config ships two safeguards:--query-driver=/usr/bin/*(clangd asks your real compiler for include paths) and~/.config/clangd/config.yaml(fallback compiler for database-less files). The root cause is usually a version mismatch between installedgccand thelibstdc++headers — installing the matchingg++-Nfixes it permanently.
<leader>dd (or :ArmDebug) runs the whole pipeline:
- Resolve the ELF:
<current>.elfnext to the source, orbuild/<current>.elf - Guess the MCU from the ELF name:
stm32f1/f103/bullet→ STM32F1,h7/mc02→ STM32H7, otherwise → STM32F4 - Start OpenOCD in a terminal tab using
openocd/<mcu>/daplink.cfg - Wait for the probe, reset-halt, flash, and attach the DAP UI
Expected project layout:
project/
├── openocd/{stm32f1,stm32f4,stm32h7}/daplink.cfg
├── build/*.elf
└── src/*.c
The whole debug stack (nvim-dap + dap-ui + virtual-text) is lazy — it costs nothing until you press <leader>dd.
- Compile:
,ll(latexmk) · view:,lv(zathura + Synctex forward search;Ctrl+Clickin zathura jumps back) - Conceal renders inline math; spell-check and soft wrap are on by default (
after/ftplugin/tex.lua) - Requires TeX Live and zathura — see Prerequisites
- Markdown renders live (bold, headings, code blocks); spell-check and soft wrap on
.txtgets the same spell/wrap treatment- Indent guides are disabled in text/markdown/tex to keep prose clean
The Lua language server resolves symbols of the workspace it opened — and nothing else. When definitions live outside it (the Neovim runtime, a vendored SDK, a sibling library), declare them in the project, not in this config. .luarc.json in the project root is read natively by lua-language-server, is editor-agnostic, and is safe to commit:
{
"runtime.version": "LuaJIT",
"diagnostics.globals": ["vim"],
"workspace.library": ["${env:VIMRUNTIME}", "../sibling-lib/src"]
}- Keys are dotted and unprefixed in
.luarc.json(elsewhere they need theLua.prefix) - LuaLS expands
${env:FOO},${workspaceFolder},${3rd}itself; relative paths resolve against the file's directory - Everything the library pulls in is visible for completion, hover and go-to-definition, but is read-only for renames
- Adding a
.luarc.jsonto an nvim-config repo is howvim.*gets resolved without a plugin and without a machine-specific path baked into the shared nvim config
To keep it on the Neovim side instead, a project-local .nvim.lua (needs vim.o.exrc = true, trusted once) does the same:
vim.lsp.config('lua_ls', { settings = { Lua = { workspace = { library = { vim.env.VIMRUNTIME } } } } })Three steps, one rule:
- Add the LSP server binary to
ensure_installedinmason.lua - In
lsp.lua:vim.lsp.config["<server>"] = { cmd = { <absolute mason bin path> } }+vim.lsp.enable("<server>") - (Optional) Add a formatter to conform's
formatters_by_ft
Rule: a given plugin must be declared in exactly one spec file — lazy.nvim executes only one
configwhen the same plugin appears in multiple files. That's why all LSP/conform configs live together inlsp.lua.
- Colorscheme: vscode.nvim (dark) with custom group overrides — VS Code-style syntax colors tuned for Rust/C/C++
- Indent guides:
┊with scope highlighting; disabled in prose files - Rainbow delimiters for nested brackets
- Flash highlights follow the theme palette (gold labels, orange targets, gray backdrop)
- Gitsigns uses subtle bar glyphs in the sign column; two sign columns — git marks in column 1, diagnostics in column 2 (one sign per line, worst severity only)
🌐 中文文档 · English