Rでもflymakeしたいじゃない?みたいな

Emacsで編集してて、文法エラーみたいな単純なやつは実行とかコンパイルする前に指摘して欲しい。そんな欲求を満たしてくれるのがflymakeだそうです。エラーがあると下みたいに指摘してくれる。

Eclipseみたいなやつですね。

で、flymakeはPerlとかRubyとかに対応しているらしい。Rubyは速攻導入した。

僕はR userなので、Rのやつが欲しい。でも、Rにはなんと対応してないっぽい!!なんということだ!!

Erlangとかもあるのになー。完全にはぶられている。しかし、ここで諦めてはいけない。ないなら作れ。というわけで作った。

こういうの

flymakeがないと、下のようなミスになかなか気が付かない。

でも、flymakeがあればハイライトされるので、実行しなくても気づくことができる。

ハイライトされているところで、C-cdと押すとこんな感じでポップアップが出てくれて、エラーの原因も分かる。

やり方

Rubyとかだと-cオプションを付ければ、文法チェックみたいなことができる。でも、Rだとそれらしきものがないっぽい*1。評価せずに、syntaxだけチェックさせるにはどうすればいいかなーと思ってたんだけど、そういえばparse関数があることを思い出した。例えば、Rのワンライナーとかを使ってやると、こういう風に文法チェックをすることができる。

/Users/yasuhisa/ruby% R --vanilla -q -e "parse(file=\"/tmp/yasuhisa.emacsclient.R\")"
> parse(file="/tmp/yasuhisa.emacsclient.R")
 以下にエラー parse(file = "/tmp/yasuhisa.emacsclient.R") : 
    予想外の ')' です  場所 
 14:  
 15: apply(cars,2,function(x){sum(x)) 
 実行が停止されました 

これをやって、この標準エラー出力(ただの出力じゃないところにはまった)を解析してやるという手順を踏むことにした。

実行&解析させるためにRubyのコードを書いた。正規表現の部分とかまずすぎる感じだけど、まあとりあえずという感じで。

#! /opt/local/bin/ruby
# -*- coding: utf-8 -*-
require 'open3'

error = ""
Open3.popen3("R --vanilla -q -e \"parse(file=\\\"#{ARGV[0]}\\\")\""){|stdin, stdout, stderr|
  error = stderr.read.to_s
}

error = error.split("\n")

message = ""
error.each{|e|
  if e =~ /(^\s{4})(.*?)(場所\s$)/
    message = $2
  end
  if e =~ /(\d+)\:(.*)/
    STDERR.puts "#{ARGV[0]}:#{$1}: #{message}"
  end
}

これをパスの通っているところに実行権限付きで置いておく。

以下を.emacsに書いておく。これで準備ができた。

(require 'flymake)

(custom-set-faces
  '(flymake-errline ((((class color)) (:background "Gray30"))))
  '(flymake-warnline ((((class color)) (:background "Gray20")))))

(defun credmp/flymake-display-err-minibuf ()
  "Displays the error/warning for the current line in the minibuffer"
  (interactive)
  (let* ((line-no             (flymake-current-line-no))
         (line-err-info-list  (nth 0 (flymake-find-err-info flymake-err-info line-no)))
         (count               (length line-err-info-list))
         )
    (while (> count 0)
      (when line-err-info-list
        (let* ((file       (flymake-ler-file (nth (1- count) line-err-info-list)))
               (full-file  (flymake-ler-full-file (nth (1- count) line-err-info-list)))
               (text (flymake-ler-text (nth (1- count) line-err-info-list)))
               (line       (flymake-ler-line (nth (1- count) line-err-info-list))))
          (message "[%s] %s" line text)
          )
        )
      (setq count (1- count)))))

(defun flymake-R-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 "flychech_R.rb" (list local-file))))  

(push '(".+\\.r$" flymake-R-init) flymake-allowed-file-name-masks)

(add-hook 'ess-mode-hook
          '(lambda ()
             (flymake-mode t)
	     ;;; ポップアップが嫌orできないならこっち
	     ;;; (define-key ess-mode-map "\C-cd" 'credmp/flymake-display-err-minibuf)
	     (define-key ess-mode-map "\C-cd" 'flymake-display-err-menu-for-current-line)))

*1:コマンドラインとかの起動オプションを探しまくったけど、見あたらない