shell まわり

いろいろいじってるうちに混乱してきたので以下は根本的に間違ってるかもしれない。

'encoding' を "utf-8" にした方が楽だといったけど ( http://d.hatena.ne.jp/janus_wel/20091203/1259837870 ) まだ Win32 環境 ( 具体的には Windows XP ) の default ではいろいろ不具合がある感じ。で、その不具合のうちのひとつが外との接点である shell 呼び出しとそれの上に乗っかっている機能なんだけどそこらへん何とかしようと思っていろいろやってみた結果。結論は「いじれば何とかなる」。まぁこれやる前から vim 内部で全部済ませるか ASCII characters しか使わないのが smart というのはわかりきっているんだけど。とりあえず Win32 の default shell である cmd.exe の特徴 ( ? ) 。

  • input encoding
    • 標準で付属している command ( 内部 command ) は system の言語 ( ? ) 固有の code page を使うぽい。日本語の場合だと cp932 固定。というのは違うな。えーと正確に言うと決めうちで入力に cp932 を期待している、と言った方がいいか。なんらかの処理をする場合入力文字列は ucs-2le ( Microsoft 社のいう Unicode ) に問答無用で変換されてから処理される ( つまり日本語 system の場合は cp932 -> ucs-2le の変換が暗黙に行われている ) 上にこの部分の変更ができないので融通が利かない、だけならまぁ外側で対応すればいいんだけど扱える文字集合が小さいという欠点はどうしようもない。
    • 標準以外は個別に見ていくしかないんだけど大体何らかの入力を受け取るものは encoding も指定できることが多いので呼び出す際の command を調整すると幸せになれることが多い。
  • output encoding
    • どんな command の出力でも "chcp" で指定された code page に変換される。これはわかりやすいんだけど encoding 指定して出力させたい場合やめてほしいしなんで出力だけ設定できるのかがわからない。
    • さらに内部 command で pipe や redirect すると cmd.exe 起動時の option に "/U" が指定されている場合は ucs-2le 、 "/A" が指定されているもしくはどちらも指定がない場合は cp932 で出力される。それぞれの場合で cp932 な non-ASCII characters を含んだ file を "type" で適当に redirect すると ( type cp932.txt > a.txt ) 結果が違う。
    • 外部 command で pipe や redirect した場合は上記の条件が適用されないのでその command が出力したものがそのまま pipe 先や file に出力される。

で、 vim 側で 'shell' の値を使うのは大体以下の場合かな。抜けがあるかもしれない。

  • filter
    • :help filter
    • とりあえず filter 時に vim が何をやっているかというと filter 対象文字列を該当 buffer の fileencoding で temporary file に書き出し -> 'shell' を使って filter program 起動 -> その結果を temporary file に書き出し -> vim 側でその temporary file を encoding 判別しながら読み込みという流れらしい。なので data を渡す直前に fileencoding を filter program が受け付けるものに変更することで入力はなんとかなる。
    • というわけで手動で fileencoding を set してから filter をかけて最後に fileencoding を元に戻すというのが基本。でまぁ、それもめんどくさいという場合は FilterWritePre と FilterWritePost を使って以下のようにするとおk。 s:SetFileEncoding() に指定する encoding は filter program が解釈できるもの。でもすべての buffer に適用させてしまうと ":make" や ":grep" で都合が悪いこともあるかもしれないので適当な file に書いといて必要になったら source するとかの方がいいかもしんない。
function! s:SetFileEncoding(fileencoding)
    if !exists('b:save_fileencoding')
        let b:save_fileencoding = &fileencoding
    endif
    execute 'set fileencoding=' . a:fileencoding
endfunction

function! s:RestoreFileEncoding()
    if exists('b:save_fileencoding')
        execute 'set fileencoding=' . b:save_fileencoding
        unlet b:save_fileencoding
    endif
endfunction

augroup filterwin32encoding
    autocmd! filterwin32encoding

    autocmd FilterWritePre * call s:SetFileEncoding('cp932')
    autocmd FilterWritePost * call s:RestoreFileEncoding()
augroup END
  • system()
    • :help system()
    • これは 'encoding' と外部 command との変換を行えばいいわけなので iconv を使えば楽。てか以下のようなテケトー関数定義した方がはやい。ホントにテケトーだな。
function! IconvSystem(cmd, input, ...)
    if a:0 < 1
        if a:input !=# ''
            return system(a:cmd, a:input)
        else
            return system(a:cmd)
        endif
    endif

    let to = a:1
    if exists('a:2')
        let from = a:2
    else
        let from = a:1
    endif

    let cmd = iconv(a:cmd, &encoding, to)
    if a:input !=# ''
        let input = iconv(a:input, &encoding, to)
        return iconv(system(cmd, input), from, &encoding)
    else
        return iconv(system(cmd), from, &encoding)
    endif
endfunction
  • execute cmd
    • :help :!
    • :execute iconv('!mkdir はてな', &encoding, 'cp932') とかすれば一応なんとかなる。なんとかなるけどこの書法から手軽さを取ってしまうと何も残らないので残念な結果に。もう :shell してしまった方がはやいかもしれない。
  • execute cmds as stdin
    • :help :write_c
    • buffer に command を羅列するとそれを実行してくれるステキ機能。大量 rename の例が :help rename-files に載ってたりするけど Windows でも rename や git-mv なんかと組み合わせると普通に使える機能。でまぁこいつの場合は普通に ++enc の指定が可能なので :w ++enc=cp932 !cmd とかすると file 名が日本語でもちゃんと処理してくれる。
  • make, external grep
    • :help :grep
    • 両方とも一応何とかなる。
    • 多分 make では encoding を意識することの方が少ないと思うんだけどたとえば xml ( xhtml ) が valid か調べたい場合はやっぱり encoding を指定できる check program を使わないとダメぽい。この場合なんかは HTML Tidy を使って -raw 指定すればいいんだけど外部 command の選定が必要になってくるのでめんどくさいというか。
    • external grep はもう本当に 'grepprg' に指定する program の処理能力にかかっている感じ。多分最適解は lgrep ( http://www.ff.iij4u.or.jp/~nrt/lv/index.html ) だと思うんだけど自前で make しないといけないのでやっぱりめんどくさい。

でまぁ標準である cmd.exe が貧弱すぎるというのもここらへんの煩雑さの要因なのでちょっと powershell も見てみたりしてるんだけどけっこう感覚が違うのでちょいと時間がかかりそうな予感。まぁ別に powershell を使ったからといってここらが解決できるかというとそれもわからないので徒労に終わる可能性もある。あと vim がここらへんの encoding を指定するといい感じにやってくれる option を support してくれればいいなと思っている。思っているだけ。