数字旗手

电气化、自动化、数字化、智能化、智慧化

0%

Neovim预配置库NvChad探索

——————更新日志——————
2022.10.5 更新:简化了plugin

简介

vim是一个非常强大的文本编辑器,而Neovim是对原vim的一个分叉,不过最简单的Neovim显得太过朴素,只提供了基本的编辑器功能;然而有赖于它提供了强大的插件系统,可以通过各种插件将其由一个简约的文本编辑器转化为强大的代码开发IDE。
NvChad就是一个Neovim的预配置库,可以使得Neovim开箱即获得各种强大的功能:

  • NvChad是一个用lua编写的neovim配置,旨在提供一个具有非常漂亮的用户界面和极快的启动时间的基本配置(在基础硬件上约0.02秒至0.07秒);
  • 懒加载的机制使得插件不会被默认加载,只有在需要的时候才会被加载,包括特定的命令和vim事件等。这有助于减少启动时间,从而使启动时间比平时快;
  • NvChad不是一个框架,它是作为大众的 “基础 “配置使用的。它的目的是提供一个特定的默认插件集。

安装

前提条件

在使用NvChad之前,要有一些前提依赖:

  • 终端
    Neovim运行的环境,Linux系统推荐TerminatorWindows推荐Windows TerminalMac OS推荐iTerm2
  • Neovim 0.7.2及以上
    安装教程在这里
  • 字体及图标:
    一个推荐的编程字体是Fira Code字体,这里不光安装它,还安装它的一个扩展,即Nerd fontsNerd fonts 本身并不是一种新的字体,而是把常用图标以打补丁的方式打到了常用字体上。)
    具体到官网这里进行下载。
    对于Linux字体的安装,步骤为:
    1
    2
    sudo unzip FiraCode.zip -d /usr/share/fonts
    sudo fc-cache -fv
    对于Windows版本,注意在下载的文件中选择XXXX Windows Compatible.ttf。然后在Windows Terminal的字体中选择FiraCode NF字体即可。)
    为了测试是否成功,可以到这个网址,点击 Show All Icons 按钮,选择一个图标,点击右上角的 Copy Icon,然后粘贴到命令行里即可。
  • 保证所需的目录干净
    对于Linux和MacOS系统,删除~/.local/share/nvim这个文件夹;对于Windows系统,删除~\AppData\Local\nvim~\AppData\Local\nvim-data
  • 如果要用到Telescope的模糊搜索,还要提前安装ripgrep。具体安装方法可以参考官方文档BurntSushi/ripgrep

安装

对于Linux/MacOS系统:

1
git clone https://github.com/NvChad/NvChad ~/.config/nvim --depth 1 && nvim

对于Windows系统:
(注意:还需提前安装mingw,以及在环境变量中配置其路径)
1
git clone https://github.com/NvChad/NvChad $HOME\AppData\Local\nvim --depth 1 && nvim

升级

NvChad有一个内置的升级机制,快捷键是<leader> + uu
注意,默认配置下<leader>键是空格键<space>
具体地,它在后台会使用git reset --hard来获取官方git库中的更新,因此在lua/custom文件夹外的改动都会被丢弃(因此,如果想在NvChad上自定义配置,需要在lua/custom文件夹内进行)。

卸载

1
2
3
4
5
6
7
8
# linux/macos (unix)
rm -rf ~/.config/nvim
rm -rf ~/.local/share/nvim
rm -rf ~/.cache/nvim

# windows
rd -r ~\AppData\Local\nvim
rd -r ~\AppData\Local\nvim-data

安装之后

上一步安装好NvChad后,还可以做一些进阶动作,当然不做也不影响使用。

创建自定义配置

  • NvChad的更新不会覆盖lua/custom目录,因为它被gitignored了,因此所有的用户修改都必须在这个文件夹中完成。
  • lua/custom/init.luainit.lua主文件的最后被加载,可以在这里添加自定义的命令等。
  • lua/custom/chadrc.lua用于覆盖lua/core/default_config.lua并基本上控制整个nvchad,因此必须采用跟default_config.lua一样的代码结构。

NvChadexamples文件夹中提供了init.luachadrc.lua,可以将其作为默认初始的自定义配置文件,将其复制到custom文件夹中。
NvChad提供了一个example_config库,可以将其直接复制到lua文件夹下,作为起始的自定义配置文件(注意删掉.git文件夹)。

NvChad提前预置了很多常用的插件及其配置,但是每个人有不同的喜好,因此通常情况下需要自己配置custom目录来进一步定制化NvChadNvChad/example_config: example custom config 该库已经提供了很好的模板,可以在此基础上进行配置。
那么怎样对自己定制的部分也进行同步呢,就像NvChad主库那样存储在GitHub上,需要时直接拉取下来即可。
一个可行的方法是像NvChad/example_config: example custom config 这样,为custom文件夹单独建立一个仓库,在安装好NvChad主库后,再将这个库给拉取并合并进去。
但是这里有一个问题:NvChad库更新是比较频繁的,有时候其API变动比较剧烈,可能当前的配置需要与特定版本的主库对应才行。如果将custom和主库分离,就会遇到两者版本不一致的情形。
因此建议通过下面这种方式进行定制化及同步:
(1)对NvChad主库进行Fork,如我的这个库qixinbo/NvChad
(2)创建一个custom分支,该分支相对于main分支就是添加了custom目录及其自定义的文件。
(3)同理,本地也会有这两个分支,对自定义部分的改动是发生在custom分支上。

NvChad主库有更新时,
(1)在GitHub的网页上,通过Sync fork按钮将NvChad主库与自己fork版本的main分支进行同步
(2)在本地,首先切换分支到main

1
git checkout main

(3)本地main与远程main同步:
1
git pull

(4)切换到custom分支并将main分支merge进来:
1
2
git checkout custom
git merge main

(5)将本地custom分支推送到远程:
1
git push

那么,最终的效果就是,在qixinbo/NvChad 仓库中:
对于main分支,它始终与NvChad主库保持同步;
对于custom分支,它始终比NvChad主库要多若干个commits(因为要自定义开发)。
在不同电脑进行配置时,只需要克隆custom分支即可,这样既能保证NvChad是最新的,也能自动同步自己的自定义配置。

安装Treesitter解析器

nvim-treesitter提供了代码高亮、缩进和折叠等功能。
nvim-treesitterNvChad中是默认配置安装的,但是其正常运行需要满足以下条件:

  • 在环境变量中能找到tarcurl命令(或者git命令)
  • 在环境变量中有C编译器和libstdc++
    Linux系统中,使用sudo apt install build-essential即可安装相应依赖,在Windows系统中,可查看该详细教程

所以要保证nvim-treesitter的正常使用,需要提前配置好以上依赖。待treesitter安装好后,接下来就是安装其针对于不同编程语言的解析器。
安装解析器使用以下命令(以Python为例):

1
:TSInstall python

可以通过:TSModuleInfo查看安装情况。

安装Node.JS

Node.js对于后面的LSP是有用的,比如安装pyright时需要用到npm,所以这里也可以事先安装。
安装包在这里
对于UbuntuLinux系统,也可以使用包管理器来安装,教程见这里
Ubuntu为例:

1
2
curl -fsSL https://deb.nodesource.com/setup_16.x | sudo -E bash -
sudo apt-get install -y nodejs

mac系统的话直接用brew安装即可:

1
brew install node

Lua简明教程

Print

1
print("Hi")

注释

1
2
3
4
5
6
7
-- comment
print("Hi") -- comment

--[[
multi-line
comment
]]

变量

1
2
3
4
5
6
-- Different types

local x = 10 -- number
local name = "sid" -- string
local isAlive = true -- boolean
local a = nil --no value or invalid value

条件表达式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
-- Number comparisons
local age = 10

if age > 18 then
print("over 18") -- this will not be executed
end

-- elseif and else
age = 20

if age > 18 then
print("over 18")
elseif age == 18 then
print("18 huh")
else
print("kiddo")
end
1
2
3
4
5
6
7
8
9
10
11
12
13
-- Boolean comparison
local isAlive = true

if isAlive then
print("Be grateful!")
end

-- String comparisons
local name = "sid"

if name ~= "sid" then
print("not sid")
end

组合表达式

1
2
3
4
5
6
7
8
9
local age = 22

if age == 10 and x > 0 then -- both should be true
print("kiddo!")
elseif x == 18 or x > 18 then -- 1 or more are true
print("over 18")
end

-- result: over 18

反转

1
2
3
4
5
local x = 18

if not x == 18 then
print("kiddo!") -- prints nothing as x is 18
end

函数

1
2
3
4
5
6
7
8
9
10
11
local function print_num(a)
print(a)
end

or

local print_num = function(a)
print(a)
end

print_num(5) -- prints 5
1
2
3
4
5
-- multiple parameters

function sum(a, b)
return a + b
end

作用域

1
2
3
4
5
function foo()
local n = 10
end

print(n) -- nil , n isn't accessible outside foo()

循环

While

1
2
3
4
5
6
local i = 1

while i <= 3 do
print("hi")
i = i + 1
end

For

1
2
3
4
for i = 1, 3 do
print("hi")
i = i + 1
end

Tables

数组(列表)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
local colors = { "red", "green", "blue" }

print(colors[1]) -- red
print(colors[2]) -- green
print(colors[3]) -- blue

-- Different ways to loop through lists
-- #colors is the length of the table, #tablename is the syntax

for i = 1, #colors do
print(colors[i])
end

-- ipairs
for index, value in ipairs(colors) do
print(colors[index])
-- or
print(value)
end

-- If you dont use index or value here then you can replace it with _
for _, value in ipairs(colors) do
print(value)
end

字典

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
local info = { 
name = "sid",
age = 20,
isAlive = true
}

-- both print sid
prrint(info["name"])
print(info.name)

-- Loop by pairs
for key, value in pairs(info) do
print(key .. " " .. tostring(value))
end

-- prints name sid, age 20 etc

嵌套Tables

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-- Nested lists
local data = {
{ "Sid", 20 },
{ "Tim", 90 },
}

for i = 1, #data do
print(data[i][1] .. " is " .. data[i][2] .. " years old")
end

-- Nested dictionaries
local data = {
sid = { age = 20 },
time = { age = 90 },
}

模块

1
require("path")

Neovim中的Lua应用

Neovim中的配置文件和插件都可以使用Lua进行编写,具体的教程,可以参考该文档

配置

总览

NvChad的文件结构如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
├── init.lua

├── lua
│ │
│ ├── core
│ │ ├── default_config.lua
│ │ ├── mappings.lua
│ │ ├── options.lua
│ │ ├── packer.lua -- (新版删除了。。bootstrap packer & installs plugins)
│ │ ├── utils.lua -- (core util functions) (i)
│ │ └── init.lua -- (autocmds)
│ │
│ ├── plugins
│ │ ├── init.lua -- (default plugin list)
│ │ └── configs
│ │ ├── cmp.lua
│ │ ├── others.lu -- (list of small configs of plugins)
│ │ └── many more plugin configs
| |
│ ├── custom *
│ │ ├── chadrc.lua -- (overrides default_config)
│ │ ├── init.lua -- (runs after main init.lua file)
│ │ ├── more files, dirs

配置主题

快捷键是:<leader> + th

快捷键映射

:Telescope keymaps

默认设置

默认设置在lua/core/default_config.lua

选项

custom/init.lua中可以进行如下操作:

  • 重载默认选项
  • 设置autocmdsglobal全局变量
  • 懒加载,具体可以查看packer readme
  • 代码片段,比如:vim.g.luasnippets_path = "your snippets path"

插件

NvChad在底层使用packer.nvim,不过它重新定义了一套语法,比如:

  • packer.nvim定义插件的方式:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    use { "NvChad/nvterm" }, -- without any options

    -- with more options
    use {
    "NvChad/nvterm"
    module = "nvterm",
    config = function()
    require "plugins.configs.nvterm"
    end,
    },
  • NvChad定义插件的方式:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    ["NvChad/nvterm"] = {}, -- without any options

    -- with more options
    ["NvChad/nvterm"] = {
    module = "nvterm",
    config = function()
    require "plugins.configs.nvterm"
    end,
    },

安装、卸载和重载插件

以下格式不需要硬记,可以参考NvChad/example_config: example custom config这个库。

1
2
-- chadrc
M.plugins = require "custom.plugins"

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
-- custom/plugins/init.lua
-- we're basically returning a table!
return {

-- Install plugin
["Pocco81/TrueZen.nvim"] = {},

-- Override plugin definition options
["goolord/alpha-nvim"] = {
disable = false,
cmd = "Alpha",
},

-- Override plugin config
["williamboman/mason.nvim"] = {
override_options = {
ensure_installed = { "html-lsp", "clangd" }
}
},

-- Override plugin config if it has a module called
-- If you wish to call a module, which is 'cmp' in this case
["hrsh7th/nvim-cmp"] = {
override_options = function()
local cmp = require "cmp"

return {
mapping = {
["<C-d>"] = cmp.mapping.scroll_docs(-8),
},
}
end,
},

-- remove plugin
["neovim/nvim-lspconfig"] = false
}

新版NvChad对插件的编写进一步简化,与旧版有了显著区别。下面的是旧版的插件操作方式,可以不用看了。

旧版的插件安装、重载和卸载

首先创建lua/custom/plugins/init.lua,按以下格式添加插件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
-- custom/plugins/init.lua has to return a table
-- THe plugin name is github user or organization name/reponame

return {

["elkowar/yuck.vim"] = { ft = "yuck" },

["max397574/better-escape.nvim"] = {
event = "InsertEnter",
config = function()
require("better_escape").setup()
end,
},
}

然后在lua/custom/chadrc.lua中引入该文件(实际写完一次后就不用更改了):
1
2
3
4
5
-- chadrc.lua

M.plugins = {
user = require "custom.plugins"
}

最后执行:PackerSync即可。

重载插件的默认配置

当想从一个插件的默认配置选项中改变一个东西,但又不想复制粘贴整个配置时,这个功能就很有用了。
如下:

1
2
3
4
5
6
7
8
9
10
M.plugins = {
override = {
["nvim-treesitter/nvim-treesitter"] = {
ensure_installed = {
"html",
"css",
},
}
}
}

但以上面这种方式的话,配置一多了就会显得很乱,可以采用如下这种方式:
1
2
3
4
5
6
7
8
local pluginConfs = require "custom.plugins.configs"

M.plugins = {
override = {
["nvim-treesitter/nvim-treesitter"] = pluginConfs.treesitter,
["kyazdani42/nvim-tree.lua"] = pluginConfs.nvimtree,
},
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
-- custom/plugins/configs.lua file

local M = {}

M.treesitter = {
ensure_installed = {
"lua",
"html",
"css",
},
}

M.nvimtree = {
git = {
enable = true,
},
view = {
side = "right",
width = 20,
},
}

-- you cant directly call a module in chadrc thats related to the default config
-- Thats because most probably that module is lazyloaded
-- In this case its 'cmp', we have lazyloaded it by default
-- So you need to make this override field a function, instead of a table
-- And the function needs to return a table!

M.cmp = function()
local cmp = require 'cmp'

return {
mapping = {
["<C-d>"] = cmp.mapping.scroll_docs(-8),
}
}
end

return M

然后执行:PackerSync

修改插件的配置

比如在lua/custom/plugins/init.lua中是这样定义nvimtree的:

1
2
3
4
5
6
7
8
9
10
11
["kyazdani42/nvim-tree.lua"] = {
cmd = { "NvimTreeToggle", "NvimTreeFocus" },

setup = function()
require("core.mappings").nvimtree()
end,

config = function()
require "plugins.configs.nvimtree"
end,
}

现在想修改其中的configcmd配置,那么可以保持该文件不动,在安装它们时再改:
1
2
3
4
5
6
7
8
9
10
11
12
M.plugins = {
user = {
["kyazdani42/nvim-tree.lua"] = {
cmd = { "abc" },
config = function()
require "custom.plugins.nvimtree"
end,
}
}

-- This will change cmd, config values from default plugin definition
-- So the setup value isnt changed, look close!

删除插件

1
2
3
4
5
6
7
8
M.plugins = {
remove = {
"andymass/vim-matchup",
"NvChad/nvterm",
},
}

-- now run :PackerSync

快捷键映射

  • CCtrl
  • <leader><space>
  • AAlt
  • SShift
  • 默认配置在core/mappings.lua中定义。

快捷键映射格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
-- opts here is completely optional

["keys"] = {"action", "icon mapping description", opts = {}},

-- more examples
["<C-n>"] = {"<cmd> NvimTreeToggle <CR>", "Toggle nvimtree", opts = {}},

["<leader>uu"] = { "<cmd> :NvChadUpdate <CR>", " update nvchad" },

[";"] = { ":", "enter cmdline", opts = { nowait = true } },
["jk"] = { "<ESC>", "escape insert mode" , opts = { nowait = true }},

-- example with lua function
["<leader>tt"] = {
function()
require("base46").toggle_theme()
end,
" toggle theme",
},
  • 映射描述对Whichkey是必要的,对于非Whichkey则不是必需
  • 可以使用图标来帮助阅读,不过也不是必选项,而是可选项
  • 可以从这里来复制和粘贴图标
  • 默认的opts的值有:
    1
    2
    3
    4
    5
    6
    7
    8
    {
    buffer = nil, -- Global mappings. Specify a buffer number for buffer local mappings
    silent = true,
    noremap = true,
    nowait = false,

    -- all standard key binding opts are supported
    }

增加新的映射

默认配置在core/mappings.lua中定义,然后可以在custom/mappings.lua中增加新的映射:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- lua/custom/mappings 
local M = {}

-- add this table only when you want to disable default keys
M.disabled = {
n = {
["<leader>h"] = "",
["<C-s>"] = ""
}
}

M.abc = {

n = {
["<C-n>"] = {"<cmd> Telescope <CR>", "Open Telescope"}
}

i = {
-- more keys!
}
}

M.xyz = {
-- stuff
}

return M

注意在自己的custom/chadrc.lua中引入:
1
2
-- chadrc
M.mappings = require "custom.mappings"

  • 上面的abcxyz是随意取的,为了可读性,可以改成插件名字
  • 上面的映射关系是自动加载的,不需要手动加载。

UI插件

NvChad使用了自己的UI插件

LSP

以下出在掘金上的这个小册

想要在 Neovim 中配置代码补全、代码悬停、代码提示等等功能,首先要了解什么是 LSP (Language Server Protocol) 语言服务协议。
在 LSP 出现之前,传统的 IDE 都要为其支持的每个语言实现类似的代码补全、文档提示、跳转到定义等功能,不同的 IDE 做了很多重复的工作,并且兼容性也不是很好。 LSP 的出现将编程工具解耦成了 Language Server 与 Language Client 两部分。定义了编辑器与语言服务器之间交互协议。
Client 专注于显示样式实现, Server 负责提供语言支持,包括常见的自动补全、跳转到定义、查找引用、悬停文档提示等功能。
而这里所说的 Neovim 内置 LSP 就是说 Neovim 内置了一套 Language Client 端的实现,这样就可以连接到和 VSCode 相同的第三方 language servers ,实现高质量的语法补全等功能。

还有一个简单的教程见这里

为了简化 LSP 的安装和配置,NeoVim 官方专门创建了 nvim-lspconfig 插件来帮助我们。这个插件把所有 LSP 背后的繁琐都封装到其内部,让使用者再也毋需担心出现费了大半天功夫结果仍然无法用起来的事。

搭建内部LSP

1
2
3
4
5
6
7
8
9
-- we are just modifying lspconfig's packer definition table
-- put this in your custom plugins section i.e M.plugins.user field

["neovim/nvim-lspconfig"] = {
config = function()
require "plugins.configs.lspconfig"
require "custom.plugins.lspconfig"
end,
},
1
2
3
4
5
6
7
8
9
10
11
12
13
-- custom.plugins.lspconfig
local on_attach = require("plugins.configs.lspconfig").on_attach
local capabilities = require("plugins.configs.lspconfig").capabilities

local lspconfig = require "lspconfig"
local servers = { "html", "cssls", "clangd", "pyright"}

for _, lsp in ipairs(servers) do
lspconfig[lsp].setup {
on_attach = on_attach,
capabilities = capabilities,
}
end

然后执行:PackerCompile

外部LSP server

可以使用Mason.nvim来安装外部LSP Server。
输入命令:Mason,就能打开它的浮动窗口,来安装、更新、卸载相关的安装包(比如lspservers、linters和formatters等)。
最好是使用配置文件来使用Mason.nvim,如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
["williamboman/mason.nvim"] = {
ensure_installed = {
-- lua stuff
"lua-language-server",
"stylua",

-- web dev
"css-lsp",
"html-lsp",
"typescript-language-server",
"deno",
"emmet-ls",
"json-lsp",

-- shell
"shfmt",
"shellcheck",
},
},

然后执行:MasonInstallAll(注意该命令是NvChad的定制命令,不是官方原有命令)。

代码调试DAP

DAP,即Debug Adapter Protocol,是neovim最常用的代码调试工具。
不过当前的NvChad并没有内置DAP,据说可能在NvChad 2.0中会加入。
当前可以参考我的配置,在这里
使用DAP需要安装一各特定语言的Debug Adapterpython的话就是使用debugpy`。

使用conda安装debugpy

正常安装miniconda或者ananconda后,激活某个虚拟环境,然后安装:

1
pip install debugpy

然后在custom/plugins/dap/dap-python.lua中使用nvim-dap-python的默认配置即可:
1
require('dap-python').setup('python')

在venv中debugpy

1
2
3
sudo apt install python3.10-venv
python -m venv path/to/virtualenvs/debugpy
path/to/virtualenvs/debugpy/bin/python -m pip install debugpy

然后在custom/plugins/dap/dap-python.lua中将该路径配置进去:

1
require('dap-python').setup('path/to/virtualenvs/debugpy/bin/python')