K*BUGで発表したネタなのですが、ある程度バグが取れたので再度報告。
emacsのspeedbar.elには関数一覧を表示する機能があるんですが、FreeBSD portsからいれたデフォルトの状態では色々問題があります。それを色々解決しつつ、便利に改造するにはどうしたら良いかの実験です。
speedbar.elは「imenu」と「etags」という二種類のタグ抽出エンジンを使っいます。通常は設定でどちらか一方を使うのですが、それぞれ問題があります。
- imenuは簡単な正規表現のみで切り出していて、関数宣言・定義の前にスペースがあるとこける(namespaceでインデントすると死ぬ)
- etagsは割と正確にC++のソースをパースするものの、マルチバイト文字があるとタグの位置がずれる。
- imenuはTODOを正規表現で拾ってくるなど独自の拡張が簡単だがetagsは外部コマンドなので不可
それらの問題に対して、etagsでC++の基本的なパースを行い、さらにimenuの機能でTODO等の独自タグを抽出しようというのが以下の設定です。.emacsファイルに追記すれば動くはずです。
修正点は以下の通り
- etagsの結果とimenuの結果をマージする関数を追加
- etagsの結果からバイト数で文字位置を取得していたが、マルチバイト文字があるので行数を利用してジャンプするように変更
- imenuに元々あったタグ切り出し正規表現を、「//TODO」,「//BUG」「#define」抽出用正規表現に置き換え
- define文はetagsよりimenuをつかって一つにまとめたほうが個人的に便利なのでimenuで処理
(require 'speedbar)
(require 'imenu)
(require 'cc-menus)
(defun my-dfl (file)
(if (string-match "\\.\\([cpC]\\|cpp\\|cc\\|cxx\\)$" file )
(let (el e i)
(setq i (speedbar-fetch-dynamic-imenu file))
(if (string= (car (car i)) "*Rescan*")
(setq i (cdr i)))
(setq el (speedbar-fetch-dynamic-etags file))
(set-buffer (find-file-noselect file))
(while el
(setq e (cons (cons
(car (car el))
(save-excursion
(goto-line (+ -1 (cdr (car el))))
(point))) e))
(setq el (cdr el)))
(if (eq e t)
(if (eq i t) t i)
(if (eq i t) e (append i e))
)
)
t
)
)
(defun speedbar-parse-c-or-c++tag ()
"Parse a C or C++ tag, which tends to be a little complex."
(save-excursion
(let ((bound (save-excursion (end-of-line) (point))))
(cond
((re-search-forward "\C-?\\([^\C-a]+\\)\C-a" bound t)
(buffer-substring-no-properties (match-beginning 1)
(match-end 1)))
(t nil)
)
)))
(defun my-il (indent lst)
"At level INDENT, insert the etags generated LST."
(speedbar-insert-generic-list indent lst
'speedbar-tag-expand
'speedbar-tag-find))
(set
'cc-imenu-c++-generic-expression
'(
("define" "\\#define[ \t]*\\(.+\\)+$" 1)
("TODO" "//TODO[ \t]*\\(.+\\)+$" 1)
("BUG" "//BUG[ \t]*\\(.+\\)+$" 1)
)
)
(add-to-list 'speedbar-dynamic-tags-function-list '(my-dfl . my-il) )
(defun speedbar-extract-one-symbol (expr)
"At point, return nil, or one alist in the form (SYMBOL . POSITION).
The line should contain output from etags. Parse the output using the
regular expression EXPR."
(let* ((sym (if (stringp expr)
(if (save-excursion
(re-search-forward expr (save-excursion
(end-of-line)
(point)) t))
(buffer-substring-no-properties (match-beginning 1)
(match-end 1)))
(funcall expr)))
(pos (let ((j (re-search-forward "[\C-?\C-a]\\([0-9]+\\),\\([0-9]+\\)"
(save-excursion
(end-of-line)
(point))
t)))
(if (and j sym)
(1+ (string-to-number (buffer-substring-no-properties
(match-beginning 1)
(match-end 1))))
0))))
(if (/= pos 0)
(cons sym pos)
nil)))
このspeedbarとhideshowvisを使うとわりと、emacsでのC++開発環境が派手になって自尊心が満たされます。