Mac の Emacs でツールチップ辞書を実現しよう。

id:peccu さんからバトンを受けとりました。2日目は インターバルタイマーインプット - ぺっくブログミラーでした。
Emacs Advent Calendar 2009 の3日目というわけで、相撲大好き Emacser の tomoya です。どすこんばんわ。
早速ですが、Mac では、OSX 10.5 から、ネイティブアプリケーションで、Control + Commnad + d を押しながらクリックすると、辞書のツールチップが表示されて超便利です。

前から Emacs で実現したかったこの機能ですが、ようやく実現できるようになったので紹介します。

コマンドラインから辞書をひけるようにする。

Emacs から Dictionary.app を開くのは、dict:// なスキームを使うと可能なのですが、それだと Dictionary.app にフォーカスが移ってしまうため、サクサク使うには億劫です。
コマンドラインから辞書が扱えるようになれば、Emacs で結果を受けとることができるのですが、tekezo さんが コマンドラインから Dictionary.appという記事で公開しているコードを使うと、コマンドラインから高速に辞書をひくことが可能になります。
お好きなディレクトリで、

$ hg clone http://hg.pqrs.org/commandline-dictionary-app

として、ソースを取得するか、http://hg.pqrs.org/commandline-dictionary-app/index.cgi/archive/tip.tar.gz からファイルをダウンロードしましょう。
僕は、/Users/tomoya/projects ディレクトリ以下に色々なソースコードリポジトリを貯めているので、もれなく ~/projects/commandline-dictionary-app にクローンしています。
src ディレクトリに移動して、make すると、dict コマンドが生成されます。
表示の関係から、僕は dict.m の 47行目付近の区切り線を消してから make しました。

popup.el でツールチップを実現。

id:m2ym さんが auto-complete.el から独立させた popup.el を使います (参考 popup.elによるポップアップメニュー、カスケードポップアップメニュー、ツールチップの実現

git clone git://github.com/m2ym/auto-complete.git

で全体を取得するか、http://github.com/m2ym/auto-complete/raw/master/popup.el からダウンロードできます。
僕は、~/projects/auto-complete に取得しています。
で、Emacs から呼び出すための関数を作ります。
辞書引き関数を書き換えました。Mac の Emacs でツールチップ辞書 パート2。を参考にしてください。

(defun ns-popup-dictionary ()
   "カーソル付近の単語を Mac の辞書でひく"
   (interactive)
   (let ((word (substring-no-properties (thing-at-point 'word)))
	 (old-buf (current-buffer))
	 (dict-buf (get-buffer-create "*dictionary.app*"))
	 (view-mode-p)
	 (dict))
     (set-buffer dict-buf)
     (erase-buffer)
     (call-process "/Users/tomoya/projects/commandline-dictionary-app/src/dict"
		   nil "*dictionary.app*" t word
		   "Japanese-English" "Japanese" "Japanese Synonyms")
     (setq dict (buffer-string))
     (set-buffer old-buf)
     (when view-mode
       (view-mode)
       (setq view-mode-p t))
     (popup-tip dict)
     (when view-mode-p
       (view-mode))))

(define-key global-map (kbd "C-M-d") 'ns-popup-dictionary)

とりあえず動作するように、先程でっちあげた代物なのでまだまだ問題がありますが、Control + Meta + d のキーバインドで、カーソル位置の単語が辞書でひかれて、ツールチップ出来るようになりました。

関数の簡単な解説。
  1. 内部変数として「カーソル付近のワードを取得したもの : word」「現在の利用中のバッファ : old-buf」「辞書読み込み用のバッファ : dict-buf」「view-mode であるかどうか : view-mode-p」「辞書の内容の文字列 : dict」を格納するものを用意しました。
  2. set-buffer 関数は表示を切り替えずに、バッファのフォーカスを変更する関数です。*dictionary.app* バッファにフォーカスを変更します。
  3. erase-buffer 関数はフォーカスされているバッファの内容を消去する関数です。*dictionary.app* バッファを初期化します。
  4. call-process は同期的に、外部プロセスを呼び出します。*dictionary.app* バッファにコマンドラインで引いた辞書の内容を読み込んでいます。参考:Emacs内でのプロセス制御について - 武蔵の日記
  5. dict 変数に *dictionary.app* バッファの文字列を代入します。
  6. (set-buffer old-buf) で、元のバッファにフォーカスを戻します。
  7. when view-mode... は、もし、view-mode であれば、view-mode を解除して、view-mode であったことを記録します。view-mode だと、popup.el によるツールチップが表示できないので、一時的にオフにします。
  8. (popup-tip dict) で辞書の内容をツールチップ表示します。
  9. when view-mode-p... は、view-mode だった場合、view-mode に戻します。

という内容の関数を ns-popup-dictionary と定義しました。
キーバインドの割り当ては、僕は Command を Super にしているので、本当は Control + Super + d にしたいのですが、なんかこのキーバンドに上手く割り当てが出来なくて、とりあえず C-M-d にしています。

辞書の表示はこんな感じです。幅とか調整すれば、もう少し綺麗になるはずです。

動画公開(はてなフォトライフのちっちゃいw 後で、YouTube に上げなおそうかしら。でも、雰囲気は十分伝わるかな?)

まとめ。

コードが非常にダサいです。もう少し、関数を整理しておきたいところです。
辞書をサクサク利用しているムービーを公開する予定。録画が終ってるので、あとは、編集だけ。した。
というわけで、もう十分遅くなってしまったので、0時を回ってしまうまえに、公開しておきたいと思います。
明日は、iws さんです。よろしくお願いします。でも、ブログのアドレスを知らない><。。
追記
4日目はiwsさんの 名前を付けた: Emacsと別アプリとの切り貼り連携をホームポジションでです。