沒穿方服

索引

顯示╱隱藏內文

預設分頁列的不足

  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

Vim 外掛程式 FuzzyFinder 實在方便。(但這篇不含介紹)

除了找檔案、buffer、help 等 13 目標外,亦有 API 讓其他資料享用其搜尋介面。

以下使用 FuzzyFinder 的 Callback-Item 模式,列出所有 register,選取項目並開啟時,便直接貼上 register 內容,或噴到一個新視窗(可編輯後再手動 :%y x 存回 x register)。 尚有毛病但還堪用。

具體用例:

  • <LocalLeader>fp → 開啟 registers 選單。選好按 <CR> 貼上,或按 <LocalLeader><CR> 開到新視窗。
  • <LocalLeader>fP → 同上,但貼在游標之前。

選單展開(看得出顯示有問題)

單行太長時,顯示有問題

將 @7 內容開到新視窗

上面視窗即 @7 的內容


程式碼展開如下(放在 .vimrc)

let g:fuf_regListener = {}
let g:fuf_regListener.putBefore = 0   " 0: p, 1: P

function! g:fuf_regListener.onComplete(item, method)
  let l:regName = strpart(a:item, 1, 1)
  if a:method == 4
    silent exec 'normal "' . l:regName . (g:fuf_regListener.putBefore ? 'P' : 'p')
  else
    exec '7new [@' . escape( l:regName, '"' ) . ']'
    setlocal noswapfile buftype=nofile bufhidden=wipe
    exec '0put=@' . l:regName
    redraw
    setlocal nomodified
  endif
endf

function! g:fuf_regListener.onAbort()
endf

function! g:fuf_regFinder(putBefore)
  let g:fuf_regListener.putBefore = a:putBefore
  redir => l:regs
  silent exec ':registers'
  redir END
  let l:regList = split(l:regs, '\n')
  let l:regList = filter(l:regList, 'v:val =~ "' . escape('\m^".\s\{3,}\S\+', '\"') . '"')  " remove non-register lines
  "let l:regList = map(l:regList, 'substitute(v:val, "\\m.\\{' . &columns . '}\\zs.*", "...", "")')  " has problem with long line
  call fuf#callbackitem#launch('', 1, 'registers>', g:fuf_regListener, l:regList, 0)
endf

nnoremap <LocalLeader>fp :call g:fuf_regFinder(0)<CR>
nnoremap <LocalLeader>fP :call g:fuf_regFinder(1)<CR>

其中 onCompletemethod 參數,取決於選擇項目時按了哪個 FuzzyFinder 的 mapping(詳見 |fuf-options|):

  • 1 → 普通開啟
  • 2 → split 開啟
  • 3 → vsplit 開啟(我 map 到 <LocalLeader><CR>
  • 4 → tab 開啟(我 map 到 <CR>

程式在 register 單行內容很長時會有問題:選單顯示錯誤/無法取得 register 名稱。把後面截斷可解決(但截掉部份當然搜不到)。暫且加上 nowrap 減少狀況:autocmd BufEnter \[fuf\] setlocal nowrap


幾支 register 相關 plugin,也許這才是你要的:

  • YankRing.vimp 貼完以後,按 <CTRL-P> 可以立刻換成其他剪下的內容。也可開視窗檢視過去 yank 內容。
  • tregisters: 可列出 register 還能直接編輯,不過我沒裝成功。

2016-08-06: 新版的 Vim 已支援 TabClosed 事件,更正確/簡單的實作見 Vim 7.4.2077 新增 TabClosed 事件

網路上有看到在 TabLeave 時,記錄最後 tab 編號的方法;缺點是 tab 關閉時,次序就亂了。

可能的困難是沒有 TabClose 事件,既有的 TabLeave 和 TabEnter 要判斷 tab 關閉又有點麻煩。例如開了 4 個 tab,關閉 tab 3 時,會進入 TabLeave,再 TabEnter 到 tab 4,這時 tabpagenr() 還是 4 —— 也就是會先進旁邊的 tab,TabEnter 事件後 tab 總數才會減少,而不是先關閉 tab 再移動。

雖然沒想出不同角度的解法,但以下 function 也算達到目的:

function LastTab(act)

    let lt = g:lasttab
    let tabClosed = tabpagenr('$') < lt.knownLength ? 1 : 0

    if a:act ==# 'TabLeave'
        if ! tabClosed
            let lt.prevLeave = lt.leave
        elseif lt.prevLeave > tabpagenr()
            let lt.prevLeave -= 1
        endif
        let lt.leave = tabpagenr()
    elseif a:act ==# 'switch'
        if tabClosed
            let lastTab = lt.prevLeave > tabpagenr() ? lt.prevLeave -1  : lt.prevLeave
        else
            let lastTab = lt.leave
        endif
        if lastTab == tabpagenr()
            echo 'Already on last tab.'
        else
            exe "tabn " . lastTab
        endif
    endif

    let lt.knownLength = tabpagenr('$')

endfunction

if ! exists('g:lasttab')
    let g:lasttab = {'leave':1, 'prevLeave':1, 'knownLength':1}
endif
au TabLeave * :call LastTab('TabLeave')
au TabEnter * :call LastTab('TabEnter')
nnoremap <LocalLeader>t :call LastTab('switch')<CR>
inoremap <LocalLeader>t <C-\><C-N>:call LastTab('switch')<CR>

這段放到 .vimrc,之後按 <LocalLeader>t 就能切換至最後選取的分頁了。
不過剛進入 Vim 時,第一次切換會跳到第一個 tab。

我認為眼鏡行只要別破壞市容就謝天謝地了,所以傾向支持外觀比較乾淨的某連鎖店。
可惜專業程度的落差,堅持之餘還是失守了……生活品質啊,一團瘴氣。

  大眼鏡 創眼鏡
服務態度 不錯,隨店員而異 禮尚往來,細心解說
驗光 每次驗都不一樣,且度數都超過實際值(要配新的囉) 一次就發現配太重
重新驗光 有點不耐 沒驗幾次,也不需要
我懷疑多焦點鏡片有角度差異 不會呀,沒有這種問題 有問題(拿出鏡片講解),且不是人人能適應
舊的鏡片 沒問我怎麼處理。
後來要找,從垃圾桶翻出來……
包裝好歸還
新鏡片度數不適應 請我跑診所檢查,事後在店裡發現是鏡架調整問題 商量之下,讓我換了一副,不過有貼錢
價格 沒感覺 似乎有點貴,不出名的品牌
爛藝人海報、看板 這間還算窗明几淨 大量噁心海報。
經老闆說明現狀、成因,雙方溝通後,已成功徹下指標性的嫌惡廣告

週末讀到 par 這個小巧指令,用於文字段落(paragraph)的格式化,例如在 Vim 裡面用 :'<,'>!par 把選取區內特定格式的段落重排。

看看 Descripton 裡的範例 Par 1.52 - paragraph reformatter 便能大概瞭解其用途。
有趣的是說明文件 par.doc,以我水平實在難懂,好像不屬這世界的寫法;如做智力測驗題目的兒時記憶,讓我半夜對著螢幕靜靜傻笑。

以上樂趣分享,以下簡易筆記(私用性質,請小心誤導


par 轉換大略過程 (how it works)

  1. Text

  2. 根據換行字元拆成 lines

                    line
    Text     =>     line
                    line
                      ⋮
  3. 將 lines 分為幾組 segment

    會找出 protected line(開頭字元符合 protected 字,具體哪些字可另外設定)或 blank line(僅含空白、Tab 等字的行)——這兩種行將直接輸出而不會轉換;而剩下來一段段的 lines 就是 segment。
    line (protected)
    line                        segment 1
    line                        segment 1
    line                 =>     segment 1 (3 lines)
    line (blank)
    line                        segment 2
    line                        segment 2 (n lines)
      ⋮                               ⋮
  4. 將 segment 內容(lines)分為幾組 block

    會找出 bodiless line(內容不是真的本文,例如 /*** ... ****/)和 superfluous line(冗行:整行空白、或既 bodiless 且中間重複字元全為空白)。
    bodiless line - 根據選項(repeat)調整輸出方式,
    superfluous line - 根據選項(expel)可能不輸出。
    segment line                         (只有一行應該不算 block?但仍會在後續步驟處理)
    segment line (bodiless)               
    segment line                 =>       block 1
    segment line                          block 1
    segment line (superfluous)
    segment line                          block 2
          ⋮
  5. 將 block 分為幾組 IP (Input paragraph)

    若選項(div)沒開,一個 block 就是一個 IP
  6. 對每個 IP 中的 lines,拿掉 <prefix>, <suffix>,剩下的字重排

    /* PREFIX WORD, WORD SUFFIX */
    /* PREFIX WORD SUFFIX */
    /* PREFIX WORD. WORD WORD SUFFIX */
                  ↓
    WORD, WORD
    WORD
    WORD. WORD WORD
                  ↓
    WORD, WORD WORD 
    WORD. WORD WORD
  7. 把 <prefix>, <suffix> 插回去

實際上根據選項,過程中各種細節都會影響輸出,不過這邊就沒研究了。


par 使用小抄 (how-to-use-it)

此為上課順手記,不是實戰用的。

par 59f         f (fit) 盡可能保持原寬度(59 字)
par 59l         l (last) 盡可能讓最後一行與其它行同寬
par 59t0        t0 (touch 0) 輸出寬固定為 59
par 59j         j (just) text justified
par 59h3        h (hang) 分開前 3 行與之後行的 prefix, suffix 計算
par 59p         p (prefix) 保證每行的前 59 字和 Input 相同
par 59d         d (div) 每行依照 comprelen+1 個字是否為空格,切割為不同 IP
par 59r         r (repeat, r 不給參數則為 3) 變更 bodiless line 定義,repeat 字元 3 次以上也算為 bodiless(原本只有空白才算)
                r 不為 0 時,會使 repeat 字元最多調整到 59
par 59e         e (expel) 不輸出 superfluous line
par 59q         q (quote) 於不同級的引用插入 vacant line
par 59i         i (invis) 不輸出 quote 插入的 vacant line
par 59g         g (guess) 判斷相連的 curious word, capitalized word 如何連接(例:Dr. Jon 分行時被當作一個字)
par 59gc        c (cap) 所有的詞皆視為 capitalized word
par Q+:+ q      將 "+ " 也視為引用字元(加入 PARQUOTE)再執行 q