imenu与函数指针

imenu可以定位C/C++文件里的函数,配合anything使用,非常方便。可是我却发现,它无法定位带有函数指针作为参数的函数,如gcc/genattrtab.c里面的

static rtx
substitute_address (rtx exp, rtx (*no_address_fn) (rtx),
                    rtx (*address_fn) (rtx))

查看imenu的代码后发现,原来imenu是一个framework,具体的定位功能,是由相应的major mode实现的,我发现的问题,其实是cc-mode的实现有缺陷。在报了bug几天后,CC mode maintainer解决了这个问题。如果暂时没法(或不愿意)升级到最新的开发版,可以在自己的.emacs文件里添加如下配置,对于已经打开的文件,需要revert-buffer才能奏效。两个setq语句是从lisp/progmodes/cc-menus.el里摘录出来,如果下面的代码看不清楚,可以从源文件里直接复制。

(eval-after-load 'cc-mode
  '(progn
     (setq cc-imenu-c++-generic-expression
	   `(
	     ;; Try to match ::operator definitions first. Otherwise `X::operator new ()'
	     ;; will be incorrectly recognised as function `new ()' because the regexps
	     ;; work by backtracking from the end of the definition.
	     (nil
	      ,(concat
		"^\\< .*"
		"[^" c-alnum "_:<>~]"                  ; match any non-identifier char
					; (note: this can be `\n')
		"\\("
		"\\([" c-alnum "_:<>~]*::\\)?"      ; match an operator
		"operator\\>[ \t]*"
		"\\(()\\|[^(]*\\)"                  ; special case for `()' operator
		"\\)"

		"[ \t]*([^)]*)[ \t]*[^ \t;]"           ; followed by ws, arg list,
					; require something other than
					; a `;' after the (...) to
					; avoid prototypes.  Can't
					; catch cases with () inside
					; the parentheses surrounding
					; the parameters.  e.g.:
					; `int foo(int a=bar()) {...}'
		) 1)
	     ;; Special case to match a line like `main() {}'
	     ;; e.g. no return type, not even on the previous line.
	     (nil
	      ,(concat
		"^"
		"\\([" c-alpha "_][" c-alnum "_:<>~]*\\)" ; match function name
		"[ \t]*("			      ; see above, BUT
		"[ \t]*\\([^ \t(*][^)]*\\)?)"          ; the arg list must not start
		"[ \t]*[^ \t;(]"                       ; with an asterisk or parentheses
		) 1)
	     ;; General function name regexp
	     (nil
	      ,(concat
		"^\\< "                                 ; line MUST start with word char
		;; \n added to prevent overflow in regexp matcher.
		;; http://lists.gnu.org/archive/html/emacs-pretest-bug/2007-02/msg00021.html
		"[^()\n]*"                             ; no parentheses before
		"[^" c-alnum "_:<>~]"                  ; match any non-identifier char
		"\\([" c-alpha "_][" c-alnum "_:<>~]*\\)" ; match function name
		"\\([ \t\n]\\|\\\\\n\\)*("	      ; see above, BUT the arg list
		"\\([ \t\n]\\|\\\\\n\\)*"            ; must not start
		"\\([^ \t\n(*]"                              ; with an asterisk or parentheses
		"[^()]*\\(([^()]*)[^()]*\\)*"        ; Maybe function pointer arguments
		"\\)?)"
		"\\([ \t\n]\\|\\\\\n\\)*[^ \t\n;(]"
		) 1)
	     ;; Special case for definitions using phony prototype macros like:
	     ;; `int main _PROTO( (int argc,char *argv[]) )'.
	     ;; This case is only included if cc-imenu-c-prototype-macro-regexp is set.
	     ;; Only supported in c-code, so no `:<>~' chars in function name!
	     ,@(if cc-imenu-c-prototype-macro-regexp
		   `((nil
		      ,(concat
			"^\\< .*"                   ; line MUST start with word char
			"[^" c-alnum "_]"          ; match any non-identifier char
			"\\([" c-alpha "_][" c-alnum "_]*\\)" ; match function name
			"[ \t]*"                   ; whitespace before macro name
			cc-imenu-c-prototype-macro-regexp
			"[ \t]*("                  ; ws followed by first paren.
			"[ \t]*([^)]*)[ \t]*)[ \t]*[^ \t;]" ; see above
			) 1)))
	     ;; Class definitions
	     ("Class"
	      ,(concat
		"^"                                  ; beginning of line is required
		"\\(template[ \t]*<[^>]+>[ \t]*\\)?" ; there may be a `template < ...>'
		"\\(class\\|struct\\)[ \t]+"
		"\\("                                ; the string we want to get
		"[" c-alnum "_]+"                    ; class name
		"\\(< [^>]+>\\)?"                     ; possibly explicitly specialized
		"\\)"
		"\\([ \t\n]\\|\\\\\n\\)*[:{]"
		) 3)))
     (setq cc-imenu-c-generic-expression
	   cc-imenu-c++-generic-expression)))

Emacs find file后续

Emacs find file utility的最后,我声称“wl-find-file-in-other-root是functional的”,惭愧啊,偌大的一个find-file-existing在里面调用着,还敢声称是functional的。

知耻后勇,于是重写了部分实现,并且重新设计了接口,无需为每个root-list新写一个调用命令。代码如下:

(defvar wl-find-file-gcc-root-list nil)
(defvar wl-find-file-binutils-root-list nil)
(defvar wl-find-file-completing-read-function
  'wl-iswitchb-completing-read)

(defun wl-iswitchb-completing-read (prompt choices
                                    &optional dummy require-match)
  "Use iswitchb completion functionality."
  (let ((iswitchb-make-buflist-hook
         (lambda ()
           (setq iswitchb-temp-buflist
                 (cond ((consp (car choices))
                        (mapcar 'car choices))
                       ((stringp (car choices))
                        choices)
                       ((symbolp (car choices))
                        (mapcar 'symbol-name choices))
                       (t
                        (error "Unknown type of choices.")))))))
    (iswitchb-read-buffer prompt nil require-match)))

(defun wl-find-file-root (filename root-list)
  (find-if (lambda (dir)
             (when (> (length filename)
                      (length dir))
               (string-equal dir (substring filename
                                            0 (length dir)))))
           root-list))

(defun wl-find-file-other-root-list (filename root-list-list)
  (when root-list-list
    (let ((root (wl-find-file-root filename (car root-list-list))))
      (if root
          (list root (car root-list-list))
        (wl-find-file-other-root-list filename (cdr root-list-list))))))

(defun wl-find-other-file-1 (filename &rest root-list-list)
  (let* ((root (wl-find-file-other-root-list filename root-list-list))
         (relative-path (substring filename (length (car root)))))
    (remove-if (lambda (file)
                 (not (file-exists-p file)))
               (mapcar (lambda (dir)
                         (expand-file-name relative-path dir))
                       (remove-if (lambda (elem)
                                    (string-equal elem (car root)))
                                  (cadr root))))))

(defun wl-find-other-file-other-window (filename)
  (interactive
   (list
    (let ((completion-ignore-case t)
          (file-list (wl-find-other-file-1 (buffer-file-name)
                                           wl-find-file-gcc-root-list
                                           wl-find-file-binutils-root-list)))
      (if file-list
          (funcall wl-find-file-completing-read-function
                   "Open file: "
                   file-list)
        (error "%s does not exist in other directories"
               (file-name-nondirectory (buffer-file-name)))))))
  (find-file-other-window filename))