Vim 使用技巧

 

使用折叠

查看 :h fold.txt 手册了解 Vim 中的折叠功能。

折叠方式

Vim 的 foldmethod 有以下 6 种折叠方式

foldmethod value description
fold-manual manual 手动建立折叠。
fold-indent indent 相同缩进距离的行构成折叠。
fold-expr expr ‘foldexpr’ 给出每行的折叠级别。
fold-marker marker 标志用于指定折叠。
fold-syntax syntax 语法高亮项目指定折叠。
fold-diff diff 没有改变的文本构成折叠。

比如: 为 json 文本创建折叠

set ft=json
syntax on
set fdm=syntax

查看 :h 'fold-methods', :h 'foldmethod' 了解折叠方式的详细说明。

折叠命令

所有的折叠命令用 z 开头。如果你从侧面看 z 像一张叠起来的纸。foldlevel 用来设置折叠层数,高于该值的层数会被折叠,foldlevel=0,会关闭所有折叠,数字越大,关闭的折叠越少。

打开关闭折叠

命令 效果
zo 打开光标下折叠 (open)
zO 循环打开光标下所有折叠 (Open)
zc 关闭光标下折叠 (close)
zC 循环关闭光标下所有折叠 (Close)
za 切换光标下折叠 (打开或关闭)
zA 切换光标下所有折叠 (循环打开或循环关闭)
zv 仅打开足够的折叠使光标所在的行不被折叠 (view)
zx 更新折叠,恢复原来的 foldlevel, 然后使用 zv 查看光标所在行
zX 恢复被打开和关闭的折叠, 重置 foldlevel
zm foldlevel 减 1,折叠更多层 (more)
zM 关闭所有折叠,foldlevel 设置为 0
zr foldlevel 加 1,减少折叠 (reduce)
zR 打开所有折叠,foldlevel 设置为最高

查看 :h fold-commands 了解更多折叠命令。

character-classes 和 collection

Vim 与其它正则引擎有点不同的是,不支持将字符类 (:h /character-classes,如 \i, \w, \f\ \p 等) 放入到集合中 (:h /collction[]),例如不支持 [\w\s]\+ 这种语法。但支持在集合中使用 POSIX character class expressions (如 [:alnum:]),因此我们可以使用以下格式来匹配常用字符

" Match UUID, [:alnum:] equlas [A-Za-z0-9]
g/[[:alnum:]-]\+/

将多行合并为一行并用逗号分隔

使用 paste 命令

%!paste -sd, -

在 vim 中格式化 xml 文本

使用 xmllint 命令 (macOS 系统自带)

%!xmllint --format -

在 vim 中格式化 json 文本

安装 jq 命令

brew install jq

使用 vim 打开 json 文件,在 vim 使用下面的命令。

:%!jq .

tabstop, softtabstop, expandtab, shiftwidth 和 retab 的关系

  • tabstop
    • 打开文件时,将一个 TAB 字符显示成几个空格的长度。
  • softtabstop
    • 在 Insert 模式下,按下 Tab 键后,应当插入的空格的数量;
    • 如果未设置 expandtab,插入的仍会是 TAB 字符。
  • expandtab
    • 告诉 Vim 在按下 Tab 键时插入空格而不是 TAB 字符。
  • shiftwidth
    • 在换行时,自动缩进的空格长度,
    • 使用 ><= 做缩进操作时的空格长度。
  • retab
    • 重置 TAB 字符,将文件中的 TAB 字符转换为空格。

总结一下,softtabstop 控制着 TAB 和退格键的表现,而 expandtab 决定了是否将 TAB 转换为空格。当两者结合使用时,可以创建一个只使用空格,而不使用 TAB 字符的编辑环境,并且能够精确控制缩进的大小。

Vim Modeline

Modeline 有两种写法,第一种形式是 vi: 后以 set 开始,遇到第一个冒号 : 后结束,中间的选项以空格分隔

# vi: set ff=unix ts=2 sw=2 :

第二种形式是 vi: 后直接跟选项列表,选项之间以冒号 : 或空格分隔

# vi: ff=unix ft=sh ts=4 sw=4 expandtab
# 或
# vi: ff=nnix:ft=sh:ts=4:sw=4:et

具体语法查看 Vim 文档 :h modeline

比如在 Jenkinsfile 中,想让 Vim 打开文件时自动识别文件类型为 groovy,可以在文件顶部加入下面的注释

// vi: ft=groovy

注意:在不同的文件类型内,要用对应的注释语句,比如在 python 文件内应该使用

# vi: ft=python

类似的还可以设置文件的缩进等,比如为 shell 文件设置缩进为 4

# vi: ts=4 sw=4 expandtab

在 Vim 中插入当前文件名

在 Insert 模式中,按下 Ctrl-R,然后再按 % 即可直接插入当前文件名。

Ctrl-R 的作用是插入寄存器中的内容,当按下 CTRL-R 时,屏幕会显示一个 " 字符,提示你输入一个寄存器的名字。下面是 Vim 中的特殊寄存器:

'"'     无名寄存器,包含最近一次删除或复制的内容
'%'     当前文件名
'#'     轮换文件名
'*'     剪贴板的内容 (X11: 主选择区)
'+'     剪贴板的内容
'/'     最近一次的查找模式
':'     最近一次在命令行输入的命令
'-'     最近一次小的 (少于一行) 删除
'.'     最近插入的文本
'='     表达式寄存器: 会提示你输入一个表达式。

使用 :h <c-r> 查看该命令帮助文档,使用 :h registers 查看寄存器帮助文档。

Vim 中 dos 换行符与 unix 换行符相互转换

set ff=unix
set ff=dos

将查找到的字符串改为大(小)写形式

" 全部大写
:g/string/s//\U\0\E/

" 首字母大写
:g/string/s//\u&/

\0&表示引用整个匹配字符串,\u表示下一个字符变为大写,\U表示其后字符变为大写,直到\e\E出现。具体内容可以查看帮助:h :s\=

执行外部命令

比如需要运行当前编辑的 python 代码

:!python %

!{cmd}在 Vim 中执行外部命令(查看帮助:h :!),%为当前编辑的文件名(查看帮助:h cmdline-special:h :_%)。

使用 sudo 强制更新文件

:w !sudo tee %

w !{cmd}将整个 buffer 作为标准输入执行{cmd}(查看帮助:h :w_c),%为当前编辑的文件名,tee从标准输入读取内容后再将内容写到标准输出及文件中(类似 T 型三通)

排序,去重

" 倒序
:sort!

" 按数字排序
:sort n

" 去重
:sort u

" 忽略大小写
:sort iu

统计个数

" 查看count-items帮助
:h count-items

" 查看s_flags了解替换标志
:h s_flags

" 使用substitute命令
:%s/./&/gn            " characters
:%s/\i\+/&/gn         " words
:%s/^//n              " lines
:%s/the/&/gn          " 'the' anywhere
:%s/\<the\>/&/gn      " 'the' as a word

将 g/pattern/搜索到的行写入新文件

:redir > match.txt
:g/regex/
:redir END

要注意的是,:g/regex/w match.txt并不会将匹配的行写入新文件,而是在每次匹配到的行上执行w match.txt命令,并且是将当前 buffer 的所有内容写入新文件。

给每行自动编号

使用 Vim 内置命令 line(),用法可以查看帮助 :h line():h :.

:%s/^/\=line('.') . '、'/

'.'表示所在行的行号。当:s 命令的替换字符串以"\="开头时,表示以表达式的计算结果作为替换值,.用于连接字符串。

多行匹配(multiline match)

Vim 中默认是单行匹配,比如/hello\sworld/,只能匹配单行中 “hello world”,其中 \s 匹配空格或 tab。如果希望多行匹配,可以使用/hello\_sworld/,这里的 \_s 表示匹配空格、tab 和换行符。

搜索 /^hello 会在行首匹配到 hello,搜索 /hello\$ 会在行尾匹配到 hello。但是在模式 /hello^world 和 /hello\$world 里的 ^ 和 $ 只是普通字符,没有特殊含义。

  • \n a newline character (line ending)
  • \_s a whitespace (space or tab) or newline character
  • \_^ the beginning of a line (zero width)
  • \_$ the end of a line (zero width)
  • \_. any character including a newline

例如,搜索

  • /hello\n*world

    1. Finds hello followed by zero or more newlines then world.
    2. Finds helloworld or hello followed by blank lines and world.
    3. The blank lines have to be empty (no space or tab characters).
  • /hello\_s*world

    1. Finds hello followed by any whitespace or newlines then world.
    2. Finds helloworld or hello followed by blank lines and world.
    3. The blank lines can contain any number of space or tab characters.
    4. There may be whitespace after hello or before world.
  • /hello\_$\_s*world

    1. Finds hello at end-of-line followed by any whitespace or newlines then world.
    2. There must be no characters (other than a newline) following hello.
    3. There can be any number of space, tab or newline characters before world.
  • /hello\_s*\_^world

    1. Finds hello followed by any whitespace or newlines then world where world begins a line.
    2. There must be no characters (other than a newline) before world.
    3. There can be any number of space, tab or newline characters after hello.
  • /hello\_$world
    Finds nothing because \_$ is “zero width” so the search is looking for helloworld where hello is also at end-of-line (which cannot occur).

  • /hello\_^world
    Finds nothing because \_^ is “zero width” so the search is looking for helloworld where world is also at beginning-of-line (which cannot occur).

  • /hello\_.\{-}world

    1. Finds hello followed by any characters or newlines (as few as possible) then world.
    2. Finds helloworld or hello followed by any characters then world.
  • /hello\(\_s.*\)\{0,18\}\_sworld

    1. Finds a block of 0 to 18 lines enclosed by hello and world.
    2. limiting the number of lines is important, replacing this by a star will cause vim to consume 100% CPU.