はじめに

これはVim2 Advent Calendar 2017 13日目の記事です
前日の方に引き続き,自作vim pluginの紹介です.

VinConf2017 の感極まり駆動開発で,vim-shinyというプラグインを作成した.
今までに,いくつかプラグインを実装したことはあものの, そのほとんどが,syntax-highlightを行うためのもの (例えば,vim-jsx-pretty) だった.

そのため,あまりふさわしくないコードなどが含まれているかもしれない.

背景

Atomのpluginである,vim-mode-plus の紹介をVimConf2017でされているときに,視覚的にわかりやすいインタフェース,具体的には,モーションを活用してyankなどを行ったり,ペーストしたりした時に,フェードアニメーションが表示されるのはわかりやすいと感じた,
実用時にもわかりやすいし,デモのときなどもわかりやすい.
好みはあるが,Vimにもほしいという声は少なくないと思う.

また,Vimには,オペレーターに限ってこのようにハイライトする機能を提供しているプラグインはすでに存在していた.
(haya14busa/vim-operator-flashy)

今回は,vim-mode-plus と vim-operator-flashy を参考にしつつ,Vimをより視覚的にわかりやすくするためのプラグイン vim-shiny の実装を行った.

特徴

現在,vim-shinyが提供している機能を紹介する.

1つは,ペースト箇所のハイライトである.

  • ペースト時のハイライト

Paste demo

この機能は,'termguicolors' または,has('gui_running') のどちらかが有効なときに, g:vim_shiny_enable_fade_on_gui を有効することでフェードさせることができる.

Fade animation demo

  • ウィンドウ変更時のハイライト

change window demo

これは,colorcolumn を使って無理やり実装したので,あまり良くないと思う.
詳細は,次の章で触れる.

使い方

お好みのプラグインマネージャーで,本プラグインを導入した後に,vimrcに次の設定を追記..

nmap p  <Plug>(shiny-p)
nmap P  <Plug>(shiny-P)
nmap gp <Plug>(shiny-gp)
nmap gP <Plug>(shiny-gP)

実装について

ペースト時のハイライト

いまは,色を決め打ちで決めているので,rgbaからカラーコード,カラーコードからrgbaに変換するような関数を実装し,指定されたhighlight groupの持っている色から,カラーコードを取得し,それをrgba に変換してalpha値を基準にフェード前後の色を自動で補完しても良いかもしれない.

実装は,結構原始的な実装方法をしており,(forループを回して,色を都度変更) 環境によってはハイライトが重かったりするかもしれない.

function! s:flash(patterns, group) abort
  let i = 0
  for p in a:patterns
    if getchar(1)
      break
    endif
    call s:Highlight.highlight('ShinyFlash' . i, a:group, p, 1)
    let i += 1
  endfor
  redraw
  call s:clear(i)
endfunction

部分的かつ一時的にハイライトする方法は,vim-operator-flashyを参考にした.
Vitalの外部拡張モジュールである,Coasterというものを使用した.

Coasterの作者さま,vim-operator-frashyの作者さま,ありがとうございました :bow:

ウィンドウ変更時のハイライト

少し前でも触れたが,ウィンドウ変更時のハイライトは 'colorcolumn' を利用している. 'colorcolumn' は,特定の列に hl-ColorColumn グループでハイライトするというものである.
通常,メールなど横幅に制限のある文章を書く時や,lintなどで1行にかける文字の量を制限しているときに活用する.

これを,1列目からWindow幅目までsetすることで,擬似的にマスクをかけたようにしている.

function! shiny#window#flash() abort
  for i in range(1, tabpagewinnr(tabpagenr(), '$'))
    let range = ''
    if i == winnr()
      let l:width = 256
      let l:range = join(range(1, l:width), ',')
      exec 'highlight! link ColorColumn ' . s:vim_shiny_hi_window_change
      call setwinvar(i, '&colorcolumn', range)
      call s:clear()
    endif
  endfor
endfunction

tput コマンドで Window幅を取得しても良かったが,GUIのときどうなるか想像できなかったので,256と決め打ちにしている.

悩み

10p などをノーマルモードで押下したときのrange指定は対応したが,まだ ドットリピートに対応できていない.
ドットリピートができないのは結構致命的なので何らかの方法で実装したい.

プラグインでキーマッピングを提供したとき,それをドットリピートさせたいとき他の人はどうしてるのだろうか・・・?

他にも,undo や . で バッファの内容が増えることはよくあるだろう.
そのときに,どうにかしてハイライトしたいなーという思いがある.

両方,ドットリピートの話でややこしいのでまとめておく.

  • ペーストのマッピングを上書きしたあとでも . で繰り返しペーストさせたい
  • . によって何らかの文字列が挿入されたとき,その挿入された差分をハイライトさせたい

まとめ

  • vim-shinyというものを作った.
  • vim-mode-plusと比べるとハイライトしている箇所やハイライトのわかりやすさは現状劣っている.
  • 少しづつ良くしていきたい
    • ここ2ヶ月触れていないので,年末年始のまったりとした時間をつかって,これをいじって遊びたい.