Neovim - 打造一款属于自己的编辑器(一)

发布于:2025-06-07 ⋅ 阅读:(25) ⋅ 点赞:(0)

前言(劝退)

neovim 是一个现代化,高扩展的 vim 编辑器 fork 版本,适合程序员打造极致高效的开发环境。

在正式开始 neovim 配置之前,我还是要劝退一下的。

很多人说使用 neovim 的都是变成高手,但我认为,能搞明白 neovim 一定是高手居多,但不一定都是顶尖的大牛,但起码技术一定不差,也正因如此,很多人使用使用 neovim 单纯是因为脑子一热,导致最后不仅耽误了手头的工作还啥都没学会。要学习neovim,建议先把 vim 的操作搞熟悉(可以全程使用 vim 的语法做出一个完整的项目,注意我说的只是语法),而 vim 学习的曲线前期有点难度,导致很多人也挂在了第一关,所以:

  • 如果你编程能力不足,那么请不要碰 vim / neovim 等任何需要大量自定义的编辑器。IDEA / VsCode 就够了。
  • 如果你对 "折腾" 这件事没有太大兴趣,那么请不要碰 vim / neovim 等任何需要大量自定义的编辑器。IDEA / VsCode 就够了。虽然也有现成的 LazyVim,但是我不建议这样做(原因后面会讲)。

neovim 安装

1)github 上搜索 neovim,在 Releases 中找到一个最近发的稳定版(https://github.com/neovim/neovim/releases),下载

安装一路 next 即可。

2)打开终端,输入 nvim test.txt 可以正常打开文本编辑器即表示安装成功

neovim 配置

配置文件位置

neovim 会在启动时加载 init.lua 文件,当我们修改了配置文件之后,重启 neovim 即可生效(虽然也可以通过 :so 实现,但是配置复杂了之后,就不好用了)

1)init.lua 文件的位置在哪?(以下路径和lua文件如果不存在,需要手动创建)

  • Windows:~/AppData/Local/nvim/init.lua(~ 表示 C:\Users\${你的用户名}\)
  • Unix:~/.config/nvim/init.lua

2)你也打开终端,输入 nvim 打开 neovim 编辑器,然后输入 :=vim.fn.stdpath("config") 来查看配置文件位置.

  • : 表示 vim 中的命令模式
  • = 表示执行 lua 代码并输出结果(当然,你也可以通过 :lua 执行代码,但是不会有任何输出结果)
  • vim.fn.stdpath("config") 表示答应配置文件的完整路径

输出结果如下:

第一个 hello world 代码

1)通过 nvim init.lua 编辑配置文件,如下:

2)保存退出后,输入 nvim 重新打开 neovim,可以看到左下角的显示,如下图,表示配置生效。

拆分 neovim 配置

随着我们的配置不断增多,全写到一个 init.lua 文件中肯定是不利于维护的,因此我们将配置拆分成多个模块。

一个通用的做法如下:

1)在 init.lua 的同级目录下创建 lua 文件夹,然后再 lua 文件夹下创建 module.lua 文件,文件内容输入print("hello world")(为了验证后续正确引入)。

2)那么就可以在 init.lua 中使用 require("module") 引入 module.lua 文件。

在 neovim 中,打开文件的命令是:e <filename>,因此在通过 nvim 创建 module.lua 之后,直接可以通过 e: ./init.lua 打开 init.lua 文件

Ps:e 表示 edit,编辑的意思。

在 init.lua 中使用 `require(“module”),如下:

3)保存退出后,再打开 nvim,可以看到左下角 hello world 打印正常,表示上述操作步骤正确。

Ps:这里只是简单认识一下怎么拆分模块,后续的操作步骤中,会把上面演示的 module.lua 删掉(包括 init.lua 中 require 也删掉),开始正式配置 neovim。

正式配置 neovim

基础配置

1)这里我习惯将基础配置放到 ~/AppData/Local/nvim/lua/core/basic.lua 中。

同样,记得在 init.lua 中引入这个文件,如下:

2)nvim 打开 basic.lua,配置如下:

-- 显示行号
vim.opt.number = true
-- 相对行号
vim.opt.relativenumber = true

-- 高亮当前光标所在行
vim.opt.cursorline = true

-- 将 tab 水平制表符转化成空格
vim.opt.expandtab = true
-- tab 长度设置成4个空格
vim.opt.tabstop = 4
-- shiftwidth 设置成0,可以避免换行后回车带来的 tab 长度不一致问题
vim.opt.shiftwidth = 0

-- 当 neovim 中打开的文件被其他程序修改了(例如idea),neovim 会自动加载
vim.opt.autoread = true

Ps:win 终端通过 cat 读取 lua 文件会中文乱码,可以通过 Get-Content .\lua\core\basic.lua -Encoding utf8 来查看.

自定义键位

1)这里先说一下,neovim 向下是兼容通过 vimscript 配置自定义键位的,但是我们既然使用 neovim,还是建议使用 lua 进行统一化配置。

2)自定义键位的 api:vim.keymap.set(mode, lhs, rhs, opts)

  • mode:快捷键生效的模式.
    • 不同模式用一个字母表示,例如 n 表示普通模式、i 表示插入模式、c 表示命令模式…
    • 可以是 字符(单一模式生效,例如n表示在普通模式下生效),或者是 table(多模式生效,例如{'n', 'i'}表示在普通模式和插入模式下都生效)
  • lhs:表示要改哪个键位.
    • 如果是普通键位,就直接写了.
    • 但如果要改 Ctrl + a,就要写成 <C-a>
    • 但如果要改 Alt + a,就要写成 <A-a>
  • rhs:表示要改成什么样子,可以映射另一组按键,或者一个 lua 函数
  • opts:是一个 table,包含了对快捷键一些额外的配置

3)案例一

vim.keymap.set("n", "<C-a>", ":lua print('hello world')<CR>", { silent = true })
  • 在 normal 模式下生效.
  • 按下 Ctrl + a 后,会打印 hello world,其中 <CR> 表示回车
  • silent = true 表示让快捷键 “静默执行”,不显示执行过程中的命令或提示信息

演示如下:

然后按下 Ctrl + a ,效果如下:

4)案例二:如果想让快捷键在 插入模式 下也生效.

错误的写法如下(有坑):

vim.keymap.set({"n", "i"}, "<C-a>", ":lua print('hello world')<CR>", { silent = true })

真的可以这样么?思考一下,因为我们在插入模式下,直接按下 <C-a>,他会替换成 ":lua print('hello world')<CR>",相当于直接把这段话直接写下来了(模拟键盘输入:lua print('hello world')<CR> 的场景),如下:

正确的写法如下:

vim.keymap.set({"n", "i"}, "<C-a>", "<Cmd>lua print('hello world')<CR>", { silent = true })
  • <Cmd> 并不是一个具体的按键,只表示进入命令模式。

此时你在 insert模式 按下 Ctrl + a 虽然是正确的,但是看不到效果,因为在默认 neovim 会在右下加固定显示你当前的模式,不过你也可以通过 :lua vim.opt.showmode = false 关闭他,然后在 insert 模式下按下 Ctrl + a 就可以看到效果了

5)rhs 也可以是一个函数,例如如下

vim.keymap.set("n", "<C-a>", function ()
    print("hello world")
end, { silent = true})

这里写的是 lua 中的匿名函数,使用 end 表示结尾

6 )leader key

可以理解成在配置中定义了一个叫做 <leader> 的变量,实现复用效果。

例如如下,设置 leader key 为 空格,当我在 normal 模式下按下 空格 + a + a,就会打印 cyk 字符串。

vim.g.mapleader = " "
vim.keymap.set("n", "<leader>aa", ":lua print('cyk')<CR>", { silent = true})

7)正式配置,创建 ~/AppData/Local/nvim/lua/core/keymap.lua 文件,如下(这里是我自己习惯的):

vim.keymap.set("i", "jk", "<esc>", { silent = true })

vim.keymap.set("n", "L", "$", {silent = true})
vim.keymap.set("v", "L", "$", {silent = true})

vim.keymap.set("n", "H", "^", {silent = true})
vim.keymap.set("v", "H", "^", {silent = true})

最后在 init.lua 中引入,如下:

require("core.keymap")

Lazy 插件管理器配置

1)neovim 的插件一般都需要从 github 上手动 clone 并在 neovim 中引用。但是随着插件的逐渐增多,这种方式会变得非常繁琐,这就需要使用插件管理器来自动化这个过程。我使用 lazy 插件管理器进行配置。

lazy.nvim 是 Neovim 的一款现代插件管理器,主打的是"懒加载"理念:只有当我使用某个插件时才加载他,从而极大的提升启动性能。

lazy.nvim 会读取 ~/AppData/Local/nvim/plugins/ 目录下的所有 lua 插件配置文件。常见的写法如下:

return {
  "插件名",        -- 如 "nvim-treesitter/nvim-treesitter"
  name = "别名",   -- 可选,为插件指定别名
  event = "VeryLazy", -- 懒加载触发时机,支持多种事件
  cmd = "命令名",   -- 使用命令时加载
  ft = "lua",       -- 打开某种文件类型时加载
  keys = {...},     -- 使用某些快捷键时加载
  opts = {...},     -- 插件的配置(直接传入插件的 `setup` 或 `opts`)
  config = function() ... end, -- 更复杂的配置
  dependencies = {...}, -- 插件依赖
}

每项具体如何编写,会在后续的使用中讲到。

2)首先需要安装 git 和 nerd font。git 就不多说了,这里主要讲一下 nerd font(https://www.nerdfonts.com/font-downloads

Ps:如果不安装字体,将来很多符号无法显示。

这里建议使用 JetBrainsMono Nerd Font

下载后解压到你常用的目录即可。

3)在 neovim 配置中自动安装和加载插件管理器逻辑。

创建 ~/AppData/Local/nvim/lua/core/lazy.lua 文件,内容如下:

-- 构造 lazy.nvim 的安装路径
-- vim.fn.stdpath("data") 表示返回 neovim 的数据目录,例如 win 是 ~/AppData/Local/nvim-data;linux/mac 是 ~/.local/share/nvim
-- .. 是 lua 用来拼接字符串的,
-- win 最后拼接成 ~/AppData/Local/nvim-data/lazy/lazy.nvim
local lazypath = vim.fn.stdpath("data") .. "/lazy/lazy.nvim"

-- 如果 lazypath 不存在(lazy.nvim 未安装),就执行内部逻辑
if not vim.uv.fs_stat(lazypath) then
    -- 把 lazy.nvim 下载到 lazypath 目录
    vim.fn.system({
        "git",
        "clone",
        "--filter=blob:none",
        "https://github.com/folke/lazy.nvim.git",
        "--branch=stable",
        lazypath
    })
end

-- 把 lazy.nvim 加入到 neovim 的运行时路径(runtimepath)的最前面
-- 这样才能使用 require("lazy") 正确加载插件管理器
-- 此处的 : 表示 lua 中的方法调用(表示传入一个方法)
vim.opt.rtp:prepend(lazypath)

-- 加载 lazy.nvim 插件管理器,并调用 setup 函数
-- 传入 {} 空表,表示还没有配置任何插件,后续可以加
require("lazy").setup({})

保存后,记得在 init.lua 中引入,如下:

require("core.lazy")

保存退出后重新打开 neovim,你会发现卡住了,这是因为正在执行刚刚写的命令(neovim 是单线程的,会阻塞主线程)。

4)打开 neovim,然后输入 “:Lazy”,就可以看到一个面板如下:

例如按下 Shift + p 就可以看到插件的一些启动时的性能情况

面板上其他选项的介绍:

  • Install:用来安装插件的,相当于运行 :Lazy install ,只安装在 lazy.nvim 中声明的,并且还没有下载的插件。不会更新已有的插件
  • Update:更新插件,相当于运行 :Lazy Update,他会执行 git pull 来拉取所有插件的最新版本
  • Sync:同步插件状态(安装缺失的插件 + 更新已安装的插件 + 清理不在配置中的插件 + 构建插件),相当于执行 :Lazy sync,通常用来保证插件和配置完全一致
  • Clean:清理插件,相当于执行 :Lazy clean,用来删除配置中不再引用的插件
  • Check:检查插件状态,相当于执行 :Lazy check,用来删除配置中不再引用的插件
  • Log:查看插件管理器中日志的输出,主要调试用的
  • Restore:恢复快照,从 snapshot 文件中恢复插件状态,回滚版本(需要提前使用 Lazy snapshot 创建快照)
  • Profile:性能分析,分析各个插件加载时的性能情况
  • Debug:调试 lazy.nvim 插件管理器的自身行为,会打开一个窗口输出插件加载时的详细信息(哪些插件怎么加载的、懒加载生效情况、报错信息、路径、加载顺序等),适合在插件不工作,配置失效、性能分析时使用

tokyonight 插件配置

1)如下配置,会让 neovim 自动读取 lua/plugins 目录下的文件,用来对插件进行配置。

require("lazy").setup({
    spec = {
        { import = "plugins" }
    }
})

然后创建 ~/AppData/Local/nvim/lua/plugins 文件夹,之后在这个此目录下,每配置一个插件就是一个 lua 文件(这种写法看起来直观)。

此时使用 nvim 可能会报错,不过不用管,这是因为 plugins 目录下还没有任何插件。

2)安装 tokyonight 主题

创建 ~/AppData/Local/nvim/lua/plugins/tokyonight.lua 文件,内容如下(从 tokyonight 的 github 上就可以看到怎么配置):

return {
    -- 这里写主题的 github 链接
    -- 但是由于插件基本都来自github,因此 lazy 会自动补全前缀 "https://github.com/",因此插件的前缀就不用写了
    "folke/tokyonight.nvim"
}

保存退出后重新打开,就可以看到 lazy 就直接开始帮我们安装了,然后输入 :colorscheme tokyonight ,回车后就可以看到效果了

3)如果我们再重新打开 neovim 的时候就会发现,还需要继续手动让主题生效,为了解决这个问题,可以重写 tokyonight 的 config 项,如下:

return {
    -- 这里写主题的 github 链接
    -- 但是由于插件基本都来自github,因此 lazy 会自动补全前缀 "https://github.com/",因此插件的前缀就不用写了
    "folke/tokyonight.nvim",
    -- 可以通过此配置项更换成 tokyonight 的浅色主题
    opts = {
        style = "day"
    },
    -- 下面的 config 本来是 lazy 默认配置好的,但是为了能在插件启动时自定义一些东西(比如启动时就加载好主题),可以重写此配置
    -- 这个方法的第一个参数几乎用不上,opts 就是上一个配置项
    config = function (_, opts)
        require("tokyonight").setup(opts) -- 这一步是 lazy 给我们自动配置好的,这里再写一遍主要是因为我们自己手动重写了此函数
        vim.cmd("colorscheme tokyonight") -- 执行这个命令
    end
}

Ps:大多数情况,是不用手动编写 config 的,除非在 lazy 默认加载之外,还有些别的操作。

4)这里你可能会有一个疑问,为什么一定要把加载主题的配置写到 config 中,而不写外部去加载?

因为切换主题这个命令,必须要在插件加载完成之后才能执行。

例如如下,我这样写,就是会报 “找不到主题的错误”。

原因:lazy.nvim 是异步懒加载插件的(读取所有的插件完之后,再统一加载),而 vim.cmd("colorscheme tokyonight") 这行代码在 tokyonight 插件真正被加载之前,就被执行了,所以就会报错 E185: Cannot find color scheme 'tokyonight'

BufferLine 插件配置

默认情况下,所有的 buffer 都是隐藏的,通过使用 bufferline 插件,可以使所有的 buffer 以类似浏览器标签页的方式呈现在页面的最上方。

1)首先创建 ~/AppData/Local/nvim/lua/plugins/bufferline.lua 文件,内容如下:

return {
    "akinsho/bufferline.nvim",
    -- 此插件必须手动指定一下 opts 才会进行 setup
    opts = {}
}

2)使用 neovim 随便打开一个文件,例如 nvim aaa 打开文件 aaa,就可以看到效果如下:

再使用 e bbb 编辑一个新文件 bbb,都是可以看到 tab 的

3)可以通过 :BufferLineCyclePrev 前往上一个 tab,使用 :BufferLineCyclePrev 前往下一个 tab… 但是这样太麻烦了

但是我们使用的使 neovim 啊,用的就是的自定义,当然这里还有一种更强的自定方式,就是直接在插件上配置 keys 来自定义键位,例如如下:(我这样配置主要是为了减少和 IDEA 相关操作迁移的成本)

return {
    "akinsho/bufferline.nvim",
    -- 此插件必须手动指定一下 opts 才会进行 setup
    opts = {},
    keys = {
        { "<leader>bn", ":BufferLineCycleNext<CR>", silent = true },
        { "<leader>bo", ":BufferLineCloseOthers<CR>", silent = true },
        { "<leader>bp", ":BufferLineCyclePrev<CR>", silent = true },
        { "<leader>bd", ":bdelete<CR>", silent = true },
        { "<leader>bh", ":BufferLinePick<CR>", silent = true },
    },
}

Ps:keys 中配置的键位默认就是在 normal 模式中生效,不用手动指定 mode

4)此处重新打开 nvim 会发现 tab 不见了?这里涉及到一个高级特性就是 懒加载,实际上我们配置了 keys 他的实际含义是开启懒加载,只有在按下自定义 keys 中的键位时开始加载

为什么要会这样设计呢?因为 neovim 在启动时会加载所有插件,一旦插件变多了,neovim 的打开速度会越来越慢,因此就设计成在 neovim 启动后进入主页用不上的插件就先不加载,只有用到这个插件的时候再加载。而 keys 就是启动懒加载开关的其中之一(还有的后续会讲到)

当然对于 bufferline 显示 tab 这种在启动后就会用到的,我们还是希望他不要懒加载,因此可以手动设置 lazy 属性为 false,如下:

return {
    "akinsho/bufferline.nvim",
    -- 此插件必须手动指定一下 opts 才会进行 setup
    opts = {},
    keys = {
        { "<leader>bn", ":BufferLineCycleNext<CR>", silent = true },
        { "<leader>bo", ":BufferLineCloseOthers<CR>", silent = true },
        { "<leader>bp", ":BufferLineCyclePrev<CR>", silent = true },
        { "<leader>bd", ":bdelete<CR>", silent = true },
        { "<leader>bh", ":BufferLinePick<CR>", silent = true },
    },
    lazy = false
}

5)目前的 bufferline 配置出来还是有点丑的,因为 icon 图标,因此这里还需要一个插件给 tab 显示小图标。

但是由于这个插件只是辅助于 bufferline 的,因此没有必要单独在 plugins 下创建一个 lua 文件,可以通过 lazy 提供的 dependencies 属性实现,如下:

return {
    "akinsho/bufferline.nvim",
    dependencies = {
        "nvim-tree/nvim-web-devicons"
    },
    -- 此插件必须手动指定一下 opts 才会进行 setup
    opts = {},
    keys = {
        { "<leader>bn", ":BufferLineCycleNext<CR>", silent = true },
        { "<leader>bo", ":BufferLineCloseOthers<CR>", silent = true },
        { "<leader>bp", ":BufferLineCyclePrev<CR>", silent = true },
        { "<leader>bd", ":bdelete<CR>", silent = true },
        { "<leader>bh", ":BufferLinePick<CR>", silent = true },
    },
    lazy = false
}

保存后重新进入,效果如下:

自动补全括号 / 引号 插件配置

这个见名思意,输入 {,自动补全 },其他的也同理。

如果删除 { 也会自动把配对的 } 删除,其他也同理。

这时候我们可以思考一个关于优化的问题,这个插件在 neovim 启动的时候真的需要加载么么?并不需要,因为他不涉及 ui 的渲染,并且 normal 模式下也不会用到,因此我们希望他进入 insert 模式下才需要使用到,此时就可以使用 event 属性进行懒加载,如下:

return {
    "windwp/nvim-autopairs",
    event = "InsertEnter", -- 仅在 insert 模式才加载此插件
    opts = {}
}

1)那么重新进入 neovim,并输入 :Lazy 可以看到 nvim-autopairs 并没有被加载。

2)当进入 insert 的模式之后,再查看就加载了,说明懒加载配置成功。

Ps:有的插件可以懒加载,但是是在 normal 模式下就使用的,然而已进入 neovim 就是 normal 模式,类似这种你不清楚什么时候进行懒加载的情况,就可以配置成 event = VeryLazy,这也是 lazy 插件管理器提供的一种方式。