给 MinGW64 / MSYS2 配置一个简单的 Neovim

给 MinGW64 / MSYS2 配置一个简单的 Neovim

RayAlto OP

1. 废话

我本来没有给 Windows 配置开发环境,只装了个 MSYS2 ,给 MinGW64 装了一套最简单的工具链,因为一般都直接在我的另一台 ArchLinux 机器下进行开发,但是最近有时需要 #include "windows.h" 来对 Windows 进行适配(比如 argv 的编码这种 libstdc++ 没有解决/解决不了的问题),只用 Neovim 没有 completion 、没有 diagnostic 实在是难用,所以为什么不配置一个最简单的 Neovim 呢。

Q: 为啥不用 VSCode
A: 能避免 electron 就避免,感觉用久了会让我分辨不出巧克力味的屎和屎味的巧克力
Q: 为啥不用宇宙第一的 Visual Studio
A: 宇宙第一的称号不容置疑,但体积大又笨重也是不容置疑
Q: Windows 下 shell 性能一坨,为啥还要折腾 neovim - tui
A: 我是失败人士,失败人士配失败工具

2. 环境相关

我的 Windows 装了 MSYS2 ,目录为 C:/msys64 ,主要使用里面的 MinGW64 环境,里面提供了所有基本的开发工具,我主要用 neovim 进行编辑, gcc 用于编译, cmake 用于组织项目,因为 Python 之类的其他语言对于 Windows 的适配做的比较好,至少我目前没有遇到 Windows exlusive 的问题

MSYS2 中提供了很丰富软件包,以 C++ 开发为例, MinGW64 环境下需要安装:

  1. mingw-w64-x86_64-toolchain 包含了 gcc, gdb 一类的开发工具
  2. mingw-w64-x86_64-cmake 用来组织项目
  3. mingw-w64-x86_64-neovim 用来编辑
  4. mingw-w64-x86_64-clang 提供了 clang-format 格式化工具
  5. mingw-w64-x86_64-clang-tools-extra 提供了 clangd, clang-tidy 一类 lint 工具

当前 ArchLinux 下( 2023.11.01 ) extra/clang 软件包包含了 clang-analyzerclang-tools-extra ,而 MSYS2 则是分开的 mingw-w64-x86_64-clang-analyzermingw-w64-x86_64-clang-tools-extra

3. neovim 基本配置

既然选择了编辑器中的原神 neovim ,自然要选择用 lua 做配置文件, neovim 在 vim 的基础上把许多功能设置为默认启用了,我设置的功能:

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
40
41
42
vim.opt.cursorcolumn = true   -- 高亮光标所在列
vim.opt.cursorline = true -- 高亮光标所在行
vim.opt.expandtab = true -- 用空格代替 tab
vim.opt.guicursor = '' -- 禁用鼠标
vim.opt.mouse = '' -- 禁用鼠标
vim.opt.foldenable = false -- 不自动折叠
vim.opt.number = true -- 显示行号
vim.opt.shiftwidth = 4 -- 自动缩进时用 4 个空格
vim.opt.signcolumn = 'number' -- 设置 sign 显示在行号里
vim.opt.softtabstop = 4 -- tab 尺寸为 4 个空格
vim.opt.tabstop = 4 -- tab 尺寸为 4 个空格
vim.opt.shellcmdflag = '-c' -- 让 MSYS2 的 shell 能正常工作
vim.opt.shellxquote = '(' -- 让 MSYS2 的 shell 能正常工作
vim.opt.shellslash = true -- 让 MSYS2 的 shell 能正常工作

-- <leader> 设置成 ; 键
vim.g.mapleader = ';'
-- 禁用内置的 netrw 插件
vim.g.loaded_netrw = true
vim.g.loaded_netrwPlugin = true

-- Shift+Alt+j/k 下/上移动当前行
vim.keymap.set('n', '<A-J>', '<Cmd>m .+1<CR>==', { silent = true, remap = false })
vim.keymap.set('n', '<A-K>', '<Cmd>m .-2<CR>==', { silent = true, remap = false })
vim.keymap.set('i', '<A-J>', '<Esc><Cmd>m .+1<CR>==gi', { silent = true, remap = false })
vim.keymap.set('i', '<A-K>', '<Esc><Cmd>m .-2<CR>==gi', { silent = true, remap = false })
vim.keymap.set('v', '<A-J>', ':m \'>+1<CR>gv=gv', { silent = true, remap = false })
vim.keymap.set('v', '<A-K>', ':m \'<-2<CR>gv=gv', { silent = true, remap = false })
-- <leader>rn 切换相对行号
vim.keymap.set('n', '<Leader>rn', function() vim.opt.relativenumber = not vim.opt.relativenumber:get() end, { silent = true, remap = false })
-- <leader>%/" 分割窗口,类似 tmux 键位
vim.keymap.set({ 'n', 'i', 'v' }, '<Leader>%', vim.cmd.vsplit, { silent = true, remap = false })
vim.keymap.set({ 'n', 'i', 'v' }, '<Leader>"', vim.cmd.split, { silent = true, remap = false })

-- Indent <num> 设置缩进尺寸为 num
vim.api.nvim_create_user_command('Indent', function(opts)
local width = tonumber(opts.fargs[1])
if width == nil then return end
vim.opt.tabstop = width
vim.opt.softtabstop = width
vim.opt.shiftwidth = width
end, { nargs = 1 })

Windows 剪切板默认状态下是不可用的,其实 neovim 会尝试在 Windows 调用 win32yank 以实现剪切板功能,去 Releases · equalsraf/win32yank 里下载解压,把 win32yank.exe 放进 PATH 里就可以。

4. 常用插件

写到这里的时候我在现实生活中发生了一件很重要的事,大体上是因为我过于失败引起的,总之痛苦、丑陋不堪的挣扎了几天,现在算是尽力处理了,但还不知道结果,所以心理状态可能有一些变化

既然决定用 lua 写配置文件了,那不如 lua 到底,尽量选择以 lua 为主语言编写的插件。

4.1. 插件管理器

之前我一直在用 vim-plug ,这绝对是一个简洁又好用的插件管理器,但现在我非要找一个更时髦,更 lua 的插件管理器,好评如潮的 packer.nvim 停止维护了,所以采用多半好评的 lazy.nvim ,项目里给了一段自动 clone 并加载自己的 lua 代码,可以放进 init.lua 里:

1
2
3
4
5
local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim'
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({ 'git', 'clone', '--filter=blob:none', 'https://github.com/folke/lazy.nvim.git', '--branch=stable', lazypath, })
end
vim.opt.rtp:prepend(lazypath)

这样就不用像我之前 clone 之后 ln 了,以后配置一个新的开发环境就真的只需要把 init.lua 复制过去。

init.lua 里是这种格式:

1
2
3
4
5
6
require('lazy').setup({
-- 这里放插件
}, {
-- 不喜欢图标,所以换成字符
-- ui = { icons = { cmd = 'cmd', config = 'config', event = 'event', ft = 'ft', import = 'import', init = 'init', keys = 'keys', lazy = 'lazy', list = { '.', '➜', '*', '-', }, loaded = 'loaded', not_loaded = 'not_loaded', plugin = 'plugin', require = 'require', runtime = 'runtime', source = 'source', start = 'start', task = 'task', }, },
})

4.2. OneDark 主题

之前一直在用 material.vim ,但它不够 lua ,所以打算换成 material.nvim ,用了之后发现这个东西有很多小问题,比如浮动窗口的背景颜色与普通的背景颜色相同,十分难以分辨、它会改变光标颜色,退出 neovim 不会恢复等等。所以改用一个看起来不错的 onedark.nvim ,用 lazy.nvim 加载这个插件:

1
2
3
require('lazy').setup({
{ 'navarasu/onedark.nvim', lazy = false, },
})

然后设置主题:

1
2
3
4
5
6
7
8
9
10
11
12
13
-- 更丰富的色彩
if vim.fn.has('termguicolors') then
vim.opt.termguicolors = true
end
-- 使用 warmer 风格
local onedark_theme = require('onedark')
onedark_theme.setup({ style = 'warmer' })
onedark_theme.load()
-- 整点儿斜体,能更好看点
vim.cmd.highlight({ 'StatusLine', 'cterm=italic', 'gui=italic'})
vim.cmd.highlight({ 'String', 'cterm=italic', 'gui=italic'})
vim.cmd.highlight({ 'Comment', 'cterm=italic', 'gui=italic'})
vim.cmd.highlight({ 'Todo', 'cterm=italic', 'gui=italic'})

4.3. 提示缩进等级

之前一直在用 indentLine ,但这个东西是 Vim Script 写的,而且也 archived 了,所以找到了一个功能更丰富的 indent-blankline.nvim ,用 lazy.nvim 加载这个插件:

1
2
3
require('lazy').setup({
{ 'lukas-reineke/indent-blankline.nvim', main = 'ibl', },
})

这里注册一个 <Leader>ii 来 toggle 缩进提示:

1
2
3
4
local ibl = require('ibl')
local ibl_conf = require('ibl.config')
ibl.setup()
vim.keymap.set({ 'i', 'n', 'v' }, '<Leader>ii', function() ibl.update { enabled = not ibl_conf.get_config(-1).enabled } end, { silent = true, remap = false})

4.4. 更好看的 statusline

之前一直在用 vim-airline ,现在改用 lualine.nvim ,感觉自由度更高一些。用 lazy.nvim 加载这个插件:

1
2
3
require('lazy').setup({
{ 'nvim-lualine/lualine.nvim', },
})

配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
require('lualine').setup({
options = {
-- 使用 Material 主题
theme = 'material',
-- 在指定 filetype 下不使用 lualine
disabled_filetypes = { 'Outline', 'NvimTree' },
-- 不喜欢图标所以禁用
icons_enabled = false,
component_separators = '',
section_separators = '',
},
-- 配置顶部的条
tabline = {
-- 左边显示 buffer 信息
lualine_a = { { 'buffers', show_filename_only = false, show_modified_status = false, mode = 2 }, },
-- 右边显示 tab 信息
lualine_z = { 'tabs' },
},
})

5. 进阶配置

之前一直在用 coc.nvim ,现在看来这种完全另起炉灶的做法有点用力过猛,而且现在有点讨厌把 js/ts 放进开发环境里,所以决定使用 Neovim 内置的 LSP Client 。

5.1. LSP 支持

现在用 nvim-lspconfig ,算是比较方便了,用 lazy.nvim 加载这个插件:

1
2
3
require('lazy').setup({
{ 'neovim/nvim-lspconfig', },
})

C++ 使用 clangd 作为 LSP Server ,在 mingw-w64-x86_64-clang-tools-extra 里,配置几乎是开箱即用:

1
2
local lspconfig = require('lspconfig')
lspconfig.clangd.setup({})

Python 使用 pyright 作为 LSP Server ,但 MSYS2 貌似没有打包,可以先安装 mingw-w64-x86_64-nodejs ,用里面的 npm 安装 pyright ,虽然需要捏着鼻子用 ts/js ,但目前也没有更好的 LSP Server 了 。配置稍微复杂一点,因为 npm 安装的 pyright 不在 PATH 里,直接运行里面的 pyright-langserver 会因为环境不完整出现各种问题,所以我最后采用了这样的配置:

1
2
local lspconfig = require('lspconfig')
lspconfig.pyright.setup({ cmd = { 'C:/msys64/usr/bin/zsh.exe', 'C:/msys64/mingw64/lib/pyright-langserver', '--stdio' } })

5.2. 快速注释

之前一直在用 vim-commentary ,现在看来也是简洁好用的插件,但还是不够 lua ,所以改用 Comment.nvim ,这个插件对块注释的支持更好一些,用 lazy.nvim 加载这个插件:

1
2
3
4
require('lazy').setup({
{ 'numToStr/Comment.nvim', lazy = false, },
})
require('Comment').setup()

项目 Readme 说要加 lazy = false

gcc 是行注释, gbc 是块注释

5.3. LSP 符号列表

之前在用 Vista.vim ,这个插件虽然简洁可用,但有点过于简洁,自定义程度太低了,顺便不够 lua ,所以改用 symbols-outline.nvim ,用 lazy.nvim 加载这个插件:

1
2
3
require('lazy').setup({
{ 'simrat39/symbols-outline.nvim', },
})

因为不喜欢图标所以换成 ASCII 字符:

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
require('symbols-outline').setup({
fold_markers = { '>', '-' },
symbols = {
Array = { icon = 'arr', hl = '@constant' },
Boolean = { icon = 'bool', hl = '@boolean' },
Class = { icon = 'class', hl = '@type' },
Component = { icon = 'comp', hl = '@function' },
Constant = { icon = 'const', hl = '@constant' },
Constructor = { icon = 'ctor', hl = '@constructor' },
Enum = { icon = 'Enum', hl = '@type' },
EnumMember = { icon = 'enum', hl = '@field' },
Event = { icon = 'event', hl = '@type' },
Field = { icon = 'field', hl = '@field' },
File = { icon = 'file', hl = '@text.uri' },
Fragment = { icon = 'frag', hl = '@constant' },
Function = { icon = 'func', hl = '@function' },
Interface = { icon = 'iface', hl = '@type' },
Key = { icon = 'key', hl = '@type' },
Method = { icon = 'method', hl = '@method' },
Module = { icon = 'module', hl = '@namespace' },
Namespace = { icon = 'ns', hl = '@namespace' },
Null = { icon = 'NULL', hl = '@type' },
Number = { icon = 'num', hl = '@number' },
Object = { icon = 'obj', hl = '@type' },
Operator = { icon = 'op', hl = '@operator' },
Package = { icon = 'pkg', hl = '@namespace' },
Property = { icon = 'prop', hl = '@method' },
String = { icon = 'str', hl = '@string' },
Struct = { icon = 'struct', hl = '@type' },
TypeParameter = { icon = 'type', hl = '@parameter' },
Variable = { icon = 'var', hl = '@constant' },
},
})

5.4. 文件浏览器

之前在用 coc-explorer ,现在不用 Coc 体系了,所以换成 nvim-tree.lua ,用 lazy.nvim 加载:

1
2
3
require('lazy').setup({
{ 'nvim-tree/nvim-tree.lua', },
})

因为不喜欢图标所以禁用:

1
require('nvim-tree').setup({ renderer = { icons = { show = { file = false, folder = false, folder_arrow = false, git = false, }, }, }, })

5.5. LSP 热键&指令

我绑了下面这些热键:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
-- <Leader>? 显示 hover 信息
vim.keymap.set({ 'n' }, '<Leader>?', function(opts) vim.lsp.buf.hover() end, { silent = true, remap = false})
-- <Leader>qf 进行 code action (quick fix)
vim.keymap.set({ 'n' }, '<Leader>qf', function(opts) vim.lsp.buf.code_action() end, { silent = true, remap = false})
-- <Leader>F 格式化
vim.keymap.set({ 'n' }, '<Leader>F', function(opts) vim.lsp.buf.format() end, { silent = true, remap = false})
-- <Leader>gd 跳转到定义
vim.keymap.set({ 'n' }, '<Leader>gd', function(opts) vim.lsp.buf.definition() end, { silent = true, remap = false})
-- <Leader>pp 开关 diagnostic
vim.keymap.set({ 'n' }, '<Leader>pp', function(opts) if vim.diagnostic.is_disabled() then vim.diagnostic.enable() else vim.diagnostic.disable() end end, { silent = true, remap = false})
-- <Leader>p? 显示当前 diagnostic 信息
vim.keymap.set({ 'n' }, '<Leader>p?', function(opts) vim.diagnostic.open_float() end, { silent = true, remap = false})
-- <Leader>pj 跳转到下一个 diagnostic
vim.keymap.set({ 'n' }, '<Leader>pj', function(opts) vim.diagnostic.goto_next() end, { silent = true, remap = false})
-- <Leader>pk 跳转到上一个 diagnostic
vim.keymap.set({ 'n' }, '<Leader>pk', function(opts) vim.diagnostic.goto_prev() end, { silent = true, remap = false})

这样可以实现高亮光标下的符号:

CursorHold 事件触发与 updatetime 有关,想要更灵敏地触发 CursorHold 需要设置更小的 updatetime 比如我设置成 500 (毫秒)

1
vim.opt.updatetime = 500
1
2
vim.api.nvim_create_autocmd({'CursorHold', 'CursorHoldI'}, { callback = function(opts) vim.lsp.buf.document_highlight() end })
vim.api.nvim_create_autocmd({'CursorMoved'}, { callback = function(opts) vim.lsp.buf.clear_references() end })

定义 LspRefs 列出光标下符号的所有出现位置

1
vim.api.nvim_create_user_command('LspRefs', function(opts) vim.lsp.buf.references() end, { nargs = 0 })

定义 LspRename 重命名光标下符号

1
vim.api.nvim_create_user_command('LspRename', function(opts) vim.lsp.buf.rename() end, { nargs = 0 })

6. 小结

到现在为止的配置:

点击展开: init.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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
vim.opt.cursorcolumn = true
vim.opt.cursorline = true
vim.opt.expandtab = true
vim.opt.guicursor = ''
vim.opt.mouse = ''
vim.opt.foldenable = false
vim.opt.number = true
vim.opt.shiftwidth = 4
vim.opt.signcolumn = 'number'
vim.opt.softtabstop = 4
vim.opt.tabstop = 4
vim.opt.shellcmdflag = '-c'
vim.opt.shellxquote = '('
vim.opt.shellslash = true

vim.g.loaded_netrw = true
vim.g.loaded_netrwPlugin = true
vim.g.mapleader = ';'

vim.keymap.set('n', '<A-J>', '<Cmd>m .+1<CR>==', { silent = true, remap = false })
vim.keymap.set('n', '<A-K>', '<Cmd>m .-2<CR>==', { silent = true, remap = false })
vim.keymap.set('i', '<A-J>', '<Esc><Cmd>m .+1<CR>==gi', { silent = true, remap = false })
vim.keymap.set('i', '<A-K>', '<Esc><Cmd>m .-2<CR>==gi', { silent = true, remap = false })
vim.keymap.set('v', '<A-J>', ':m \'>+1<CR>gv=gv', { silent = true, remap = false })
vim.keymap.set('v', '<A-K>', ':m \'<-2<CR>gv=gv', { silent = true, remap = false })
vim.keymap.set('n', '<Leader>rn', function() vim.opt.relativenumber = not vim.opt.relativenumber:get() end, { silent = true, remap = false })
vim.keymap.set({ 'n', 'i', 'v' }, '<Leader>%', vim.cmd.vsplit, { silent = true, remap = false })
vim.keymap.set({ 'n', 'i', 'v' }, '<Leader>"', vim.cmd.split, { silent = true, remap = false })

vim.api.nvim_create_user_command('Indent', function(opts)
local width = tonumber(opts.fargs[1])
if width == nil then return end
vim.opt.tabstop = width
vim.opt.softtabstop = width
vim.opt.shiftwidth = width
end, { nargs = 1 })

-- download lazy.nvim
local lazypath = vim.fn.stdpath('data') .. '/lazy/lazy.nvim'
if not vim.loop.fs_stat(lazypath) then
vim.fn.system({ 'git', 'clone', '--filter=blob:none', 'https://github.com/folke/lazy.nvim.git', '--branch=stable', lazypath, })
end
vim.opt.rtp:prepend(lazypath)

require('lazy').setup({
{ 'lukas-reineke/indent-blankline.nvim', main = 'ibl', },
{ 'marko-cerovac/material.nvim', },
{ 'neovim/nvim-lspconfig', },
{ 'numToStr/Comment.nvim', lazy = false, },
{ 'nvim-lualine/lualine.nvim', },
{ 'simrat39/symbols-outline.nvim', },
{ 'nvim-tree/nvim-tree.lua', },
}, {
-- ui = { icons = { cmd = ' ', config = '', event = '', ft = ' ', import = ' ', init = ' ', keys = ' ', lazy = '󰒲 ', list = { '●', '➜', '★', '‒', }, loaded = '●', not_loaded = '○', plugin = ' ', require = '󰢱 ', runtime = ' ', source = ' ', start = '', task = '✔ ', }, },
ui = { icons = { cmd = 'cmd', config = 'config', event = 'event', ft = 'ft', import = 'import', init = 'init', keys = 'keys', lazy = 'lazy', list = { '.', '➜', '*', '-', }, loaded = 'loaded', not_loaded = 'not_loaded', plugin = 'plugin', require = 'require', runtime = 'runtime', source = 'source', start = 'start', task = 'task', }, },
})

require('material').setup()
if vim.fn.has('termguicolors') then
vim.opt.termguicolors = true
end
vim.g.material_style = 'darker'
vim.cmd.colorscheme('material')
vim.cmd.highlight({ 'StatusLine', 'cterm=italic', 'gui=italic'})
vim.cmd.highlight({ 'String', 'cterm=italic', 'gui=italic'})
vim.cmd.highlight({ 'Comment', 'cterm=italic', 'gui=italic'})
vim.cmd.highlight({ 'Todo', 'cterm=italic', 'gui=italic'})

local lspconfig = require('lspconfig')
lspconfig.clangd.setup({})
lspconfig.pyright.setup({ cmd = { 'C:/msys64/usr/bin/zsh.exe', 'C:/msys64/mingw64/lib/pyright-langserver', '--stdio' } })

local ibl = require('ibl')
local ibl_conf = require('ibl.config')
ibl.setup()
vim.keymap.set({ 'i', 'n', 'v' }, '<Leader>ii', function() ibl.update { enabled = not ibl_conf.get_config(-1).enabled } end, { silent = true, remap = false})

require('Comment').setup()

require('lualine').setup({
options = {
theme = 'material',
disabled_filetypes = { 'Outline', 'NvimTree' },
icons_enabled = false,
component_separators = '',
section_separators = '',
},
tabline = {
lualine_a = { { 'buffers', show_filename_only = false, show_modified_status = false, mode = 2 }, },
lualine_z = { 'tabs' },
},
})

require('symbols-outline').setup({
fold_markers = { '>', '-' },
symbols = {
Array = { icon = 'arr', hl = '@constant' },
Boolean = { icon = 'bool', hl = '@boolean' },
Class = { icon = 'class', hl = '@type' },
Component = { icon = 'comp', hl = '@function' },
Constant = { icon = 'const', hl = '@constant' },
Constructor = { icon = 'ctor', hl = '@constructor' },
Enum = { icon = 'Enum', hl = '@type' },
EnumMember = { icon = 'enum', hl = '@field' },
Event = { icon = 'event', hl = '@type' },
Field = { icon = 'field', hl = '@field' },
File = { icon = 'file', hl = '@text.uri' },
Fragment = { icon = 'frag', hl = '@constant' },
Function = { icon = 'func', hl = '@function' },
Interface = { icon = 'iface', hl = '@type' },
Key = { icon = 'key', hl = '@type' },
Method = { icon = 'method', hl = '@method' },
Module = { icon = 'module', hl = '@namespace' },
Namespace = { icon = 'ns', hl = '@namespace' },
Null = { icon = 'NULL', hl = '@type' },
Number = { icon = 'num', hl = '@number' },
Object = { icon = 'obj', hl = '@type' },
Operator = { icon = 'op', hl = '@operator' },
Package = { icon = 'pkg', hl = '@namespace' },
Property = { icon = 'prop', hl = '@method' },
String = { icon = 'str', hl = '@string' },
Struct = { icon = 'struct', hl = '@type' },
TypeParameter = { icon = 'type', hl = '@parameter' },
Variable = { icon = 'var', hl = '@constant' },
},
})

require('nvim-tree').setup({ renderer = { icons = { show = { file = false, folder = false, folder_arrow = false, git = false, }, }, }, })

目前没有配置补全,因为加上补全时候卡顿十分严重,所以我个人觉得到这里就是极限了,现在大概像这样:

配置好的 Neovim 截图

之后有动力给 Linux 的 neovim 做 migrate 的时候估计会补上补全部分,到时候再说吧。