沒穿方服

索引

顯示╱隱藏內文

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

有時會發現 command line 輸出怪異單引號,例如某些機器上
rm all humans
輸出 `all' 和 `humans',最左邊的符號是 backtick(`)而不是引號,難道是某種內行人的黑話?

查了一些資料,原來是錯誤的歷史用法。

簡短說明

書寫引號原本是以「」和「」為一對,不過早期電腦只支援垂直的「'」, 於是許多輸出設備把

  • 重音符號`」顯示為「
  • 單引號 「'」顯示為「

這樣輸入「``I can't''」就能顯示為「‘‘I can’t’’」—— 顯示正確,但是資料錯誤。

直到 Unicode (1991) 制定各種型式的引號,才算可以處理這個需求。
既然這個月我們還在用 Big-5 的系統報稅,看到這種引號似乎也不必太驚訝。

我的精神一直受噪音(不需要大聲)折磨。
夜半流浪狗、餐廳咀食客、車廂吸牙人、俗不可耐的電視新聞、傍若無人的蠢動、……。

在露天拍賣買了 Mack's 矽膠耳塞(4 顆,NT$ 229),真是相見恨晚。

矽膠耳塞

拆封時有包裝紙,所以照片反光;實際像黏土,顏色是胡蘿蔔色

安裝還算方便。 按一按、調一調,展開就能隔音,也不容易掉。
毋需侵入耳道(也不允許,有標明 Do not insert),所以戴起來也很舒適。

隔音程度是聽得見講話內容,但聽不見有人在你旁邊插口袋。
例子有點爛,總之大部分的聲音只是變小,不會消滅; 其餘小雜音,真的會消失

缺點是會聽到骨導傳遞的聲音,參考常見問題 13 的解釋。
自己身體的聲音,一旦在意起來,也很不自在。