標籤:

更好的使用 Vim的標籤(Tab)和 Alt映射功能

更好的使用 Vim7.0以後推出的標籤(TAB)功能,同現代編輯器一樣用標籤(TAB)來管理多文件,代替傳統 Buffer List,直接使用標準的標籤功能會比使用 Vim 7.0以前各種 buffer 切換更加直觀方便。

快捷鍵切換 TAB

第一件事情就是要搞定標籤快速切換問題,不管是:tabn X還是 Xgt都十分低效,我們需要更快速的在各個文件之間切換。最簡單的是設置 <leader>0-9 來快速切換tab(默認leader是反斜杠,即先按下鍵,再按數字鍵),不管終端還是GVIM都兼容:

noremap <silent><tab>m :tabnew<cr>noremap <silent><tab>e :tabclose<cr>noremap <silent><tab>n :tabn<cr>noremap <silent><tab>p :tabp<cr>noremap <silent><leader>t :tabnew<cr>noremap <silent><leader>g :tabclose<cr>noremap <silent><leader>1 :tabn 1<cr>noremap <silent><leader>2 :tabn 2<cr>noremap <silent><leader>3 :tabn 3<cr>noremap <silent><leader>4 :tabn 4<cr>noremap <silent><leader>5 :tabn 5<cr>noremap <silent><leader>6 :tabn 6<cr>noremap <silent><leader>7 :tabn 7<cr>noremap <silent><leader>8 :tabn 8<cr>noremap <silent><leader>9 :tabn 9<cr>noremap <silent><leader>0 :tabn 10<cr>noremap <silent><s-tab> :tabnext<CR>inoremap <silent><s-tab> <ESC>:tabnext<CR>

其次,GVIM/MacVim 下設置 ALT-0-9 來切換TAB(GVim/MacVim可以直接映射ALT):

" keymap to switch tab in guiif has(gui_running) set winaltkeys=no set macmeta noremap <silent><c-tab> :tabprev<CR> inoremap <silent><c-tab> <ESC>:tabprev<CR> noremap <silent><m-1> :tabn 1<cr> noremap <silent><m-2> :tabn 2<cr> noremap <silent><m-3> :tabn 3<cr> noremap <silent><m-4> :tabn 4<cr> noremap <silent><m-5> :tabn 5<cr> noremap <silent><m-6> :tabn 6<cr> noremap <silent><m-7> :tabn 7<cr> noremap <silent><m-8> :tabn 8<cr> noremap <silent><m-9> :tabn 9<cr> noremap <silent><m-0> :tabn 10<cr> inoremap <silent><m-1> <ESC>:tabn 1<cr> inoremap <silent><m-2> <ESC>:tabn 2<cr> inoremap <silent><m-3> <ESC>:tabn 3<cr> inoremap <silent><m-4> <ESC>:tabn 4<cr> inoremap <silent><m-5> <ESC>:tabn 5<cr> inoremap <silent><m-6> <ESC>:tabn 6<cr> inoremap <silent><m-7> <ESC>:tabn 7<cr> inoremap <silent><m-8> <ESC>:tabn 8<cr> inoremap <silent><m-9> <ESC>:tabn 9<cr> inoremap <silent><m-0> <ESC>:tabn 10<cr>endif

或者在 MacVim 下還可以用 CMD_0-9 快速切換,按著更舒服:

if has("gui_macvim") set macmeta noremap <silent><c-tab> :tabprev<CR> inoremap <silent><c-tab> <ESC>:tabprev<CR> noremap <silent><d-1> :tabn 1<cr> noremap <silent><d-2> :tabn 2<cr> noremap <silent><d-3> :tabn 3<cr> noremap <silent><d-4> :tabn 4<cr> noremap <silent><d-5> :tabn 5<cr> noremap <silent><d-6> :tabn 6<cr> noremap <silent><d-7> :tabn 7<cr> noremap <silent><d-8> :tabn 8<cr> noremap <silent><d-9> :tabn 9<cr> noremap <silent><d-0> :tabn 10<cr> inoremap <silent><d-1> <ESC>:tabn 1<cr> inoremap <silent><d-2> <ESC>:tabn 2<cr> inoremap <silent><d-3> <ESC>:tabn 3<cr> inoremap <silent><d-4> <ESC>:tabn 4<cr> inoremap <silent><d-5> <ESC>:tabn 5<cr> inoremap <silent><d-6> <ESC>:tabn 6<cr> inoremap <silent><d-7> <ESC>:tabn 7<cr> inoremap <silent><d-8> <ESC>:tabn 8<cr> inoremap <silent><d-9> <ESC>:tabn 9<cr> inoremap <silent><d-0> <ESC>:tabn 10<cr> noremap <silent><d-o> :browse tabnew<cr> inoremap <silent><d-o> <ESC>:browse tabnew<cr>endif

這下很舒服了,和大部分主流編輯器一樣切換tab十分輕鬆。那終端下 alt沒法很好的映射怎麼辦呢?

終端下映射 ALT-0-9 快速切換標籤

不推薦把中斷里的ALT鍵設置成ESC+,大部分終端(XShell, SecureCRT, iTerm, gnome-terminal)都提供ALT鍵改為<ESC>+的模式,即按下ALT-A,終端會連續發送ESC(27)和 A(65)兩個位元組,這樣在emacs下挺好用,但是在vim下就糟糕了,比如你映射了<ESC>A來代替 ALT-A,那麼當你用ESC退出insert模式時,一秒鐘內馬上按下A來在行末追加內容,Vim會去觸發你之前設置的ALT-A的功能,這就對 Vim 原有的功能造成了擾亂,那終端下如何正確設置 ALT功能鍵呢?

我們使用終端自定義按鍵序列,大部分終端都支持自定義按鍵發送特定字元串(XShell,SecureCRT,iTerm,gnome-terminal),比如我們可以把ALT-1設置成發送 <ESC>]{0}1~ 這個字元串,而 ALT-2設置成發送 <ESC>]{0}2~ 這個字元串,於是我們可以在Vim裡面建立快捷映射:

" set terminal and map alt+n or alt+shift+n to "<ESC>]{0}n~"if !has(gui_running) noremap <silent><ESC>]{0}1~ :tabn 1<cr> noremap <silent><ESC>]{0}2~ :tabn 2<cr> noremap <silent><ESC>]{0}3~ :tabn 3<cr> noremap <silent><ESC>]{0}4~ :tabn 4<cr> noremap <silent><ESC>]{0}5~ :tabn 5<cr> noremap <silent><ESC>]{0}6~ :tabn 6<cr> noremap <silent><ESC>]{0}7~ :tabn 7<cr> noremap <silent><ESC>]{0}8~ :tabn 8<cr> noremap <silent><ESC>]{0}9~ :tabn 9<cr> noremap <silent><ESC>]{0}0~ :tabn 10<cr> inoremap <silent><ESC>]{0}1~ <ESC>:tabn 1<cr> inoremap <silent><ESC>]{0}2~ <ESC>:tabn 2<cr> inoremap <silent><ESC>]{0}3~ <ESC>:tabn 3<cr> inoremap <silent><ESC>]{0}4~ <ESC>:tabn 4<cr> inoremap <silent><ESC>]{0}5~ <ESC>:tabn 5<cr> inoremap <silent><ESC>]{0}6~ <ESC>:tabn 6<cr> inoremap <silent><ESC>]{0}7~ <ESC>:tabn 7<cr> inoremap <silent><ESC>]{0}8~ <ESC>:tabn 8<cr> inoremap <silent><ESC>]{0}9~ <ESC>:tabn 9<cr> inoremap <silent><ESC>]{0}0~ <ESC>:tabn 10<cr>endif

如此由於終端上映射了 ALT-0-9 為 <ESC>]{0}N~ 所以在 XShell/SecureCRT/iTerm下按 ALT-0-9 可以發送對應的字元串給 Vim並被上面的配置代碼識別。

有人問XShell/SecureCRT下面ALT-0-9是用來快速切換終端標籤的,怎麼辦呢?好辦,在終端下把 ALT-SHIFT-0-9映射成剛才的字元串即可啊,這樣保證不影響終端快捷鍵的同時,方便的使用<M-S-0/9>來切換標籤很方便,或者用先前的0-9來代替。

終端下避免按鍵衝突

有人說這會不會和<ESC>退出insert後馬上按 ]又發生衝突?其實不用當心,<ESC>後很少有機會按],即使你按了 ]也很少有機會繼續迅速按下{,0,}三鍵,連終端下 F5即被映射為字元串:<ESC>[15~ 所以你放心大膽的用<ESC>]{開頭吧,即使你正常使用下按了馬上按],你會發現VIM等待了1秒鐘,發現期間沒有後續的{0}1~,就馬上判斷不是該映射,所以正常執行]功能。

這和 Vim中按下 F1的情況一樣,xterm終端下 F1被映射為字元串<ESC>OP,你在XShell / iTerm / SecureCRT 下面敲下 F1 實際會發送:0x1b (ESC), 0x4f(O), 0x50(P) 三個位元組到服務端,所以你在Vim下一秒鐘內快速敲入 <ESC>OP三個鍵和 F1鍵的功能相同–打開幫助。那麼你可能會問,如果我想按<ESC>離開INSERT模式後馬上按O在游標上面插入一行,在VIM里到底是被判斷成我想要的模式?還是F1的前綴呢?其實是靠超時(help timeout),你馬上<ESC>離開插入模式後按了O,VIM會等待一秒鐘知道確認沒有後續追加按鍵了,才判斷為離開插入模式和向上插入一行兩個獨立的動作,否則在timeout(默認1秒)內你又按了下P,那VIM就真的以為你按下 <F1>了。

如果你想避免退出插入模式馬上按O,P被識別成F1,那麼你可以先按O,然後等一秒過了真的插入一行了,再按下P,問題不大。或者用ko來代替,又或者直接使用GVIM/MacVim就沒這個歧義了。

所以在終端下通過配置鍵盤映射,當按下ALT-X時,發送<ESC>]{0}X~字元串,可以比直接把ALT鍵設置成+能更好的減少按鍵歧義,正常使用很少後緊跟 ]的。

設置標籤文本

默認的標籤文本包含文件路徑,十分凌亂,需要進一步修改,讓它只顯示文件名:

" make tabline in terminal modefunction! Vim_NeatTabLine() let s = for i in range(tabpagenr($)) " select the highlighting if i + 1 == tabpagenr() let s .= %#TabLineSel# else let s .= %#TabLine# endif " set the tab page number (for mouse clicks) let s .= % . (i + 1) . T " the label is made by MyTabLabel() let s .= %{Vim_NeatTabLabel( . (i + 1) . )} endfor " after the last tab fill with TabLineFill and reset tab page nr let s .= %#TabLineFill#%T " right-align the label to close the current tab page if tabpagenr($) > 1 let s .= %=%#TabLine#%999XX endif return sendfunc " get a single tab name function! Vim_NeatBuffer(bufnr, fullname) let l:name = bufname(a:bufnr) if getbufvar(a:bufnr, &modifiable) if l:name == return [No Name] else if a:fullname return fnamemodify(l:name, :p) else return fnamemodify(l:name, :t) endif endif else let l:buftype = getbufvar(a:bufnr, &buftype) if l:buftype == quickfix return [Quickfix] elseif l:name != if a:fullname return -.fnamemodify(l:name, :p) else return -.fnamemodify(l:name, :t) endif else endif return [No Name] endifendfunc " get a single tab labelfunction! Vim_NeatTabLabel(n) let l:buflist = tabpagebuflist(a:n) let l:winnr = tabpagewinnr(a:n) let l:bufnr = l:buflist[l:winnr - 1] return Vim_NeatBuffer(l:bufnr, 0)endfunc " get a single tab label in guifunction! Vim_NeatGuiTabLabel() let l:num = v:lnum let l:buflist = tabpagebuflist(l:num) let l:winnr = tabpagewinnr(l:num) let l:bufnr = l:buflist[l:winnr - 1] return Vim_NeatBuffer(l:bufnr, 0)endfunc " setup new tabline, just like %M%t in macvimset tabline=%!Vim_NeatTabLine()set guitablabel=%{Vim_NeatGuiTabLabel()}

在GUI模式下,還可以配置標籤的TIPS,滑鼠移動到標籤上,會自動顯示該TIPS:

" get a label tipsfunction! Vim_NeatGuiTabTip() let tip = let bufnrlist = tabpagebuflist(v:lnum) for bufnr in bufnrlist " separate buffer entries if tip != let tip .= "
"
endif " Add name of buffer let name = Vim_NeatBuffer(bufnr, 1) let tip .= name " add modified/modifiable flags if getbufvar(bufnr, "&modified") let tip .= [+] endif if getbufvar(bufnr, "&modifiable")==0 let tip .= [-] endif endfor return tipendfuncset guitabtooltip=%{Vim_NeatGuiTabTip()}

快速在標籤中打開文件

除了:tabedit 外,Vim自帶netrw插件可以瀏覽目錄,是代替nerdtree的好東西,使用:Explore打開netrw的文件瀏覽器後,選中文件按t鍵即可以再新標籤打開對應文件了。

快捷鍵在標籤打開新文件

我們配置按下 ALT-O 的時候在當前位置打開文件瀏覽器:

" Open Explore in new tab with current directoryfunction! Open_Explore(where) let l:path = expand("%:p:h") if l:path == let l:path = getcwd() endif if a:where == 0 exec Explore .fnameescape(l:path) elseif a:where == 1 exec vnew exec Explore .fnameescape(l:path) else exec tabnew exec Explore .fnameescape(l:path) endifendfunc

這樣可以把 call Open_Explore(2) 映射成ALT-O,這樣如果在編輯一個文件時候按下 ALT-O,那麼會在該文件的路徑下展開一個新的tab並顯示netrw的文件列表。

如果使用 GVIM/MacVim可以更優雅點,直接在當前文件的路徑下,使用系統的文件查找窗口,選中文件後在新的Tab里打開:

function! s:Filter_Push(desc, wildcard) let g:browsefilter .= a:desc . " (" . a:wildcard . ") " . a:wildcard . "
"
endfunc let g:browsefilter = call s:Filter_Push("All Files", "*")call s:Filter_Push("C/C++/Object-C", "*.c;*.cpp;*.cc;*.h;*.hh;*.hpp;*.m;*.mm")call s:Filter_Push("Python", "*.py;*.pyw")call s:Filter_Push("Text", "*.txt")call s:Filter_Push("Vim Script", "*.vim") function! Open_Browse(where) let l:path = expand("%:p:h") if l:path == | let l:path = getcwd() | endif if exists(g:browsefilter) && exists(b:browsefilter) if g:browsefilter != let b:browsefilter = g:browsefilter endif endif if a:where == 0 exec browse e .fnameescape(l:path) elseif a:where == 1 exec browse vnew .fnameescape(l:path) else exec browse tabnew .fnameescape(l:path) endifendfunc

然後把 call Open_Browse(2) 映射成 ALT-O。

尋找Tag時在新標籤打開

以前尋找tag時使用<C-]>,現在改為<c-w><c-]>即可在新標籤打開tag定義。

Quickfix中在新標籤打開文件

編譯或者 grep 的時候,信息輸出到 Quickfix窗口中,按回車就容易把buf給切換走,使用標籤後設置下面一項,即可更改quickfix行為為:如果有已打開文件,先復用,沒有的話使用標籤:

set switchbuf=useopen,usetab,newtab

如此Tab已經很好用了,再配置些快捷鍵按ALT-方向鍵來tabmove,ALT-w來關閉標籤,基本上和其他現代編輯器行為很類似了。


推薦閱讀:

哪些是 Emacs 可以做而 Vim 做不到的?
Vim 和 Emacs 都用過兩年以上的人,說說它們使用起來感覺最大的區別是什麼?
Vim C/C++函數名,宏定義和變數的高亮
YouCompleteMe 配合 UltiSnips 補全 C/C++ 函數參數
Vim提高生產力的技巧

TAG:Vim |