读书笔记:《Vim实用技巧》第二版

[TOC]

前言

记得在刚学 Vim 的时候,就把 《Vim实用技巧》(英文名:Practical Vim)通读了一遍(当时读的应该是第一版),可以说就是这本书让我真正进入了 Vim 的世界。

也因为在很早期就系统学习了 Vim 的相关技巧,因此使用 Vim 还是挺得心应手的。只是 Vim 的功能实在是繁多,日常使用也就只是用了 Vim 特性的十之一二,很多的东西不常用,慢慢地也就忘记了。

刚好看到 《Vim实用技巧》 第二版,就想着温故知新,重新再读一遍,把自己不常用但感觉好用的技巧给记录下来,方便以后查阅。

:据网上介绍,《Vim实用技巧》 第二版与第一版内容上区别不大,主要改动为:

  • 技巧36:批处理运行Ex命令
  • 技巧84:对完整的查找匹配进行操作
  • 技巧86:统计当前模式的匹配个数
  • 技巧97:在多个文件中执行查找与替换
  • 技巧111:使用Vim内置正则表达式引擎的Grep
  • 技巧117:自动补全单词序列

摘抄

:以下只会记录本人不熟悉或觉得需要记录的内容,不会事无巨细记录完整内容。

  • Vim 在线文档:vimhelp.org
    Vim 在线中文文档 1:vimdoc
    Vim 在线中文文档 2:vimdoc_github

  • 查看按键符号:h key-notation

    读书笔记:《Vim实用技巧》第二版_第1张图片
    key-notation

  • 使用 Vim 的出厂配置:vim -u NONE -N
    其中:
    -u NONE:该标志让 Vim 在启动时不加载.vimrc文件
    -N:该标志使能nocompatible选项,防止进入 vi 兼容模式
    -u标识可在 Vim 启动时加载指定配置文件,如:

" 只加载 code/essential.vim 配置文件
vim -u code/essential.vim
  • 查看 Vim 版本::version

  • S:删除整行并进入插入模式,相当于^C

  • 当使用f{char}/t{char}时,;正向寻找下一个,,反向回到上一个。

  • i{insert some text}称为一次修改,也即进入insert模式到退出回到normal模式,则完成一次修改。因此通过合适的控制一次修改的粒度,就可以控制撤销u的粒度。

  • vu:转成小写
    vU:转成大写
    ~:反转大小写
    gxx:转换当前行。比如:

guu " 当前行转成小写
gUU " 当前行转成大写
g~~ " 当前行反转大小写
  • Vim 模式:
  1. normal mode:正常模式

  2. insert mode:插入模式

  3. command-line mode:命令行模式,即按下:键时,Vim 就会切换到命令行模式。
    ▪ 出于历史原因,在命令行模式中执行的命令又被称为 Ex 命令
    ▪ 在按/调出查找提示符或用=访问表达式寄存器时,命令行模式也会被激活。
    ▪ 更多命令行模式提供的命令,请查看: :h ex-cmd-index

  4. visual mode:可视模式

  5. select mode:选择模式,与其他的文本编辑器类似的功能,在文本选中状态下,按键会直接替换选中区域。
    :在选中状态下,按可切换select modevisual mode,下方提示栏可查看当前模式:

    visual-select-mode

  6. operator-pending mode:操作符待决模式,即在输入操作符(比如c/d/y,具体查看::h operator)后,进入只接受动作命令的状态。比如在执行动作命令dw时,当按下操作符d时,该模式立即激活,此时 Vim 会记录d这个按键并等待后续命令动作,当按下w时,该模式立即结束,并执行dw操作。

更多模式,请查看::h mode

  • Vim 在insert mode时插入寄存器内的文本时(比如,{register}),其插入方式相当于键盘一个一个字符输入插入的文本,因此,如果激活了textwidth或者autoindent选项的话,则可能会出现不必要的换行或额外的缩进。
    如果想按原义进行插入,可使用{register} 或者直接回到normal mode进行粘贴。

  • gv高亮上一次选中区域

  • 对于列块可视模式(/),I/A命令会分别置于当前光标之前/之后,而不是跳转到开头/结尾。
    在可视模式以及操作符待决模式中,i/a命令会被当做一个文本对象的组成成分,而不是直接插入。

  • command-line mode中,%代表所有行/当前文件名(参见::h cmdline-special)。因此:

:%d                " 删除所有行
:%s/target/replace " 对所有行执行替换操作
:echo expand('%')  " 显示当前文件名
  • ://,/<\/html>/p:打印文中标签的所有内容。

  • ://+1,/<\/html>/-1p:打印文中标签内的所有内容。

  • 命令行模式使用normal命令结合.命令更加强大

" 注释所有行
:%normal I// 
" 指定宏命令
:normal @q
" 执行点命令
:%normal .
  • .:重复上次的普通模式命令
    @::重复上次的 Ex 命令
    执行一次 Ex 命令后,后续相同命令可使用@@命令进行重复
    ▪ 寄存器:总是保存着最后一次执行的命令行命令(参见::h quote
    ./:都是寄存器,可直接使用:register查看所有寄存器内容
:reg . " 查看 . 寄存器
:reg / " 查看 /  寄存器
:reg 0 " 查看复制专用寄存器
:reg a " 查看寄存器 a
  • bn[next]:打开缓冲区列表下一个
    bp[revious]:打开缓冲区列表上一个

  • 命令行模式下,命令可以显示所有可用的补全列表::col

  • 把当前单词插入到命令行
    把当前字符串插入到命令行

  • 命令行窗口:命令行历史记录,可进行选择与修改。

  • q::打开命令行历史窗口
    q/:打开搜索历史窗口

  • 如果在 bash shell 中运行 Vim 时,可通过Ctrl-z挂起 Vim 进程回到 bash;
    在 bash 中,可以用fg命令回到后台 Vim 进程。

  • 把 bash 命令的结果读入到 Vim 缓冲区::read !{cmd}
    把 Vim 缓冲区内容作为{cmd}命令的标准输入::write !{cmd}

:read !ping www.baidu.com

ping www.baidu.com
:w !sh

:可以通过范围指定传递给{cmd}的缓冲区内容:

ping www.baidu.com

echo "only run this command"

" 高亮选择 echo 语句,然后输入以下命令,就只会执行 echo 这句的命令
:'<,'>w !sh
  • bufdo可以对:ls列出的所有缓冲区上执行 Ex 命令:
:bufdo echo expand('%')
  • 通配符*用于匹配 0 个或多个字符,仅限当前指定目录,不递归子目录
    通配符**用于匹配 0 个或多个字符,仅会递归子目录
:args *.*     " 将当前目录所有文件都加入到参数列表中,不包含子目录文件

:args **/*.*  " 将当前目录及其子目录的所有文件都加入到参数列表中

:args **/*.js " 将当前目录及其子目录的所有 .js 文件都加入到参数列表中

:args **/*.js **/*.css " 将当前目录及其子目录的所有 .js 和 .css 文件都加入到参数列表中

:使用args添加文件到参数列表时,会同时将该文件添加到缓冲区中。

  • 参数列表比缓冲区列表更容易管理,这使其成为对缓冲区进行分组的理想方式。
    args:打印数列表。
    args {arglist}:设置参数列表。
    arga[dd] {names}:添加文件参数列表。
    argd[elete] {pattern}:删除参数列表文件。
    %argd[elete]:删除参数列表所有文件。
    :next:跳转到下一个参数列表文件。
    :prev:跳转到上一个参数列表文件。
    :argdo:对参数列表中的每个缓冲区都执行同一条 Ex 命令。
    :参数列表是缓冲区的强烈补充。
    更多详细信息,请查看::h args

  • :lcd {path} 命令让我们可以设置当前窗口的本地工作目录。

  • :e %会展开%代表的含义,即文件名
    同理::e %:p/:e %:h···

  • find命令允许通过文件名查找打开文件,查找目录由path决定。因此可以通过设置path选项,结合find命令可快速打开某个文件:

" ** 表示当前文件夹及其子文件夹内所有文件
set path+=**,app/**,/usr/local/include

:find nameoffile.vim
" 或 tab键自动补全路径
:find nameof
  • e:正向移动到当前单词/下一单词的结尾
    ge反向移动到上一单词的结尾

  • word:单词(跨单词移动使用:w/b/e/ge
    WORD:字串(跨字串移动使用:W/B/E/gE

  • b:表示圆括号()
    B表示花括号{}

  • Vim 的自动位置标记

位置标记 跳转位置
'' 当前文件中上次跳转动作之前的位置
'. 上次修改的地方
'^ 上次插入的地方
'[ 上次修改或复制的起始位置
'] 上次修改或复制的结束位置
'< 上次高亮选区的起始位置
'> 上次高亮选取的结束位置
  • gi:回到上次插入模式离开的位置,并再次进入插入模式。此命令使用'^标记恢复光标位置,并切换进入插入模式。
    gf:跳转到光标所在的文件。如果文件缺少后缀名,可通过set suffixesadd+=.js进行指定。
  • Vim 不区分键,因此当被重新映射时,也会被重映射,不会再有跳转功能。

  • 无名寄存器"":Vim 默认使用的寄存器。
    黑洞寄存器_:即直接删除:_d{motion}
    详情参考::h quote_quote/:h quote_

  • 串行执行宏:10@{reg}
    并行执行宏:1,$ normal @{reg},对全部行分别同时执行宏。
    :这里的 并行 指定不是同时执行,而是对每个行单独执行,某一行操作失败也不会导致其余行的宏停止。

  • \V原义查找:
    1)正向查找时,需转义字符/
    2)方向查找时,需转义字符?
    3)任何方向查找,都需转义反斜杠字符\
    :可用编程方式转义字符:

escape({string}, {chars})
   " {chars} 参数指定哪些字符串需要进行转义:
   " 正向查找:escape({string},'/\')
   " 反向查找:escape({string},'?\')

getcmdtype() " 该函数在正向查找时,返回'/',方向查找时,返回'?'

" 正向查找
:/\V=escape("https://www.baidu.com?key=whyn",getcmdtype().'\')
" 反向查找
:?\V=escape("https://www.baidu.com?key=whyn",getcmdtype().'\')
  • 使能*/#支持可视模式搜索全部高亮字串:
xnoremap * :call VSetSearch('/')/=@/
xnoremap # :call VSetSearch('?')?=@/
function! s:VSetSearch(cmdtype)
    let temp = @s
    norm! gv"sy
    let @/ = '\V' . substitute(escape(@s, a:cmdtype.'\'), '\n', '\\n', 'g')
    let @s = temp
endfunction
  • substitute替换命令:其格式如下:
:[range]s[ubstitue]/{pattern}/{string}/[flags]

以下介绍substitute常用的标志位:flags
g:修改一行内所有匹配。
c:交互式确认。
n:不进行替换,只是显示匹配个数。
e:匹配不到时,不显示错误信息。
&:重用上一次substitute的所使用的标志位。
更多详细信息,请查看::h s_flags/

以下介绍替换域一部分常用特殊符号:

符号 描述
\r 插入一个换行符
\t 插入一个制表符
\\ 插入一个反斜杠
\1 插入第一个子匹配
\2 插入第二个子匹配(以此类推,最多到\9
\0 插入匹配模式pattern的所有内容
& 插入匹配模式pattern的所有内容
~ 使用上一次substitute{string}
\={Vim script} 执行{Vim script}表达式,并将其结果作为替换{string}

▪ 如果substitute命令使用了寄存器内容,而该寄存器内容存在特殊字符(如&~)时,则需要手动进行转义,当然还有更简单的方法,就是直接引用寄存器内容即可:

:%s//\=@{register}/g

" 比如,替换域直接引用复制专用寄存器
:%s//\=@0/g

*:当执行替换:s/target/replacement后,后续使用&可重复执行上一次的substitue命令,normal模式按下&Ex模式输入:&均可起作用。
:正常模式下使用g&会在全局执行上一次的substitute命令,相当于执行了::%s//~/&

  • quickfix列表中的每个条目文件执行命令:
:vimgrep /\Vleader/ **/*.vim " 递归查找当前文件夹所有拥有字符串 leader 的 .vim 文件,存储到 quickfix 列表中
:set hidden                  " 对 quickfix 列表进行命令执行前,需要设置该选项
:cfdo %s//Leader/gc          " 使用 cfdo 命令对 quickfix 列表各条目文件进行替换命令操作
:cfdo update                 " update 用于保存文件(当文件有改动时,才进行保存)

quickfix列表中的每个条目文本执行命令::cfdo {cmd}
更多信息,请查看::h quickfix

  • global命令:该模式结合了 Ex 命令和 Vim 的模式匹配这两方面的能力,即该命令可以让我们在符合模式匹配的那些行上执行 Ex 命令。其格式如下:
:[range] g[lobal][!] /{pattern}/[cmd]

global命令缺省作用范围为整个文件(%),其他大多数命令(如::delete/:substitute/:normal)的缺省作用范围为当前行。
[cmd]命令可以是除:global命令之外的任何 Ex 命令。缺省则默认为:print
global命令的执行机制global命令在指定的[range]内的文本上执行时通常分为两轮操作:
第一轮:Vim 标记匹配[pattern]的所有行
第二轮:在所有标记的文本行上执行[cmd]

举个栗子:将含有TODO注释的行复制到寄存器中

:qaq " 清空寄存器 a
:g/\VTODO\yank A " 将匹配内容追加到寄存器 a 中。注:不能使用小写,否则最后一条会覆盖前面内容
:reg a " 此时查看下寄存器 a 的内容,应该可以看到操作成功的内容

可以结合t命令将匹配文本行复制到文本结尾

:g/\VTODO/t$ 

global命令后的[cmd]也可以带一个范围,此时[cmd]命令的范围是基于global范围之上的,其格式为:

:[range1] g[lobal][!] /{pattern}/[range2][cmd] " range2 基于 range1,即 cmd 在匹配行内在进行范围选择

举个栗子:对以下 css 属性按字母进行排序,即排序{}内的所有内容:

html {
  border: 0;
  font-size: 100%;
  margin: 0;
  font: inherit;
  vertical-align: baseline;
  padding: 0;
}

body {
  background: white;
  color: black;
  line-height: 1.5;
}

执行使用以下命令即可完成:

:g/\V{/ .+1,/\V}/-1 sort

上述代码中:
/\V{/:表示global命令的匹配
.+1,/\V}/-1:表示cmd的范围,此处范围基于global的匹配范围
上述代码其真实形式如下:

:g/\V{/ (.+1,/\V}/-1 sort) " 括号()范围内即为 cmd
  • 定制grep程序:Vim 中:grep命令可以调用外部grep命令,并对外部grep命令结果进行解析,然后输出到quickfix窗口进行查看。
    Vim 通过配置grepprggrepformat两个选项对外部grep命令进行包装:
    grepprg:指定调用的 shell 程序(参见::h 'grepprg'
    grepformat:配置 Vim 解析外部grep命令输出结果的格式(参见::h 'grepformat')。
    ▪ Vim 在 Unix 系统中的缺省设置如下:
grepprg="grep -n $* /dev/null"  
grepformat="%f:%l:%m,%f:%l%m,%f %l%m

其中:
grepprg中,$* 表示占位符,最终会被:grep命令的参数代替。
grepformat中,各占位符的意思如下:

占位符 描述
%f 文件名
%l 行号
%c 列号
%m 匹配行的文本

grepformat字符串可以使用,分割指定多组解析格式,比如 Vim 缺省的grepformat

grepformat="%f:%l:%m,%f:%l%m,%f %l%m

即表示对格式为%f:%l:%m%f:%l%m%f %l%m的行可以进行解析,比如,外部grep的输出如下所示:

department-store.txt:1:Waldo is beside the boot counter.
goldrush.txt:6:Waldo is studying his clipboard.
goldrush.txt:9:The penny farthing is 10 paces ahead of Waldo.

可以看到是符合%f:%l:%m的解析格式的,因此 Vim 可以对外部命令grep进行解析并输出到quickfix窗口中。

因此,自定义一个外部grep命令传递给 Vim 简直不要太容易了。

举个栗子:比如我们使用rgrep(号称当前最快的文本搜索神器)进行文本搜索,外部命令如下:

rg --line-number --column --no-heading --smart-case {word}

上述命令输出如下:

basic\settings.vim:2:5:let {word}=' '

根据命令与输出,我们就可以对 Vim 内置的:grep命令进行配置了:

set grepprg=rg\ --line-number\ --column\ --no-heading\ --smart-case\ $*
set grepformat=%f:%l:%c:%m

配置完成后,在 Vim 中输入::grep! {word},然后使用命令:copen打开quickfix窗口,就可以看到搜索结果了。

  • 查找参数列表文件内容vimgrep中,##会被扩展为参数列表中的所有文件:
:args *.txt " 把当前目录所有 .txt 文件加到参数列表中(不递归子目录)
:vimgrep /\Vgoing/g ## " 搜索参数列表中所有出现 going 内容的文件

其他

以下是对 Vim 的一些补充内容。

  • Vim 提供了键,用于作为用户自定义命令的命名空间。

  • 书签:m{char}
    跳转到书签(光标到书签所在行首):'{char}
    跳转到书签(具体到定义书签时的光标位置)`{char}

  • 搜索后光标停留在匹配单词的第一个字符上(默认):/word/s
    搜索后光标停留在匹配单词的最后一个字符上:/word/e
    搜索后光标停留在匹配单词的最后一个字符的下一个字符上:/word/e+1
    搜索完成后,可通过命令行输入://e//s改变匹配单词字符位置。

  • %s/{pattern}//gn:统计pattern出现的次数。
    :vimgrep /{pattern}/g %:统计pattern出现的位置。需要使用:copen打开quickfix窗口进行查看。

  • gn代表当前位置的高亮区域。比如搜索时,当前光标所在的匹配单词即可使用gn进行操作:dgn,删除当前高亮单词。

  • /Practical \zsVim:查找Practical Vim,但只高亮Vim
    /Practical \zsVi\zem:查找Practical Vim,但只高亮Vi

  • 查看加载的插件:scriptnames

  • 更改到当前文件所在的目录::lcd %:p:h
    lcd:改变当前窗口的工作路径
    %:代表当前文件的文件名
    :p:当前文件名全路径
    :h:取出当前文件所在的目录

  • 查看按键绑定信息::verbose map {keys}

:verbose map q " q按键绑定信息
  • H:移动光标到屏幕的首行
    M:移动光标到屏幕的中间一行
    L:移动光标到屏幕的尾行

zz:让光标所在的行居屏幕中央
zt:让光标所在的行居屏幕最上一行
zb:让光标所在的行居屏幕最下一行

  • 删除空行::g/^\s*$/d

  • 删除行尾空白::%s/ *$//

  • 删除DOS方式的回车^M:%s/\r//g

  • 移动光标到上一次的修改行'.
    移动光标到上一次的修改点`.
    列出你跳转的足迹::ju(mps)
    依次沿着你的跳转记录向回跳
    依次沿着你的跳转记录向前跳

  • 缓冲区转成标签页:tab sb[uffer] {num}

  • 为每个窗口都设置选项:windo setlocal scrollbind

  • 好像有很多g命令啊:
    gv:高亮上一次选中区域。
    ge:反向移动到上一单词的结尾
    gi:回到上次插入模式离开的位置,并再次进入插入模式。此命令使用'^标记恢复光标位置,并切换进入插入模式。
    gf:跳转到光标所在的文件。如果文件缺少后缀名,可通过set suffixesadd+=.js进行指定。
    gn代表当前位置的高亮区域。比如搜索时,当前光标所在的匹配单词即可使用gn进行操作:dgn,删除当前高亮单词。

你可能感兴趣的:(读书笔记:《Vim实用技巧》第二版)