首页 > C/C++, IDE, 中级 > 用flymake检测C/C++语法

用flymake检测C/C++语法

1 前言

前段时间ahei使劲推荐flymake,而且在dea中还给出一段flymake配置,勾起了我学习flymake的兴趣。在此之前只是听说过flymake,偶尔浅尝一下还没学会怎么用就放弃了,这几天折腾flymae后觉得实在很给力。生活不是缺少美,而是缺少发现美的眼睛这话说得还真有点道理。

2 flymake基本用法

flymake是一个实时的语法检查工具,好像是从emacs22开始已经自带flymake,自带的flymake提供了对C,C++,XML,HTML,C#,perl,php,java,tex,idl的支持。查看flymake-allowed-file-name-masks这个变量可以得到支持语言的详细信息。想要了解其它语言的支持,可以看看http://www.emacswiki.org/FlyMake

在以下四种情况下,flymake会执行语法检测:

  • 打开文件时
  • 换行(可通过flymake-start-syntax-check-on-newline配置)
  • 代码改变0.5秒后(可通过flymake-no-changes-timeout配置)
  • 手工执行flymake-start-syntax-check

下面是flymake基本配置:

(autoload 'flymake-find-file-hook "flymake" "" t)
(add-hook 'find-file-hook 'flymake-find-file-hook)
(setq flymake-gui-warnings-enabled nil)
(setq flymake-log-level 0)

加上以上配置后,打开文件时会检测是否是flymake支持的语言,是的话就会自动打开flymake-mode。flymake-gui-warnings-enabled设置为nil表示出错时不弹个对话框显示错误;flymake-log-level设置为0表示记录错误日志。

本文主要讨论C/C++(因为别的我不会),自带的flymake对C/C++的支持是通过Makefile实现的,Makefile中必须有一个check-syntax目标,比如我在Linux下用automake,那我在Makefile.am中加了这么一段(其实就是调用gcc):

?View Code MAKEFILE
check-syntax:
    $(CXXCOMPILE) -Wall -Wextra -pedantic -fsyntax-only $(CHK_SOURCES)

如果不用automake,手写Makefile的话,相应地修改一下就行了。

有了这些设置后,打开项目中的cpp文件,应该会自动检测语法了,语法有错误的话会用颜色标识出错的行,M-x flymake-goto-next-error和M-xflymake-goto-prev-error可以在错误行间移动;鼠标在错误行上停留会用tooltip显示错误信息;M-x flymake-display-err-menu-for-current-line能弹出一个菜单显示错误。

另外,ahei写了几个函数可以在错误行间移动时在minibuffer显示出错误信息:

(defun flymake-display-current-error ()
  "Display errors/warnings under cursor."
  (interactive)
  (let ((ovs (overlays-in (point) (1+ (point)))))
    (catch 'found
      (dolist (ov ovs)
        (when (flymake-overlay-p ov)
          (message (overlay-get ov 'help-echo))
          (throw 'found t))))))
(defun flymake-goto-next-error-disp ()
  "Go to next error in err ring, then display error/warning."
  (interactive)
  (flymake-goto-next-error)
  (flymake-display-current-error))
(defun flymake-goto-prev-error-disp ()
  "Go to previous error in err ring, then display error/warning."
  (interactive)
  (flymake-goto-prev-error)
  (flymake-display-current-error))

我把它绑定到了这几个按键:

(defvar flymake-mode-map (make-sparse-keymap))
(define-key flymake-mode-map (kbd "C-c <f4>") 'flymake-goto-next-error-disp)
(define-key flymake-mode-map (kbd "C-c <S-f4>") 'flymake-goto-prev-error-disp)
(define-key flymake-mode-map (kbd "C-c <C-f4>")
  'flymake-display-err-menu-for-current-line)
(or (assoc 'flymake-mode minor-mode-map-alist)
    (setq minor-mode-map-alist
          (cons (cons 'flymake-mode flymake-mode-map)
                minor-mode-map-alist)))

编辑过程中,每次修改过代码0.5秒后,flymake都会检测错误,这就可以随时发现代码编写的错误了。

在这补充一下flymake检测的方法:

对于cpp文件,每次检测时,flymake是把buffer内的内容另存一份,再检测另存出来的文件。

而对于h文件,gcc没办法单独检查头文件,flymake会在flymake-master-file-dirs设定的目录中查找include过这个头文件的实现文件,把buffer另存为xxx_flymake.h,把查找到的第一个满足条件的实现文件另存yyy_flymake_master.cpp,并把里面的include语句改为include另存的文件,然后通过yyy_flymake_master.cpp来间接检测头文件。

3 ahei的改进

自带的flymake对C++只能通过Makefile来支持,还必须在Makefile中加入check-syntax目标,实在很麻烦。要是代码不是通过Makefile来管理的,flymake就无能为力了。

其实Makefile也是通过gcc来检测代码了,那要是跳过Makefile直接调用gcc来检测代码该多好啊,所以aheiDEA中配置了flymake直接调用gcc检测C++代码,具体代码在flymake-setting.el中,主要是以下几个函数:

(defvar flymake-makefile-filenames '("Makefile" "makefile" "GNUmakefile") "File names for make.")
 
(defun flymake-get-make-gcc-cmdline (source base-dir)
  (let (found)
    (dolist (makefile flymake-makefile-filenames)
      (if (file-readable-p (concat base-dir "/" makefile))
          (setq found t)))
    (if found
        (list "make"
              (list "-s"
                    "-C"
                    base-dir
                    (concat "CHK_SOURCES=" source)
                    "SYNTAX_CHECK_MODE=1"
                    "check-syntax"))
      (list (if (string= (file-name-extension source) "c") "gcc" "g++")
            (list "-o"
                  "/dev/null"
                  "-S"
                  source)))))
 
(defun flymake-simple-make-gcc-init-impl (create-temp-f use-relative-base-dir use-relative-source build-file-name get-cmdline-f)
  "Create syntax check command line for a directly checked source file.
Use CREATE-TEMP-F for creating temp copy."
  (let* ((args nil)
         (source-file-name buffer-file-name)
         (buildfile-dir (file-name-directory source-file-name)))
    (if buildfile-dir
        (let* ((temp-source-file-name  (flymake-init-create-temp-buffer-copy create-temp-f)))
          (setq args
                (flymake-get-syntax-check-program-args
                 temp-source-file-name
                 buildfile-dir
                 use-relative-base-dir
                 use-relative-source
                 get-cmdline-f))))
    args))
 
(defun flymake-simple-make-gcc-init ()
  (flymake-simple-make-gcc-init-impl 'flymake-create-temp-inplace t t "Makefile" 'flymake-get-make-gcc-cmdline))

主要思路就是先检测Makefile文件,如果存在就调用make,否则就直接调用gcc检测。

4 我的修改

ahei配置的gcc非常实用,我就是因为它喜欢上flymake的,不过用过几天后,发现ahei的配置里有几个问题没解决:

  • 没有对make或gcc进行检测

    flymake进行语法检测都是通过调用外部程序来实现的,比如make或gcc,如果没有安装这两个程序,flymake还是会死心眼地启动一个process去调用。

  • 不支持用gcc直接检测头文件

    ahei好像是把头文件忘掉了。

  • 目标没有权限写入时会出错

    因为flymake检测文件时会把buffer内容另存到一个临时文件中再检测,如果我以普通用户身份打开/usr/include/下的头文件,或者/usr/src/下的实现文件时,flymake会报靠说权限有问题,并且你会发现这个文件没在emacs中被打开。

  • 不支持父目录中的Makefile

    原始的flymake调用flymake-init-find-buildfile-dir来查找Makefile,它会从当前目录一直往上找,直到根目录为止,只要找到Makefile都可以。这有一个好处就是对一个有很多子目录的大工程,不需要对每个子目录下的Makefile文件都加上check-syntax目标,只需要在最顶层的Makefile中加就可以了。但ahei修改的时候可能是因为flymake-init-find-buildfile-dir找不到Makefile就会报错退出而无法转而使用gcc而放弃了这个函数,改为只在当前目录下找Makefile文件而不支持查找父目录了。

我的配置主要是在ahei的基础上进行修改,解决了这几个我发现的问题:

1.没有检测make或gcc存在的问题,我在配置文件中先检测有没有对应的外部程序,没有的话就不配置到flymake-allowed-file-name-masks中了。

2.我加了两个函数:flymake-master-make-gcc-header-init和flymake-master-make-gcc-init来支持直接用gcc检测头文件。

3.用ignore-error忽略掉权限错误,用flymake-report-fatal-status把错误通过minibuffer报告出来。

4.用flymake-find-buildfile来查找Makefile文件,能支持父目录Makefile查找。

修改后的代码如下(完整的配置见http://github.com/meteor1113/dotemacs/blob/master/init-basic.el):

(setq flymake-allowed-file-name-masks '())
(when (executable-find "texify")
  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.tex\\'" flymake-simple-tex-init))
  (add-to-list 'flymake-allowed-file-name-masks
               '("[0-9]+\\.tex\\'"
                 flymake-master-tex-init flymake-master-cleanup)))
(when (executable-find "xml")
  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.xml\\'" flymake-xml-init))
  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.html?\\'" flymake-xml-init)))
(when (executable-find "perl")
  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.p[ml]\\'" flymake-perl-init)))
(when (executable-find "php")
  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.php[1]?\\'" flymake-php-init)))
(when (executable-find "make")
  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.idl\\'" flymake-simple-make-init))
  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.java\\'"
                 flymake-simple-make-java-init flymake-simple-java-cleanup))
  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.cs\\'" flymake-simple-make-init)))
(when (or (executable-find "make")
          (executable-find "gcc")
          (executable-find "g++"))
  (defvar flymake-makefile-filenames '("Makefile" "makefile" "GNUmakefile")
    "File names for make.")
  (defun flymake-get-gcc-cmdline (source base-dir)
    (let ((cc (if (string= (file-name-extension source) "c") "gcc" "g++")))
      (list cc
            (list "-Wall"
                  "-Wextra"
                  "-pedantic"
                  "-fsyntax-only"
                  "-I.."
                  "-I../include"
                  "-I../inc"
                  "-I../common"
                  "-I../public"
                  "-I../.."
                  "-I../../include"
                  "-I../../inc"
                  "-I../../common"
                  "-I../../public"
                  source))))
  (defun flymake-init-find-makfile-dir (source-file-name)
    "Find Makefile, store its dir in buffer data and return its dir, if found."
    (let* ((source-dir (file-name-directory source-file-name))
           (buildfile-dir nil))
      (catch 'found
        (dolist (makefile flymake-makefile-filenames)
          (let ((found-dir (flymake-find-buildfile makefile source-dir)))
            (when found-dir
              (setq buildfile-dir found-dir)
              (setq flymake-base-dir buildfile-dir)
              (throw 'found t)))))
      buildfile-dir))
  (defun flymake-simple-make-gcc-init-impl (create-temp-f
                                            use-relative-base-dir
                                            use-relative-source)
    "Create syntax check command line for a directly checked source file.
Use CREATE-TEMP-F for creating temp copy."
    (let* ((args nil)
           (source-file-name buffer-file-name)
           (source-dir (file-name-directory source-file-name))
           (buildfile-dir
            (and (executable-find "make")
                 (flymake-init-find-makfile-dir source-file-name)))
           (cc (if (string= (file-name-extension source-file-name) "c")
                   "gcc"
                 "g++")))
      (if (or buildfile-dir (executable-find cc))
          (let* ((temp-source-file-name
                  (ignore-errors
                    (flymake-init-create-temp-buffer-copy create-temp-f))))
            (if temp-source-file-name
                (setq args
                      (flymake-get-syntax-check-program-args
                       temp-source-file-name
                       (if buildfile-dir buildfile-dir source-dir)
                       use-relative-base-dir
                       use-relative-source
                       (if buildfile-dir
                           'flymake-get-make-cmdline
                         'flymake-get-gcc-cmdline)))
              (flymake-report-fatal-status
               "TMPERR"
               (format "Can't create temp file for %s" source-file-name))))
        (flymake-report-fatal-status
         "NOMK" (format "No buildfile (%s) found for %s, or can't found %s"
                        "Makefile" source-file-name cc)))
      args))
  (defun flymake-simple-make-gcc-init ()
    (flymake-simple-make-gcc-init-impl 'flymake-create-temp-inplace t t))
  (defun flymake-master-make-gcc-init (get-incl-dirs-f
                                       master-file-masks
                                       include-regexp)
    "Create make command line for a source file
 checked via master file compilation."
    (let* ((args nil)
           (temp-master-file-name
            (ignore-errors
              (flymake-init-create-temp-source-and-master-buffer-copy
               get-incl-dirs-f
               'flymake-create-temp-inplace
               master-file-masks
               include-regexp)))
           (cc (if (string= (file-name-extension buffer-file-name) "c")
                   "gcc"
                 "g++")))
      (if temp-master-file-name
          (let* ((source-file-name buffer-file-name)
                 (source-dir (file-name-directory source-file-name))
                 (buildfile-dir
                  (and (executable-find "make")
                       (flymake-init-find-makfile-dir source-file-name))))
            (if (or buildfile-dir (executable-find cc))
                (setq args (flymake-get-syntax-check-program-args
                            temp-master-file-name
                            (if buildfile-dir buildfile-dir source-dir)
                            nil
                            nil
                            (if buildfile-dir
                                'flymake-get-make-cmdline
                              'flymake-get-gcc-cmdline)))
              (flymake-report-fatal-status
               "NOMK"
               (format "No buildfile (%s) found for %s, or can't found %s"
                       "Makefile" source-file-name cc))))
        (flymake-report-fatal-status
         "TMPERR" (format "Can't create temp file for %s" source-file-name)))
      args))
  (defun flymake-master-make-gcc-header-init ()
    (flymake-master-make-gcc-init
     'flymake-get-include-dirs
     '("\\.cpp\\'" "\\.c\\'")
     "[ \t]*#[ \t]*include[ \t]*\"\\([[:word:]0-9/\\_.]*%s\\)\""))
  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.\\(?:h\\(?:pp\\)?\\)\\'"
                 flymake-master-make-gcc-header-init flymake-master-cleanup))
  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.\\(?:c\\(?:pp\\|xx\\|\\+\\+\\)?\\|CC\\)\\'"
                 flymake-simple-make-gcc-init)))

5 检测python语法

因为偶尔我也用下python,所以我也希望flymake能把python也支持了。python有三个语法检测工具,我比较之后选择了pyflakes。

装好pyflakes后,加入以下配置就可以像检测cpp那样检测py文件了:

(when (executable-find "pyflakes")
  (defun flymake-pyflakes-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 "pyflakes" (list local-file))))
  (add-to-list 'flymake-allowed-file-name-masks
               '("\\.py\\'" flymake-pyflakes-init)))

如果是在windows下的话,可能会找不到pyflakes这个外部程序,因为C:\Python25\Scripts\pyflakes没被windows识别为可执行文件,我是在C:\Python25\Scripts\下加了个pyflakes.bat的文件,文件里写入以下内容就能正常检测了:

C:\Python25\python.exe C:\Python25\Scripts\pyflakes %*

6 遗留问题

有两个问题我还没解决:

一、对C++来说,找不到Makefile的时候自动改用gcc语法检测,但有时候看别人工程时会碰到有Makefile,但里面没有写check-syntax目标的问题(从网上下载的开源代码很少有在Makefile中写check-syntax目标的)。要是能配置成在Makefile中找不到check-syntax目标后也能自动改用gcc检测就好了。

二、flymake通过定时器,改变代码超过0.5秒后进行语法检测,其实我不太喜欢这种立即检测的方式,相比之下我更喜欢保存文件后进行检测。要是下个版本的flymake可以把这两种方式做成可配置就好了。

分享家:Addthis中国
GD Star Rating
loading...
用flymake检测C/C++语法, 7.8 out of 10 based on 24 ratings 该日志未加标签。
分类: C/C++, IDE, 中级
  1. 2017年6月14日22:02 | #1

    buy sildenafil citrate online
    where can i buy sildenafil
    sildenafil buy online
    buy cheap sildenafil citrate 100mg

    [回复]

  2. 2017年6月17日12:02 | #2

    Yoᥙ could certainly see your expertise in the article you write.

    The sector hopes for more passionate writers such as you who are not
    afraid to say һow tһey believe. All the time go after your heart.

    [回复]

  3. 2017年6月17日12:02 | #3

    Hі there everyone, it’s my first go to sᥱe at thiѕ website,
    and post is actᥙally fruitful designed fߋr me, keep up posting these types of articles.

    [回复]

  4. 2017年6月21日07:59 | #4

    Heya juѕt wanted to give yoᥙ a brief heaԁs սp and let you know a
    few of the images aren’t loading coгrectly.
    I’m not sure why but I think its a linking issue. I’ve tried it in two different
    wеb browsers and both sһow the same outcome.

    [回复]

  5. 2017年6月23日09:56 | #5

    you aгe really a just right webmaster. The wеb site loading pace
    is incгediblᥱ. It kind of feelѕ that you’re doing any unique trick.
    Moreover, The сontentѕ are masterwork. ʏoᥙ’ve performed a
    grᥱat process in this matter!

    [回复]

  6. 2017年6月23日10:02 | #6

    you’re аctually a just right webmaster. The web
    site loading spеed is amazing. It sort of feels that you are
    doing any unique trick. Furthermore, The cߋntents are maѕterpiece.
    you’ve done a magnificent pгocess on thіs
    subϳect!

    [回复]

评论分页
1 2 41759
  1. 2010年12月6日11:39 | #1
:wink: :-| :-x :twisted: :) 8-O :( :roll: :-P :oops: :-o :mrgreen: :lol: :idea: :-D :evil: :cry: 8) :arrow: :-? :?: :!: