flymake (いままでこれ無しでどうやってプログラム書いてたんだろう)
id:nushioさんの日記(id:nushio:20071201)経由。
Eclipseではコードを書くと随時文法チェックが行われ、エラーのある行がエディタ上に表示される。Eclipseの便利な機能はたくさんあるけど、中でもこの機能はかなり強力で、いままでJavaのコードを書くときはこのためだけにEclipseを使っていたようなもの。
で、これをEmacs上でやってしまうのがflymake。id:nushioさんの記事で初めて知って、さっそく設定してみたのだけど、これは手放せそうにない。いままでどうやってEmacsでプログラムを書いてたんだか分からなくなってきてしまった。
というわけで、布教のために簡単な説明を。
とりあえず使ってみる
flymake.elはEmacs22以降では標準で付属しているらしい。場所はlisp/progmodes/flymake.el。Emacs21以前の場合は自分でflymake.elをインストールする必要がある。
で、とりあえず使ってみたい人は次のelispを.emacsに追加してみるとすぐに体験できるはず。
(require 'flymake) (defun flymake-cc-init () (let* ((temp-file (flymake-init-create-temp-buffer-copy 'flymake-create-temp-inplace)) (local-file (file-relative-name temp-file (file-name-directory buffer-file-name)))) (list "g++" (list "-Wall" "-Wextra" "-fsyntax-only" local-file)))) (push '("\\.cc$" flymake-cc-init) flymake-allowed-file-name-masks) (add-hook 'c++-mode-hook '(lambda () (flymake-mode t)))
.emacsを保存してEmacsを再起動したら、*.ccファイルを開いてみる。すると、文法ミスがある行に色がついているはず。
一般的な使い方
まず、
(require 'flymake)
しないと始まらない。あと、ソースコードを開いたときに自動的にflymake-modeが起動するように、適切なモードにフックを設定しておく。たとえば、C++の場合はこんなかんじ。
(add-hook 'c++-mode-hook '(lambda () (flymake-mode t)))
これで、とりあえずflymake-modeは走るようになる。次に、ソースコードの各種類について、どんなコマンドラインで文法チェックを走らせるかを指定する。
ソースコードの拡張子と、それに対するコマンドラインの指定は、flymake-allowed-file-name-masks変数に格納されている。デフォルトではこんなかんじ。
(defcustom flymake-allowed-file-name-masks '(("\\.c\\'" flymake-simple-make-init) ("\\.cpp\\'" flymake-simple-make-init) ("\\.xml\\'" flymake-xml-init) ("\\.html?\\'" flymake-xml-init) ("\\.cs\\'" flymake-simple-make-init) ("\\.pl\\'" flymake-perl-init) ("\\.h\\'" flymake-master-make-header-init flymake-master-cleanup) ("\\.java\\'" flymake-simple-make-java-init flymake-simple-java-cleanup) ("[0-9]+\\.tex\\'" flymake-master-tex-init flymake-master-cleanup) ("\\.tex\\'" flymake-simple-tex-init) ("\\.idl\\'" flymake-simple-make-init) ...
flymake-simple-make-initは、コマンドラインをelisp内で指定するかわりにmakeを使う。たとえば、flymake-simple-make-initを使ってCのコードを文法チェックしたかったら、ソースコードのディレクトリの中に、以下のようなターゲットを記述したMakefileを置いておく。
.PHONY: check-syntax check-syntax: gcc -Wall -fsyntax-only $(CHK_SOURCES)
これを置いておくと、flymake-simple-make-initは
make -s -C (ディレクトリ) CHK_SOURCES=(ソースファイル名) SYNTAX_CHECK_MODE=1 check-syntax
というコマンドラインを随時呼び出す。この結果を見て、ソースコードの彩色が行われる。
他の言語のデフォルトのコマンドラインは... 何やってるか知らないけど、よしなにしてくれるんじゃないかな。たぶん。
自分でハンドラを定義する
基本的には
(defun flymake-hoge-init () (let* ((temp-file (flymake-init-create-temp-buffer-copy 'flymake-create-temp-inplace)) (local-dir (file-name-directory buffer-file-name)) (local-file (file-relative-name temp-file local-dir))) (list "hoge" (list "--option-1" "--option-2" local-file))))
という感じで定義すれば良いと思う。hogeなところとオプションは適宜設定。
あとは、定義したハンドラを登録しておく。
(push '(".+\\hoge$" flymake-hoge-init) flymake-allowed-file-name-masks)
例: Haskellの場合
EmacsWiki FlymakeHaskellを参考に少し手を加えてみた版。
; .emacs (defun flymake-haskell-init () (let* ((temp-file (flymake-init-create-temp-buffer-copy 'flymake-create-temp-inplace)) (local-dir (file-name-directory buffer-file-name)) (local-file (file-relative-name temp-file local-dir))) (list "~/local/bin/flycheck_haskell.pl" (list local-file local-dir)))) (push '(".+\\hs$" flymake-haskell-init) flymake-allowed-file-name-masks) (push '(".+\\lhs$" flymake-haskell-init) flymake-allowed-file-name-masks) (push '("^\\(\.+\.hs\\|\.lhs\\):\\([0-9]+\\):\\([0-9]+\\):\\(.+\\)" 1 2 3 4) flymake-err-line-patterns) (add-hook 'haskell-mode-hook '(lambda () (if (not (null buffer-file-name)) (flymake-mode)) ))
あと、~/local/bin/flycheck_haskell.plに次のようなperlスクリプトを置いておく。(chompをやめたのは、Windowsで改行コードの問題からうまく動かなかったため。tempfileも削除しておいた。)
#!/usr/bin/env perl # flycheck_haskell.pl use strict; ### Please rewrite the following 3 variables ### ($ghc, @ghc_options and @ghc_packages) my $ghc = 'ghc'; # where is ghc my @ghc_options = ('-Wall'); # e.g. ('-fglasgow-exts') my @ghc_packages = (); # e.g. ('QuickCheck') ### the following should not been edited ### my ($source, $base_dir) = @ARGV; my @command = ($ghc, '--make', '-fno-code', "-i$base_dir", $source); while(@ghc_options) { push(@command, shift @ghc_options); } while(@ghc_packages) { push(@command, '-package'); push(@command, shift @ghc_packages); } open(MESSAGE, "@command 2>&1 |"); while(<MESSAGE>) { if(/(^\S+(\.hs|\.lhs))(:\d+:\d+:)\s?(.*)/) { print "\n"; print $1; print $3; my $rest = $4; $rest =~ s/\s*$//s; print $rest; next; } elsif(/\s+(.*)/) { my $rest = $1; $rest =~ s/\s*$//s; print $rest; print " "; next; } } print "\n"; close(MESSAGE);
あとは、id:nushioさんのクールなエラー表示関数を追加すれば良いと思う。