沒穿方服

封存

顯示╱隱藏內文

檔案改到一半發現自己失憶,忘記改了哪些東西,只好跟上次 git push 的狀態比較看看了。

正統作法是在 shell 下指令 git diff,或下 git difftool 用 vimdiff 分割視窗顯示,如下圖,前者在檔案多時容易快速概觀,但單一檔案時還是 vimdiff 方便。

狀況單純的話也很好用 缺點是一次只 diff 一個檔案

目前看到兩個插件 hypergitvcscommand 都可以在 Vim 裡直接顯示 diff 結果,前者是 git-diff 型式,後者是 vimdiff 型式(取出舊版內容,再切割視窗作 vimdiff)。我的需求比較接近後者,但又不想裝整支 vcscommand,於是擅自取出原始碼,改寫進 vimrc。

用到的 git 指令

  • git rev-parse --show-prefix
    若指令來自 git 的子目錄,輸出該目錄對 .git 的相對路徑。
  • git log -1 --format=format:%s\ %n%h' -- '%an' -- '%ai\ '('%ar') HEAD
    顯示 HEAD 所在的一條 log,輸出格式為 [主題] \n[hash 縮寫] -- [作者] -- [日期] ([相對日期]),
    例如 fefe3c8 -- bootleq -- 2010-08-30 18:14:37 +0800 (13 hours ago)
  • git show HEAD:some_path/some_file
    顯示 HEAD 中 some_file 的檔案內容。

原始碼

gist: 558647 - [.vimrc] git diff.

功能說明

  • GitDiff 指令

    輸入 :GitDiff 即可切割視窗顯示目前檔案的 HEAD 版本,可加一個參數指定更早的 HEAD,例如 :GitDiff 99 取得 HEAD~99 版本。顯示後會進入 diff 模式。另外可用 echo t:git_diff_info 顯示該 commit 的簡易資訊。
    GitDiff t:git_diff_info 顯示
    註:指令取的 HEAD~ 並無限制「有變更當前檔案」所以可能往前跳好幾個 HEAD 也看不出變化。
  • MyDiffOff 指令

    因為預設的 :diffoff 指令可能會把本來的 foldmethod 等 diff 相關選項蓋掉,所以改用 :MyDiffOff 來關閉 diff。
    不過也只是設回我的預設值,沒有真的建立暫存/重設機制。
  • GitDiffOff 指令

    進入 :GitDiff 後,用 :GitDiffOff 快速恢復到原始狀態。(目前只支援從兩視窗的狀態復原)

Vim 簡短入門提示

艱難學習至今,提出三項。


<Esc> 難按

切換 normal mode 是最硬的基本技,<Esc> 不快你立馬被打爆。
可以用 <C-[> 取代,或配合 <Leader>(預設是 \,Enter 鍵是一字或 L 型會影響其手感)或 <LocalLeader> 鍵。
例:按兩下 , 即進入 normal mode

let maplocalleader = ","
noremap  <LocalLeader>, <C-\><C-N>
noremap! <LocalLeader>, <C-\><C-N>

早裝早輕鬆的插件

GSession 配合 git branch(不過 Windows 下比較難使用 git)是管理 session 的快樂方案。參考 Vim-Taiwan 上的討論串
因為多檔編輯需求大,但 session 操作繁瑣,所以認定首要插件就此一支。


學會查 help

大量招式(睇 :help quickref)不是單純常用就能學會的,已知指令也值得翻文複習。畢竟軟體演變下來,誰比較懂得善用靠的還是知識。

  • 更新 help

    如果 Vim 版本很舊,可考慮更新整個 $VIMRUNTIME(包含 help 以外的各種檔案,所以會花點時間)。
    以下為系統有 aap 支援的情形:
    :!cd $VIMRUNTIME
    :!aap -f ftp://ftp.vim.org/pub/vim/runtime/main.aap fetch
    另外安裝插件等操作動到 doc 檔案時,也需要重建一下 tag(讀 help 時用來 jump 的、類似超連結的東西)
    :helptags ~/.vim/doc
    (其中 ~/.vim/doc 可能是其他位置,即放說明文件的地方)
  • 益於求助的 map

    預設 <F1> 等同於 :help 其實不易利用,可改為:

    • 於 normal mode 查詢游標所在單字的說明。
    • 於 visual mode 查詢選取文字的說明,對付 'sb' 這般有引號的項目較方便。
    • 以上若先按 <LocalLeader> 則將說明開在新分頁。
    map  <F1> :help <C-R>=expand('<cword>')<CR><CR>
    map  <LocalLeader><F1> :tab help <C-R>=expand('<cword>')<CR><CR>
    xmap <F1> :<C-U>call RegStash(1)<CR>gvy:let b:tempReg=@"<CR>:call RegStash()<CR>:help <C-R>=b:tempReg<CR><CR>
    xmap <LocalLeader><F1> :<C-U>call RegStash(1)<CR>gvy:let b:tempReg=@"<CR>:call RegStash()<CR>:help <C-R>=b:tempReg<CR><CR>
    (其中 RegStash 來自暫存與還原 register 的 function,也可移掉)

    在 help 文件中,預設可用 <C-]> 跳到游標所在單字的說明(類似超連結),按 <C-O><C-I> 則是在次次跳轉的位置來回(類似上一頁、下一頁),這裡也可稍微 map:

    • 若檔案唯讀(help 文件皆唯讀),就讓 Enter 也可跳進 tag、BS 也可回上一頁。
    • Alt-LeftAlt-Right 當成上一頁、下一頁使用。
    nnoremap <expr> <CR> &modifiable ? "i<CR><C-\><C-N>" : "<C-]>"
    nnoremap <expr> <BS> &modifiable ? "i<C-W><C-\><C-N>" : "<C-O>"
    nnoremap <M-Left> <C-O>
    nnoremap <M-Right> <C-I>

註:以上只是讓動作變簡單,要真正愛用 help,還是要親身體會、自求多福~

部分操作會將相關資訊暫存備用,例如 d 掉的文字會被放進 register 變數,以便之後貼上。

執行 vim script(例如自訂的 map)時若做了這類操作,便可能把本來的 register 等資料蓋掉。以下幾個 function 先將暫存資訊另存,以供 script 結束後還原。

原始碼

gist: 555806 - [.vimrc] simple functions to save/restore position, register or mark.

使用例

:call PosStash(1)   " 暫存目前游標位置
:call PosStash()    " 還原游標位置
:call RegStash(1)   " 暫存目前 register
:call RegStash()    " 還原 register
:call MarkStash(1)  " 暫存目前 mark
:call MarkStash()   " 還原 mark

一般選擇連續多行的註解時,會按 V 進入 visual mode,然後移動游標框住想要的;行數一多,懶惰就出現了。

選取整個註解區是件懊懆事

以下簡陋模擬一個註解用的 text object,方便在 normal mode 按 vac 選取整塊註解(不含空行);同樣按 dac 刪除、yac 複製。

gist: 554634 - [.vimrc] Simple text object for continuous comment

實作上只判斷每行的第一個非空白字元是否被 syntax highlight 為 Comment,所以並不十分可靠,也僅能做 linewise 的選取。若要改善功能,可考慮導入 'comments''commentstring''filetype' 等選項(參考 EnhancedCommentifytComment 的做法),不過複雜度的增加也令人卻步。

附 tComment 用的 map,若游標在註解區就反轉整塊註解,否則照常 :TComment。

map <silent><expr> <LocalLeader>ca IsInComment() ? "vac:TComment<CR>" : ":TComment<CR>"

以前都靠 PSPad 用來製作 bookmarklet 的簡易 script,現在改讓 Vim 處理。

:Bookmarklet 沒錯誤的話即出現此視窗

這個 funcion 需要上一篇的 Vim funcion:使用 closure compiler 壓縮 js 文件,還有 Vim 的 ruby 支援(其實只是用來 encodeURI ……)

原始碼展開如下,但以後更新會放在 gist: 546828 - [.vimrc] make bookmarklet

command! -nargs=* Bookmarklet call Bookmarklet(<f-args>)
fun! Bookmarklet(...)
  let result = JsCompress(0, 0, '--compilation_level=WHITESPACE_ONLY')
  if len(getqflist()) == 0 && strlen(result) > 0
    let reuse_win = 0
    for winnr in tabpagebuflist(tabpagenr())
      if bufname(winnr) == '[Bookmarklet]'
        exec winnr . 'wincmd w'
        let reuse_win = winnr
      endif
    endfor
    if ! reuse_win
      exec 'belowright 5new [Bookmarklet]'
      setlocal noswapfile bufhidden=wipe winfixheight filetype=javascript
    endif
    let result = substitute(result, '[\n]', '', '')
    setlocal buftype=
    call setline(1, 'javascript:(function(){' . result . '})();')
    if has('ruby')
      ruby require("uri"); VIM::Buffer.current.line = URI.escape(VIM::Buffer.current.line);
    else
      echohl WarningMsg | echomsg "No ruby support. Can't do URI encoding." | echohl None
    endif
    setlocal buftype=nofile
  endif
endf

註:僅測試於 Cygwin console Vim + Windows 版的 java,其他環境只測過 Win32 gVim ——失敗。

通用的解決方案請參考 othree 寫的 Vim 儲存完 JavaScript 檔案後自動用 yuicompressor
本篇特徵為:

  • 只使用 google closure-compiler
  • 採用 :make 執行,故能以 Quick Fix 視窗除錯。

    warning_level 設 DEFAULT 的話,壓 MooTools 居然不過……

  • 壓縮前未存檔、實行壓縮前、……幾個場合能先詢問確認。

    不存檔是壓什麼意思的? 若不詢問,每次 :w 就自動跑也是很費時的

  • 壓縮結果可選擇存到改名的檔案(傳回檔名)或存到暫存檔(傳回壓縮後的文字)。
    自動命名規則為 foo.js → foo.min.js 或 foo-debug.js → foo.js。

使用方式

  1. 把本文原始碼加進 .vimrc。 gist: 545665 - [.vimrc] function to use closure-compiler
  2. 下載 closure-compiler 將 jar 放到程式找得到的地方(如程式第一行 let jar = '/scripts/google-compiler-20100616.jar'
  3. 幾種用法:
    • function:call JsCompress(save [, interact [, options ]])
      save: 若為 1 則自動命名存檔,0 則存到暫存檔。
      interact: 若為 1,執行 make 前會詢問確認。
      options: compiler 用的參數字串,例如 '--compilation_level=WHITESPACE_ONLY'。這裡留意若不指定的話, warning_level 用的並非預設值而是 QUITE,因為 warnning 就會有 quickfix 而中斷壓縮。
      :call JsCompress(1, 0, '--warning_level=DEFAULT')
    • command:JsCompress[!] [interact] [options]
      同上但 save 參數被 ! (bang) 取代。
      :JsCompress! 1 '--compilation_level=WHITESPACE_ONLY'
    • autocmd:在 .vimrc 加上 au FileWritePost,BufWritePost *-debug.js :JsCompress! 1
      以後編完 foo-debug.js 存檔,便會詢問是否壓縮。

已知問題

中文檔名在 make 出錯時,quickfix 和產生的新檔是亂碼。

懷疑是 Windows java 的問題所以先不處理

Color scheme: bootleg

用了半年的 Vim,一向認為預設的 slate 加上少許自訂就相當管用,不必再創招煩人了。
直到開始調 gVim,才發現 gui 配色跟 term 實在差很多……
(對付此問題可參考依云寫的 gui2term.py,由 gui 轉出 256 term 的配色。 ——只可惜我的需求是 term2gui)

8/22 放上自訂的 colorscheme bootleg(不是 bootleq 喔)

  • 對稱性:只支援 256 色 term 和 gui,且盡量讓兩者顯示效果相同。
    這裡會特別處理粗體(bold):gui 不使用粗體,cterm 以 MinTTY 預設顯示為準(粗體會被轉為亮色)。
  • 保守:基本配色是從 slate(256 色 term,非 gui 的)繼承而來。
  • 易讀:暗色背景、高對比。
    如果還是有字感覺太暗,歸咎於螢幕設定不同吧。
  • 個人調整:最後原則還是以個人使用為重,像 Pmenu、Diff 的顏色都跟 slate 沒有關聯了。另外還有加幾個自訂 highlight group(主要是 TabLine 系列)若沒特別配合調過,這些都是沒用的。

原始碼

gist: 543652 - Vim colorscheme: bootleg

擷圖參考 (cterm/gui)

畫面很亂不全是配色的錯啊 gui 較少用,問題可能較多

另付敝人調色時愛用的寶貝色表

這圖我也忘記怎麼做的了

Vim Tips Wiki 已經有一篇 CamelCase to under lined and vice versa 提供 visual mode 用的兩份 map。

這篇則是在 normal mode 取得游標所在的 word,若符合 CamelCase 或 under_score 型式便直接變更文字。

特別考慮游標不在 keyword 上的情形,例如 <camelCase>,游標在 < 上也能轉換。
但游標在行尾時,因為 non-blank 的字皆被 <cword> 接受,就先不研究怎麼排除 non-keyword 了。

原始碼:gist: 541723 - [.vimrc] toggle between CamelCased/under_scored word

TYPE IV~沒穿方服~ 從地下的屍體放置場冒出來的僵屍,即使已經死了,但還保留著吃東西的本能。 
文獻來自 PS 恐怖遊戲之非官方印刷品。

遊戲結局數分鐘前才登場的 lo-fi 多邊形喪屍,已經失去 9 成以上的嚇人能力,然後惡靈古堡就爆炸了。

跟洋館的怪物相比實在是很無味的角色,幸好有個多麼好聽的名字而被人記住。

編譯沒有 gui 的 Vim 7.3,無特殊需求,純升級。

  1. ftp.vim.org/pub/vim/unix/ 下載 unix 用的原始碼 vim-7.3.tar.bz2,解開後進入 vim73 目錄。
  2. 直接選 big 包的 features(包括 multi_byte 等沒理由不要的功能)。
    ./configure --with-features=big

(也可加 --enable-rubyinterp 等 optional feature,詳見 ./configure --help)

  1. make
  2. make install

之後下 vim --version 應該就能看到新設定。


部份相關設定(非全部的新功能)

vimrc 基本設定部份

if version >= 703
  set conceallevel=1
  set concealcursor=nc
  set colorcolumn=+1
  set cinoptions+=L0
  set undofile
  set undodir=~/.vim/undofiles
  if !isdirectory(&undodir)
    call mkdir((&undodir, "p")
  endif
  map  <C-ScrollWheelDown> <ScrollWheelRight>
  map  <C-ScrollWheelUp>   <ScrollWheelLeft>
  imap <C-ScrollWheelDown> <ScrollWheelRight>
  imap <C-ScrollWheelUp>   <ScrollWheelLeft>
endif
  • conceal 的設定以不影響排版為主。下圖說明:

    上半 normal mode,下半 insert mode

    以 help 檔為例,平時只要把 |:autocmd| 左右的「|」塗黑,不必真的移除。
    但游標移到該行時,因為 CursorLine 有上深灰,所以能識別有文字被隱藏掉。
    最後在 insert  mode(如圖下半部)或  visual mode,應照實顯示原文。

  • colorcolumn 在 textwidth 右邊標顏色。
    從 PSPad 轉到 Vim 的時候就想要這功能,不過出來以後效果挺醜的(也太麤了吧),所以最後配合 colorscheme 調成只有在游標所在的行能看見。前面擷圖中最右邊黑 column 即是。
  • L 是 cindent 新增的設定值,參考 :help cinoptions-values。
    另有新增 J 給 JavaScript 用,不過似乎預設就包含了。
  • undofile 沒設定的話就完全沒效果……
    這可是讓 :e! 或下次進入 Vim 後還能 undo 的便利功能啊。
  • 新增的水平 ScrollWheel 系列,測試沒效,先放置不管。

偵測到檔案類型為 help 時要跑的設定(help_rc 這種寫法是參考 c9s 的 gist 學來的)

fun! s:help_rc()
  set number
  if version >= 703
    set conceallevel=1
    set concealcursor=nc
    set colorcolumn=+1
  endif
endf
au FileType help :call s:help_rc()

因 help 類型檔案會重設 conceal 設定所以為之。

colorscheme

if version >= 703
  hi Conceal ctermfg=240 ctermbg=Black guifg=#585860 guibg=Black
  hi ColorColumn ctermbg=Black guibg=Black
endif

為了前述效果所用的顏色設定(換個跟我不一樣的 colorscheme 就不適用了。)

FuzzyFinder 找分頁

如果用了之前寫的 Vim 分頁列(tabline)設定,script 空間就會出現變數 s:tabLineTabs,內含所有分頁編號、檔名等資料。

於是可以用 FuzzyFinder 尋找分頁,例如 map <Leader>fg 叫出下面選單:

僅供參考,某些檔案是沒法用 Vim 編輯的

接著按 <CR> 便會跳到該分頁,按 <LocalLeader><CR> 則是關閉該分頁。(當然 g:fuf_keyOpen 系列變數要先設好)


原始碼展開如下(放在 .vimrc)

let g:fuf_tabListener = {}

function! g:fuf_tabListener.onComplete(item, method)
  let l:tabnr = matchstr(a:item, '\d\+')
  if a:method == 4
    silent exec 'normal ' . l:tabnr . 'gt'
  else
    silent exec 'tabclose ' . l:tabnr
  endif
endf

function g:fuf_tabListener.onAbort()
endf

function! g:fuf_tabFinder()
  if exists("s:tabLineTabs")
    let l:tabList = []
    for tab in s:tabLineTabs
      let label = tab.n . '. ' . (strlen(tab.split) > 0 ? ('(' . tab.split . ')') : '') . tab.flag . tab.filename
      if tab.n == tabpagenr()
        let label = '*' . label
      endif
      call add(l:tabList, label)
    endfor
    call fuf#callbackitem#launch('', 1, 'tabs>', g:fuf_tabListener, l:tabList, 0)
  endif
endf

nnoremap <LocalLeader>fg :call g:fuf_tabFinder()<CR>

若沒有用之前寫的 tabline 設定,也可嘗試取該 tabline 原始碼產生 s:tabLineTabs 的部分來用;或從 :tabs 抓分頁資料,再仿照 用 FuzzyFinder 尋找 register 自己建立分頁資料 list。

預設分頁列的不足

  1. 無顯示分頁編號。想用 Ngt 跳到第 N 個分頁時,不易判斷 N 要下多少。
  2. 分頁很多時,超出畫面外的分頁無法顯示。(gVim 會顯示捲動按鈕,故無此問題)
原始的 tabline

修改方式

修改方式是重寫 tabline 這個選項,實際上頗為麻煩,因為整條 tabline 必須一次輸出,而非設定個別 tab 即可。參考 :help setting-tabline,其中給的例子也只提示基本功能而已。

受惠於 Vim 7.3 的幾個進化,這才把 tabline 調成比較堪用的狀態。調整如下:

  1. 顯示分頁編號
  2. 變更了分頁文字內容
    • 僅顯示檔名(不含路徑)
    • 顯示是否修改過(+)和分頁中的分割視窗數(n),大致上和預設相同
    • 新 buffer 顯示 '[New]' ,無檔案的 buffer 顯示 '[Scratch]'
  3. 不顯示關閉按鈕(原本擺在分頁列尾端的 'X')
  4. 使用幾個額外的自訂 highlight group
  5. 可指定分頁最小/最大寬度
  6. 分頁過多時,可指定是否自動平均分頁寬度;且可指定此狀況下的最小分頁寬度
  7. 可指定分頁過長而被截短時,要補上去的省略字串(例:'…'
  8. 可指定目前分頁左右,至少要顯示幾個分頁

原始碼

gist: 523783 - Vim tabline setting

console 擷圖

最後分頁第一個 ~ 來自 strCrop(中文不能只砍半個字),第二個 ~ 來自分頁的截短

gui 擷圖(「分割視窗數」使用了斜體)

前後都有省略符號

選項

因為沒寫成 plugin,只是放在 .vimrc 的 function,所以請直接改原始碼的下列項目:

  • let tabMinWidth = 0
    最小分頁寬度(0: 不限)
  • let tabMaxWidth = 40
    最大分頁寬度(0: 不限)
  • let tabMinWidthResized = 15
    設定 tabDivideEquel 有效時,採用的最小分頁寬度
  • let tabScrollOff = 5
    目前分頁左右至少顯示幾個分頁
  • let tabEllipsis = '…'
    分頁過長而被截短時,要顯示的替代文字
  • let tabDivideEquel = 0
    分頁總長超出畫面時,是否自動均分各分頁寬度

已知問題

在 Vim 7.3e BETA 測試,全型字和自訂 highlight 有時會造成 tabline 重繪不正確。有先發到 vim-dev 再看看情況。
不幸地 Vim 7.3f 狀況仍然存在。

為什麼要 Vim 7.3?

為了計算字串的實際顯示寬度(column 數),需用到新增的 strwidth() 函數。(目前程式若找不到此函數,就會改用 strlen(),計算全型字寬度時結果便不正確)
註:關於全型半型甚至無法確定的狀況,可參考 LGJ Notes | 全型字(Full-Width)、半型字(Half-Width)

此外 Vim 7.3 還新增了滑鼠拖曳分頁調整順序的功能,比起以往只能用 :tabmove N 方便得多。

相關配套調整

  • 永久顯示分頁列
    set showtabline=2
  • gui 也使用純文字的 tabline
    set guioptions-=e
  • 未知寬度的字以兩個 column 顯示
    一般中文字型建議設定 set ambiwidth=double 以避免 '…' 等不定寬度的字顯示錯誤。
  • 相關 highlight group
    因為增加了新的 gorup 而非使用預設值,所以需特別設定下列 highlight。
    建議放在自己的 colorscheme 裡面,或寫成 function 在 autocmd ColorScheme 時呼叫,否則根據經驗,Vim 讀取 session 後自訂的 highlight 會被重設。
    hi TabLine           cterm=underline ctermfg=15    ctermbg=242   gui=underline guibg=#6c6c6c guifg=White
    hi TabLineSel        cterm=bold      gui=NONE      guifg=White
    hi TabLineNr         cterm=underline ctermbg=238   guibg=#444444
    hi TabLineNrSel      cterm=bold      ctermfg=45    guifg=#00d7ff
    hi TabLineFill       cterm=reverse   gui=reverse
    hi TabLineMore       cterm=underline ctermfg=White ctermbg=236   gui=underline guifg=White   guibg=#303030
    hi TabLineSplitNr    cterm=underline ctermfg=148 ctermbg=240   gui=underline,italic guifg=#afd700   guibg=#6c6c6c
    hi TabLineSplitNrSel cterm=NONE      ctermfg=148 ctermbg=236   gui=NONE,italic      guifg=#afd700   guibg=#303030