hasciiの日記

多分プログラミング関係

Vim で if endif とか対応するキーワードを強調表示する

昨日の続き

はじめに

カーソルが if とか対応するものが存在するキーワード上にあるとき、endif とか対応するキーワードを強調表示させる。この機能も多分大体の IDE にはあると思う。
これも Vim でコード読むときあったら便利な気がしたので調べた。

これを実現するための要素

  • カーソルの下の単語が if とか for とか対応するワードが存在するものかどうかの判別
  • 対応するキーワードの取得 (できればネストの考慮)

他、前回の内容

コード

実は "matchit highlight" でググれば同じようなことをしている人が見つかった。
(リンクはっていいか不明なので省略、ググってください)
それを参考にしつつ、記述したのが以下のコード

function! s:GetMatchitPattern(cwd)
  if !exists('b:match_words')
    return ''
  endif
  if !exists('b:mw')
    call s:InitMatchit()
  endif
  for pat in b:mw
    if a:cwd =~# pat
      let l:cmw = pat
      break
    endif
  endfor
  if !exists("l:cmw")
    return ''
  endif

  let lcs = []
  let wsv = winsaveview()
  while 1
    exe 'normal %'
    let lc = {'line': line('.'), 'col': col('.')}
    if len(lcs) > 0
      if (lc.line == lcs[0].line && lc.col == lcs[0].col) || (lc.line == lcs[-1].line && lc.col == lcs[-1].col) 
        break
      endif
    endif
    call add(lcs, lc)
  endwhile
  call winrestview(wsv)
  call map(lcs, '"\\%" . v:val.line . "l\\%" . v:val.col . "c"')
  let lcre = join(lcs, '\|')
  return '.*\%(' . lcre . '\).*\&' . b:mwre
endfunction

function! s:InitMatchit()
  if !exists('b:match_words')
    return
  endif
  let b:mw = split(b:match_words, ',\|:')
  let l:mw = filter(b:mw, 'v:val !~ "^[(){}[\\]]$"')
  let mwre = '\%(' . join(l:mw, '\|') . '\)'
  let b:mwre = substitute(mwre, "'", "''", 'g')
endfunction 

(ほとんど参考元と同じ)
matchit.vim を有効にしていないと動作しません
上記の s:GetMatchitPattern の戻り値を前回同様 matchadd で指定してやれば、if, else, endif などが強調表示される。

内容メモ

ポイントは以下

  • b:match_words
  • マッチング用の正規表現に行番号と列番号を使用
  • winsaveview() と exe 'normal %', winrestview()
b:match_words

b:match_words は matchit.vim プラグインが ( や { 以外の言語特有の対応パターンを認識するのに使用している変数。
file type のプラグインなどで、 <開始パターン>[:<中間パターン>]:<終了パターン>[,他パターン...] の形式で定義されている。

例) filetype=vim の場合(一部抜粋)

\<fu\%[nction]\>:\<retu\%[rn]\>:\<endf\%[unction]\>,\<\(wh\%[ile]\|for\)\>:\<brea\%[k]\>:\<con\%[tinue]\>:\<end\(w\%[hile]\|fo\%[r]\)\>

このパターンを使用して、現在のカーソル下の単語が特殊なキーワードかどうか判別できる
その場合は、split() を使用して、',' と '|' で分割してやればよい

マッチング用の正規表現に行番号と列番号を使用

Vim正規表現では、行番号と列番号を指定可能

例えば、"\%100l" のように指定するとそのファイルの 100 行目がマッチング対象となる。
さらに、"\%100l\%2c" と指定すれば、ファイルの 100 行目の 2 文字目がマッチング対象となる。
後は、その文字の後ろに b:match_words から取り出した単語をマッチング対象として加えれば良い。

winsaveview() と exe 'normal %', winrestview()

簡単に説明すると、
1. winsaveview() で現在の画面の状態を保存
2. exe 'normal %' で '%' を押したのと同じ状態にする (この後、現在の行番号と列番号を取得)
3. winrestview() で最初に保存した画面の状態を復元

関連する help

  • b:match_words
  • len()
  • add()
  • exists()
  • line()
  • col()
  • map()
  • filter()
  • split()
  • join()
  • execute
  • winsaveview()
  • winrestview()

課題

  • 相変らず CursorMove 系の autocmd から呼んでいるので、非力な環境だと辛いかも

(しかし、独立して使用可能なタイマー機能が Vim にはない…)