Practical Vim

每日Vim

规则:

操作 = 操作符 + 命令

一.模式

普通模式

  1. 无论光标在何处,删除一个单词最快daw,记忆delete a word

  2. <C-a>命令 对数字执行加法,<C-x>命令 对数字执行减法, 在命令前加上count可以进行算数运算

  3. gu修改为小写/gU修改为大写(operator)

  4. ap表示一段a paragraph

插入模式

  1. backspace的代替操作:

    • <C-h>删除一个字符
    • <C-w>删除前一个单词
    • <C-u>删除至行首
  2. <C-o>zz,进入插入-普通模式,zz重新绘制屏幕

  3. <C-r> {register},插入寄存器的内容,

    • 插入表达式寄存器的内容 = 利用表达式寄存器做计算
    • 插入复制寄存器的内容 = <C-r> 0

可视模式

v:面向字符的可视模式

V:面向行的可视模式

<C-v>:面向列的可视模式

gv:重新选择上次的high light区域

  1. 切换选区的活动端点,正常是左上的端点固定,右下的端点活动,o使得右下的端点固定,左上的端点活动

    使用o切换端点配合w :下一个单词 b上一个单词 e单词的词尾

  2. <C-v>切换到列模式,配合I在行首插入,和r单次替换

  3. 同时修改列文本:<C-v>切换到列模式选中区域后,c替换所有.

  4. <C-v>模式中ia不在是插入,请使用I,和A

命令行模式

  1. ex命令格式:

    : [range] delete/substitute/ []/{}

    命令:

    :[range]delete [x] 删除指定行到寄存器x

    :[range]yank [x] 复制指定范围行到寄存器x

    :[line]put [x] 在指定行后粘贴寄存器x中的内容

    :[range] copy {address} 把指定范围内的行拷贝到{address}所指定的行下

    :[range] move {address}把指定范围内的行移动到{address}所指定的行下

    :[range] join 连接指定范围内的行

    :[range] normal {commands} 对指定范围的每一行执行普通模式命令

    :[range] substitute/{pattern}/{string}/{flags} 把指定范围内出现的{pattern}地方替换为{string}

    :[range] global/{pattern}/[cmd] 对指定范围内出现匹配{pattern}的所有行,在其上执行ex命令

  1. 特殊符号含义:

    • 单个数字表示行,
    • $表示文件尾部(在普通模式下表示行尾部),
    • .表示当前行
    • %表示文件中的所有行. 全局替换 :%s/待替换字符串/新字符串
    • {address}+n表示行偏移. 显示当前行,到偏移3行后的内容 : .,.+3p
  2. 地址指定范围:

    • 格式:{start},{end}
  3. normal命令,(指定范围上执行普通模式命令),一般配合.使用,进行大量重复操作,

    • 如果其他普通模式命令一定要和normal分开,
    • normal命令会自动将光标移动到每一行的起始处

    例如给指定行添加分号 :.+1,$ normal A;

  4. <C-r><C-w>将当前光标的单词插入到命令行,

    1. 在执行ex命令时使 插入单词到command line
    2. 查看vimrc文档,:h <C-r><C-w>
  5. 完整的命令行窗口:

    • q:打开ex命令历史的命令行窗口 (注意和退出命令是相反的,难怪有时候会按错按出来)
    • q/:打开查找命令历史的命令行窗口
    • <C-f>从command line mode切换到命令行窗口(保留当前输入的命令)
  6. 使用shell的几种操作

    • <C-z>挂起vim,然后fg回到vim
    • :shell命令行启动shell,然后exit回到vim
    • :!{cmd}命令行执行一条shell命令,%表示当前文件名
  7. 有关标准输入输出

    • :read !{cmd}将命令的标准输出插入到光标下
    • :[range] write !{cmd},选中范围,让后将其作为命令的标准输入
      • 好用:[range] write !tee xxx.file:将当前选中的buffer的内容写入到外面的一个文件中
    • 使用命令作为过滤器(filter),(选中输入->修改->写回). [range] !{cmd}

二.文件

buffer基本管理:

vim中buffer是文件在内存的映像

  1. buffer列表的基本操作:
    • 显示缓冲区列表ls,
    • b{n}切换到buffer n,
    • b {bufname}等价于上面
    • bp=bprev切换到上一个buffer,
    • bn=bnext切换到下一个buffer,
    • bf=bfirst切换到第一个buffer,
    • bl=blast切换到最后一个Buffer
    • %表示当前显示的buffer,
    • #表示轮换的Buffer(使用<C-^>可以快速切换到轮换buffer)、
    • 删除buffer(不影响文件,删除的内存的数据)bdelete
  2. 参数列表(args)记录了启动时传递给vim的文件列表.这个不是固定的,是可以改变的.
    • glob模式打开文件args *.*打开当前模式的任意文件,arg **/*.css,打开上一层目录中所有子目录包含的css文件,所以*只能在当前目录用,**可以递归进入子目录.
    • 参数列表执行shell命令 :args `pwd`
  3. 隐藏缓冲区hidden buffer:
    1. 当修改当前buffer后不保存想要切换到另一个buffer中需要加!强制执行,此时被修改的Buffer有h标记表示变成hidden buffer.
    2. edit!重新将磁盘文件覆盖到当前被修改的Buffer中
    3. write=w将buffer内容写回到磁盘中
    4. qa!强制退出所有的窗口
    5. wa!所有buffer写回到磁盘
  4. :argdo {cmd},对所有的缓冲区执行命令cmd

window(窗口)基本管理:

窗口是显示buffer的

打开新窗口默认的buffer是上一个窗口的buffer

  1. 创建窗口:

    • <C-w>s:水平切割窗口 水平当做split默认记
    • <C-w>v:竖直切割窗口 vertical
    • edit给窗口更换buffer
  2. 创建窗口+更换buffer(<C-w>s+edit):

    • split {filename}
    • vsplit {filename}
  3. 窗口切换:

    • <C-w> {hjkl}和normal mode下的移动是一样的
    • <C-w>w在窗口间循环切换
  4. 关闭窗口(buffer是仍然存在的)

    • :close命令 关闭活动窗口
    • :only命令 关闭除活动窗口外的所有窗口
    • <C-w>c同上
    • <C-w>o同上
  5. 窗口大小设置

    • <C-w>= 窗口等宽,高.
  6. 类似:argdo {cmd}, :windo {cmd}是对所有的窗口执行cmd命令.

    例如 设置所有窗口的当前工作目录 :windo lcd Documents/

标签页将窗口分组

vim的标签页是vim窗口的容器,将窗口组织到工作区中,类比桌面workspace

  1. 创建标签页 tabedit {filename}
  2. 关闭标签页 tabc{lose}
  3. 关闭除当前标签页外的所有标签页 tabo{nly}
  4. 标签页切换
    • ex命令(不方便) 去第n个标签页::tabn {N}, 下一个标签页:tabn,上一个标签页tabp
    • normal mode: 去第n个标签页{N}gt, 下一个标签页gt, 上一个标签页gT
  5. 标签页移动tabm{ove} {N}:方便重新给标签页排序

基本文件管理

  1. ex命令edit打开文件:edit {filename}

    配置技巧: cmd mode下:%%得到当前文件所在目录(配合edit使用)

    cnoremap <expr> %% getcmdtype() == ':' ? expand('%:h').'/' : '%%'

  2. vim默认的文件管理器,edit {path}打开文件管理器窗口

  3. :edit %,%符号表示当前文件的完整路径

    :edit %:h,:h会去掉当前文件,只留下路径,

  4. vim打开一个不存在的文件后如何保存:

    1. 先创建此文件存在的目录:!mkdir -p %:h
    2. 保存文件就行:w

三.快速移动和快速转跳

  1. 行之间的基本移动:

    实际行和屏幕行之间的移动

    jk命令会根据实际行,gjgk按照屏幕行来上下移动.

    移动到行首0,移动到屏幕行首g0

    移动到行首^((自己单独做了映射)),移动到屏幕行首g^

    移动到行尾$((自己单独做映射)),移动到屏幕行尾g$

  2. 基于单词和字符串的移动

    • 单词(之间)的移动

    w 下一个单词

    b上一个单词

    e单词的词尾

    ge上一个单词的词尾

    • 字符串对应的移动()

      vim的字符串比单词更简单,非空白字符序列组成(以空白字符分割):

      W下一个字符串

      B上一个字符串

      E下一个字符串词尾

      gE上一个字符串词尾

  3. 基于字符查找的移动

    1. 通过normal mode下的f{char}来快速移动,所以f{char}配置,;移动非常快

      N,和n是重复搜索模式的查找.

      ;,是重复f命令的查找

      最好不要将<Leader>映射为,

  4. 通过normal mode下t{char}快速移动到查找字符的前一个字符,类似f{char}.

    f记忆为find,t记忆为till,直到查找到指定的字符为止

    d/c配合t使用可以删除行中的指定的一段. 例如删除句子的后半句: f,dt.

  5. 通过查找进行移动

    /正向查找

    ?反向查找

    删除某段的基本操作:

    • 利用可视模式选中某段然后d删除v/xx<CR>d,

    删除某段的高级操作(利用操作符+动作,把查找看成动作理解)

    • d{motion}+查找动作 = d/xxx<CR>

使用文本对象 (第一类: 操作分隔符或者分隔符内部文本对象)

使用模式: 在visual mode或者操作符之后(操作符待决模式 operator-pending)

永远以a或者i(inner)开头出现,

  • aB/iB选中区块内的文本,

  • at/it选中tag标签内的文本

  • a/i +{char},char表示想选中一对符号中的其中一个,a(/a<,a",a[,a{,a'

    使用场景例如:

  1. 快速修改头文件 ci<

  2. 快速删除const char *的内容di"

  3. 快速复制const char *的内容yi"

    d配合a类文本对象(因为删除前后的空格),

    c配合i类文本对象(只选中文本对象,不修改前后的空格)

  4. 使用文本对象 (第二类: 操作文本块)

    • iw/aw 当前单词, (iw不包含空格)
    • iW,aW当前字符串
    • is,as当前句子
    • ip,ap当前段落
  5. 使用位置标记(局部/全局)

    • 标记当前光标位置: m{a-zA-Z},小写buffer内可用,大写全局可用.(m表mark)

    • 跳转到光标位置: 反引号调转到标记位置, 单引号跳转到标记的行的位置.

    单标记技巧:mm来标记,``m`跳转回来

    vim默认的位置标记

    1. `` 两个反引号: 表示跳转之前的光标位置
    2. `. 上次修改的地方
    3. `^ 上次插入的地方
    4. `[ 上次修改或复制的起始位置
    5. `] 上次修改或复制的结束位置
    6. `< 上次高亮区的起始位置
    7. `> 上次高亮区的结束位置
  6. %在当前行找到和当前字符匹配的项目,{}/[],()等,利用`` 反引号快速回到另一个匹配项的位置,然后r修改

  7. 遍历跳转列表 (each window)

  8. 遍历改变列表 changes (each buffer)

    • vim会记录每次修改的地方,:changes查看所有修改.

    • g;g,遍历每次修改的光标位置

  9. 查看光标下的指定文件gf,配合跳转命令<C-o>,和<C-i>跳去跳回

四.寄存器

常用寄存器

windows中的剪切(cut),复制(copy),粘贴(paste),

对应vim中的delete(剪切),yank(复制),put(粘贴).

windows使用的系统的剪贴板,vim默认使用的无名寄存器,

  1. 无名寄存器""

    • x,s,d{motion},c{motion},y{motion}命令即使指定了其他寄存器,但是无名寄存器也会有内容(无名寄存器发生总是缺省的)
  2. 黑洞寄存器"_

    • 真正的删除文本,因为没有把内容复制到其他的寄存器上去.

    • "_diw,真正删除一个单词

  3. 复制专用寄存器"0

    • ``y{motion}`命令复制的文本不仅在无名寄存器中,也在复制专用寄存器中.

    • Numbered register 0 contains the text from the most recent yank command,unless the command specified another register with [“x].

  4. 26个有名寄存器"a-"z ,

    • 如果使用大写字母引用,就是将新内容追加到原有内容之后.小写字母引用是覆盖.
  5. 系统剪切板"+或者"*,

    • 通过外部寄存器可以与系统剪切板交换文本
  6. 表达式寄存器"=.

    • 技巧1:在插入模式中当做计算器使用:<C-r> =使用计算功能
  7. 只读寄存器

    • "% 当前文件名
    • "#上一个文件名
    • ".上次插入的文件
    • ":上次执行的ex命令
  8. 查找寄存器"/,

    • 保存的是上一次查找的内容

寄存器内容替换高亮区文本

  1. 基本操作:yiw->ve->"0p
  2. 可视模式下的p会交换无名寄存器的内容,将寄存器内容填充到高亮区域,将高亮区域内容复制到无名寄存器中.

宏的读取与执行
  1. q{register}开始录制宏,q停止录制,@{register}执行宏,@@执行上一次执行的宏
  • 录制宏的修改序列会保存到寄存器上.
加次数回放宏
  • f+->s +<C-[> -> qq;.q ->22@q
在连续的文本行上修改
  1. 串行操作: 录制宏完毕后执行n次(宏里面有换行的操作)

    例如:qa0f.r)w~jq ->再执行3次宏3@a

  2. 并行操作:宏录制完后,可视化选中然后执行·:normal @{register} (对所有的行进行操作)

    例如:qa0f.r)w~q -> VG -> :normal @a

给宏追加命令
  • 用大写字母引用寄存器录制宏就行:

    qa0f.r)w~q->追加j,qAjq就行.

在一组文件中执行宏(x)
给列表编号
  • 使用let设一个变量后使用表达式寄存器插入.

    :let i=0

    qaI<C-r>=i<CR>) <C-[>

    :let i+=1 //目的是标号+1

    q

    然后并行执行宏

编辑宏的内容
  1. "{register}p粘贴到当前文档中,
  2. 然后再文档中修改宏的内容
  3. 然后V"ay就能又覆盖到a寄存器中.完成修改宏内容.

五.模式

查找

1. 基本查找操作
  • /正向查找,?反向查找,n跳转到下一处,N跳转到上一处.
2. 高亮查找匹配
  • 开启hlsearch可以高亮所有匹配项,:noh暂时关闭高亮

  • 重新绘制屏幕的基础上增加noh功能的新映射项配置:

    nnoremap <silent> <C-l> :<C-u>noh<CR><C-l>

3. 预览第一处匹配
  • 开启:set incsearch
4. 统计匹配个数
  • % s///gn
5.将光标偏移到查找匹配的结尾
  • 光标默认会匹配于首字母上,/{pattern}/{offset}<CR>,能够控制光标定位在匹配结果的附近,

    例如定位在匹配结果的结尾/{pattern}/e<CR>

    或者已经进行了查找操作/{pattern}<CR>,则//e<CR>,也能重定位光标

6.对完整的查找匹配进行操作

global/{pattern}/{cmd} 可以做到,

7. 向前查找当前光标下的单词
  • *

替换 (substitute命令

1. 完整格式:
  • [range]substitute/{pattern}/{string}/{flags}

    常用flags总结:

    • g 一行的所有匹配项都发生替换,而不仅是一行的第一项
    • n不进行替换,报告匹配项目数量
    • c确认每一匹配项是否需要替换
    • e没有匹配项时,不报告错误.
    • &重复上次使用的标志位

    替换域的特殊字符

    • \r 换行符
    • \t制表符
    • \\反斜线
    • \{n}插入第n个子匹配
    • \0&插入匹配模式的所有内容
    • ~上次的{string}
    • \={vim script},执行表达式
2. 全局替换
  • 利用标志位g使得每行所有匹配项都替换,%表示所有行的范围

    :% s/{pattern}/{string}/g,就能实现全局替换.

3.手动控制每一次的替换
  • 利用标志位c(confirm),同上:% s/{pattern}/{string}/gc

    y替换此处,

    n忽略此处

    q退出此处

    l 替换此处后退出

    a 替换后面所有退出

4.重用上次查找模式
  • {pattern}留空,将使用上一次的查找模式,(上一次的substitute命令和/?都算上一次查找模式)
5.用寄存器内容替换

查找域留空,使用上一次的,但是替换域留空,则空字符串替换,等价于删除

  1. 传寄存器的值,:%s//<C-r>{register}/g,寄存器的值会直接复制到命令行上.(特殊字符需要提前进行转义)

  2. 引用寄存器.:%s//\=@{register}/g,如上,=表示执行vim脚本,@{register}在普通模式下是执行宏,在这里表示引用某个寄存器.

    固定场景(充分利用寄存器):

    1. 现在文档中复制替换的字符串(到”0寄存器中).

    2. /查找所有替换域

    3. :%s//\=@0, 利用//表示上一次查找域,@0表示引用赋值寄存器. (充分利用了vim的特性)

6.重复上一次的substitute命令
  1. 重复上一次s命令: :s//~/&, //表使用上一次的查找域,~表使用上一次替换字符串,&表使用上一次标志位.
  2. g&快捷键对应:%s//~/&,比上面多一个全局的范围,弥补没有加%的单次替换

global命令

1.完整格式:
  • :[range] global[!]/{pattern}/ [range][cmd]

    缺省的范围是%,查找域留空//也是使用上一次的查找域. cmd缺省是print命令.

    还有global!vglobal命令,

    vglobal在匹配的行的剩下的行上执行命令.(v表示invert)

  • 清空寄存器: q{register}q,没有指令就是空

2.利用global提取内容到寄存器

​ 例:提取所有注释内容到寄存器B

:global/\/\//yank B,为什么要用大写,(小写 覆盖只剩下第一条匹配的,大写追加)

​ 例:提取所有注释内容到文件尾部

:global/\/\//t$,

3.给指定区域属性排序
  1. 给单条规则(大括号内的属性)排序:

    vi{:sort,(利用文本对象)

    1. 给所有规则排序

    :global/{/ .+1,/}/-1 sort 解释:

    • ex命令会接受 范围 作为参数(也就是接受global传来的范围)
    • [cmd],准确来说是[range][cmd],也就是在传入的范围的基础上再次展开

    .+1,/}/-1符合/{pattern}/{offset}模式,也就是在查找模式下偏移光标,

    .,/}/去掉偏移表示 从当前行开始到}行结束,

4. global的广义形式(global与任意ex命令组合范式)

:g/{start} .,{finish} [cmd]

​ 例如给 {} 区域内文本缩进

:global/{/ .+1,/}/-1 >

基本操作-(单字母)

因为不熟悉单独拧出来

  1. r替代一个字符,R替换模式(插入模式的一种特例)
  2. f{char}行内字符查找,F{char}行内字符反向查找
  3. s删除后插入,S删除整行后插入
  4. cd是一样的类型(操作符),d是删除,c是修改,所以c后面接命令/次数,C修改至行尾
  5. A对应的在 行首插入的是 I.
  6. t/T{char}将光标定位到前/后字符,t{char}可作为{motion}使用.
  7. m{position}在当前光标位置设置标记,:marks查看设置的标记,`{position}定位到标记处

其他模式

  1. 普通,插入,命令,可视化,模式外,还有一种模式叫做**operator pending mode,操作符待决模式,意思就是输入了gu/d/c/y这个操作符,在状态栏可以看到情况,等待输入command来完成一个操作**
  2. 在插入模式下,想执行一条普通模式的命令,使用ctrl-o进入插入-普通模式,然后执行一条命令后就自动回到了插入模式
  3. <C-g>在可视模式和选择模式切换,选择模式就是Windows的可视模式,输入使得选中的文本被删除

奇怪场景解决办法:

  1. (以超级用户权限保存文件)在修改权限不够的文件后无法保存,只能退出重新sudo编辑的解决办法:

    :write !sudo tee %

    原理:

    write将当前Buffer内容作为 !{cmd}的标准输入,tee命令将标准输入转为文件,%表示当前文件名,所以就很好理解:将当前修改的Buffer的内容重新以root写到当前文件.

  2. 标准语法操作=操作符+动作motion,可以在前缀时用可视模式替换后面的动作.

我尝试做的vim配置

1
2
3
4
5
6
7
8
9
10
11
12
" normal mode下快速到达行首行尾
nnoremap H ^
nnoremap L $

" jk快速进入normal mode
inoremap jk <Esc>

" insert mode下快速移动光标
inoremap <C-h> <Left>
inoremap <C-l> <Right>
inoremap <C-j> <Down>
inoremap <C-k> <Up>

Surround.vim插件可以用用


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!