Life is short, you need nvchad!

neovim/vim使用者需要经常维护配置文件与插件库。由于插件API变更的频繁性,自己维护插件的配置需要付出一定精力,且很容易将配置目录弄得混乱。那么一个结构良好、层次分明、易于维护的配置框架成为我们的一大诉求。而nvchad正是解决此类麻烦的典型代表。

1. nvchad的优势

  1. 结构良好nvchad高度可定制化,在默认情形下提供了一个开箱即用的配置,默认配置我们无需变动,只需要保持和上游的同步就能体验最新最酷炫最高效的插件。而当我们需要自定义配置或者需要增删插件时,可以在custom目录下进行相应修改。
  2. 层次分明nvchad按照功能对用户提供了如下配置接口:
    • neovim选项设置。控制neovim的基本功能,如行号、缩进、高亮等。
    • 快捷键映射。按照用户习惯设置常用快捷键。
    • 插件设置。根据用户喜好来控制插件行为与交互。
  3. 易于维护
    • nvchad采用纯lua进行配置文件的编写,相比vimscript更具可读性。
    • 默认配置与自定义配置互相隔离,不必担心弄脏环境。

2. 关于nvchad的一点小背景知识

neovim是基于vim复刻的一个编辑器,在原有vim的基础上做了一些改进与调整。其中最大的特性是内置lua引擎,其配置可以直接使用lua进行开发,这一特性大大增强了neovim的功能,诞生了许多以lua进行开发的插件;同时neovim兼容vim的原有配置,既能享受强大的lua插件同时也能继续使用原有vim插件,是一个更值得推荐的编辑器。

lua是一种轻量级的脚本语言,语法特征和python有点类似,因为非常高效小巧,因此很适合将其嵌入到其他软件项目中。关于如何在neovim中使用lua,可以参考这篇文档

3. nvchad框架介绍

3.1 目录架构

neovim所有配置相关的文件位于目录$HOME/.config/nvim1,安装好的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           // 所有默认插件的配置文件

3.2 执行流程分析

  1. init.lua

    根据neovim说明文档neovim会从~/.config/nvim/init.lua中加载用户配置,该文件是neovim读取配置的起始文件。下面是该文件的主体流程:

    1. 执行lua/core/init.lua来加载用户配置。

      require "core"
      
    2. 判断是否有用户自定义的配置文件,如果有则执行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
      
    3. 执行lua/core/utils.lua中的load_mapping函数加载快捷键设置。

      require("core.utils").load_mappings()
      
    4. 安装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
      
    5. 加载插件配置。

      require "plugins"
      
  2. lua/core/init.lua

    此文件是NvChad的核心配置文件,主要包含UI、插件、快捷键映射、neovim选项、autocmd以及全局变量的一些配置。并且会根据custom下的设置来确定最终的配置结果。其执行流程如下:

    1. 调用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.uiM.pluginsM.mappings确定。

    2. 定义neovim选项、autocmd等其他变量或命令。

      local g = vim.g
      opt.clipboard = "unnamedplus"
      opt.cursorline = true
      ...
      
  3. lua/core/bootstrap.lua

    在安装NvChad后首次启动时,会调用bootstrap.lua中的gen_chadrc_templatelazy函数,分别用于生成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安装所有插件,最后执行MasonLSP的相关安装并显示最终的结果屏幕。

  4. 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_configchadrc中的配置,确定是否有用于自定义的插件。如果存在用户自定义的插件,则在default_plugins中加入一条{ import = config.plugins }的表项,这条记录的含义可以参考lazy.nvim中的README
    • 最后,传入default_plugins并加载lazy.nvimNvChad就可以使用lazy.nvim管理插件了。

4. 自定义配置

4.1 自定义快捷键

可以在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

4.2 安装vimscipt插件

NvChad不仅可以安装lua插件,也可以安装vimscript插件。以vimtex为例:

  1. 我们首先需要在lua/custom/plugins.lua中加入vimtex

      {
        "lervag/vimtex",
        ft = "tex", -- 按照filetype进行lazy加载,只有处理.tex文件时该插件才会被加载
        config = function() -- 加载插件时会执行的钩子函数
          require("custom.configs.vimtex").setup() -- 执行我们自定义的vimtex配置文件中的setup函数
        end,
      },
    
  2. 之后我们需要在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的配置

注解

  1. 下文所有路径默认都以该目录为根目录。