对目前配置使用的Neovim做一个小结回顾梳理。

NeoVim

使用的是v0.9版本。

1
2
3
NVIM v0.10.0-dev-340+g33687f5e8
Build type: RelWithDebInfo
LuaJIT 2.1.0-beta3

插件管理器

插件管理器多种选择,如:packer.nvimwbthomason/packer.nvimsavq/paq-nvimNTBBloodbath/cheovim,我选用的是vim-plug(原因是他很常见,刚接触插件安装时遇到的就是它,后面也没有再做调整)。

后续进行启动时间优化时,将插件管理改为了 packer.nvim,并改用纯 lua 进行配置 neovim。 使用纯lua进行一些优化后,成功将启动时间从214.900ms 降低到了121.062ms。 优化方向:

  1. 使用效率更好的插件进行替代;
  2. 延迟加载插件;
  3. 使用基于Lua编写的插件;
  4. 使用纯lua替代VimScript进行配置NeoVim;
  5. 缓存lua模块编译 lewis6991/impatient
  6. 设置Lazyredraw;
  7. 删除或者禁用不用的插件;
  8. 禁用一些内置插件

最后,我将iterm2 更换成alacritty + tmux + Hammerspoon , Neovim 平均启动时间进一步降低到了98ms。 在使用 Packer 延迟加载功能,对相关插件进行延迟加载;使用filetype.nvim 优化filetype.vim在启动过程中的行为,将平均启时间降低到42ms

插件

字体

使用的是Nerd Fonts中的Blex*(详见:https://github.com/ryanoasis/nerd-fonts#option-4-homebrew-fonts) 。macOS平台上 Nerd Fonts所有字体在Homebrew Cask Fonts 都是可用的:

1
2
brew tap homebrew/cask-fonts
brew install --cask font-blex-mono-nerd-font

Awesome

  • Awesome Neovim - Collections of awesome Neovim plugins. Mostly targeting Neovim specific features.
  • Vimawesome - Showcases various plugins for vim and has a neovim tag for other plugins targeting Neovim.
  • awesome-vim - Short list of vim plugins and helpful guides.

小技巧

img

文档操作

  • :e- 重新加载当前文档
  • :w- 保存修改
  • :wq- 保存并退出
  • ZZ- 保存并退出
  • :x- 保存并退出
  • :q[uit]- 退出当前窗口(CTRL-W q或CTRL-W CTRL-Q)
  • :gf- 跳转到当前光标下文字对应的文件
  • vim -o file1.txt file2.txt- 同时打开文件,水平分割窗口
  • vim -O file1.txt file2.txt- 同时打开文件,垂直分割窗口

切分窗口

  • <CTRL+w>+s- 水平切分当前窗口,新窗口仍显示当前缓冲区
  • <CTRL+w>+v- 垂直切分当前窗口,新窗口仍显示当前缓冲区
  • :sp[lit] {file}- 水平切分当前窗口, 并在新窗口中载入{file}
  • :vsp[lit] {file}- 垂直切分当前窗口, 并在新窗口中载入{file}

更改分割窗口大小

  • <CTRL-w>-- 将分割窗口高度减少一行
  • <CTRL-w>+- 高度增加一行
  • <CTRL-w>>- 将分割窗口宽度增加一行
  • <CTRL-w><- 宽度减少一行

关闭窗口

Ex 命令 普通模式命令 用途
:clo[se] <CTRL+w>+c 关闭活动窗口
:on[ly] <CTRL+w>+o 只保留活动窗口,关闭其他所有窗口

切换窗口

  • <CTRL+w>w- 在窗口见循环切换
  • <CTRL+w>h- 切换到左边窗口
  • <CTRL+w>l- 切换到右边窗口
  • <CTRL+w>k- 切换到上边窗口
  • <CTRL+w>j- 切换到下边窗口

缓存区

  • :buffers:files:ls- 列出当前缓冲区,默认情况下列出可以显示的缓存区
  • :ls!- 列出所有缓存区,包括不显示的
  • :bNext- 切换到下一个缓冲区
  • :bprevious- 切换到上一个缓冲区
  • :blast- 切换到最后一个缓冲区
  • :bfirst- 切换到第一个缓冲区
  • :bdelete- 删除缓冲区
  • :bunload- 卸载缓冲区,为缓冲区打开的窗口也会关闭,但文件名仍保留在缓冲区列表中
  • :badd- 添加缓冲区

移动

  • H- 把光标移到屏幕最顶端一行
  • M- 把光标移到屏幕中间一行
  • L- 把光标移到屏幕最底端一行
  • gg- 到文件头部
  • G- 到文件尾部

基本插入

  • i- 在光标前插入;一个小技巧:按8,再按i,进入插入模式,输入=, 按esc进入命令模式,就会出现8个=。 这在插入分割线时非常有用,如30i+就插入了36个+组成的分割线
  • I- 在当前行第一个非空字符前插入
  • I- 在当前行第一列插入
  • a- 在光标后插入
  • A- 在当前行最后插入
  • o- 在下面新建一行插入
  • O- 在上面新建一行插入
  • :r filename- 在当前位置插入另一个文件的内容
  • :[n]r filename- 在第n行插入另一个文件的内容
  • :r !date- 在光标处插入当前日期与时间。同理,:r !command可以将其它shell命令的输出插入当前文档

搜索

  • *- 向下搜索光标所在词
  • g*- 同上,但部分符合即可
  • #- 向上搜索光标所在词
  • g#- 同上,但部分符合即可
  • g CTRL-g- 统计全文或统计部分的字数

改写

  • c[n]w- 改写光标后1(n)个词
  • c[n]l- 改写光标后n个字母
  • c[n]h- 改写光标前n个字母
  • [n]cc- 修改当前[n]行
  • C- C可更改光标所在位置到此行结尾间的文本。它的功用和c加上特殊行末指示符$的效果―样(c$)
  • [n]s- 以输入的文本替代光标之后1(n)个字符,相当于c[n]l
  • [n]S- 删除指定数目的行,并以所输入文本代替之
  • r- 把一个字符替换成宁外一个,结束后不需要按 ESC 回到命令模式

全局替换

  • s/old/new- 将当前行第一个出现的模式 old 改为 new
  • s/old/new/g- 将当前行每一个 old 改为 new
  • 50,100s/old/new/g- 将第50行到第100行之间每一个 old 修改为 new
  • %s/old/new/g- 全局替换每一个出现的 old 为 new
  • %s/bacsdfws/&,HELLO/- 将每一行的第一个 bacsdfws 替换为 bacsdfws,HELLO 。& 在模式匹配替换字符串时,会被替换成搜索模式匹配出的完整文本,这可以避免重复输入要被替换的文本
  • %s/\(foo\)hello\(world\)/\1 \2/g 将每一行的foohelloworld 替换为fooworld.

    • \(foo\)foo反向引用
    • \& 插入搜索模式匹配的文本
    • \0 插入与整个模式匹配的文本
    • \1 插入第一个反向引用的文本
    • \2 插入第二个反向引用, \3,\4 依次类推
  • s#/usr/local/bin#/usr/sbin#g - 将行中所有的usr/local/bin 替换成/usr/sbin,vim/neovim 允许在在模式替换中使用任何正则表达式 分隔符,根本不需要使用 /,使用#进行替代,这样就可以避免转义斜线

删除

  • dw- dw会删除光标所在位置的单词,请注意单词后的空白也会被删除
  • ddD- 删除当前光标所在行
  • d0- 从光标位置删除当前光标所在行的开头
  • d$- 从光标位置删除当前光标所在行的结尾
  • x- 它只会删除光标所在位置的字符
  • :g/re/d- 删除所有的匹配行,global 命令允许在某个指定模式的所有匹配行上运行Ex命令: :[range] global[!] /{pattern}/ [cmd]
  • :v/re/d- 只保留匹配行
  • :g/^$/d - 删除掉所有的空白行
  • :g/^#/d - 删除所有#开头的行

重复上一个命令

  • .- “想要重复相同的编辑命令时,可以使用重复命令——句号(.),以节省时间”

撤销

  • u- “以撤销上一个命令只要按下u即可,光标不需要在原来下命令时所在的位置”

缩进操作

  • >>- 向右缩进
  • <<- 向左缩进

翻屏

  • ctrl+f- 下翻一屏
  • ctrl+b- 上翻一屏
  • ctrl+d- 下翻半屏
  • ctrl+u- 上翻半屏
  • ctrl+e- 向下滚动一行
  • ctrl+y- 向上滚动一行
  • n%- 到文件n%的位置
  • zz- 将当前行移动到屏幕中央
  • zt- 将当前行移动到屏幕顶端
  • zb- 将当前行移动到屏幕底端

编程辅助

  • [[- 跳转到上一个函数块开始,需要有单独一行的{
  • ]]- 跳转到下一个函数块开始,需要有单独一行的{
  • []- 跳转到上一个函数块结束,需要有单独一行的}
  • ][- 跳转到下一个函数块结束,需要有单独一行的}
  • ci’, di’, yi’- 修改、剪切或复制’之间的内容
  • ca’, da’, ya’- 修改、剪切或复制’之间的内容,包含’
  • ci”, di”, yi”- 修改、剪切或复制”之间的内容
  • ca”, da”, ya”- 修改、剪切或复制”之间的内容,包含”
  • ci(, di(, yi(- 修改、剪切或复制()之间的内容
  • ca(, da(, ya(- 修改、剪切或复制()之间的内容,包含()
  • ci[, di[, yi[- 修改、剪切或复制[]之间的内容
  • ca[, da[, ya[- 修改、剪切或复制[]之间的内容,包含[]
  • ci{, di{, yi{- 修改、剪切或复制{}之间的内容
  • ca{, da{, ya{- 修改、剪切或复制{}之间的内容,包含{}
  • ci<, di<, yi<- 修改、剪切或复制<>之间的内容
  • ca<, da<, ya<- 修改、剪切或复制<>之间的内容,包含<>
  • va'- 选中’之间的内容,包括`
  • vi'- 选中’之间的内容
  • xp- 交换两个字母位置
  • :23t.- 复制第23行且将其粘贴到当前行的下一行
  • :t23- 复制当前行到第23行的下一行

修改大小写

  • Vu- 整行小写
  • guu- 整行小写
  • VU- 整行大写
  • gUU- 整行大写
  • ~- 反转光标所在字符的大小写

忽略大小写

If the ignorecase option is on, the case of normal letters is ignored. smartcase can be set to ignore case when the pattern contains lowercase letters only. When \c appears anywhere in the pattern, the whole pattern is handled like ignorecase is on. The actual value of ignorecase and smartcase is ignored. \C does the opposite: Force matching case for the whole pattern. {only Vim supports \c and \C} Note that ignorecase, \c and \C are not used for the character classes.

1
2
3
4
5
6
7
8
Examples:
      pattern   'ignorecase'  'smartcase'       matches
        foo       off           -               foo
        foo       on            -               foo Foo FOO
        Foo       on            off             foo Foo FOO
        Foo       on            on                  Foo
        \cfoo     -             -               foo Foo FOO
        foo\C     -             -               foo

拼写

  • [s: 跳转到上一处拼写错误
  • ]s: 跳转到下一处拼写错误
  • z=: 对光标下或之后的单词,显示建议正确的拼写单词
  • zg: 将光标下的单词作为正确拼写单词加入到‘spellfile’
  • zw: 和zg类似,只是zw是将这个单词标记为拼写错误的单词

执行外部命令

  • :!- cmd 执行外部命令
  • :!!- 执行上一次的外部命令
  • :sh- 调用shell,用exit返回vim
  • :r !cmd- 将命令的返回结果插入文件当前位置
  • :m,nw !cmd- 将文件的m行到n行之间的内容做为命令输入执行命令

  • qa- 开始录制宏a(键盘操作记录)
  • q- 停止录制
  • @a- 播放宏a

命令行

  • :6t.- 把第六行复制到当前行下方,:t是命令:copy简写形式 [range]copy {address}
  • :6m.- 把第六行移动到当前行下方,:move是命令:move简写形式,格式:[range] {address}
  • :'<,'>m$- 把选中的文本移动到文件结尾,其中:'<,'>代表高亮选区,’<是代表高亮选区行首的位置标记,’>是代表高亮选区的最后一行
  • :'<,>'p- 回显上一次高亮选区选中的内容

排序

  • :sort- 升序重排所有行
  • :sort!- 降序重排所有行
  • :sort u- 升序号所有行,并去除重复行
  • :sort n- 按数值升序重拍所有行(此时100排在20后面)
  • 1,100sort- 第1行到第100行,进行升序重排
  • :g/start/.,/end/-1 sort n- 使用正则表达式进行匹配开始和结束行,对从start开始到end结束的所有文本行,进行数值升序重排
  • 1,$!sort -t',' -k2 -r- 使用外部命令sort,将第一行到最后一行,-t参数指定以,为分隔,-k参数指定按第二个字段进行降序排列

可视模式

  • v- 进入可视化模式, 移动光标高亮选择, 然后可以对选择的文本执行命令(比如y-复制)
  • V- 进入可视化模式(行粒度选择)
  • o- 切换光标到选择区开头/结尾
  • Ctrl + v- 进入可视化模式(矩阵选择)
  • O- 切换光标到选择区的角
  • aw- 选择当前单词
  • ab- 选择被 () 包裹的区域(含括号)
  • aB- 选择被 {} 包裹的区域(含花括号)
  • at- 选择被 <> 标签包裹的区域(含<>标签)
  • ib- 选择被 () 包裹的区域(不含括号)
  • iB- 选择被 {} 包裹的区域(不含花括号)
  • it- 选择被 <> 标签包裹的区域(不含<>标签)
  • Esc- 退出可视化模式
  • 使用可视模式选择和normal 命令来注释和取消注释代码:
    • 可视模式下选择目标
    • normal i#norm i#: 添加 #
    • normal xnorm x: 取消每行开头的第一个字符,这里也就是开头的#

可视化模式命令

  • >- 向右缩进
  • <- 向左缩进
  • y- 复制
  • d- 剪切
  • ~- 大小写切换
  • u- 将选中文本转换为小写
  • U- 将选中文本转换为大写

代码折叠

  • zo- 在光标下打开折叠
  • zc- 在光标下关闭折叠
  • za- 切换光标下打开、关闭状态
  • zr- 将所有折叠打开一层
  • zm- 将所有折叠关闭一层
  • zR- 打开全部折叠
  • zM- 关闭全部折叠

其他

1、启动耗时检查

检查Neovim启动耗时情况,可以使用参数--startuptime <file>,将启动时间消耗信息写入的文件<file>中。

2、格式化JSON文件

可以通过执行外部命令:

1
:%!python -m json.tool 

也可以通过NeoVim支持的LSP功能进行格式化,先通过安装JSON LSP server:

1
npm i -g vscode-langservers-extracted

再在配置文件中激活LSP server:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
local on_attach = function(client, bufnr)
    buf_set_keymap('n', '<space>f', '<cmd>lua vim.lsp.buf.formatting()<CR>', opts)
end

local servers = { 'jsonls' }
for _, lsp in ipairs(servers) do
  nvim_lsp[lsp].setup {
    on_attach = on_attach,
    flags = {
      debounce_text_changes = 150,
    }
  }
end

配置成功后,在使用<sapce>f快捷键就可以完成格式化JSON文件

3、快速计算一列数字的平均值

如有下面的一段文本,要计算第一列表数字的平均值:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
106.826  000.003- --- NVIM STARTED ---
107.274  000.003- --- NVIM STARTED ---
107.374  000.003- --- NVIM STARTED ---
107.436  000.003- --- NVIM STARTED ---
108.689  000.004- --- NVIM STARTED ---
108.824  000.004- --- NVIM STARTED ---
109.007  000.003- --- NVIM STARTED ---
109.509  000.003- --- NVIM STARTED ---
109.715  000.003- --- NVIM STARTED ---
110.581  000.003- --- NVIM STARTED ---
111.096  000.004- --- NVIM STARTED ---

可以使用Ex命令 :r!awk '{total+=$1} END {print "Average- " total/NR}' %, 文件末尾添加:

1
Average-  111.244

命令详解:

  • :r- 读取文件内容插入到光标所在位置的下一行
  • !- 执行外部命令
  • awk- 外部命令awk
  • {total+=$1}- 累加第一列数值求和赋值给total
  • {print "Average- " total/NR}:计算并输出平均值(NR:已读取的记录数)
  • %- 当前文件

4、给文件的每一行开头添加序号

1
2
3
:%!nl -ba 
:%!cat -n
:%!awk '{print NR $0}'

5、tabstop, softtabstop, expandtab, shiftwidth 和 空格

  • tabstop: tabstop选项设置tab字符显示宽度为多少个空格
  • expandtab: set expandtab后,在插入模式,按TAB键所插入的tab字符会被替换成合适数目的空格
  • softtabstop: softtabstop 选项影响Vim在插入模式下按TAB键所得到的实际字符,可能是特定数目的空格,也可能是一个tab字符。具体受到 tabstopexpandtab 配置项影响
  • shiftwidth: 配置进行缩进时插列数

使用下面的配置:

1
2
3
4
: set tabstop=2
: set shiftwidth=2
: set softtabstop=2
: set expandtab

或者

1
: set tabstop=2 shiftwidth=2 softtabstop=2 expandtab

将tab字符显示为2个空格,缩进时使用2个空格,插入模式下键入TA替换成两个空格 在上述配置下,如果文件中已经有tab字符,此时要将替换成2个空格,可以使用命令:

1
: retab

6、Listing autocommands

  • auto[cmd] [group]: 显示所有autocommands,如果提供[group]参数,Vim 仅会列出[group]相关的autocommands
  • verbose autocmd BufEnter: 显示最后定义的BufEnter autocommands
  • auto[cmd] [group] {event}: 显示{event}事件相关的所有 autocommands

7、查看highlight group

  • verbose highlight: 显示所有 hightlight 组样式定义
  • verbose highlight GroupName: 显示指定的 highlight 组的样式定义

8、设置透明背景

系统环境:

1
2
3
4
5
6
7
❯ kitty --version
kitty 0.31.0 created by Kovid Goyal
❯ nvim --version
NVIM v0.10.0-dev-1873+g8f08b1efb
Build type: RelWithDebInfo
LuaJIT 2.1.1702233742
Run "nvim -V1 -v" for more info
  1. 修改 kitty 背景透明度为0.9, macos_traditional_fullscreen 从 no 设置为 yes
  2. 修改 neovim 配置,设置:
1
2
3
4
vim.cmd [[highlight Normal guibg=none]]
vim.cmd [[highlight NonText guibg=none]]
vim.cmd [[highlight Normal ctermbg=none]]
vim.cmd [[highlight NonText ctermbg=none]]

有趣的历史

Vim(及其家族)的词源

ed 是最初的 UNIX 文本编辑器,它编写于图形显示器很稀有的年代,那时源代码通常是打印在纸带上,并在电传终端机 上编辑。 在终端上输入的命令被送到大型机上进行处理,每条命令的输出会被打印出来。在那个年代,从终端到大型机之间的连接很慢,以至于一个快速打字员比网络还快,他们输入命令的速度要比命令被发出去处理更快。 在这种情况下,ed 能够提供一个简洁的语法变得异常重要。p 被用来打印当前行,而 %p 被用来打印整个文件,皆缘于此。

ed 历经了几代的改进,包括em (意为“editor for mortals”,即“人类的编辑器”)、en ,最终到ex。此时图形显示器已经比较普及了,ex 增加了一个把终端屏幕设置成交互窗口的功能,并在窗口内显示文件的内容。这样,在做修改时实时看到变化成为了可能。此屏幕编辑模式由 :visual 命令激活,其简写为 :vi,这即是 vi 这个名字的由来。

Vim 代表改进版的 vi (vi improved),更多信息通过查阅 :h vi-differences,我们可以看到Vim支持而 vi 不支持的功能列表。 Vim 对功能的增强是必要的,但另一方面它却仍继承了大量的遗产。这些指导 Vim 先祖们设计的约束,提供了一个非常高效的命令集,这在今天依然很有价值。

学习资料

引用参考

  1. Neovim
  2. Awesome Neovim
  3. VIM 命令操作教程
  4. 简明 VIM 练级攻略
  5. 130+vim基本命令
  6. vi / vim 初学者入门(系列文章)
  7. nanotee/nvim-lua-guide
  8. getting-started-with-vim
  9. 《学习vi和vim编辑器》
  10. 《Vim实用技巧》
  11. nvim lua guide
  12. The 300 line init.lua challenge
  13. configuring neovim using lua
  14. Speedup neovim
  15. reduce startup and improve lua config
  16. turning neovim into a full fledged code editor with lua
  17. Vim Tips Wiki
  18. Neovim Tips
  19. Vim Cheat Sheet
  20. Easier buffer switching
  21. Variables Reference in Debugging configuration files
  22. Motion
  23. Using marks