寄存器用於儲存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這類複雜、強大的命令,更需要精通像寄存器這樣的小東西,因為這才是決定編輯效率的關鍵所在——點滴加速,高效之源。