Tlistを改造してExuberant Ctagsにファイルエンコーディングを指定できるようにした

Windows上のKaoriYa版VimにてExuberant Ctags日本語対応版taglist.vimを組み合わせて使っているのだが、最近になってCtagsのタグ生成に失敗することがあることに気づいた。

具体的には、UTF-8で書かれているC++ソースコードのタグを生成させた時に、ソースファイルの途中のシンボルまでしかタグが生成されないことがあった。特に、C++の1行コメントのスタイルで日本語でコメントを記述していると、こういう現象が起きることがあるようだ。

Ctagsのマニュアルを見たところ、日本語対応版には--jcodeという入力ファイルのエンコーディングを指定できるオプションが追加されているようだ。このオプションを指定しない場合、Windowsでは入力ファイルをシフトJISとみなして処理するらしい。

ということは、UTF-8シフトJISとみなして解釈している時に、何らかの原因でファイル途中がファイル末端と誤認識されるとか、途中からずっとコメントアウトされているものと誤認識されるとか、1行コメントの次の行もコメントの続きと誤認識されることでソースコードのパース処理が滅茶苦茶になるとか、おそらくそういうことが起きているのだろう。

この問題が起きないようにするには、UTF-8で書かれたファイルをCtagsで扱う時に、「--jcode=utf8」という風なオプションを付け加えればよい。

taglist.vim 4.6の中身を見てみたところ、内部でCtagsのコマンドを組み立てているようだ。その処理中に--jcodeを付け加えられるようにすれば、適切なエンコーディングでファイルを解析できるようになるはずだ。

そこでtaglist.vimを改造してみた。ひとまず以下のコマンドを追加して、手動で--jcodeオプションに渡すエンコーディングを設定できるようにしてみた。

TlistSetCtagsEncoding {encoding}
--jcodeオプションで指定するエンコーディングを設定する。設定できるのはascii、eucsjis、utf8。設定すると、TlistからCtagsを実行する際に--jcodeを付加するようになる。
TlistUnsetCtagsEncoding
--jcodeオプションを使わないようにする。

デフォルトでは従来通り--jcodeオプションを使わない。ctagsのタグ生成に失敗した場合にTlistSetCtagsEncodingでエンコーディングを設定してTlistUpdateする、という使い方だ。

以下はtaglist.vim 4.6の修正ヶ所の差分パッチだ。

*** taglist.vim	2013-02-26 19:47:16.000000000 +0900
--- taglist.mod.vim	2015-07-24 22:42:08.922663100 +0900
***************
*** 241,246 ****
--- 241,251 ----
          let Tlist_Max_Tag_Length = 10
      endif
  
+     " File encoding for Exuberant Ctags Japanized Edition (--jcode option)
+     if !exists('Tlist_Ctags_Encoding')
+         let Tlist_Ctags_Encoding = ''
+     endif
+ 
      " Do not change the name of the taglist title variable. The winmanager
      " plugin relies on this name to determine the title for the taglist
      " plugin.
***************
*** 301,306 ****
--- 306,318 ----
      command! -nargs=0 -bar TlistUndebug  call s:Tlist_Debug_Disable()
      command! -nargs=0 -bar TlistMessages call s:Tlist_Debug_Show()
  
+     " File encoding for Exuberant Ctags Japanized Edition (--jcode option)
+     command! -nargs=1 -complete=customlist,s:Tlist_Ctags_Encodings -bar
+                 \ TlistSetCtagsEncoding 
+                 \ let Tlist_Ctags_Encoding = s:Tlist_Sanitize_Encoding(<q-args>)
+     command! -nargs=0 -bar TlistUnsetCtagsEncoding
+                 \ let Tlist_Ctags_Encoding = ''
+ 
      " Define autocommands to autoload the taglist plugin when needed.
  
      " Trick to get the current script ID
***************
*** 2332,2337 ****
--- 2344,2355 ----
      " Add the filetype specific arguments
      let ctags_args = ctags_args . ' ' . s:tlist_{a:ftype}_ctags_args
  
+     " Add --jcode option
+     if g:Tlist_Ctags_Encoding != ''
+         let ctags_args = ctags_args . ' "--jcode=' .
+                     \ s:Tlist_Sanitize_Encoding(g:Tlist_Ctags_Encoding) . '"'
+     endif
+ 
      " Ctags command to produce output with regexp for locating the tags
      let ctags_cmd = g:Tlist_Ctags_Cmd . ctags_args
      let ctags_cmd = ctags_cmd . ' "' . a:filename . '"'
***************
*** 4618,4623 ****
--- 4636,4663 ----
      call s:Tlist_Window_Refresh()
  endfunction
  
+ " Tlist_Ctags_Encoding_List
+ " Return encoding list for Exuberant Ctags Japanized Edition.
+ function! s:Tlist_Ctags_Encoding_List()
+     return ['ascii', 'euc', 'sjis', 'utf8']
+ endfunction
+ 
+ " Tlist_Ctags_Encodings
+ " Return encoding list for Exuberant Ctags Japanized Edition.
+ function! s:Tlist_Ctags_Encodings(A, L, P)
+     return s:Tlist_Ctags_Encoding_List()
+ endfunction
+ 
+ " Tlist_Sanitize_Encoding
+ " XXX
+ function! s:Tlist_Sanitize_Encoding(fenc)
+     if index(s:Tlist_Ctags_Encoding_List(), a:fenc) >= 0
+         return a:fenc
+     else
+         return ''
+     endif
+ endfunction
+ 
  " Tlist_Set_App
  " Set the name of the external plugin/application to which taglist
  " belongs.

本当はファイルのエンコーディングに応じて--jcodeで設定するエンコーディングを自動的に選択させたかったのだが、カレントのバッファに関してはfileencodingやencodingでエンコーディングを推測できそうだったものの、カレント以外のバッファに関しては外部ツールを使わずに文字コードを推測する方法が分からなかったので、諦めて手動対応にした。

さて、ここまでやってからuniversal-ctagsエンコーディングを指定出来るオプションが追加されたという記事を発見し、何となく最近のVim使い的にはExuberant Ctags日本語対応版ではなくuniversal-ctagsを使うほうが主流なのではないかという気がしてきた。

universal-ctagsを使って.ctagsに「--input-encoding-c=sjis」とか書いておけば、プログラミング言語によってソースファイルのエンコーディングが違う程度ならtaglist.vimを改造せずとも今回のような問題は起きないはずだ。

もっとも、今の私の作業環境は同じC++でもシフトJIS(CP932)とUTF-8 BOM付きとUTF-8 BOM無しが混在しているというやや鬼畜仕様なので、やはり最終的にはこの改造版のように手動でエンコーディングを設定できる仕組みが欲しいのかもしれない。