沒穿方服

封存

顯示╱隱藏內文

Vim 開啟大檔案會變慢、慢到難以忍受的問題,通常停用一些功能就會改善了。 極端點用 vim -u NONE filename(不讀 vimrc 也不開任何外掛)開檔,就知道是檔案太大還是設定問題。

Dr Chip 的 LargeFile plugin 就是在偵測到大型檔案時,自動關閉 syntax、folding 等昂貴功能,離開 buffer 時再調回來。 使用上不必多做什麼動作,就能達到明顯加速效果,20 萬行的 rails log 開起來也沒什麼感覺。


原版 LargeFile 設定較少,所以我的 .vimrc 一直有整段抄襲版的 BigFile 實作,用了好幾年,一直有解不開的怪問題,發作起來會把全域 'undolevels' 設為 -1 害我完全不能 undo,非常困擾。

這次直接 fork 原版 LargeFile,程式先重構一下,居然就解掉了……
自訂的選項也都加了進去,專案在 bootleq/LargeFile,再測一陣子就會送交原作者處置。


具體設定例

基本選項,就這幾個:

let g:LargeFile           = 40            " 超過這個 size 才會處理。 預設是 20
let g:LargeFile_size_unit = 1024          " g:LargeFile 的單位,1024 就是 KB。 預設是 MB
let g:LargeFile_patterns  = '*.log,*.sql' " 只處理 log 和 sql 檔案。 預設是 *
let g:LargeFile_verbose   = 0             " 不要顯示訊息(例如 LargeFile 生效時)。 預設是 1

LargeFile 會停用 FileType autocmd,如果想對特定檔案動手腳,可使用新增的 autocmd 事件:

autocmd User LargeFileRead call s:large_file_read()   " 每次 Large File 發生 BufRead 時發動
autocmd User LargeFile call s:large_file_detected()   " 偵測到 Large File 時發動,包含 :edit! 或 jump 進入時

function! s:large_file_detected()
  let ext_name = expand('%:e')
  if ext_name == 'log'
    " nnoremap <buffer> <LocalLeader>ddd :EmptyFile<CR>   " 按這個鍵把 log 全清掉(實作省略……)
  endif
endfunction

function! s:large_file_read()
  let dir_name = expand('%:p:h')
  if dir_name =~ '/home/www/some_rails_app/\(\w\+/\)\?log'
    if &fileencoding != 'utf-8'   " 不是 utf-8 就重讀吧
      edit! ++enc=utf-8
    endif
    syntax match railslogEscape '\e\[[0-9;]*m' conceal    " 有這個才不會看到一堆色碼(需要裝 rails.vim)
  elseif dir_name == '/home/www/logs'
    set syntax=httplog    " 上色(需要裝 httplog)
  endif
endfunction

xml.vim(原 vim script 1397)這個 plugin 提供一些小功能,寫 HTML 的時候很實用。
不過檢討一下發現,我只用到其中一兩個 mapping 而已…… 最後決定移除,自己重製。

各種功能重現

  • 自動關閉 tag

    <div 後面輸入 > 時,會自動補上 </div> 並把游標放回 tag 中間。
    現改為以下 function,按 >> 發動。
    inoremap <buffer> >> <C-\><C-N>:call <SID>html_close_tag()<CR>
    function! s:html_close_tag()
      " Find < or >, should take action only when < appears first.
      if search('\v(\<)|(\>)', 'bnpW') == 2
        let missing_gt = getline('.')[col('.')-1] == '>' ? '' : '>'
        execute "normal! a" . missing_gt . "</" . "\<C-X>\<C-O>" . "\<C-\>\<C-N>F<"
        call feedkeys('i', 'n')
      else
        call feedkeys('a>>', 'n')
      endif
    endfunction
    無論在 <div<div> 後都能正常運作。
  • 將 tag 拆為多行

    前述 <div>|</div>再按一次 >(| 為游標位置)的話,tag 會被拆為
    <div>
      |
    </div>
    這個我沒做。 但可配合下面的 function 改為按 >> 後再按 ;; 達成。
  • 自動建立 tag

    輸入 div;; 就會展開為
    <div>
      |
    </div>
    且如果該 tag 前面已有文字,就不會拆為多行,而是變成單行的 <div>|</div>
    新的實作如下:
    inoremap <buffer> ;; <C-\><C-N>:call <SID>html_make_tag()<CR>
    function! s:html_make_tag()
      let save_reg = [getreg(), getregtype()]
      execute "normal! a \<C-\>\<C-N>db"
      let tag_name = @"
      call setreg(v:register, save_reg[0], save_reg[1])
    
      " Skip invalid tag_name (non-words)
      if tag_name =~ '\W'
        " Special case to break single-line tag into multiline form. (that is, 'make' an existed tag)
        " e.g.: when input ;; between <div></div>
        if tag_name == '>' && search('\%#\s*<', 'cnW', line('.'))
          execute "normal! cl" . tag_name . "\<C-\>\<C-N>a\<CR>"
          call feedkeys('O', 'n')
        else
          execute "normal! cl" . tag_name
          call feedkeys('a;;', 'n')
        endif
        return
      endif
    
      let unaryTagsStack = get(b:, 'unaryTagsStack', "area base br col command embed hr img input keygen link meta param source track wbr")
      if index(split(unaryTagsStack, '\s'), tag_name, 'ic') >= 0
        execute "normal! a<" . tag_name . ">"
        call feedkeys('a', 'n')
      else
        " Don't break tag into multi lines if current line is not empty.
        if getline('.') =~ '\S'
          execute "normal! cl<" . tag_name . "></" . tag_name . ">\<C-\>\<C-N>F>"
          call feedkeys('a', 'n')
        else
          execute "normal! cl<" . tag_name . ">\<CR></" . tag_name . ">\<C-\>\<C-N>O"
          call feedkeys('"_cc', 'n')
        endif
      endif
    endfunction
  • 在開/關 tag 之間移動

    <div> 上按 % 就能跳到 </div> 的位置,也可來回跳。
    這個其實不必裝 xml.vim,啟用 matchit 就能辦到了,見 :help matchit
  • change 外圍 tag

    <di|v>foobar</div>\c,可以把成對的 div 取代為別的值。
    這個也不必裝 xml.vim,改用 surround.vim 比較實在。
    按鍵是 cst< (Change Surrounding Tags with Tag...) 就會提示你輸入 tag name。

備註

  • 本文的 2 個 inoremap 正確用法是在 filetype 為 html 時才生效。
  • 以上實作會依賴一些選項,譬如 auto close tag 是藉由 </ 後的 omni-completion 達成,換行部分可能也需要正確的 indent 設定。
  • xml.vim 還有一些額外功能,例如處理 HTML comment 什麼的,因為沒在用,我就沒研究了。

zsh 還未正式支援 cap (Capistrano) 自動完成,不過以下專案有實作:

  • zsh-users/zsh-completions
    準官方的 _cap function,能補全命令列參數和 tasks。 不過沒做快取,使用上有延遲感。
  • oh-my-zsh
    只能補 tasks,且不包含 task 的說明。
    有快取,會在 Rails 目錄下存一個 .cap_tasks~ 暫存檔,當 config/deploy.rb 變動時更新。
    問題是比較過時,至少要等 Pull Request #367 後才支援 Capistrano 2.0。

我不用 oh-my-zsh,所以我想你們也不會。

好掌握的擴充方式是自己 mkdir 放自己的補全方數,譬如放 ~/.zsh/completions/,就要配合 .zshrc 這麼寫(在 compinit 之前):

# user completion plugins
if [[ -d ~/.zsh/completions ]]; then
  fpath=(~/.zsh/completions $fpath)
fi

我選擇 oh-my-zsh 的 task 補全(不顯示說明,比較簡短)和快取(但換成擺到 tmp/ 裡),加上 zsh-completions 的選項補全。 組成的 ~/.zsh/completions/_capistrano 內容在 zsh _capistrano completeion function — Gist