Flymakeの設定

Flymakeはソースコードの文法エラーを,編集中にリアルタイムでチェックしてくれる機能である.
これを用いれば,例えばC言語で行末にセミコロンを忘れてコンパイラに怒られるといった凡ミスはなくなる.

設定

とりあえず,以下のコードを使用すればC/C++で文法チェックができる.

(require 'flymake nil t)

(defun flymake-get-make-cmdline (source base-dir)
  "redefinition to remove 'check-syntax' target"
  (list "make"
        (list "-s" "-C"
              base-dir
              (concat "CHK_SOURCES=" source)
              "SYNTAX_CHECK_MODE=1"
              )))

(defun flymake-simple-make-or-generic-init (cmd &optional opts)
  "force to check syntax of C/C++ without Makefile"
  (if (file-exists-p "Makefile")
      (flymake-simple-make-init) ;; flymake built-in
    (flymake-simple-generic-init cmd opts)))

(defun flymake-simple-generic-init (cmd &optional opts)
  "Makefileがないときのコードチェック用関数"
  (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 cmd (append opts (list local-file)))))

;; syntax checkが異常終了しても無視する
(defadvice flymake-post-syntax-check
  (before flymake-force-check-was-interrupted activate)
  (setq flymake-check-was-interrupted t))

;; C
(defun flymake-c-init ()
  (flymake-simple-make-or-generic-init
   "gcc" '("-Wall" "-Wextra" "-pedantic" "-fsyntax-only" "$CPPFLAGS")))

;; C++
(defun flymake-cc-init ()
  (flymake-simple-make-or-generic-init
   "g++" '("-Wall" "-Wextra" "-pedantic" "-fsyntax-only" "$CPPFLAGS")))

(push '("\\.[cCh]\\'" flymake-c-init) flymake-allowed-file-name-masks)
(push '("\\.\\(?:cc\|cpp\|CC\|CPP\\)\\'" flymake-cc-init) flymake-allowed-file-name-masks)

(add-hook 'c-mode-common-hook
          '(lambda ()
             (flymake-mode t)))

エラー表示

さて,エラーチェックした結果をいかにうまく表示させるかというのが問題になってくる.
記事としてはid:syohex氏の

が参考になる.
ミニバッファに表示させるには以下の関数を用いるとよい.私のオリジナルではない.

(defun 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)))))

popup.elを用いる方法については,id:syohex氏の記事に以下の関数が紹介されていた.

(defun flymake-display-err-menu-for-current-line ()
  (interactive)
  (let* ((line-no (flymake-current-line-no))
         (line-err-info-list (nth 0 (flymake-find-err-info flymake-err-info line-no))))
    (when line-err-info-list
      (let* ((count (length line-err-info-list))
             (menu-item-text nil))
        (while (> count 0)
          (setq menu-item-text (flymake-ler-text (nth (1- count) line-err-info-list)))
          (let* ((file (flymake-ler-file (nth (1- count) line-err-info-list)))
                 (line (flymake-ler-line (nth (1- count) line-err-info-list))))
            (if file
                (setq menu-item-text (concat menu-item-text " - " file "(" (format "%d" line) ")"))))
          (setq count (1- count))
          (if (> count 0) (setq menu-item-text (concat menu-item-text "\n")))
          )
        (popup-tip menu-item-text)))))

上記の関数を使用してみたが,ソースコードのとある1行にエラーが複数個含まれる場合でも,1個分しか表示されなかった.
そこで私は以下のように修正を試みた.

(defun flymake-display-err-menu-for-current-line ()
  "Displays the error/warning for the current line via popup-tip"
  (interactive)
  (let* ((line-no (flymake-current-line-no))
         (line-err-info-list (nth 0 (flymake-find-err-info flymake-err-info line-no))))
    (when line-err-info-list
      (let* ((count (length line-err-info-list))
             (menu-item-text "")
             (menu-item-text-tmp ""))
        (while (> count 0)
          (setq menu-item-text-tmp
                (flymake-ler-text (nth (1- count) line-err-info-list)))
          (let* ((file (flymake-ler-file (nth (1- count) line-err-info-list)))
                 (line (flymake-ler-line (nth (1- count) line-err-info-list))))
            (if file
                (setq menu-item-text-tmp (concat menu-item-text-tmp " - " file "(" (format "%d" line) ")"))))
          (cond
           ((> count 1)
              (setq menu-item-text (concat menu-item-text
                                           menu-item-text-tmp "\n")))
           (t
              (setq menu-item-text (concat menu-item-text
                                           menu-item-text-tmp))))
          (setq count (1- count)))
        (popup-tip menu-item-text)))))

これで少なくとも私は複数個のエラーを表示できるようになった.
…が,実はここまで複雑なコードを書く必要はない.以下の簡潔なコードで事足りるのだった.

(defun my-flymake-display-err-menu-for-current-line ()
  "Displays the error/warning for the current line via popup-tip"
  (interactive)
  (let* ((line-no (flymake-current-line-no))
         (line-err-info-list (nth 0 (flymake-find-err-info flymake-err-info line-no)))
         (menu-data (flymake-make-err-menu-data line-no line-err-info-list)))
    (if menu-data
        (popup-tip (mapconcat #'(lambda (err)
                                  (nth 0 err))
                              (nth 1 menu-data) "\n")))))

エラー表示する関数や,次のエラー箇所へジャンプする関数は是非押しやすいキーにバインドされたい.
作業効率がさぞ改善することだろう.