neovim/vim使用者需要经常维护配置文件与插件库。由于插件API变更的频繁性,自己维护插件的配置需要付出一定精力,且很容易将配置目录弄得混乱。那么一个结构良好、层次分明、易于维护的配置框架成为我们的一大诉求。而nvchad正是解决此类麻烦的典型代表。
nvchad高度可定制化,在默认情形下提供了一个开箱即用的配置,默认配置我们无需变动,只需要保持和上游的同步就能体验最新最酷炫最高效的插件。而当我们需要自定义配置或者需要增删插件时,可以在custom目录下进行相应修改。nvchad按照功能对用户提供了如下配置接口:
    neovim选项设置。控制neovim的基本功能,如行号、缩进、高亮等。nvchad采用纯lua进行配置文件的编写,相比vimscript更具可读性。neovim是基于vim复刻的一个编辑器,在原有vim的基础上做了一些改进与调整。其中最大的特性是内置lua引擎,其配置可以直接使用lua进行开发,这一特性大大增强了neovim的功能,诞生了许多以lua进行开发的插件;同时neovim兼容vim的原有配置,既能享受强大的lua插件同时也能继续使用原有vim插件,是一个更值得推荐的编辑器。
lua是一种轻量级的脚本语言,语法特征和python有点类似,因为非常高效小巧,因此很适合将其嵌入到其他软件项目中。关于如何在neovim中使用lua,可以参考这篇文档。
neovim所有配置相关的文件位于目录$HOME/.config/nvim下1,安装好的nvchad的目录结构如下:
├── init.lua  // 配置起始文件
└── lua
    ├── core // 默认配置目录
    │   ├── bootstrap.lua      // 提供首次安装时需要执行的一些函数
    │   ├── default_config.lua // 默认配置的实际设置文件
    │   ├── init.lua           // core目录的配置起始文件
    │   ├── mappings.lua       // 默认快捷键映射
    │   └── utils.lua          // 封装几个功能函数,如load_config,lazy_load等
    ├── custom // 自定义用户配置目录
    │   ├── chadrc.lua         // 自定义配置的设置文件,用于覆盖core/default_config.lua
    │   ├── configs            // 自定义插件的配置文件,作用于cusom/plugins.lua
    │   │   ├── lspconfig.lua
    │   │   ├── vimtex.lua
    │   │   └── overrides.lua
    │   ├── highlights.lua
    │   ├── init.lua           // 自定义的lua代码片段,可覆盖或扩充core/init.lua
    │   ├── mappings.lua       // 自定义的映射,可覆盖core/mappings.lua
    │   ├── plugins.lua        // 自定义插件配置,可覆盖或扩充plugins/init.lua
    │   └── README.md
    └── plugins // 默认插件配置目录
        ├── configs
        │   ├── cmp.lua
        │   ├── lazy_nvim.lua
        │   ├── lspconfig.lua
        │   ├── mason.lua
        │   ├── nvimtree.lua
        │   ├── others.lua
        │   ├── telescope.lua
        │   └── treesitter.lua
        └── init.lua           // 所有默认插件的配置文件
init.lua是配置的起始文件,neovim在启动时会按照该文件来初始化配置。lua目录下则是一些具体的配置子目录,包括默认的核心配置目录core,自定义配置目录custom以及默认插件配置目录plugins。几乎所有默认配置均可由custom目录下对应的文件进行覆盖或者扩充。init.lua
根据neovim的说明文档,neovim会从~/.config/nvim/init.lua中加载用户配置,该文件是neovim读取配置的起始文件。下面是该文件的主体流程:
执行lua/core/init.lua来加载用户配置。
require "core"
判断是否有用户自定义的配置文件,如果有则执行lua/custom/init.lua的自定义lua代码片段。
local custom_init_path = vim.api.nvim_get_runtime_file("lua/custom/init.lua", false)[1]
      
if custom_init_path then
  dofile(custom_init_path)
end
执行lua/core/utils.lua中的load_mapping函数加载快捷键设置。
require("core.utils").load_mappings()
安装lazy.nvim以及所有插件。
local lazypath = vim.fn.stdpath "data" .. "/lazy/lazy.nvim"
      
-- bootstrap lazy.nvim!
if not vim.loop.fs_stat(lazypath) then
  require("core.bootstrap").gen_chadrc_template() -- 生成lua/custom/chadrc.lua模板
  require("core.bootstrap").lazy(lazypath) -- 安装lazy.nvim以及插件
end
      
dofile(vim.g.base46_cache .. "defaults") -- 主题颜色相关
vim.opt.rtp:prepend(lazypath) -- 将lazy.nvim安装目录加入rtp
加载插件配置。
require "plugins"
lua/core/init.lua
此文件是NvChad的核心配置文件,主要包含UI、插件、快捷键映射、neovim选项、autocmd以及全局变量的一些配置。并且会根据custom下的设置来确定最终的配置结果。其执行流程如下:
调用lua/core/utils.lua中的load_config函数加载并确定前述的大部分配置。
local config = require("core.utils").load_config()
该函数的定义为:
local M = {}
local merge_tb = vim.tbl_deep_extend
      
M.load_config = function()
  local config = require "core.default_config" -- 获取默认配置
  local chadrc_path = vim.api.nvim_get_runtime_file("lua/custom/chadrc.lua", false)[1] -- 自定义配置文件路径
      
  if chadrc_path then
    local chadrc = dofile(chadrc_path) -- 获取自定义配置
    -- 将自定义的快捷键映射从默认映射中移除,自定义的快捷键功能具有更高的优先级
    config.mappings = M.remove_disabled_keys(chadrc.mappings, config.mappings)
    -- 合并自定义配置与默认配置,如果key相同value则以chadrc为准,参见:help vim.tbl_deep_extend
    config = merge_tb("force", config, chadrc)
    config.mappings.disabled = nil
  end
      
  return config
end
基本思想是加载lua/core/default_config.lua以及lua/custom/chadrc.lua,并合并两者的设置,如遇冲突则以chadrc覆盖default_config。关于如何进行合并,nvchad的这篇文档有较详细的介绍。配置文件主要定义了UI、插件与映射的配置,分别由M.ui、M.plugins、M.mappings确定。
定义neovim选项、autocmd等其他变量或命令。
local g = vim.g
opt.clipboard = "unnamedplus"
opt.cursorline = true
...
lua/core/bootstrap.lua
在安装NvChad后首次启动时,会调用bootstrap.lua中的gen_chadrc_template与lazy函数,分别用于生成chadrc模板配置文件与安装lazy.nvim,下面分析下lazy.nvim的安装方式:
M.lazy = function(install_path)
  ------------- base46 ---------------
  local lazy_path = vim.fn.stdpath "data" .. "/lazy/base46"
   
  M.echo "  Compiling base46 theme to bytecode ..."
   
  local base46_repo = "https://github.com/NvChad/base46"
  shell_call { "git", "clone", "--depth", "1", "-b", "v2.0", base46_repo, lazy_path }
  vim.opt.rtp:prepend(lazy_path)
   
  require("base46").compile()
   
  --------- lazy.nvim ---------------
  M.echo "  Installing lazy.nvim & plugins ..."
  local repo = "https://github.com/folke/lazy.nvim.git"
  shell_call { "git", "clone", "--filter=blob:none", "--branch=stable", repo, install_path }
  vim.opt.rtp:prepend(install_path)
   
  -- install plugins
  require "plugins"
   
  -- mason packages & show post_boostrap screen
  require "nvchad.post_bootstrap"()
end
该函数首先下载base46主题仓库并编译成字节码,之后下载lazy.nvim,然后调用lua/plugins/init.lua安装所有插件,最后执行Mason中LSP的相关安装并显示最终的结果屏幕。
lua/plugins/init.lua
该文件是管理插件的顶层配置文件,其结构如下:
local default_plugins = { -- 所有nvchad默认使用的插件
    ...
    -- nvchad plugins
    { "NvChad/extensions", branch = "v2.0" },
    ...
}
-- 加载default_config与chadrc中的配置并进行合并,这里主要用到其中的`plugins`表
local config = require("core.utils").load_config()
-- 如果有自定义的插件设置,那么插入一条{import = config.plugins}的表项到default_plugins中
if #config.plugins > 0 then
  table.insert(default_plugins, { import = config.plugins })
end
   
-- 加载lazy.nvim,传入default_plugins
require("lazy").setup(default_plugins, config.lazy_nvim)
default_plugins的table,其中声明了需要管理的插件以及相关配置参数lua/plugins/init.lua会使用load_config加载default_config与chadrc中的配置,确定是否有用于自定义的插件。如果存在用户自定义的插件,则在default_plugins中加入一条{ import = config.plugins }的表项,这条记录的含义可以参考lazy.nvim中的README。default_plugins并加载lazy.nvim,NvChad就可以使用lazy.nvim管理插件了。可以在lua/custom/mappings.lua中添加自定义的快捷键,以下是我的一部分快捷键配置:
local M = {}
M.general = {
  n = {
    -- 分割窗口
    ["<C-w>k"] = { ":abo split <CR>", "split window to top" },
    ["<C-w>h"] = { ":abo vsplit <CR>", "split window to left" },
    ["<C-w>j"] = { ":rightbelow split <CR>", "split window to bottom" },
    ["<C-w>l"] = { ":rightbelow vsplit <CR>", "split window to right" },
    -- 关闭窗口
    ["q"] = { ": <ESC>:close <CR>", "close window", opts = { silent = true } },
    -- 使用回车打开关闭折叠
    ["<CR>"] = { "za", "open fold with enter" },
    -- 将:替换成;
    [";"] = { ":", "enter command mode", opts = { nowait = true } },
    -- 使用Q进行宏录制
    ["Q"] = { "q", "use Q to record macro" },
  },
  v = {
    -- 关闭窗口
    ["q"] = { ": <ESC>:close <CR>", "close window"},
  },
  i = {
    -- jk 快速退出
    ["jk"] = { "<ESC>", "quick escape" },
  }
}
M.lspconfig = {
  plugin = true,
  n = {
    -- 查找定义
    ["gd"] = {
      function()
        require("telescope.builtin").lsp_definitions({ fname_width = 100, reuse_win = true })
      end
    },
    -- 查找引用
    ["gr"] = {
      function()
        require("telescope.builtin").lsp_references({ show_line = false })
      end,
      "LSP signature help",
    },
    -- 查找全局符号
    ["gs"] = {
      function()
        require("telescope.builtin").lsp_workspace_symbols({ fname_width = 100 })
      end,
    }
  }
}
return M
以上配置中,M.general、M.lspconfig称为section,是一个个逻辑独立的映射。如M.general是一些通用的快捷键,M.lspconfig则是针对lspconfig插件专用的快捷键,用户可以根据自己的意愿来划分section。
一个section的快捷键设置的格式大致为:
section_name = {
    mode1 = {
        [key1] = mapping_info1,
        [key2] = mapping_info2,
        ...
    },
    mode2 = {
        [key3] = mapping_info3,
        ...
    },
    ...
}
mode表示该快捷键作用于哪种模式,如n表示normal模式,v表示visual模式,i表示insert模式等;key则是要绑定的快捷键;mapping_info则是具有特定格式的表,表的第一项是该快捷键对应的操作,可以是一个函数,也可以是一个普通的neovim操作。表的第二项是一个可选的描述字符串。
每个section内部可以添加一个plugin = true的表项,如M.lspconfig中我们就定义了这个plugin。这个表示该section的快捷键需要由插件进行加载。默认情形下,NvChad可以使用load_mappings()设置所有不含plugin的section快捷键,而如果我们想在使用某个插件时才设置对应的快捷键,就可以加入plugin,并在插件的配置中使用load_mappings(section_name)来进行加载了。
上面的M.lspconfig是在NvChad的lua/plugins/configs/lspconfig.lua中的M.on_attach函数进行加载的:
M.on_attach = function(client, bufnr)
  ...
  utils.load_mappings("lspconfig", { buffer = bufnr })
  ...
end
NvChad不仅可以安装lua插件,也可以安装vimscript插件。以vimtex为例:
我们首先需要在lua/custom/plugins.lua中加入vimtex:
  {
    "lervag/vimtex",
    ft = "tex", -- 按照filetype进行lazy加载,只有处理.tex文件时该插件才会被加载
    config = function() -- 加载插件时会执行的钩子函数
      require("custom.configs.vimtex").setup() -- 执行我们自定义的vimtex配置文件中的setup函数
    end,
  },
之后我们需要在lua/custom/configs下新建一个插件的配置文件。对于本例就是vimtex.lua,在该文件中实现前面的setup函数:
local M = {}
   
M.setup = function ()
  vim.cmd([[
  " 在这里就可以加入vimscript的配置命令了
  let g:vimtex_compiler_latexmk_engines = {
      \ '_' : '-xelatex',
      \}
   
  ]])
end
   
return M
   
可以利用vim.cmd([[ ... ]])来执行vimscript的配置
下文所有路径默认都以该目录为根目录。 ↩