vim 用 Template-Toolkit のシンタックスハイライト

ひさびさに Template Toolkit を触ってて,普通に html のシンタックスハイライトで書いていたんですが,< とか > とかの演算子がでてくると,ハイライトがおかしくなるのが煩わしい!

と思ってたら,TT2 用の文法ファイルを書いてくださった方がいらっしゃるんですね。

ということで,以下の先人の知恵をほぼそのままお借りしました。

シンタックスファイルのインストール

以下は「個人ユーザ」用のインストール方法です。システムワイドな場合の方法はホームディレクトリのかわりに $VIMRUNTIME 以下のディレクトリでやるのかな?よくわかりません。

  1. TT2 syntax - Syntax Highlight for Template-Toolkit 2.x : vim online からファイル群を落とす。
  2. ~/.vim/syntax/ というディレクトリを(なければ)掘る
  3. 落としたアーカイブに含まれる tt2.vimtt2html.vim というファイルをそのディレクトリにコピーする

シンタックスハイライトを有効にする

んで,インストールしただけだと使えないので,それを autocmd という機能で使えるようにします。

この autocmd をどこに書くかが問題ですが,~/.vimrc に書いてもたぶん大丈夫かなぁと思いますが,慣例的に ftdetect/ というディレクトリにスクリプトを置くみたい

今回の場合(個人ユーザ向けなので)~/.vim/ftdetect/ というディレクトリを(なければ)掘って,その下にたとえば tt2.vim などのファイルを作成します。

あとは,そのファイルに TT2 syntax - Syntax Highlight for Template-Toolkit 2.x : vim online にあるように

au BufNewFile,BufRead *.tt2 setf tt2

と記述すると,.tt2 という拡張子のファイルの場合に Template Toolkitシンタックスハイライトが有効になります。


んが,この tt2 という syntax はあくまで Template Toolkit の文法しか解釈しません。普通 Template Toolkit を使うのは HTML ですよね。なので,HTML と Template Toolkit の文法をどちらもシンタックスハイライトしてくれる syntax も定義されています。それが tt2html というシンタックスです。たとえば,

au BufNewFile,BufRead *.thtml setf tt2html

のようにすると,.thtml という拡張子のファイルの場合に,HTML と TT2 両者のシンタックスハイライトが反映されます。

ファイルの内容に応じて tt2tt2html を切り替える

んで,拡張子に応じてファイルタイプを設定するのなら(あるいはモードラインで設定するのなら)以上の設定でいいんですが,同じ .tt2 という拡張子で,HTML の場合には自動的に tt2html のファイルタイプにしてくれるスクリプトが,配布元(TT2 syntax - Syntax Highlight for Template-Toolkit 2.x : vim online)に記述されています。それが,これ。

au BufNewFile,BufRead *.tt2
    \ if ( getline(1) . getline(2) . getline(3) =~ '<\chtml'
    \           && getline(1) . getline(2) . getline(3) !~ '<[%?]' )
    \   || getline(1) =~ '<!DOCTYPE HTML' |
    \   setf tt2html |
    \ else |
    \   setf tt2 |
    \ endif 

なんかめまいがしますが,やっていることはなんとなく想像つきますね。

めまいがする理由は 「\」 と 「|」 の氾濫です。vim では,行頭に 「\」 があると,前の行からの継続行になります。一般的なシェルスクリプトスクリプト言語だと行末に 「\」 を置くもんですが,vim だと逆なんですね。それで,「|」というのが,シェルスクリプトでいうところの「;」にあたるものです。一行内に複数のコマンドを詰め込むときに使います。

このようにがんばって継続行で定義しなくても,ローカルな関数を定義してそれを呼び出すようにしてあげれば,みやすくはなります。

function! s:FTtt2()
    if ( getline(1) . getline(2) . getline(3) =~ '<\chtml'
              && getline(1) . getline(2) . getline(3) !~ '<[%?]' )
       || getline(1) =~ '<!DOCTYPE HTML'
        setf tt2html
    else
        setf tt2
    endif 
endfunction

autocmd BufNewFile,BufRead *.tt2 call s:FTtt2()

関数名の「s:」というプリフィックスが,このスクリプトローカルでしか見えない関数の定義です(たしか変数でも同じようなプリフィックスを指定するとスクリプトローカルなスコープを指定できたはず)。それを autocmdcall してやればよい,と。これでスクリプトとしては見やすくなりました。


んで,自分の環境だと,先頭に Template Toolkit のコマンド群がだだっと書いてあるテンプレートファイルを扱ったりするんで,3行だけで判断されるとうまく判定できない場合がありました。なので,まったく違うアプローチで自分用に書いてみました。

function! s:FTtt2()
    let save_cursor = getpos('.')
    call cursor(1, 1)
    if search('\<\c\%(html\|head\|body\|div\)', 'cn') > 0
        setf tt2html
    else
        setf tt2
    endif
    call setpos('.', save_cursor)
endfunction

au BufNewFile,BufRead *.tt2 call s:FTtt2()

検索機能を使って HTML かどうか判断してます(かなり適当ですが)。でもファイルタイプの判定にこういうことあんまりしないほうがいいのかなぁ。検索バッファに残る?重い?

よくわかりませんが,一応動くのでこれでよしとしています。識者のご指摘を待ちたいところです。

拡張子 .html でも TT2 ハイライトを行いたい

それ

au BufNewFile,BufRead *.html setf tt2html

で,もういいんじゃないかと思ったりしますが,念のために TT2 のコマンドが入っていないファイルは純粋な html のシンタックスハイライトを行ってほしい。

そう思ったので書いてみました。

function! s:FTtt2html()
    let save_cursor = getpos('.')
    call cursor(1, 1)
    if search('\[%', 'cn') > 0
        setlocal filetype=tt2html
    endif
    call setpos('.', save_cursor)
endfunction

autocmd BufReadPost *.html call s:FTtt2html()

vim のドキュメントを読む限り,同一のイベント・ファイル名に対して複数の autocmd を指定してもうまくいくはずなのですが,うまくいかなかったので autocmd! でもともと設定されている内容を消去してます。んで,$VIMRUNTIME/filetype.vim で定義されてる s:FThtml() という関数は呼び出せないので(先ほど書いたようにスクリプトローカルなスコープなのであたりまえ),自分なりの関数を書いて呼び出してます。

ここらへんをもっとうまくやる方法があったら知りたい!
(既存の autocmd を生かした方法…… BufEnter 以外で……というか BufEnter を使った方がいいの?)

2011-06-03: コメントでご指摘の内容に基づき改変済


最後になりますが,真面目?な方は,これまでの内容を if has("autocmd") で囲んだほうがいいかもしれません。

if has("autocmd")

    " ここにさきほどまでの設定内容をいれる

endif

まぁたいてい autocmd は使えると思いますけど。

TT2 シンタックスファイルの修正

コメントまわりで挙動があれっという部分がありました。

# これはうまく動く(ブロックコメント)
[%# this entire directive is ignored no
    matter how many lines it wraps onto
%]

# これもうまく動く
[% # this us a comment
   theta = 20    # so is this
   rho   = 30    # <aol>me too!</aol>
%]

# これがうまく動かない(baz の部分もコメントとみなされてしまう)
[% 'foo' # bar %] baz

なので,自分用に tt2.vim を修正しました。

--- tt2.vim.orig        2009-06-24 09:56:59.000000000 +0900
+++ tt2.vim     2009-06-26 10:21:06.000000000 +0900
@@ -161,7 +161,7 @@
 syn match   tt2_operator  "\(\s\)\@<=_\(\s\)\@="            contained
 syn match   tt2_operator  "=>\|,"                           contained
 syn match   tt2_deref     "\([[:alnum:]_)\]}]\s*\)\@<=\."   contained
-syn match   tt2_comment   +#.*$+                            contained extend
+syn match   tt2_comment   +#.\{-}\%(%\]\|$\)\@=+            contained extend
 syn match   tt2_func      +\<\I\w*\(\s*(\)\@=+              contained nextgroup=tt2_bracket_r skipempty skipwhite
 "
 syn region  tt2_bracket_r  start=+(+ end=+)+                contained contains=@tt2_statement_cluster keepend extend

一行なので手であてたほうが早いと思いますが。

あと,TT2 の開始・終了タグは [% 〜 %] で決め打ちにしてます。ほんとは,b:tt2_syn_tags という変数(カスタム開始終了タグ)が定義されているときは,それに応じた内容にしなきゃいけないんですが,面倒(&自分の環境だとデフォルト)なので。