English | 中文
任何编辑器——VS Code、Emacs、Sublime、JetBrains、Neovim——本质上都是同样八件事的组合:
- 一个隐形的插件管理器——你不需要时它根本不存在
- 编辑器自身的旋钮——
vim.opt、autocmd、各种内置选项 - 一套完全属于你的模态键位层
- 有审美的 UI——dashboard、状态栏、缩进线、配色方案
- 编辑流——括号补全、文件树、模糊查找、which-key、文件标记、终端
- 对每一种语言的完整支持,包括: Tree-sitter、LSP、补全、诊断、Linter、Formatter、DAP、Snippets
- 不需要切回 shell 就能完成的版本控制
对 AI 的态度——因为这是 2026 年,盲目接受 agent 工具本身也是一种态度
这份配置直接覆盖前七层。第八层现在是一个有意的空缺。
你已经在维护自己的配置了。 你在试图组织 20+ 种语言的支持而不让整件事变成一团乱麻,你已经不想再从别人的 init.lua 复制粘贴,或者你想看看一份 from-scratch 的架构在有一定规模时长什么样。
或者:你正在用 LazyVim、NvChad、AstroVim、LunarVim,并且开始感觉到它的边界。你说不出具体想改什么,但你知道你的编辑器在做一些你不完全理解的事情。这份 repo 是一个从零开始的替代方案。
如果你完全是 Neovim 新手,先去看 kickstart.nvim。半年之后再回来这里。
Dependencies:
- Neovim 0.12+
- Git、
tree-sitterCLI、一个 Nerd Font - 一个 C 编译器(用来编 tree-sitter parser)
ripgrep(grug-far等插件需要)。
git clone https://github.com/SOV710/nvim-config ~/.config/nvim
nvim首次启动时 lazy.nvim 自举并安装所有插件,mason-tool-installer 拉取所有 mason 管理的包。
然后运行:
:checkhealth langs
:TSInstall:checkhealth langs 会列出哪些非 mason 外部工具还需要安装,并给出每个工具的安装命令。:TSInstall 会拉取各语言模块声明的 tree-sitter parser 源码,需要时运行 tree-sitter generate,编译 parser,并把 parser 与 queries 安装到这份配置管理的 Neovim runtime 里。
lazy.nvim。所有 spec 文件按用途组织在 lua/plugins/{ui,editor,langs,git}/ 下,每个目录作为一个整体被 import。Snacks 的各个模块拆成独立的 spec 文件放在 lua/plugins/ui/ 下,由 lazy.nvim 自动 merge。
lua/core/options.lua——几十行 vim.opt。能保持默认的就保持默认。
所有键位都在 lua/keymaps/ 下,按功能组织。Plugin spec 不直接调用 vim.keymap.set——它们引用返回普通 table 的 keymap 文件:
-- lua/plugins/editor/flash.lua
return {
'folke/flash.nvim',
event = 'VeryLazy',
opts = {},
keys = require('keymaps.editor.flash'),
}-- lua/keymaps/editor/flash.lua
return {
{ 's', mode = { 'n', 'x', 'o' }, function() require('flash').jump() end, desc = 'Flash jump' },
{ 'S', mode = { 'n', 'x', 'o' }, function() require('flash').treesitter() end, desc = 'Flash treesitter' },
}所有键位都可以在一个目录里 grep 到。
几个重点绑定:
s/S→ flash(覆盖原生 substitute,反正我从不用)m→ grapple toggle(覆盖原生 mark)'→ grapple 菜单(覆盖原生 jump-to-mark)+/-→ dial(替代<C-a>/<C-x>)<leader>g*→ git,<leader>h*→ hunk
Tokyo Night 统一配色。状态栏和 tabline 是用 heirline 自己搭的——tabline 和 grapple.nvim 集成,被标记的文件直接出现在顶栏里。
| 组件 | 插件 |
|---|---|
| Colorscheme | folke/tokyonight.nvim |
| Statusline + tabline | rebelot/heirline.nvim(custom) |
| Icons | nvim-mini/mini.icons |
| Messages | folke/noice.nvim(仅 messages;cmdline 留在底部内联) |
| Dashboard、indent、scroll、statuscolumn、image | folke/snacks.nvim |
| 模式感知的 cursorline | mvllow/modes.nvim |
| 颜色字面量(hex、CSS、Tailwind) | brenoprata10/nvim-highlight-colors |
🚧 Showcase pending:完整 UI 巡礼——dashboard → 编辑 buffer → grapple-aware tabline(15 秒 GIF)
| 组件 | 插件 |
|---|---|
| File explorer | stevearc/oil.nvim + snacks.explorer |
| Picker | snacks.picker(替代 telescope) |
| Motion | folke/flash.nvim |
| Surround | echasnovski/mini.surround |
| 括号补全 | windwp/nvim-autopairs |
| 括号 / tag 匹配 | andymass/vim-matchup |
| 文本对象 | nvim-treesitter-textobjects |
| Yank ring | gbprod/yanky.nvim |
| 注释 | folke/ts-comments.nvim(treesitter-aware) |
| 文件标记(harpoon style) | cbochs/grapple.nvim |
| LSP reference 跳转 | snacks.words |
| Scratch buffer | snacks.scratch |
| Split / join | Wansmer/treesj |
| Inc / dec | monaqa/dial.nvim |
| Undo tree | Neovim 内建 nvim.undotree |
| Substitute | gbprod/substitute.nvim |
| 项目级搜索替换 | MagicDuck/grug-far.nvim |
| Terminal | akinsho/toggleterm.nvim |
| Which-key | folke/which-key.nvim |
🚧 Showcase pending:10 秒 flash.nvim 跳转 GIF
🚧 Showcase pending:grapple 标记在 heirline tabline 中显示(10 秒 GIF)
每种支持的语言——Rust、Go、Python、TypeScript、Haskell、Lua、C/C++、LaTeX、Fish、SQL,外加 20 多种——在 lua/langs/ 下对应一个文件。这个文件包含关于该语言的全部声明:LSP server 配置、tree-sitter parser 源码/build/query manifest、formatter、linter、DAP adapter、snippets、mason 包、文件类型识别,以及任何语言专属插件。
一个典型的 lua/langs/<name>.lua:
return {
filetypes = { 'rust' },
treesitter = {
languages = {
rust = {
parser = {
source = {
type = 'git',
url = 'https://github.com/tree-sitter/tree-sitter-rust',
},
build = {
files = { 'src/parser.c', 'src/scanner.c' },
},
},
queries = {
sources = {
{ type = 'parser_source', lang = 'rust' },
},
},
},
},
},
lsp = {
rust_analyzer = {
settings = {
['rust-analyzer'] = {
checkOnSave = { command = 'clippy' },
},
},
},
},
formatter = 'rustfmt',
-- linter 省略:rust_analyzer 已经覆盖
dap = {
adapter = { codelldb = { type = 'server', port = '${port}', ... } },
configurations = { rust = { { name = 'Launch', type = 'codelldb', ... } } },
},
snippets = function()
local ls = require('luasnip')
return {
ls.snippet('pln', { ls.text_node('println!("'), ls.insert_node(1), ls.text_node('");') }),
}
end,
mason = { 'rust-analyzer', 'codelldb' },
external_deps = {
{ cmd = 'cargo', install = 'rustup default stable', required = true },
{ cmd = 'rustfmt', install = 'rustup component add rustfmt', required = true },
},
plugins = {
{ 'mrcjkb/rustaceanvim', version = '^5', lazy = false },
},
}lua/core/language.lua 在 require 时扫描 lua/langs/*.lua,把各语言的声明聚合成:
| 聚合后的字段 | 谁在消费 |
|---|---|
language.formatters |
conform.nvim |
language.linters |
nvim-lint |
language.treesitter |
core.treesitter 的原生 installer/runtime |
language.mason |
mason-tool-installer |
language.dap_adapters、language.dap_configurations |
nvim-dap |
language.snippets |
LuaSnip |
language.plugins |
直接 inject 进 lazy.nvim 的 spec |
| LSP servers | 通过原生 vim.lsp.config / vim.lsp.enable 注册,不经过 nvim-lspconfig |
lua/core/treesitter.lua 负责整条 tree-sitter 管线。它提供 :TSInstall、:TSUpdate、:TSStatus 和 :TSClean;按声明的 git URL checkout parser 源码;在 manifest 要求时运行 tree-sitter generate;编译 parser shared object;把 query sources 安装到受管理的 runtime;通过 vim.treesitter.language.register 注册 filetype;最后用 Neovim 原生的 vim.treesitter.start 启动高亮。
这份配置不再依赖 nvim-treesitter 的 parser registry、ensure_installed 或 installer。Parser 的来源声明放在 lua/langs/*.lua 里,而不是外部插件注册表里。
所以 conform 的整个 spec 是:
opts = {
formatters_by_ft = require('core.language').formatters,
}🚧 Showcase pending:架构图——
lua/langs/*.lua→core/language.lua→ 扇出到各个消费者
开关单种语言。 每个 lang 文件都接受 enabled = false 字段。设置后,该文件在扫描阶段被跳过,所有下游消费者(Tree-sitter、LSP、conform、lint、DAP、snippets、mason)同步看不到它。当某次插件更新后某种语言出了问题,用这个方式可以快速隔离。
外部依赖。 很多语言工具链不能由 mason 管理——tsgo 要从源码编译,ty 走 PyPI,HLS 走 GHCup,fish-lsp 走 npm,sleek 走 cargo,latexindent、chktex 随 TeX Live 一起安装。每个有非 mason 依赖的 lang 文件都在两处记录这些依赖:
- 文件顶部的块注释,写明安装命令和验证方式。
- 结构化的
external_deps字段,由:checkhealth langs消费——报告哪些依赖在、哪些缺,并给出安装命令。
:checkhealth langs
==============================================================================
langs: require("langs.health").check()
haskell ~
- OK ghc
- OK cabal
- ERROR haskell-language-server: not found
- install: ghcup install hls recommended
- WARN haskell-debug-adapter: not found (optional)
- install: cabal install haskell-debug-adapter haskell-dap ghci-dap
python ~
- OK ty
rust ~
- OK cargo
- OK rustfmt
每种语言的下层管线:
| 层级 | 插件 |
|---|---|
| Package manager | williamboman/mason.nvim + WhoIsSethDaniel/mason-tool-installer.nvim |
| Tree-sitter | core.treesitter + 原生 vim.treesitter |
| LSP | vim.lsp.config + vim.lsp.enable(原生,不走 nvim-lspconfig) |
| 补全 | saghen/blink.cmp(Rust 写的 fuzzy matcher) |
| Snippets | L3MON4D3/LuaSnip + friendly-snippets |
| Formatter | stevearc/conform.nvim |
| Linter | mfussenegger/nvim-lint |
| 诊断 | vim.diagnostic(原生)+ rachartier/tiny-inline-diagnostic.nvim |
| 诊断列表 | folke/trouble.nvim |
| DAP | mfussenegger/nvim-dap + theHamsta/nvim-dap-virtual-text |
语言专属插件。 通过每个 lang 文件的 plugins 字段注入到 lazy.nvim 的 spec 里:
| 语言 | 插件 |
|---|---|
| Rust | mrcjkb/rustaceanvim、Saecki/crates.nvim |
| Haskell | mrcjkb/haskell-tools.nvim |
| C / C++ | p00f/clangd_extensions.nvim |
| LaTeX | lervag/vimtex |
| Clojure / Lisp | Olical/conjure |
| Markdown | MeanderingProgrammer/render-markdown.nvim |
| CSV / TSV | cameron-wags/rainbow_csv.nvim |
| JSON / YAML | b0o/SchemaStore.nvim |
| Ghostty | bezhermoso/tree-sitter-ghostty |
| 组件 | 插件 | 用来做什么 |
|---|---|---|
| 边栏 hunk 提示 | lewis6991/gitsigns.nvim |
]h / [h 跳转、<leader>hs stage hunk、行 blame |
| Git 命令 | tpope/vim-fugitive |
:Git status、commit、push、pull、blame |
| Diff 浏览 | sindrets/diffview.nvim |
可视化 diff 和文件历史 |
| 浏览器打开 | Snacks.gitbrowse |
<leader>gB 打开 GitHub / Codeberg |
四个工具职责不重叠:fugitive 管命令,diffview 管看差异,gitsigns 管在 buffer 里操作 hunk,gitbrowse 管打开网页。
实际用起来,这一节里我最常打开的其实是 toggleterm。大部分 git 操作——stage、commit、rebase、push——都在 shell buffer 里完成。上面四个插件只用来补终端里做起来别扭的那几件事:inline blame、当前 buffer 里的 hunk 跳转、并排 diff,以及在浏览器里打开某次提交。
现在没有 AI 插件层了。lua/plugins/ai/ 是有意删掉的。
对我来说,Claude Code 已经变成了 a piece of bullshit。Anthropic 也不再是一家值得信任或尊重的公司:它现在的产品方向对使用者非常不友好,而 Claude Code 作为一个想进入开发环境执行 agent 工作的工具,已经带了太多不透明的遥测、检测和审查面。你可以把它叫作间谍工具,也可以把它叫作敌意工具;无论用哪个词,它都已经不适合作为这份配置里的 agent 层。
更大的趋势也已经变了。Agent 正在从旧的 "Cursor mode"——编辑器侧边栏、inline chat overlay、由 editor 接管的工作流——走向 "chatbox mode":Codex、opencode 这一类独立工具自己拥有 TUI/chat surface,把编辑器当作 context,而不是把编辑器变成整个交互界面。在这个趋势下,claudecode.nvim、avante.nvim 以及类似模仿 Cursor 模式的 Neovim 插件已经不再适合使用。
所以 AI 仍然是工作流的一部分,但它会留在 Neovim 外面。这份配置暂时不会再保留专门的 AI 插件分类。
- 新建
lua/langs/<name>.lua - 填上需要的字段(除
filetypes外都是可选的) - 重启 Neovim
聚合器在下次 require 时自动捡起新文件,所有下游插件看到新条目。
如果新语言声明了 tree-sitter manifest,重启后运行 :TSInstall <lang> 来拉取、编译并注册它的 parser/query runtime。
临时禁用一种语言而不删文件:在文件顶部加 enabled = false,重启。该语言从所有消费者——Tree-sitter、LSP、formatter、linter、DAP、snippets——中消失。
- 删掉
lua/langs/<name>.lua——或者只是把mason字段里不想要的那项去掉。 - 重启 Neovim。
- 跑
:checkhealth langs。langs.mason那一节会把所有还装着但已经不在声明里的 mason 包标出来。 - 如果被移除的语言有 tree-sitter parser,运行
:TSClean清掉陈旧的 parser/query checkout 和 runtime 条目。 - 用
:MasonUninstall <pkg>精确卸载,或:MasonToolsClean一次清掉所有孤儿包。:MasonToolsClean同时也会把当前被enabled = false的 lang 对应的包一起删掉,所以如果有禁用中的 lang,优先用 per-package 的:MasonUninstall。
init.lua -- lazy.nvim 自举,顶层 setup
lua/
├── core/
│ ├── options.lua
│ ├── language.lua -- 聚合器
│ ├── treesitter.lua -- 原生 parser/query installer 和 runtime
│ └── sysinfo.lua
├── keymaps/ -- 全部键位,按功能组织
│ ├── init.lua
│ ├── editing.lua
│ ├── winbuf.lua
│ ├── snacks.lua
│ ├── which-key.lua
│ ├── editor/
│ ├── git/
│ ├── ai/
│ └── langs/
├── plugins/
│ ├── snacks.lua -- 中心 Snacks 注册
│ ├── ui/
│ ├── editor/
│ ├── langs/ -- completion、format、lint、treesitter、dap、mason、snippets
│ ├── git/
│ └── ai/
└── langs/ -- 每种语言一个文件;声明的唯一来源
├── rust.lua
├── python.lua
├── haskell.lua
└── ...
感谢亲爱的 @Lingshinx 杰出的 ASCII art。
GPL-3.0-or-later。完整文本见 LICENSE。
每个源文件都带有 SPDX-License-Identifier 头。

