色々な言葉を理解する。
Babel: active code in Org-mode
これ、本当に凄いですね。
以前のエントリで、gauche を評価する scratch バッファもどきを作ったなんてつまらない話を書きましたが、本当は、こんなものを求めていたんだ、と思いました。
どんなものかと言うと、例えば、プログラムを書くべくアイデアを練っているとします。アイデアをただのテキストにメモしているバッファ上で、ちょっとコードを書いて確認してみたいなんて思ったら、おもむろに、
M-x org-babel-demarcate-block (C-c C-v d)
とすると、ミニバッファで、
Lang:
と訊かれるので、書きたいプログラム言語を指示してやります。すると、
#+begin_src C #+end_src
というコード断片を記述するためのメタブロックが、現在のバッファ上に作成されます。ここに、該当するプログラミング言語のコード断片を記述するのですが、そのやり方も、このブロックにポイントしている状態で、
M-x org-edit-special (C-c ')
とすると、新たなバッファをプログラミング言語に対応するモードで開いてくれるという親切設計。
更に、
M-x org-babel-execute-src-block (C-c C-c)
とすると、ポイントされているブロックのコードを実行して結果を出力してくれます。
いや、便利ですし、色んな可能性が感じられてワクワクします。チュートリアルなどを見ると、shell でコマンドを実行した結果を R に喰わせてグラフ化するとか、まあ素晴らしい。
この org-babel のインストールや使い方などは、
Emacs上のマルチな実行環境、Org-babel - sheephead Emacs org-modeを使ってみる: (35) org-babel-perlを使う1/4 - 屯遁のパズルとプログラミングの日記
などを見て貰えば詳しく解説されています。
但し、現在は org-babel が contrib から org-mode 本体に統合されたので、インストールの手順などは変わってます。まあ、楽になったので問題ないと思いますが。特別な環境でなければ make && sudo make install 的な普通の手順で org-babel までインストールされます。
私はホームディレクトリに作ってある elisp 置場におきたいのと、その際にディレクトリにまとめておきたかったので、以下の様な感じでインストールしました。
$ make prefix=~/.emacs-23.d/ lispdir=~/.emacs-23.d/share/emacs/site-lisp/orgmode $ make prefix=~/.emacs-23.d/ lispdir=~/.emacs-23.d/share/emacs/site-lisp/orgmode install
そして、.emacs とかに、
(require 'org-babel-init) (eval-after-load "org" '(progn (require 'ob-sh) (require 'ob-R) (require 'ob-ruby) (require 'ob-python)))
と書けば良い様です。
さて、本来はこれで動作する筈なんですが、私の環境では上手く動いてくれませんでした。調べてみると問題は、利用する各プログラム言語に対応するモードの hook でした。
前述の様に、コードを編集するときには、各言語の編集用モードを適用したバッファを開いてくれるのですが、そのときに、当然ながらそれぞれのモードフックが実行されます。本来ならば普通にフック関数が実行されるのですが、このバッファには visit しているファイルがありません。元の org-mode で記述されているバッファからメタブロック内のコードを抽出して別のバッファを作っていますので対応するファイルが存在しないのです。実際のところ、新たに開かれたバッファには、最終的には `buffer-file-name' が設定されるのですが、言語ごとのモードが適用されるときにはまだ設定されていないという訳です。
そのため、通常であれば対応するファイルが存在しているバッファ上で使われる各モードフックから実行される関数のうち、buffer-file-name が nil でないことを仮定しているコードがあると、それが失敗してしまいます。
自分が独自に展開しているコードは直せば良いので別に良いのですが、ちょっと困ったのは flymake でした。flymake を有効にしたいプログラミングモードでは、モードフックで `flymake-mode' を有効にしているのですが、flymake が `buffer-file-name' があることを仮定していました。syntax-check を行なうために、言語ごとに異なる設定や準備が必要だからですね。
取り敢えず、`buffer-file-name' が存在しないときには flymake-mode にはしない様に、
(add-hook 'c-mode-common-hook '(lambda () ;; ob-C のために buffer-file-name が設定されていないことを考慮。 (when buffer-file-name (flymake-mode t) (define-key (current-local-map) "\C-cc" 'flymake-start-syntax-check) (define-key (current-local-map) "\C-ce" 'flymake-show-and-sit) (define-key (current-local-map) "\C-cn" 'flymake-goto-next-error) (define-key (current-local-map) "\C-cp" 'flymake-goto-prev-error))))
などとして逃げました。ruby-mode の場合は、そもそも rhtml のために似たようなことをしてモードフックから除外していたので問題ありませんでした。
さて、これでは `org-edit-special' して編集するときに、flymake の恩恵を受けることができません。これは余りにも残念です。
なので少しばかり考えました。
(push '(".*Org Src.*\\[ ruby \\]" flymake-ruby-init flymake-simple-cleanup-extra) flymake-allowed-file-name-masks) (push '(".*Org Src.*\\[ C \\]" flymake-c-init flymake-simple-cleanup-extra) flymake-allowed-file-name-masks) (defun enable-flymake-setup () "org-babel (ob-C) のバッファに flymake-mode を適用する。" (flymake-mode t) (define-key (current-local-map) "\C-cc" 'flymake-start-syntax-check) (define-key (current-local-map) "\C-ce" 'flymake-show-and-sit) (define-key (current-local-map) "\C-cn" 'flymake-goto-next-error) (define-key (current-local-map) "\C-cp" 'flymake-goto-prev-error)) (defadvice org-src-mode-configure-edit-buffer (after org-src-mode-configure-edit-buffer-for-c activate) "c-mode-common-hook が走るタイミングでは buffer-file-name が設定されていない ため、少し遅延して setup を実施する。" (enable-flymake-setup))
それぞれコード編集用に開かれるバッファに設定される `buffer-file-name' のパターンを flymake に知らせてやった上で、`org-src-mode-configure-edit-buffer' という org-mode の「コード編集バッファを設定する関数」に無理矢理アドバイスして、flymake-mode を有効にする様にしてみました。
これで取り敢えずは使えてます。が、また更に工夫しないとならないところがあったり。