Neovim 初配置

这个就是我一月所说的最初的「篇幅可能比较大」的第三件事,那就是我投靠 Neovim 了,真香。

当然现在其实还只是一个「初配置」,而且可能会维持一段 Vim/Neovim 共存的时期,因为跟 Vim 其实绑定得有点深了,需要缓慢进行过渡。而比较有意思的是,Neovim 在这段时期可以说基本与 Vim 没啥冲突,抢占的更像是 VS Code 的生态位。

在过渡期中,Vim 的角色大概就是固定为了 Markdown 编辑器,这可以说是目前我的配置中 Vim 的立身之本,也是根深蒂固的存在。而 Neovim 则获得了其他文件的编辑/审阅权力。同时为何说抢占了 VS Code 部分生态位呢?因为 Neovim 主题挺好看了,看代码不输 VS Code。当然,Neovim 的存在实际上是提高了 VS Code 的地位。

总的来说就是三赢,Vim 短期仍然无法脱离,Neovim 引入自然是最大赢家,VS Code 也更爱不释手。不过长久来看会逐渐脱离 Vim,所以其实是 Neovim 与 VS Code 的以 Vim 未来为代价的双赢。

废话不多扯了,寒假在家的最后一天的最后一个小时争取写完。配置暂时在 nvim 仓库,这个目前只是个 WSL 与 Windows 配置互连的中转仓库,后续可能调整。

LazyVim

毫无疑问地要采用所谓的「发行版」。

Neovim 那个高速更新迭代的速度,是我一个追新的都受不了的。要是普通的迭代快也就罢了,更新就是了,但是 breaking changes 也多,我个人是断无可能有精力去维护自己的配置的。

然后就是采用大概是社区呼声最高的 LazyVim 了。我之前还是听过部分发行版的,什么 SpaceVim, LunarVim 之类的都有所耳闻,不过一见到 LazyVim,即便当时还没切 Neovim,就决定了到时候将其作为我采用的发行版。

LazyVim 维护者是 folke,这位在 Neovim 社区的地位似乎很高,开发了很多几乎可以称得上是必备的插件。有点像 tpope 之于 Vim,但更甚?

反正就凭这一点就足以让我选中 LazyVim 了,毕竟发行版的维护应该是重中之重,而上面说的 LunarVim 就已经沉寂了。

Lua

然后自然就是 Lua 了,既然用了 Neovim,入乡随俗,会全 Lua 配置(当然其实没做到)。

下面就是分 lua/configlua/plugin,分文件介绍,这样比较快。

config

options.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
vim.o.breakindent = true
vim.o.showbreak = " ↪"

vim.g.snacks_animate = false

local opt = vim.opt

opt.spelllang = "en_us,cjk"
opt.wrap = true
opt.shiftwidth = 4
opt.tabstop = 4

require("config/clipboard")

Vim 很多设置我都没有迁过来,因为 Neovim 做了很多调整,然后还有 LazyVim,所以我目前只改了几个。

首先就是比较新的特性 breakindent,这个才在 Vim 的配置里加了没多久,说不定我 Vim 配置的 Git 仓库还没提交呢。大概就是我下面开了 wrap,长行会折过来,前面加上一点字符表明这是折行。

然后 snacks_animate 关掉动画。这个似乎是 LazyVim 14 引入的?虽然确实很炫酷,但是我感觉很鸡肋。例如说 gg G 什么的,还要滚一下屏幕,就感觉很多余。

然后就是 Tab 设置了,LazyVim 默认设置的是 2,对我来说有点不习惯,我大部分其实都是 4。当然也许后面也会改。

另外就是我对 Neovim 有关 Vim 部分的 API 还不太熟悉,上面 vim.ovim.opt 什么的还不太了解,也是后续可能会进行变动的。不过因为这本身就是初配置,文章就不会改了。

clipboard.lua

然后就是上面最后一行的 require("config/clipboard"),就是这个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
vim.opt.clipboard:append("unnamed")
vim.opt.clipboard:append("unnamedplus")
if vim.fn.has("wsl") and not vim.g.vscode then
vim.g.clipboard = {
name = "OSC52",
copy = {
["+"] = require("vim.ui.clipboard.osc52").copy("+"),
["*"] = require("vim.ui.clipboard.osc52").copy("*"),
},
paste = {
["+"] = "win32yank.exe -o --lf",
["*"] = "win32yank.exe -o --lf",
},
}
end

这个就是 Neovim 的剪贴板配置。

Windows 下的 Neovim 比较简单,没啥需要配置的,但是 WSL 上就有点麻烦了。

不过好在 Windows Terminal 支持 OSC 52,于是上面 copy 部分就采用了这种方案。

但遗憾的是,上面链接中的 PR 说了 OSC 52 的粘贴有安全隐患,而没有得到支持,因此 paste 依旧还是要用 win32yank 方案。Windows 上本来安装 Neovim 就有 win32yank,只是 Scoop 没有将其加入环境变量,为了方便我就自己再装了个 win32yank,后面可能会自己写 manifest。

keymaps.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
vim.keymap.set("i", "jk", "<Esc>", { silent = true, desc = "Escape insert mode" })
vim.keymap.set("i", "kj", "<Esc>", { silent = true, desc = "Escape insert mode" })
vim.keymap.set("n", "U", "<C-r>", { silent = true, desc = "Redo" })
vim.keymap.set("", ";", ":", { silent = true, desc = "Command mode" })
vim.keymap.set("", ":", ",", { silent = true, desc = "Repeat opposite f/t/F/T" })
vim.keymap.set("", ",", ";", { silent = true, desc = "Repeat f/t/F/T" })
vim.keymap.set("", "`", "'", { silent = true, desc = "First non-blank location mark" })
vim.keymap.set("", "'", "`", { silent = true, desc = "Precise location" })
vim.keymap.set("", "H", "0", { silent = true, desc = "Begining of the line" })
vim.keymap.set("", "L", "$", { silent = true, desc = "End of the line" })
vim.keymap.set("n", "Y", "<Cmd>%y<Cr>", { silent = true, desc = "Yank the whole file" })
vim.keymap.set("i", "<C-s>", "<Cmd>w<Cr>", { silent = true, desc = "Save file" })

vim.keymap.set("", "gj", "j", { silent = true, desc = "Move down actual line" })
vim.keymap.set("", "gk", "k", { silent = true, desc = "Move up actual line" })
vim.keymap.set(
"n",
"<leader>o",
"<Cmd>call append(line('.'), repeat([''], v:count1))<CR>",
{ silent = true, desc = "Insert line below" }
)
vim.keymap.set(
"n",
"<leader>O",
"<Cmd>call append(line('.') - 1, repeat([''], v:count1))<CR>",
{ silent = true, desc = "Insert line above" }
)
vim.keymap.set(
"v",
"gC",
"<Cmd>'<,'>s/\\v([一-龟])@<=(\\w+)([一-龟])@=/ \\2 /e <Bar> '<,'>s/\\v([一-龟])@<=(\\w+)([一-龟])@!/ \\2/e <Bar> '<,'>s/\\v([一-龟])@<!(\\w+)([一-龟])@=/\\2 /e <Bar> noh<CR>",
{ silent = true, desc = "Add space around Chinese" }
)
vim.keymap.set("c", "w!!", "w !sudo tee > /dev/null %", { silent = true, desc = "Write as sudo" }) -- Add a command to replace

vim.opt.mouse = ""

这个也是很头疼的一个。只转了几个,而且有几个是有点问题的。

JK 什么的就不提了,是生活的必需品。

还有一些基础的,如 U, H, L, Y, Ctrl + S什么的。

不过 ;, :, ,, `, ' 这几个倒是值得说道说道。

首先是为了少按一个 Shift 而将命令模式(Command)改成的单键 ;,这个改了后有一个缺点,之前 : 按了后会立刻跳出命令模式的浮动窗口,而换成 ; 后就没有了,必须要等命令的第一个字符键入才会出现。我简单搜罗了一番,没找到相关问题。另外加 remap 也不奏效。然后也试过就使用 Vim 原版键位,多按就多按,可移植性高,但还是没能坚持下去,这个缺陷属于是能容忍的范畴。

然后就是后面几个切换方位的,flash.nvim 似乎重写了,导致了这几个重映射的键就被浪费掉了。我后续可以看看能不能接上去。

还有一个命令模式的 w!!,这个是在 Linux 中用来强制写不可写的文件的 keymap,由于 LazyVim 为了服务 which-nvim 而将 timeoutlen 设置得很小(300ms),因此必须要按得非常快才能完成这个映射的转换,所以说这个实际上已经名存实亡了。我注释也写了,做成一个命令会更合适。

禁用鼠标,这没啥好说的。

plugin

colorscheme.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
return {
{
"ellisonleao/gruvbox.nvim",
opts = {
contrast = "soft",
transparent_mode = true,
},
},

{
"LazyVim/LazyVim",
opts = {
colorscheme = "gruvbox",
},
},
}

嘿嘿,还是我最喜欢的 Gruvbox 主题,这个也用了好几年了吧,比高一高二用的 One Dark 用得还久了,到现在也还没腻。

设置了对比度低一点,默认的太黑暗了。还有就是开透明模式了,因为我终端也开透明了,感觉能看背面挺爽的。等开学记笔记看看实用性如何(不对,可能很长一段时间都记不了)。

coding.lua

1
2
3
4
5
6
7
8
9
10
return {
{
"echasnovski/mini.align",
keys = {
{ "ga", mode = { "n", "v" }, desc = "Align" },
{ "gA", mode = { "n", "v" }, desc = "Align with preview" },
},
opts = {},
},
}

插件这块我命名比较乱,也是后面可能大重构的地方。

这引入了对齐插件,这个 mini.nvim 挺强大的,它有自己的 text object 我还没去研究。总之还得适应一下新的对齐插件,现在还有对齐需求依旧得靠 vim-easy-align

editor.lua

1
2
3
4
5
6
7
8
return {
{
"folke/flash.nvim",
keys = {
{ "S", mode = { "n", "o", "x" }, false },
},
},
}

flash.nvim 默认的 S 似乎是 treesitter 的跳转?我捣鼓了一下没发现能让我眼前一亮(甚至其实用不了),所以先废了,为另一个东西腾出空间。

surround.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
return {
{
"echasnovski/mini.surround",
opts = {
custom_surroundings = {
["("] = { input = { "(().-())" }, output = { left = "(", right = ")" } },
[")"] = { input = { "(().-())" }, output = { left = "(", right = ")" } },
["【"] = { input = { "【().-()】" }, output = { left = "【", right = "】" } },
["】"] = { input = { "【().-()】" }, output = { left = "【", right = "】" } },
["《"] = { input = { "《().-()》" }, output = { left = "《", right = "》" } },
["》"] = { input = { "《().-()》" }, output = { left = "《", right = "》" } },
["‘"] = { input = { "「().-()」" }, output = { left = "「", right = "」" } },
["’"] = { input = { "「().-()」" }, output = { left = "「", right = "」" } },
["“"] = { input = { "『().-()』" }, output = { left = "『", right = "』" } },
["”"] = { input = { "『().-()』" }, output = { left = "『", right = "』" } },
["「"] = { input = { "「().-()」" }, output = { left = "「", right = "」" } },
["」"] = { input = { "「().-()」" }, output = { left = "「", right = "」" } },
["『"] = { input = { "『().-()』" }, output = { left = "『", right = "』" } },
["』"] = { input = { "『().-()』" }, output = { left = "『", right = "』" } },
},
mappings = {
add = "Sa",
delete = "Sd",
find = "Sf",
find_left = "SF",
highlight = "Sh",
replace = "Sr",
update_n_lines = "Sn",
},
},
},
}

这个也是 mini.nvim 大家族的一员。上面说废掉 S 就是为了这个腾出空间。因为 surround 用得还算是频繁的,而 LazyVim 默认分配的是 gs,多按了一个键,还是挺难受的,所以我把 S 分给了它。当然为了这个 S 还有一些要做的。

另外 mini.surround 本来是不支持一些特殊字符的自定义的,所以我提了一个 issue:Allow CJK (fullwidth) punctuations as surrounding id。本来计划要写时那会还没修,结果一直拖到现在才写,作者老早就解决了。

另外这个 mini.surround 似乎还挺强大的,我还没细细研究。而且研究这个之前可能还得研究前置组件了解它的体系?我暂时没时间,所以将就用一下。

当然现在这个配置比起 vim-surround 还是有点不便。例如都考虑可视模式(Visual),之前的添加 surrounding,只需要直接 S" 就行了,而 mini.surround 要 Sa"

不过 mini.surround 也提供了 vim-surround 式的映射方式,但我权衡了一下还是放弃了。旧习惯和新特性,我还是选择新特性吧。

1
2
3
4
5
6
7
8
9
10
11
return {
{
"folke/which-key.nvim",
opts = {
triggers = {
{ "<auto>", mode = "nixsotc" },
{ "S", mode = "nv" },
},
},
},
}

按照 which-key.nvim 的文档,因为自定义了一个,要额外配一下,手动加入支持。

latex.lua & markdown.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
return {
{
"lervag/vimtex",
ft = { "markdown", "tex" },
config = function()
vim.g.tex_conceal = "abgs"

vim.g.vimtex_view_method = "zathura"

vim.g.maplocalleader = "\\"

vim.g.tex_flavor = "latex"

vim.g.vimtex_texcount_custom_arg = " -ch -total"

vim.g.vimtex_compiler_latexmk_engines = {
_ = "-xelatex",
pdflatex = "-pdf",
dvipdfex = "-pdfdvi",
lualatex = "-lualatex",
xelatex = "-xelatex",
["context (pdftex)"] = "-pdf -pdflatex=texexec",
["context (luatex)"] = "-pdf -pdflatex=context",
["context (xetex)"] = "-pdf -pdflatex='texexec --xtx'",
}

vim.g.vimtex_compiler_latexmk = {
out_dir = function()
return "out"
end,
callback = 1,
continuous = 1,
executable = "latexmk",
hooks = {},
options = {
"-verbose",
"-file-line-error",
"-shell-escape",
"-synctex=1",
"-interaction=nonstopmode",
},
}

vim.g.vimtex_quickfix_mode = 0
vim.g.vimtex_quickfix_open_on_warning = 0

vim.g.vimtex_toggle_fractions = {
frac = "dfrac",
dfrac = "frac",
}

vim.g.vimtex_delim_toggle_mod_list = {
{ "\\left", "\\right" },
{ "\\bigl", "\\bigr" },
{ "\\Bigl", "\\Bigr" },
{ "\\biggl", "\\biggr" },
{ "\\Biggl", "\\Biggr" },
}

vim.g.vimtex_syntax_conceal = {
accents = 1,
ligatures = 1,
cites = 1,
fancy = 1,
spacing = 1,
greek = 1,
math_bounds = 0,
math_delimiters = 1,
math_fracs = 1,
math_super_sub = 1,
math_symbols = 1,
sections = 0,
styles = 1,
}
end,
},
}

latex.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
return {
{
"MeanderingProgrammer/render-markdown.nvim",
opts = {},
},
{
"iamcco/markdown-preview.nvim",
ft = "markdown",
init = function()
vim.g.mkdp_port = "18282"
vim.g.mkdp_theme = "light"
vim.g.mkdp_preview_options = {
katex = {
trust = false,
macros = {
["\\e"] = "\\mathrm{e}",
...
},
},
}
vim.cmd([[
function OpenMarkdownPreview (url)
silent execute '!"/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe" --app=' . a:url
endfunction
]])
vim.g.mkdp_browserfunc = "OpenMarkdownPreview"
end,
},
}

markdown.lua 还启用了一个新插件 markdown-preview.nvim。这也贡献了目前配置中唯一一处 Vim Script,我目前也没找到 Lua 写法。

另外这个预览插件默认是不支持 admonition 的。这可不行,于是要自己修改一下,写在了一处 issue,懒得转述了。

像这样的话其实还方便了一点,之前 COC 的 MPE,偶尔会端口变化,必须自己重新弄一个。而像这个就固定住了端口,还可以设置好相应的打开函数,直接以应用形式打开对应的网页,美哉。

文件类型配置

也就是 ftplugin 目录的东西,目前也没什么。

先是 tex.lua

1
2
3
4
5
6
7
8
vim.opt_local.spell = true
vim.opt_local.spelllang = "en_us,cjk"

vim.api.nvim_create_augroup("vimtex_config", { clear = true })
vim.api.nvim_create_autocmd({ "User VimtexEventQuit" }, {
group = "vimtex_config",
command = "call vimtex#compiler#clean(0)",
})

再是 markdown.lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
vim.opt_local.spell = true

vim.g.vimtex_syntax_conceal = {
accents = 1,
ligatures = 0,
cites = 1,
fancy = 1,
spacing = 1,
greek = 1,
math_bounds = 0,
math_delimiters = 1,
math_fracs = 1,
math_super_sub = 1,
math_symbols = 1,
sections = 0,
styles = 1,
}

vim.g.vimtex_compiler_enabled = false
vim.g.vimtex_mappings_enabled = false
vim.g.vimtex_imaps_enabled = false

都是照搬。另外 spell 似乎是多余的?LazyVim 好像都启用了?反正我看我拼写检查语言配置前 C++ 代码中文注释那里都有波浪线。

VS Code

期末那会看了看 VSCode Neovim 插件,顿时给吸引住了。也许就是那个时候决定了暑假要配一下。

之前就大概了解过,VS Code 的 Vim 方案有两种,一种是 VSCodeVim,另一种就是 VSCode Neovim。前者只是在 VS Code 中模拟 Vim,而后者则是「真正的 Neovim」。

不过因为我当时还在用 Vim,同时还听说后者不稳定,因此选择了前者,算是按着历史的路老老实实地走了吧。

VSCodeVim 性能问题确实堪忧,这个用久了确实诟病。另外由于是「模拟」,它的支持实际上也挺弱的,虽然大部分时候够用,但不够用时就会很蛋疼。

VSCode Neovim 思路就很好,Neovim Everywhere 这个思想我是很喜欢的。原生 Neovim 所以性能会好一点,这个我有切身体会。此外还能用很多原生插件,例如说 flash.nvim 什么的,所以说就能直接丢掉 VS Code 的 Leap 插件等了。

当然也不是完美无缺就是了,VSCode Neovim 最大的缺点,上面也说了,就是不稳定。有时候用着用着就定住了,动不了了,然后过一会就会跳出提示 "Syncing layout: Syncing active editor"(有时候 active 会是 visible)。

这是一个相当致命的问题,很多人也遇到了,Neovim 主要维护者也说了近期能解决,希望如此吧。

这个问题发生毫无征兆,而且一旦发生,重启也没用,不管是重启 Neovim 还是重启 VS Code。另外重启 Neovim 可以获得一定的缓冲时间,重启完后几秒钟内是可以自由活动的,然后过一阵子就重复了。

不过好在虽然致命,但也不算频繁,还是可以用的。

另外即使不使用 Neovim,只用 Vim,也更推荐 VSCode Neovim 而非 VSCodeVim(当然就需要额外装个 Neovim),因为原生 Vim 体验。

1
2
3
4
5
6
7
8
return {
{
"vscode-neovim/vscode-multi-cursor.nvim",
event = "VeryLazy",
cond = not not vim.g.vscode,
opts = {},
},
}

然后来看看 lua/plugins/vscode.lua,引入了多光标插件。另外 Neovim 的多光标支持不知道何时能实现,Roadmap 上写的是 0.12+,嗯,等待。

这个 vscode-multi-cursor.nvim 重装前可以正常使用,重装后还没试。

1
2
3
4
5
6
7
8
if vim.g.vscode then
local vscode = require("vscode")

vim.keymap.set({ "n", "x" }, "<C-e>", "mciw*<Cmd>nohl<CR>")

vim.keymap.set("n", "u", "<Cmd>call VSCodeNotify('undo')<CR>")
vim.keymap.set("n", "U", "<Cmd>call VSCodeNotify('redo')<CR>")
end

然后是 lua/config/keymaps.lua 的 VS Code 部分。

u U 用的是 VS Code 的撤销功能,因为 Neovim 的好像用不了。

此外还加了个 Ctrl + E,是原版 VS Code Ctrl + D 的功能,只不过 Ctrl + D 在 Neovim 我也难以割舍,就换了个 Ctrl + E。重装前可用,刚刚测试了一下不可用,得找时间看看了。

除了上面的 u U 不可用外,JK 之类的也是,得额外在 VS Code 配置:

1
2
3
4
5
6
7
8
"vscode-neovim.compositeKeys": {
"jk": {
"command": "vscode-neovim.escape"
},
"kj": {
"command": "vscode-neovim.escape"
}
}

浏览器

最后就是 Firenvim 了,时间也不多了我也就不多吹逼我对它的感想了,直接复制重装电脑那会的记录

说实话这个插件很早以前,恐怕是高二,就已经见过了,那时候即使是 Vim 也是早期。可以说这个概念是有点震撼到我的,Neovim everywhere。甚至可以说我的 Vark 都有一部分想法是源自这惊鸿一瞥。当然实际用处可能不多就是了,但炫就完了。

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
return {
{
"glacambre/firenvim",
lazy = not vim.g.started_by_firenvim,
cond = not not vim.g.started_by_firenvim,
module = false,
build = function()
vim.fn["firenvim#install"](0)
end,
config = function()
vim.api.nvim_create_autocmd({ "BufEnter" }, {
pattern = "github.com_*.txt",
command = "set filetype=markdown",
})

vim.g.firenvim_config = {
globalSettings = { alt = "all" },
localSettings = {
[".*"] = {
cmdline = "neovim",
content = "text",
priority = 0,
selector = "textarea",
takeover = "never",
},
},
}
end,
},
}

这是 lua/plugins/browser.lua 的配置。

不过如果是第一次启动,要把 lazycond 注释一下,因为第一次要安装东西。当然本来就是多余的,也可以直接删。

别的不多说了,就到这里吧。