助您的企业征战互联网 - 苜蓿草网络

vim寄存器使用详解

时间:2014-08-24 20:53     热度:513     来源:博客园

寄存器用于存储Vim操作中的特定内容,大多数normal命令和部分ex命令都可以指定操作关联的寄存器。寄存器同时也是Vim里特殊的变量,因此可以 在命令行和脚本中被访问,实现一些非常有用的功能。Vim有很多不同类型的寄存器,各司其职,各具其能,若得灵活运用,会令编辑工作轻松高效。本文以 Vim中常见的问题为例,介绍各类寄存器的功能和用法。

1. 编辑操作中的常用功能 

:h v_p
:h g@
:h redo-register

Vim 中最常用到的是数字寄存器。当不指定寄存器时,复制操作的内容被保存到"0,删除操作的内容被”压“到"1,同时原先"1的内容转到"2,依此类推,原 先"8转到"9,原先"9的内容丢失。如果指定操作的寄存器,如"ayy和"bdd,则上述的数字寄存器无影响(有些例外情况,详见Vim手册)。未命名 寄存器""保存最近一次复制或删除操作内容,无论是否指定寄存器。还有一个特殊的“黑洞”寄存器"_,当指定其进行删除时,包括""在内的任何寄存器都不 受影响,当然,你也没法把掉进黑洞的物质p出来。

【例1】复制-删除-粘贴 

这是经常困扰Vim新手的一个问题:当复制了一个词(yw)然后准备将另外一个词替换掉,自然的想法是删除(dw)后粘贴(p),但dw已经将""更新为被删除的词,p的内容将不是复制的那个了。有几个办法以供选择: 

A. 先p后dw,问题是要重新定位需要删除的部分。你可以用gp/gP试试,它与p/P功能一样,不过光标停留在粘贴出的文字之后,便于随后的删除; 

B. 将删除内容转到黑洞("_dw),再p; 

C. 指定复制内容("0p); 

D. 利用Visual mode下p命令的交换特性(vwp),该操作粘贴指定寄存器的内容,然后删除被选择的文字。这种方法的键盘输入比B和C都方便些。 

E. 利用下面这条map的S命令(S原有功能与cc相同,所以把它改了)。

" replace text with unnamed register (in all modes) 
function! ReplaceWithUnamed(type) 
let paste_save=&paste 
let &paste=1
if a:type == 'line'
silent exe "normal! '[V']"
elseif a:type == 'block'
silent exe "normal! `[\<C-V>`]"
elseif a:type == 'char'
silent exe "normal! `[v`]"
else
silent exe "normal! `<" . a:type . "`>"
endif
silent exe "normal! \"_c\<C-R>"\<ESC>"
let &paste=paste_save
endfunction
nmap <silent> S :set opfunc=ReplaceWithUnamed<CR>g@
vmap <silent> S :<C-U>call ReplaceWithUnamed(visualmode())<CR>

这种方法符合command-motion的操作流程,而且""里的内容不改变,在需要将多处文字替换成同一内容时非常方便。 

【例2】进行快速倒序 

有时我们需要把一系列的内容交换顺序,比如要把“0x12,0x34,0x56“改成“0x56,0x34,0x12”。假设光标初始在0x12 处,利用数字寄存器的压栈功能,用dw....将5个部分依次删除到"5至"1中,然后用"1p....依次吐出来。巧妙之处在于,若p命令时指定数字寄 存器,则后续的.命令会自动将数字寄存器的编号加一,也就是第一个.执行的是"2p,依次类推。 

【例3】多部分复制粘贴 

上述技巧还可以用来将多段的文字分别复制到不连续的目的地,比如要把一个函数中某个变量的声明和多处引用(记为A、B、C部分)复制到另外一个函 数中(记为X、Y、Z处)。我们可以依次在A、B、C部分执行删除和undo,然后在Z、Y、X处执行"1p和2个redo,这样将不用在两个函数之间跑 来跑去。 

数字寄存器的这一用法还可以用来试选历史删除内容,比如想要粘贴之前某次删除内容,但不知道已经被压到哪层了,则可以"1pu(或者比如"3p,如果你确信不是最近两次删除内容),然后执行几次.u直到想要的内容出来。当然,直接用:di看一下各个寄存器的内容也许更快。 

本例也可以把3个部分分别y到"a,"b和"c里,然后到各自的目的地"ap,"bp,"cp。 

"a 到"z这26个命名寄存器为编辑操作提供了丰富的资源,比如,程序员可以将常用的代码模版预设到某些寄存器中(利用ftplugin),需要时p出来。命 名寄存器另有一大功能是有"A到"Z与之对应,当用这些大写字母寄存器时,操作的内容是追加到小写字母寄存器原有内容的后面,这在需要收集多处内容时特别 方便,与其它命令结合使用更有妙用,见下例。 

【例4】提取匹配行内容 

:g/regex/norm "Ayy

上面这条命令把匹配regex的所有行都保存到"a中,当然在运行前需要把"a清空,比如在一个空行上"ayy。 

寄存器".保存上一次插入的文字,在需要在多个地方插入相同文字,但又因中间进行了其它修改操作而不能redo时,".非常有用。 

寄存器"%和"#保存当前文件和替换文件的文件名,编程写注释头的时候经常用到。需要注意的是,如果文件是在当前目录或其子目录下,文件名是相对路径名,否则是全路径名。 

2. 寄存器的其它引用方式 

:h i_CTRL-R 
:h autocmd 
:h redir

除了在normal命令中以"x的形式指定外,插入模式和命令行中输入<CTRL-R>-x将插入"x的内容。 因为寄存器是Vim中预定义的特殊的变量,还可以在命令行和脚本中以变量的形式(变量名字符前加@)直接读取或修改。 

Vim 将最近一次的搜索文字保存在"/中,对应的变量@/决定了n/N命令和查找高亮的对象。@/被所有buffer共享,也就是说在一个buffer里进行新 的查找,其它buffer的匹配高亮和n/N命令也随着更新。如果希望每个buffer都维护自己的查找内容,可以参考下例: 

【例5】查找内容本地化 

我们利用autocmd功能,在离开buffer时保存"/的值,在进入buffer时恢复: 

:au BufLeave * let b:search_save=@/
:au BufEnter * if exists("b:search_save") | let @/=b:search_save | endif

还可以把buffer改成window,也就是各个window维护自己的"/,这样在多窗口编辑同一文件时,可以使得查找文字互不干扰。 

【例6】信息重定向 

有时候我们想把执行ex命令的信息保存下来,Vim提供了:redir命令用来把信息输出重定向到文件或寄存器里。例如: 

:redir @a some commands) 
:redir END

可以定义一个命令来自动完成上述操作: 

:command! -nargs=* Mc redir @"> | try | exe "<args>" | finally | redir END | endtry

例如【例4】也可以这么实现,而且不需要预先清除寄存器。 

:Mc g/regex/p

3. 寄存器与宏 

:h q 
:h @ 
:h :@

宏是Vim中非常重要的功能,用来重复执行多个连续操作。当这些操作包含移动、查找、插入、修改等不同类型的命令时,宏显得尤其方便,很多时候 用:s和:g 难以实现的功能,宏都可以轻松搞定。用q录制宏实际上是将键盘输入记录到寄存器的过程,而用@运行宏则是将指定寄存器内容作为normal命令执行的过 程。q命令提供了“所做即所得”,但有时候直接修改寄存器更为方便。比如当你录制完一个非常复杂的宏,但发现有一个小毛病(例如应该是de而不是dw), 不必重新录制一遍,只需要将寄存器的内容p出来修改好再y回去。 

【例5】宏的一些技巧 

A. 容许错误:录制过程中如果有错不必放弃重来,可以undo或<BS>,只要保证这些操作和处理文本无关,寄存器里有些乱七八糟的东西又何妨。 

B. 分而治之:当录制一个很复杂的宏时可以考虑分成几段,比如qa第一步,qb第二步,然后在qc中调用a和b,各个击破简单易行。 

C. 重复运行:@@命令可以重复上次的宏调用。 

D. 另作它用:q命令是向寄存器里录入命令,你也可以什么都不录!有什么用处?【例4】中清除"a最快的方法:qaq。 

Vim 中用":寄存器记录最近一次运行的命令行命令,因此@:是重复上次的命令行操作。值得注意的是,@x宏运行的是normal命令,而@:运行的是Ex命 令。如果某个寄存器"x保存的是Ex命令,你可以用:@x来执行。比如在测试vimrc中的某条命令时,先yy,然后:@"执行。 

4. 寄存器求值 

寄存器"=与众不同,它不存储文本,而是在可以使用寄存器的场合中提供了用表达式求值并取得其结果的途径。简单的说,就是在指定"=时,Vim会提示输入一个表达式,然后将求值结果返回,至于这串文本如何使用,就看在什么地方使用了。 

【例6】十六进制转十进制 

在插入模式下输入<C-R>=,Vim会提示一个=,再输入0x1234<CR>,4660就自动插入到当前位置。 

【例7】十进制转十六进制 

上例的反操作可就没那么方便了,你得用printf函数。下面这个map是将当前的数字替换成十六进制。这个map也许不大易懂,命令其实是【例1】中提到到v_p,只不过用于替换的内容是通过"=进行求值的结果。 

:nmap \h viw"=printf("0x%X",<C-R><C-W>)<CR>p

【例8】设置字体 

一个常见的问题是如何将guifont的设置写入到_vimrc中,字体名加上字号通常很长,比如我用的是 ”Bitstream_Vera_Sans_Mono:h9:cANSI”,照着敲实在太麻烦。当然可以利用【例6】中的:redir把:set guifont的输出抓出来,但这不是最好的方法。Vim中的选项也是变量,变量名是选项名前加&,因此我们可以方便的用<C- R>=&guifont<CR>得到它。需要其它变量,比如环境变量$HOME,也可以如法炮制。 

"=不光用来插入文字,任何能使用寄存器的地方都可以引用。比如用qa录了一个宏后,执行时又想不要最后的dw命令,一个快捷的方法是执行 

@=@a[:-3] 

@=运行后面表达式的结果,[x:y]是取字符变量的子字串(串首从0编号,串尾从-1编号),因此@a[:-3]返回除最后dw之外的内容。记住,若要再次运行只需敲@@。 

【例9】寄存器求值在在命令行中的示例 

:noremap ,e :e <C-R>=expand("%:p:h") . "/" <CR><C-D> 
:noremap ,s :split <C-R>=expand("%:p:h") . "/" <CR><C-D> 
:noremap ,S :vsplit <C-R>=expand("%:p:h") . "/" <CR><C-D> 
:noremap ,t :tabnew <C-R>=expand("%:p:h") . "/" <CR><C-D>

上面几个map提示显示当前文件所作目录下的文件列表,以供edit/split/tabnew,目录名是用<C-R>=求值的方式获得。(如果你希望当前目录自动跟随当前文件,设置autochdir。) 

5. 总结 

上面介绍了大部分的寄存器,但没有提及"*、"+和"~,这些寄存器是和GUI的选择和拖拽有关,版内有文章讲解很详细,本文不再累述。有一个常见问题说一下:如果希望和其它程序方便的复制粘贴,可以将剪贴板寄存器"*设为未命名寄存器: 

:set clipboard=unnamed

另外需要说的是,用q:和q/可以打开保存有命令行和查找内容的历史窗口(命令行历史窗口也可以在命令行下敲CTRL-F打开),在其中可以查找、编辑、修改和执行历史命令或查找。 

寄存器操作看似都是些细碎的东西,但却是编辑中经常用到的。我们不仅需要掌握:s和:g这类复杂、强大的命令,更需要精通像寄存器这样的小东西,因为这才是决定编辑效率的关键所在——点滴加速,高效之源。

标签:  Vim  寄存器  复制  粘贴