2011年12月20日火曜日

speedbarを普通に使えるようにカスタマイズ



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)
;etagsとimenuの結果を混ぜ合わせる
(defun my-dfl (file)
;特定条件の時のみハイブリッド版をつかう
(if (string-match "\\.\\([cpC]\\|cpp\\|cc\\|cxx\\)$" file )
(let (el e i)
(setq i (speedbar-fetch-dynamic-imenu file))
;; Remove imenu rescan button
(if (string= (car (car i)) "*Rescan*")
(setq i (cdr i)))
(setq el (speedbar-fetch-dynamic-etags file))
;eについては行数で入っているので文字位置になおす
(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
)
)
;etagsでdefineを扱わなくてもOKなので関数上書き
(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)))
; ((re-search-forward "\\<\\([^ \t]+\\)\\s-+new(" bound t)
; (buffer-substring-no-properties (match-beginning 1)
; (match-end 1)))
; ((re-search-forward "\\<\\([^ \t(]+\\)\\s-*(\C-?" bound t)
; (buffer-substring-no-properties (match-beginning 1)
; (match-end 1)))
(t nil)

)
)))
;etagsやimenus用関数を流用してもよいが、念のため
(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))

;オリジナルのタグ(by imenu)
(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) )

;マルチバイト問題を回避するためにbyte位置ではなく、行数で返すように仕様変更
(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++開発環境が派手になって自尊心が満たされます。





0 件のコメント:

コメントを投稿