从零开始配置 Neovim(Nvim)
更新:
- 2025-09-06,去掉
nvim-lspconfig
插件,因为我更倾向于直接用自带的 LSP 配置 - 2025-05-14,Neovim 升级到 v0.11 了,对 LSP 的配置进行了简化
- 2025-03-22,将
nvim-cmp
替换为blink.cmp
,配置更简单了 - 2024-04-04,重写部分章节,Package Manager 从 packer.nvim 换成了 lazy.nvim 🤗。
- 如果之前跟着本篇教程采用
packer.nvim
进行安装的话,可以参考我迁移的时候的 commit,修改完之后记得用:checkhealth lazy
检查下,因为还需要删除packer.nvim
的一些旧文件
- 如果之前跟着本篇教程采用
进一步阅读:
- 如何为一门新的编程语言配置 Neovim ,以及支持第三方代码格式化工具,见下篇文章
版本信息
我使用的是 MacBook Air M3,系统版本为 macOS 15.5。我的 nvim
版本信息如下
NVIM v0.11.3
Build type: Release
LuaJIT 2.1.1741730670
system vimrc file: "$VIM/sysinit.vim"
fall-back for $VIM: "
/nix/store/v1m43ib6xwg3nj29n5h42j17cazd2fr4-neovim-unwrapped-0.11.3/share/nvim
"
Run :checkhealth for more info
为什么选择 Neovim
在使用 Vim
一年多之后,我越发觉得 Vim
的配置麻烦,启动加载速度也不尽人意。Vimscript 的写法我也不喜欢,所以我决定开始使用 Neovim(nvim)
。相比于迁移 Vim
的配置,我决定重新配置 nvim
。为什么会想要重新配置而不是迁移配置呢?因为我想顺便趁着这个机会,重新审视我本来 Vim
的配置,以及将本来用到的的插件替换为现在的 Best Practice。我自从看完 MIT 的 Missing semester 的课配置了 ~/.vimrc
之后就很长时间都没有再编辑 ~/.vimrc
文件了
我认为在配置 nvim
的时候弄清楚每一个选项的意思是很有必要的,因此我在这篇博客中会尽量解释清楚每个选项的含义,同时将解释放在注释里面,即我尽量让我自己的配置文件是 self-contained 而且可读性强的
当然,这难免有疏漏。别忘了我们永远可以在 nvim
里面输入 :h <name>
来看到更为详细的解释
💡 该篇博客假定你对 Vim
有基本了解
nvim 配置基础知识
Lua 语言
在配置 nvim
的时候,我会尽可能用 Lua 语言写配置,因此你有必要了解一下 Lua 的基本语法和语义。可以快速浏览一下 Learn Lua in Y minutes 了解大概
配置文件路径
在 Linux/Mac 系统上,nvim
的配置目录在 ~/.config/nvim
下,这个路径也叫做 runtimepath
,nvim
会默认读取 ~/.config/nvim/init.lua
文件,理论上来说可以将所有配置的东西都放在这个文件里面,但这样不是一个好的做法,因此我划分不同的文件和目录来分管不同的配置
首先看下按照本篇教程配置 nvim
之后,目录结构看起来会是怎么样⬇️
.
├── init.lua
├── lazy-lock.json
├── lsp
│ └── ty.lua
└── lua
├── colorscheme.lua
├── keymaps.lua
├── lsp.lua
├── options.lua
└── plugins.lua
解释如下
init.lua
为nvim
配置的 Entry point,我们主要用来导入其他*.lua
文件colorscheme.lua
配置主题keymaps.lua
配置按键映射lsp.lua
配置 LSPoptions.lua
配置选项plugins.lua
配置插件
lazy-lock.json
:使用 lazy.nvim 的时候自动生成的一个 Lock File,你不应该直接修改这个文件lsp
目录,里面的每一个文件都对应一个 LSP,后面会具体介绍如何进行设置。在这个例子里,我只设置了一个 Python 的 LSP 叫作 tylua
目录,当我们在 Lua 里面调用require
加载模块(文件)的时候,它会自动在lua
文件夹里面进行搜索- 将路径分隔符从
/
替换为.
,然后去掉.lua
后缀就得到了require
的参数格式
- 将路径分隔符从
选项配置
主要用到的就是 vim.g
、vim.opt
、vim.cmd
等,我制造了一个快速参照对比的表格
In Vim |
In nvim |
Note |
---|---|---|
let g:foo = bar |
vim.g.foo = bar |
|
set foo = bar |
vim.opt.foo = bar |
set foo = vim.opt.foo = true |
some_vimscript |
vim.cmd(some_vimscript) |
按键配置
在 nvim
里面进行按键绑定的语法如下,具体的解释可以看 :h vim.keymap.set
vim.keymap.set(<mode>, <key>, <action>, <opts>)
从零开始配置 nvim
在阅读了前面一些配置基础之后,现在我们可以从头开始,由简到易一步步配置 nvim
了
安装 nvim
我用的是 Mac,用 Homebrew 安装 nvim
非常容易,只要运行如下命令即可1
$ brew install neovim
在安装完成之后,如果 ~/.config/nvim
目录不存在,创建目录并新建 init.lua
文件
$ mkdir ~/.config/nvim
$ mkdir ~/.config/nvim/lsp
$ mkdir ~/.config/nvim/lua
$ touch ~/.config/nvim/init.lua
配置文件编辑保存之后,重启 nvim
就能看到效果,后面默认每次小章节配置完成后就重启
选项配置
选项配置功能一览:
- 默认采用系统剪贴板,同时支持鼠标操控
nvim
- Tab 和空格的换算
- UI 界面
- “智能”搜索
新建 ~/.config/nvim/lua/options.lua
文件并加入如下内容⬇️
-- Hint: use `:h <option>` to figure out the meaning if needed
vim.opt.clipboard = 'unnamedplus' -- use system clipboard
vim.opt.completeopt = { 'menu', 'menuone', 'noselect' }
vim.opt.mouse = 'a' -- allow the mouse to be used in nvim
-- Tab
vim.opt.tabstop = 4 -- number of visual spaces per TAB
vim.opt.softtabstop = 4 -- number of spaces in tab when editing
vim.opt.shiftwidth = 4 -- insert 4 spaces on a tab
vim.opt.expandtab = true -- tabs are spaces, mainly because of Python
-- UI config
vim.opt.number = true -- show absolute number
vim.opt.relativenumber = true -- add numbers to each line on the left side
vim.opt.cursorline = true -- highlight cursor line underneath the cursor horizontally
vim.opt.splitbelow = true -- open new vertical split bottom
vim.opt.splitright = true -- open new horizontal splits right
-- vim.opt.termguicolors = true -- enable 24-bit RGB color in the TUI
vim.opt.showmode = false -- we are experienced, wo don't need the "-- INSERT --" mode hint
-- Searching
vim.opt.incsearch = true -- search as characters are entered
vim.opt.hlsearch = false -- do not highlight matches
vim.opt.ignorecase = true -- ignore case in searches by default
vim.opt.smartcase = true -- but make it case sensitive if an uppercase is entered
然后打开 init.lua
,用 require
导入刚才写的 options.lua
文件
require('options')
按键配置
按键功能一览:
- 用
<C-h/j/k/l>
快速在多窗口之间移动光标 - 用
Ctrl
+ 方向键进行窗口大小的调整 - 选择模式(Visual Mode)下可以一直用
Tab
或者Shift-Tab
改变缩进
新建 ~/.config/nvim/lua/keymaps.lua
文件并放入如下内容⬇️
-- define common options
local opts = {
noremap = true, -- non-recursive
silent = true, -- do not show message
}
-----------------
-- Normal mode --
-----------------
-- Hint: see `:h vim.map.set()`
-- Better window navigation
vim.keymap.set('n', '<C-h>', '<C-w>h', opts)
vim.keymap.set('n', '<C-j>', '<C-w>j', opts)
vim.keymap.set('n', '<C-k>', '<C-w>k', opts)
vim.keymap.set('n', '<C-l>', '<C-w>l', opts)
-- Resize with arrows
-- delta: 2 lines
vim.keymap.set('n', '<C-Up>', ':resize -2<CR>', opts)
vim.keymap.set('n', '<C-Down>', ':resize +2<CR>', opts)
vim.keymap.set('n', '<C-Left>', ':vertical resize -2<CR>', opts)
vim.keymap.set('n', '<C-Right>', ':vertical resize +2<CR>', opts)
-----------------
-- Visual mode --
-----------------
-- Hint: start visual mode with the same area as the previous area and the same mode
vim.keymap.set('v', '<', '<gv', opts)
vim.keymap.set('v', '>', '>gv', opts)
然后在 init.lua
文件里面再次加上一行导入这个文件
... -- 省略其他行
require('keymaps')
...
表示省略了其他部分的代码(为了节省博客的篇幅)
安装插件管理器
一个强大的 nvim
离不开插件的支持。我选用的是当下最为流行 lazy.nvim。它支持如下许多特性:
- 正确处理不同插件之间的依赖
- 支持定制 Lazy loading,比如基于 Event、Filetype 等
- …
新建 ~/.config/nvim/lua/plugins.lua
文件并放入如下内容。下面的模板只完成了 lazy.nvim
自身的安装,还没有指定其他第三方插件
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"
if not (vim.uv or vim.loop).fs_stat(lazypath) then
vim.fn.system({
"git",
"clone",
"--filter=blob:none",
"https://github.com/folke/lazy.nvim.git",
"--branch=stable", -- latest stable release
lazypath,
})
end
vim.opt.rtp:prepend(lazypath)
require("lazy").setup({})
在 lazy.nvim
指定第三方插件很简单,只需要在 require("lazy").setup({ ... })
的 ...
里面声明插件
然后在 init.lua
文件里面再次加上一行导入这个文件
... -- 省略其他行
require('plugins')
此时你重启 nvim
会发现黑屏没显示,这是因为 lazy.nvim
在安装自己,静待片刻即可☕️。等待 Dashboard 出现之后,可以输入 :Lazy
试试,如果看到了弹出了 lazy.nvim
的窗口,那就安装成功了🎉
Tip:用 :q
退出 lazy.nvim
的窗口
主题配置
我喜欢的主题是 monokai.nvim,在 plugins.lua
进行修改
... -- 省略其他行
require("lazy").setup({
"tanvirtin/monokai.nvim",
})
保存更改并重启就可以看到 lazy.nvim
在帮我们安装插件了,新建并编辑 ~/.config/nvim/lua/colorscheme.lua
文件
-- define your colorscheme here
local colorscheme = 'monokai_pro'
local is_ok, _ = pcall(vim.cmd, "colorscheme " .. colorscheme)
if not is_ok then
vim.notify('colorscheme ' .. colorscheme .. ' not found!')
return
end
这里用到的 pcall
是 Lua 里面的 protected call,它会返回一个 bool
变量表示是否执行成功(跟 Go 语言的 err
功能类似)。这里采用 pcall
而不是直接在 init.lua
文件里面加上 vim.cmd('colorscheme monokai_pro')
是为了避免主题没有安装的情况下打开 nvim
看到一大堆报错信息2
最后在 init.lua
文件里面导入就行
... -- 省略其他行
require('colorscheme')
自动补全插件
blink.cmp 还在 beta 版本,这意味着变动会比较大,而且可能会遇到不少 Bug。但我目前日常使用下来没有问题 :)
之前本文的自动补全插件采用的是 nvim-cmp,但配置上较为繁琐。现在有了 blink.cmp 插件,配置会比较简单而且自动补全特别快
在 plugins.lua
里新增这个插件并做好配置
... -- 省略其他行
require("lazy").setup({
... -- 省略其他行
{
"saghen/blink.cmp",
-- optional: provides snippets for the snippet source
dependencies = { "rafamadriz/friendly-snippets" },
-- Use a release tag to download pre-built binaries
version = "*",
-- AND/OR build from source, requires nightly: https://rust-lang.github.io/rustup/concepts/channels.html#working-with-nightly-rust
-- build = 'cargo build --release',
-- If you use Nix, you can build from source using the latest nightly rust with:
-- build = 'nix run .#build-plugin',
opts = {
-- 'default' (recommended) for mappings similar to built-in completions (C-y to accept)
-- 'super-tab' for mappings similar to VSCode (tab to accept)
-- 'enter' for enter to accept
-- 'none' for no mappings
--
-- All presets have the following mappings:
-- C-space: Open menu or open docs if already open
-- C-n/C-p or Up/Down: Select next/previous item
-- C-e: Hide menu
-- C-k: Toggle signature help (if signature.enabled = true)
--
-- See :h blink-cmp-config-keymap for defining your own keymap
keymap = {
-- Each keymap may be a list of commands and/or functions
preset = "enter",
-- Select completions
["<Up>"] = { "select_prev", "fallback" },
["<Down>"] = { "select_next", "fallback" },
["<Tab>"] = { "select_next", "fallback" },
["<S-Tab>"] = { "select_prev", "fallback" },
-- Scroll documentation
["<C-b>"] = { "scroll_documentation_up", "fallback" },
["<C-f>"] = { "scroll_documentation_down", "fallback" },
-- Show/hide signature
["<C-k>"] = { "show_signature", "hide_signature", "fallback" },
},
appearance = {
-- 'mono' (default) for 'Nerd Font Mono' or 'normal' for 'Nerd Font'
-- Adjusts spacing to ensure icons are aligned
nerd_font_variant = "mono",
},
sources = {
-- `lsp`, `buffer`, `snippets`, `path`, and `omni` are built-in
-- so you don't need to define them in `sources.providers`
default = { "lsp", "path", "snippets", "buffer" },
-- Sources are configured via the sources.providers table
},
-- (Default) Rust fuzzy matcher for typo resistance and significantly better performance
-- You may use a lua implementation instead by using `implementation = "lua"` or fallback to the lua implementation,
-- when the Rust fuzzy matcher is not available, by using `implementation = "prefer_rust"`
--
-- See the fuzzy documentation for more information
fuzzy = { implementation = "prefer_rust_with_warning" },
completion = {
-- The keyword should only match against the text before
keyword = { range = "prefix" },
menu = {
-- Use treesitter to highlight the label text for the given list of sources
draw = {
treesitter = { "lsp" },
},
},
-- Show completions after typing a trigger character, defined by the source
trigger = { show_on_trigger_character = true },
documentation = {
-- Show documentation automatically
auto_show = true,
},
},
-- Signature help when tying
signature = { enabled = true },
},
opts_extend = { "sources.default" },
}
})
opts = { ... }
用于对插件进行具体的配置,{ ... }
里的配置内容会被自动发送给对应的插件。关键的几个解释如下
keymap
- 用于配置按键映射,格式也很好理解preset = "enter"
表示用回车键
确定当前选中的补全项select_prev, select_next
用于在各个候选项中进行选择,我这里配置了 2 套按键,支持用⬆️/⬇️,或者用 Tab/Shift-Tab 进行补全项的选择scroll_documentation_up, scroll_documentation_down
用于滚动 API 的文档,我配置的是Ctrl-b, Ctrl-f
trigger = { show_on_trigger_character = true }
- 输入字符之后就会展示所有可用补全项documentation = { auto_show = true }
- 自动显示当前被选中补全项的文档
🎙️ 到这为止,重新启动 nvim
后,等待插件安装完成后应该就能够用初步的自动补全功能了~
LSP 配置
配置完 blink.cmp
之后,已经有基本的自动补全功能,但和 IDE 相比,我们还需要定义跳转、代码补全等功能,因此需要配置 LSP3
事实上不用 mason.nvim 也可以,因为你完全可以使用你操作系统的包管理器下载 LSP,但我发现 mason.nvim
用起来确实方便不少
从 Neovim v0.11 开始引入了 vim.lsp.config
和 vim.lsp.enable
这 2 个 API 对 LSP 进行配置,这下再也不用安装 nvim-lspconfig 了。但我们可能还是需要一个 LSP Registry 用来方便地下载 LSP,比如 mason.nvim
首先修改 plugins.lua
文件,增加这个插件
... -- 省略其他行
require("lazy").setup({
... -- 省略其他行
-- LSP manager
{ "mason-org/mason.nvim", opts = {} },
})
mason.nvim
采用默认配置即可,所以用的是 opts = {}
可以用 vim.lsp.config.<lsp> = { ... }
配置,但你也可以选择在 runtimepath
(Linux/macOS 下是 ~/.config/nvim
)下创建 lsp
文件夹,在每个 LSP 的配置文件里面直接 return { ... }
,后面我会采用这种写法
接下来我们来看如何配置一个 LSP,其实很简单,只需要 3 个步骤
- 用
mason.nvim
安装 LSP,只需要打开 Neovim 输入:Mason
,找到你要的 LSP 并将光标停留在上面,随后按下i
即可 - 使用
vim.lsp.config.<lsp> = { ... }
进行配置,你需要提供如下几个配置项的信息cmd
- 如何启动 LSP?filetypes
- 这个 LSP 处理哪些类型的文件?root_markers
- 被编辑文件的 root 目录(或者说项目根目录)在哪里?同一个项目根目录会链接到同一个 LSP 上settings
(可选项)- 每个 LSP 提供了不同的定制项
- 使用
vim.lsp.enable({ ... })
启动 LSP
不知道 LSP 的配置怎么写?你可以参考 nvim-lspconfig
是怎么写的:https://github.com/neovim/nvim-lspconfig/tree/master/lsp
以 Python 的 LSP 配置为例,它有很多的 LSP 可供选择,我这里选的是 ty(uv
背后的团队开发的基于 Rust 的 LSP)。在安装完 ty
之后,在 lsp
目录下新建 ty.lua
,内容如下
-- src: https://github.com/neovim/nvim-lspconfig/blob/master/lsp/ty.lua
---@type vim.lsp.Config
return {
cmd = { 'ty', 'server' },
filetypes = { 'python' },
root_markers = { 'ty.toml', 'pyproject.toml', '.git' },
}
让我们新建 ~/.config/nvim/lua/lsp.lua
文件,用于设置 LSP 的按键映射,并启动刚才设置的 LSP
... -- 省略其他行
-- Remove Global Default Key mapping
vim.keymap.del("n", "grn")
vim.keymap.del("n", "gra")
vim.keymap.del("n", "grr")
vim.keymap.del("n", "gri")
vim.keymap.del("n", "gO")
-- Create keymapping
-- LspAttach: After an LSP Client performs "initialize" and attaches to a buffer.
vim.api.nvim_create_autocmd("LspAttach", {
callback = function (args)
local keymap = vim.keymap
local lsp = vim.lsp
local bufopts = { noremap = true, silent = true }
keymap.set("n", "gr", lsp.buf.references, bufopts)
keymap.set("n", "gd", lsp.buf.definition, bufopts)
keymap.set("n", "<space>rn", lsp.buf.rename, bufopts)
keymap.set("n", "K", lsp.buf.hover, bufopts)
keymap.set("n", "<space>f", function()
vim.lsp.buf.format({ async = true })
end, bufopts)
end
})
vim.lsp.enable({ "ty" })
这里的配置首先用 vim.keymap.del
删除了自带的一些按键映射,然后用 vim.keymap.set
配置具体的按键映射,比如在 Normal Mode 下按 gd
就可以跳转到定义 Symbo 的地方。如果你想要增加更多的按键映射,只需要添加新的行即可
最后,记得在 init.lua
文件里面加上如下内容,这样 lsp.lua
的配置才能被加载
... -- 省略其他行
require('lsp')
重启 nvim
之后,你可以打开随意一个 .py
文件并开始编辑,你应该可以看到代码补全等功能都可以正常使用了。如果你不大确定,可以用 :checkhealth vim.lsp
命令进行检查,它应该包含类似的内容
vim.lsp: Active Clients ~
- ty (id: 1)
- Version: 0.0.1-alpha.20 (f41f00af1 2025-09-03)
- Root directory: ...
- Command: { "ty", "server" }
- Settings: {}
- Attached buffers: 1, 2
总结
这样配置下来,我们成功把 nvim
变成了一个轻量级的 IDE,它支持代码高亮、代码补全、语法检查等功能,而且是完全开源免费的,虽然还有些简陋,但已经是可以用的了 🤗
我发现自从学了 Vim 之后,我总在其他各种代码编辑器、IDE 看是不是支持 Vim。大多数情况下,它们对 Vim 的支持都不是很让人满意,还容易有快捷键冲突等问题。因此我选择将 nvim
变成 IDE,并将配置文件托管在我的 Martinlwx/dotfiles 上。这样在新的机器上只要安装好 nvim
并克隆配置,静待片刻之后就可以在不同的机器上获得一样的编程体验
打磨定制化工具是需要付出一定精力的。为了理解每个选项都在干啥,我不得不查找各种资料。但我仍相信这是值得的,理解你的工具利于你做扩展和定制化。本文已经尽可能采用了比较简单的配置,还有很多美化、私人定制化的内容可以配置,更别提其他很多优秀的第三方插件都还没有提及,这些就留给读者自己去探索发现了