unite-haddock に hoogle の結果を使う source を追加した

以前インストールされているモジュール名を候補とする unite-haddock を書いた が、 それに加えて hoogle がインストールされていれば :Unite hoogle で hoogle の結果を候補として表示できるようにした。 アクションは haddock と同じで、ローカルあるいはリモート (Hackage) のドキュメントをブラウザで開くというもの。

https://github.com/eagletmt/unite-haddock

準備

hoogle が必要。cabal install hoogle でインストールできる。

さらに hoogle のためのデータベースを作る必要がある。 hoogle data で初期化した後、必要に応じて hoogle data $pkgname でデータベースを作って hoogle combine *.hoo で default.hoo を更新していくといいと思う。

使い方

unite.vim のインターフェイスで hoogle による検索結果を見ることができる。

ここから型や関数を選んで、そのドキュメントをブラウザで開くことができる。

:Unite hoogle だけでなく、:UniteWithCursorWord hoogle でカーソル位置にある単語で検索するのも便利かもしれない。

設定

g:unite_source_hoogle_max_candidates に候補として表示する結果の数の最大値を設定できる。 デフォルトでは 200。 これは unite.vim とは独立して (hoogle 側で) 最大値を設定しているため、Unite バッファ上で <Plug>(unite_toggle_max_candidates) を打鍵しても影響が無い点に注意。

また g:unite_source_haddock_browser にドキュメントを見るためのブラウザを設定できる。 多くの環境であれば勝手に適切なコマンドを用いて開いてくれるので設定する必要は無い。 しかし一部の環境では file:///path/to/doc.html#fragment という URI を開く際に /path/to/doc.html#fragment というパスのファイルを開きにいってしまうため、この変数を用意した。 具体的には、awesome のようなマイナーな DE でかつ xdg-open が利用可能なとき、xdg-open がそのような挙動をしてしまう。

他の hoogle を利用しているプラグイン

unite-haskellimportref-hoogle がある。 どちらも ujihisa さんによるもの。

ref-hoogle が機能的に近そうだが、ref-hoogle は単に hoogle での検索結果を表示するだけであり、ドキュメントを開きにいく機能は無い。 あと現状あんまりまともに動いていない気がする……

なぜ自動チェックが嫌いなのか

端的に言うと、自動的にチェッカが実行されている間は Vim への操作がブロックされてしまうから。

自動的に文法のチェックをするようなツールがある。 この手のツールでは Emacs の flymake がたぶん一番有名だけど、Vim にも似た機能をもつプラグインがある。 最近は Syntastic がかなり人気の様子。 デフォルトで多くのツールをサポートし、エラー箇所の表示もかっこいいっぽい。

Syntastic ではどのようにして自動的にチェックしているかというと、BufWritePost (や BufReadPost) のタイミングで文法チェッカを実行し、 結果を location-list に表示したり sign や balloon で強調したりしている。 ここで問題なのは、ファイルへの書き出しの後に文法チェッカが実行されている間は Vim への操作が完全にブロックされてしまう点だ。 ruby -wc のように主に動的型付けなスクリプト言語において本当に文法が正しいかどうかチェックする程度ならば一瞬でチェックが終了するけれども、 g++ -fsyntax-only とか ghc-mod check のようにちょっと一瞬で終わるとは言えないようなチェックが存在する。 そんなチェックをファイルへの書き出しの度に行って、しかもチェックしている間は操作がブロックされるというのはかなりイラつく。 正直、文法エラーを生じるようなプログラムなんてまず書かないし、文法チェック以上のチェック (lint とか型検査とか) こそが役に立つと思うんだけど、 そうなるとチェックに多少時間がかかるようになってブロックされることによるイライラが増す。

外部プロセスとの非同期的なやりとりは vimproc で達成できるので、さらに Vim に setTimeout 的なものがあるといいですね……
Issue #150: タイマーが欲しい · vim-jp/issues

neco-ghc が neocomplcache 無しでも利用できるようになりました

neco-ghc は neocomplcache 専用のプラグインでしたが,構造を変更して neocomplcache 無しでも Vim のオムニ補完の関数として動作できるようにしました. neocomplcache ユーザにとっては表面上の変更点はありません.neocomplcache の有無にかかわらず,補完能力そのものは同じです.

ujihisa/neco-ghc - GitHub

necoghc#omnifunc を 'omnifunc' に設定することで,非 neocomplcache ユーザでも neco-ghc による補完を Vim のオムニ補完として使えます. ~/.vim/ftplugin/haskell.vimsetlocal omnifunc=necoghc#omnifunc と設定すると Haskell のコードを開いたときに自動的に 'omnifunc' に設定できます. そしてインサートモードで C-x C-o とすれば補完できます.

以下は neocomplcache 専用のプラグインから 'omnifunc' としても使えるように書き換えたときにちょっとハマった部分の記録.Vim の補完関数を書いたり,それを neocomplcache からも使えるようにしたい人には参考になるかも.

補完のキャンセル

'omnifunc' の仕様は :help complete-functions にある. a:findstart が 1 のときは補完位置を返すわけだけど,

Return -1 if no completion can be done, the completion will be cancelled with an error message. Return -2 to cancel silently.

と書かれている. これを読んで俺は「-1 を返すと補完はキャンセルされるので a:findstart が 0 で再び呼ばれることはなく,さらに -2 を返すと『パターンは見つかりませんでした』のエラーメッセージも出ない」と解釈した. しかしどうやら,-1 を返したときに関しては a:findstart が 0 で再び呼ばれるのが仕様らしく, そのとき a:base は空文字列になっている. 一方 -2 を返したときは呼ばれない. なので,とりあえず neco-ghc では補完できないときは位置として -1 を返し,a:base が空のときは補完できないものとして空の補完候補を返している.

col('.') の値

a:findstart が 0 で 'omnifunc' が呼ばれたとき,col('.') は「補完位置」を返す. つまり,例えば

foo ba_

において _ の位置でオムニ補完をして,補完位置として ba の先頭を返した場合,補完候補を計算する段階では col('.') の値は 7 ではなく 5 となる.

一方 neocomplcache のプラグインとして動作するとき,s:source.get_complete_words() の内部では col('.') の値は 7 になる. そのかわり(?),s:source.get_complete_words() の第一引数で補完位置として 4 が渡される.

ghc-mod の Vim プラグイン ghcmod-vim を書いた

kazu-yamamoto さんによって開発されている ghc-mod というコマンドとそのための一連の elisp がある. ghc-mod を利用して Vim で補完をサポートする neco-ghc を以前書いたが, 今回は ghc-mod のその他の機能をサポートする Vim プラグイン ghcmod-vim を書いた.eagletmt/ghcmod-vim - GitHub

必要なもの

当然 ghc-mod が必要なので cabal install ghc-mod でインストール. ~/.cabal/bin 等に ghc-mod が置かれますが,そこに正しくパスが通っていて実行可能であることを確認してください. 特に GVim, MacVim を使っていると,シェルでの環境変数GVim, MacVim での環境変数が異なるケースがあるので…

そして vimproc が必要です. 最近 vimproc に追加された checkpid() という関数を使っているため,git pull で最新版を使うことを勧めます. フォールバックは用意しているので,ver 6.1 でも動作します.

機能

:GhcModType, :GhcModTypeClear

Haskell のコードを開き,型を知りたい場所の上にカーソルを移動して :GhcModType とすると,その部分がハイライトされて型が表示されます. 型を表示できるのは単なる式だけでなく,束縛やパターンマッチのパターンにも使えます. また,ハイライトされている位置で再び :GhcModType とするとハイライトの範囲が変わります. スクリーンショットは github のほうの README.md を見てください. ハイライトは :GhcModTypeClear で消せます.

ハイライトの色は g:ghcmod_type_highlight に設定することができます. デフォルトでは 'Search' によってハイライトされます.

本当はバッファ毎にハイライトを設定したいのだけど,難しそうだったためハイライトそのものは現在のウィンドウのみで有効になる. ハイライトしている範囲はバッファ毎に保存される.

:GhcModCheck, :GhcModLint

:GhcModCheck とすると,現在のバッファで開いている Haskell のコードに対してコンパイラのエラー・警告を quickfix ウィンドウに表示します. 何らかのエラー・警告があったときは自動的に quickfix ウィンドウを開きます (勝手に :cwindow します). 同様に :GhcModLint とすると,hlint からのメッセージを quickfix ウィンドウに表示します.

現時点で最新版である ghc-mod 1.10.9 では ghc-mod check はファイルパスではなくファイル名を出力するため, :GhcModCheck は一時的にカレントディレクトリを変更してから setqflist() するという方法でファイル名と実際のファイルパスを関連付けている. しかしこの方法では import しているモジュールが別のディレクトリにあり,そちらのほうでエラー・警告が出た場合に対処できない. これは ghc-mod の問題だと思う. 対策としては,import 先のモジュールもそのプログラムの一部なわけだから, そちらのほうで先に :GhcModCheck して警告が出ないことを確認しておく,というある意味当然な方法がある.

さいごに

ghc-mod type がとても便利そうだったので,そのときの衝動で一気に書いた.

Haskell そのものに慣れていなかったり,利用している関数の型が複雑だったりするときに「ここにどんな型の式を書けばいいんだ???」となることがある. そんなときに,とりあえずその位置に undefined を書いてコンパイルは通るコードにした後,undefined の上で :GhcModType として型を見る,というような活用方法もあると思う.

不具合の報告や機能の追加・変更の希望を待ってます. github の issue に登録したり,Twitter で @eagletmt に報告したり,ここのコメントに書いたりすれば気付きます.

Vim script で NUL 文字を扱う

vim-jp の issues をウォッチしていたら NUL 文字を扱う方法を知ることができたのでメモ. 当該の issue はこれ. #170: Vim のコマンドライン引数が取りたい - Issues - vim-jp/issues - GitHub

Vim の文字列は C と同様に NUL-terminated な表現なので,例えば :echo "foo\0bar" とすると foo と表示される. Vim script 内で完結していれば NUL 文字と出会うことも無いけれども,NUL 文字を区切り文字として使って出力するようなプログラムが一部存在する. 代表例は find -print0 だと思う. :echo system('find . -type f -name "*.vim" -print0') などとすると,最初のパスだけしか表示されない.

そこで readfile() が使える. :help readfile() を見ると

All NUL characters are replaced with a NL character.

と書かれている. readfile() は各行を要素としたリストを返すため,:call system('find . -type f -name "*.vim" -print0 > /tmp/paths.txt') と一旦ファイルに書き出した後に :echo split(readfile('/tmp/paths.txt')[0], '\n') とすれば正しくパスのリストが手に入る. ちなみに実際にこの方法を利用するときは,書き出すテンポラリファイルのパスは tempname() で得たものを使うといいと思う.

近況 2012-2

卒論

2/6 に卒論提出で 2/16 に発表だった.10月にテーマが決まって12月に問題が見つかって以降その問題をどうするか必死に考えたかんじ.結果としてはかなりイマイチな成果だった.

卒論という形でそれなりに長い文章を書くのに苦労した.TeX 力の基礎は身についた気がする.

ghc-mod

卒論提出後あたりから ghc-mod をいじっていた.ちょうど tanakh さんによって ghc-mod type が改良されていて,それを元に Vim ユーザとして出力を変えてもらったり,ghc-mod type の適用範囲を広げたりしていた. このへんを Vim から扱うプラグイン ghcmod-vim を書いていて,ある程度固まったら紹介というか宣伝エントリ書くと思う.

MPEG2-TS

あと最近 MPEG2-TS ファイルの形式について調べてる.TS 周りのツールは Windows 用のものばかりで,UNIX 系向けのが無いなら自分でなんとか作れないかなーと思ってる.