沒穿方服

索引

顯示╱隱藏內文

要怎麼把 Yes 換成 No? 標準答案是 ciw 再輸入 "No"。

更快的方法是寫 script 處理,很久以前看到一篇 Toggling yes-no, 把常用組合先定義好,只要一個鍵就能把 Yes 換成 No,再按一次就換回來,真是聰明啊~ 這方法我用了很久,當時改出來的設定如這個 gist

因為覺得方便,又有蠻多地方想改善,所以打算寫成 plugin。
搜尋 toggle 才發現真多人寫過,不過似乎大同小異,除了 SwapIt.vim

  • 用 visual 選起來,就可以處理含空白的 some words(原本的實作方式辦不到)。
  • 可以把 <H1>example</H1> 變成 <H2>example</H2>(一次換掉前後 tag)。
  • 可以吃 omni-completion 的結果,所以不必額外設定,就知道 CSS display block 可以換成 inlinenone 等等。
  • 其它。

我比較同意 SwapIt 的設計,認定 swap 這個功能,用力加強它。
SwapIt 的問題是實作還未成熟,所以我也 fork 它準備貢獻。 但是送第二個 pull request 前,覺得有點走不下去:

  • 繼續改下去,細部的實作幾乎都不一樣。
  • 重寫比重構簡單。
  • 在使用者立場,我想像的 interface 是另一番面貌,向前相容顯得多餘。
  • 我想全權處理所有問題。

另外對 SwapIt 這個名字也有點在意,至少我搜尋的時候就沒想到 swap 這關鍵字。
想起來最適合的字是 Cycle(因為是在一組字中循環,例如 top right bottom left),但是在 GitHub 一搜就發現已經有同名 plugin 了 ——zef/vim-cycle——很精簡,程式聰明漂亮,可惜作者太忙比較沒空更新。


結果我的 plugin 還是叫 cycle,這樣好嗎?

我把碰到的困難告訴兩位開發者 Michael BrownZef,他們都很開放,表示基本上不介意我這麼做(我想還是有點情緒干擾吧); 而雖然大家目標類似,但個人考量的點(有趣的是想法從 coding style 與用法設計就看得出來)還是不容易統合,變成一個專案。

這種情況下,我就先開發再說了,把手上的問題解決。

目前 0.1.0 版本已經是堪用階段: bootleq/vim-cycle - GitHub
之後會再寫一篇簡易使用說明。

最近在寫 plugin,對之前的 coding style(主要用在 vimrc)有些不同意了。
其中一些比較確定、通用的部分是關於可讀性的,整理如下:

  1. 不要用簡寫
  2. 在 modeline 寫明縮排規則
  3. 用 marker 手動折疊程式碼,建立 section
  4. 增加空行,分隔程式區塊
  5. 註解不應影響程式排版
  6. 串接多個項目時,拆成多行
  7. 適當使用 Dictionary (hash) 進行參數操作
  8. 其他相關但未規範的部分

不要用簡寫

指的是選項、指令名稱的簡寫,例如:

  • function!fun!
  • setlocal textwidth=78setl tw=78
  • execute "normal! dd"exe "norm! dd"

例子可能不夠極端(還是看得懂),但是已經失去字面上的意思(最簡單的可讀)了。

此外還有一致性的問題,完全一樣的意思卻不能預期用哪個敘述。
例如想找函數定義,直接搜尋 func! 卻找不到 function!,問題是什麼? 其實不必問了,這完全是可以避免的問題。

總之除非有足夠理由(例如故意用 fun! 定義比較 funny 的函數),還是一律用完整寫法吧。


在 modeline 寫明縮排規則

縮排和可讀性的關係是,縮排亂掉的時候會很難讀。

首先要訂好自己的縮排規範,用空白還是 Tab、要空幾格,然後確實遵守。
再來要了解每個人的規則會不一樣:

  • 你用 Tab 排好的版,別人打開還是可能亂掉。
  • 別人編輯你的 script 後,可能也塞了不一致的縮排字元進去。

減輕問題的方式是在每個檔案加上 modeline:
例如一律用空白,確保排版不會被 Tab 弄亂
" vim: expandtab softtabstop=2 shiftwidth=2
如果還是偏好 Tab(也許為了減少字數)
" vim: noexpandtab tabstop=8 shiftwidth=8

附上我的基本 modeline 如下(寫在檔案最後一行)

" modeline {{{
" vim: expandtab softtabstop=2 shiftwidth=2 foldmethod=marker

用 marker 手動折疊程式碼,建立 section

在註解中使用 marker(預設是 {{{}}})整理程式碼是很普遍的作法, script 稍大時幾乎都會用上,否則會覺得難爬。

對以下常見的 folding:

  1. 多個有關的函數,可以歸納為一個 section:和 Vim motion 的 section 無關),但是沒什麼好方法表示,於是加上註解「以下這段是做 XXX」,然後在結束的地方寫「XXX 做完了」。這整段就適合當作一個 fold
  2. 讓多次出現的語法單位(通常就是函數)可以各自折疊

規範一個通用的格式。

以下是我的例子。 從 Utils: 開始是一個叫做 Utils 的 section,用大寫字和冒號形成一個 vimCommentTitle(來自預設的 syntax 上色),又更顯目一點。

" Utils: {{{

function! s:save_reg(name) "{{{
  let s:save_reg = [getreg(a:name), getregtype(a:name)]
endfunction "}}}

function! s:restore_reg(name) "{{{
  if exists('s:save_reg')
    call setreg(a:name, s:save_reg[0], s:save_reg[1])
  endif
endfunction "}}}

" }}} Utils

折起來(一層)後,擷圖如下

折起一層


增加空行,分隔程式區塊

  • 函數之間,空 2 行。
    因為函數中本來就經常會空 1 行,所以要空超過 1 行才清楚。
  • section 之間,空 2 行。
    這裡 2 行就夠了,因為 marker(在註解中)和程式之間也有空行,所以不算註解的話,實際上 section 之間會空 6 行。

全部縮起來(zM)時,看見所有 section

全部縮起來(zM)時,看見所有 section

打開一個 section,看見所有 function

抱歉擷圖是舊程式,函數之間只空 1 行,應該空 2 行……
打開一個 section,看見所有 function(抱歉擷圖是舊程式,函數之間只空 1 行,應該空 2 行)

各個 function 之間空兩行

各個 function 之間空兩行

各個 section 之間空兩行,實際程式空了 6 行

各個 section 之間空兩行,實際程式空了 6 行

註解不應影響程式排版

手動 fold 有很多層的時候,我曾經把深層的程式加一級縮排:

" MAPPINGS             {{{1 ==================================================

  let maplocalleader = ","
  noremap  <Leader><LocalLeader> <LocalLeader>

  "   各種移動    {{{2

    noremap <expr> <Space>  repeat('<ScrollWheelDown>', 2)

看起來似乎更清楚,但這個超過註解該做的事了。
理想上要專注於程式本身的表達能力才對。


串接多個項目時,拆成多行

寫下 aaa, bbb, ccc, ... 這樣的敘述時,除非真的很簡單,否則不要擠在同一行,難讀又難改。

  • 參數很多時
  • 定義複雜的 List (Array) 或 Dictionary (Hash)

請用 backslash (\) 拆開。

call setline(
      \   a:before.line,
      \   substitute(
      \     getline(a:before.line),
      \     '\%' . a:before.col . 'c' . s:escape_pattern(a:before.text),
      \     s:escape_sub_expr(a:after.text),
      \     ''
      \   )
      \ )

這裡的縮排比較詭異,不是由 indent 選項而是由一個變數控制,見 ft-vim-indent,預設是 shiftwidth 的三倍。
let g:vim_indent_cont = &shiftwidth * 3
我沒有設這個變數,backslash 之後的縮排也是手動做的,規則是至少空一格,其餘每一層就縮一級。
單純是「採用預設值」這個考量。


適當使用 Dictionary (hash) 進行參數操作

可以考慮多用 hash(Vim 裡面叫 Dictionary)作為函數的參數,例如原本是:
function Img(src, alt, size, class) 或接受任意個參數的
function Img(src, alt, size, ...) 都可以改成
function Img(src, options)
就不必記參數的順序,或再解析 a:0a:1(額外的參數會被轉成 a:N)了。

存取 options 的時候,盡量用點(.)取代方括號([]),
例如 options.size 就比 options['size'] 簡單明瞭。

不過因為 Vim script 的特性,也有一些像陷阱的東西。
例如取值的時候要用 get(options, 'size') 比較安全,沒有 'size' 這個 key 的時候才會回 0,而不是報錯。
還有在 value 不是 Number 時,直接 if get(options, 'key') 會有型別轉換的細節要注意, 通常會加一道 if type(get(options, 'key')) == type(xxx) 檢查,比較麻煩。


其他相關但未規範的部分

  • 單行的長度

    要填滿整行的話,會用 textwidth=78,和 Vim doc 一樣。
    但是程式內文就不必了,因為怕太長其實是硬體問題,我認為不用管它,也解決不了它。

  • 變數命名

    Vim 對變數名稱有一些意見,例如 user function 第一個字要大寫,大小寫變數存進 session 時的規則也不同。
    原則上使用 underscore_names 安全又簡單,可惜有時候必須大寫,還是會破例。

    具體用字則是愈短、愈直接愈好,scope、autoload 和 Dictionary 可以善加利用,
    例如 s:widthsu#do()contribute.till_die

  • key mapping 的記法

    原則是和 Vim doc 一致,也就是用 <C-X> 而不用 <C-x>
    不過這樣會失去大小寫的區別。

  • Magic 的 regexp

    Vim 的 regexp pattern 對哪些字元有特殊意義(需要跳脫)和別家解釋不太一樣, 在 pattern 前加上 \v \m \M \V 又有不同解讀, 在 plugin 或一些內建 function 裡還會一律視為 \m。

    這個因應情境有很多變數,所以目前也沒有規範。

對齊文字用的 plugin,以前我是用 Dr. ChipAlign.vim,現在換成 Alignta 了;雖然預設功能比較不豐富,但實在好懂多了。

目前 ver. 0.2.1 只有日本語的 doc,如果 help 打不開(E426),可能要先 :set notagbsearch


SYNOPSIS of Alignta command (for v. 0.2.1, UNOFFICIAL)

                              (Shifting
                               Alignment)
                                 <- 
                                 -> 
                                 <--
                                 -->

:[range]Alignta[!] [filter]  [align method][margin]   [-p] pattern[{count}] [pattern2 ...]

                   g/pattern  (Padding                              number
                   v/pattern   Alignment)                            '+'
                                  <           n:n                
                                  |        [0-9][0-9]
                                  >          [0-9]
                                  =          
                              (repeat as     @margin
                                LM[R]...)
  • !

    有 ! 的話,pattern 會被解釋為 regexp。

  • filter

    指定忽略哪些特殊的行(通常是註解)不進行對齊。g/pattern 只處理符合的行;v/pattern 不處理符合的行。

  • align method

    Alignta 的對齊分為 Shifting Alignment 和 Padding Alignment 兩種。
    align method 參數也分為兩套,區別是哪一套的同時,也用來指定對齊細節。

    Shifting Alignment 比較簡單,只是將 pattern 的開頭垂直排在一起。
    <- 是將每一行符合的部分,對齊到其中最靠左的 column;-> 則是對齊到最靠右的 column。
    兩個 dash 的版本(例如 -->)表示使用 tab 字元排版。

    另一種 Padding Alignment 是依 patten 將文字拆成多個欄位。
    以 {L}{M}{R} 為一個單位,{M} 就是 pattern 符合的欄,{L}{R} 是它的左右側。 可以用 <|>= 指定各欄位靠左、靠中、靠右或保持原本方式對齊。
    註:{R} 可省略,表示跟 {L} 相同,例如 :Alignta <|> = 讓等號兩邊靠外對齊。
    註:若後面有指定多個 pattern,這裡可以重覆 {L}{M}{R} 來指定多個對齊方式。

  • margin

    指定各欄位的間隔。
    一般是配合 align metohd,有幾個欄位就寫幾個 margin,各個 margin 以冒號分開,例如 :Alignta <<3:1 =
    也可簡寫為 {L-margin}{R-margin} 或 {LR-margin},但 margin 必須是個位數( 0-9,10 就不行了), 例如 46 即 4:6,4 即 4:4。
    若沒有 align method,只想設 margin 時,在前面加 @ 即可,例如 :Alignta @00 =

  • -p

    如果後面的 pattern 是特殊字,加上 -p 可確保它被視為 pattern。 例如 :Alignta -p << 是以 << 作為 pattern。

  • pattern[{count}]

    pattern 指定要用什麼來對齊。
    如果有設定 alignta_default_arguments 變數,那這個也能省略。

    pattern 後面可以加 {count},指定要符合幾次,超過 count 就不會再對齊了。
    例如 :Alignta ={2} 只會處理前兩個等號,之後的就保持原狀。
    {+} 表示有 pattern 符合時才對齊,否則不理它。

    count 的預設值在 Shifting Alignment 是 {1},Padding Alignment 是 {+}

  • pattern2 ...

    支援複數的 pattern,直接寫在後面。例如 :Alignta = /* */

  • :Align

    如果沒裝 Align.vim,那 :Align 就是 :Alignta 的別名,直接用 :Align 也 OK。

Help 檔案中,超連結的寫法是用兩個星號圍起來,例如:

*starting.txt*  For Vim version 7.3.  Last change: 2010 Sep 18
                                        *bold* *underline* *undercurl*
                                        *inverse* *italic* *standout*
term={attr-list}                        *attr-list* *highlight-term* *E418*
                                                        *right-justify*
PREVENTING LOADING                                              *netrw-noload*

如果是寫在行尾的,我就每次看了都想把它靠右對齊……

目前應該是沒這規範,看過的 help 也沒特別處理;總之我寫了一個 function 來做這件事;
只支援單行、只處理最後一個 *entry*,超過 'textwidth' 的也不管:

Right justify *keyword* in Vim help files. — Gist


使用前('textwidth' 為 78,游標在 CONFIGURATION 那一行)

==============================================================================
CONFIGURATION                         *gsession-configuration*

使用後

==============================================================================
CONFIGURATION                                         *gsession-configuration*

可以在 filetype 為 help 時定義這個 key mapping

nnoremap <silent> <buffer> <LocalLeader>= :call HelpHyperTextEntryJustify()<Enter>

原版的 ShowMarks 在 2004 後就沒更新了,在 Vim7 使用會有一些問題。 我聯絡不上作者,又 script 授權是 public domain,於是直接開個版本來維護:

bootleq/ShowMarks - GitHub

其中幾個修改參考了 vi/vim使用进阶: 指随意动,移动如飞 (二) – 易水博客 的 patch。

主要問題與修改

  • key mapping

    ※如果你有舊版,可以下 :verbose map <Leader>m 檢視它定義的 5 個 mapping,另有 \smm 也是。
    commit:a7d7d62 首先要改的是 map => nmap,以免在 Visual mode 或 Select mode(例如 snipMate 停著等輸入時就是 Select mode)按 m 變成 ShowMarks 操作導致錯誤。
    再來增加了停用預設 mapping 的選項和 <Plug>ShowMarksOn 這樣的 mapping(讓使用者用 map mo <Plug>ShowMarksOn 自訂按鍵),應該是 plugin 預設 map 的最佳作法。 commit:72727b 另外原版用到 \sm 來轉接 m 指令(以便按下 m 時,除了原本 m 要做的事,還要 call 別的函數),這裡利用 :normal! 不會被 remap 的特性取代,也把它拿掉了。
  • 「刪除 mark」的實作

    commit:7ad76a
    單純是 Vim7 的新功能 :delmarks
    舊版 :ShowMarksClearMark 作法是把 mark 都移到第 0 行,幾乎是不堪用的。
  • 判斷 mark 所在行數

    commit:6e087f4
    Vim 6/7 處理 line("'g")(取 g mark 的行數)時,若 mark 在別的 buffer 就會有不一致結果。
    單純改用 getpos("'g") 來判斷。
  • 放棄 Vim 6 以下的支援

    掰掰。