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 什麼的,因為沒在用,我就沒研究了。