gtags.elとCEDETを試してみた

C++のコード読むのにglobalというのとCEDETというのを試してみた。

GNU global

GNU globalというのはC、C++など*1のコードに対して索引付けをしてやって、コード中の関数とか変数についてその定義部分を楽に参照しようというもの。
global自体はコマンドラインツールなのだけど、gtags.elというのが同梱されていてemacsといっしょに使える。

まず本体をapt-getなりで入れるか本家 http://www.gnu.org/software/global/ からもらってきて./configure && make && make installするかする。

読みたいコードのディレクトリに行って
$ gtags -v
とすると索引ファイルというかタグファイルができる。-vはverboseなのでなくてもよい。

emacsの方の設定としては

;; パスを通す、パッケージで入れると最初からパスが通ってていらないかもしれない
(load "/usr/local/share/gtags/gtags.el")

;; cとc++で自動でonに
(add-hook 'c-mode-common-hook
          '(lambda()
             (gtags-mode 1)
             ))
(add-hook 'c++-mode-hook
          '(lambda()
             (gtags-mode 1)
             ))

;;ジャンプ先が複数あった場合の選択画面の見た目
(add-hook 'gtags-select-mode-hook
  '(lambda ()
     (setq hl-line-face 'underline)
     (hl-line-mode 1)
))

キーバインドみたいな設定はいらないと思うというか、デフォルトで関数名などをマウス中ボタンクリックでジャンプして右クリックでもとに戻るという挙動になっていて既にとても便利*2

上の設定で(setq gtags-suggested-key-mapping t)もしておくといろいろキーバインドが有効になるようだけどどうもソース見るしか情報がない? とりあえずM-.でカーソル位置にあるものの定義に飛んでM-*でもとに戻れる。

これだけで十二分に快適。後述するCEDETはちょっと入れるのが大変なのでお暇ならというところ。

本家
http://www.gnu.org/software/global/

CEDET

こちらはemacs上でVisual Studioのインテリセンスみたいなことをしようとするもので、大掛かり。
書くことを考えず単純にコードを読む場合でのgtags.elに対する利点は、

  • タグファイルを毎回作らなくて良い
  • コードの上にカーソルをしばらく置くと関数の宣言などをモードラインに表示してくれる
  • 標準ライブラリなど、システム内のインクルードファイルにも飛べる
  • コードが折りたためるようになる

など。
あとなんか簡単に見た目が豪華になるけど気が散るので個人的には切ったほうがいいと思う。

インストールについて。
最近のemacsにはCEDETは同梱されているのだけど、バージョンが古いのとデフォルトの設定がちょっと違ったりするらしいので本家から最新の安定版を探して入れる。
12/07/24時点では1.1が出ている。

https://sourceforge.net/projects/cedet/files/cedet/cedet-1.1.tar.gz/download からversion 1.1をもらってきて、解凍して適当なところに置いた後makeする。

$ tar xvzf cedet-1.1.tar.gz
$ mv cedet-1.1 ~/ #とか
$ cd ~/cedet-1.1
$ make

うまくいかなかったらemacsを使ってコンパイルするなどの手があるのでcedet-1.1/INSTALLを参照。

マニュアルを入れる

$ sudo make install-info # マニュアルのインストール

次が.emacsの設定なのだけど、古いCEDETが最近のemacsに最初から同梱されている関係で設定は.emacsの前の方に書く必要がある*3

とりあえず次を.emacsの最初の方に書く。

;; Load CEDET.
;; See cedet/common/cedet.info for configuration details.
;; IMPORTANT: For Emacs >= 23.2, you must place this *before* any
;; CEDET component (including EIEIO) gets activated by another 
;; package (Gnus, auth-source, ...).
(load-file "~/cedet-1.1/common/cedet.el")

;; Enable EDE (Project Management) features
;(global-ede-mode 1)

;; Enable EDE for a pre-existing C++ project
;; (ede-cpp-root-project "NAME" :file "~/myproject/Makefile")


;; Enabling Semantic (code-parsing, smart completion) features
;; Select one of the following:

;; * This enables the database and idle reparse engines
;; (semantic-load-enable-minimum-features)

;; * This enables some tools useful for coding, such as summary mode,
;;   imenu support, and the semantic navigator
(semantic-load-enable-code-helpers)

;; * This enables even more coding tools such as intellisense mode,
;;   decoration mode, and stickyfunc mode (plus regular code helpers)
;; (semantic-load-enable-gaudy-code-helpers)

;; * This enables the use of Exuberant ctags if you have it installed.
;;   If you use C++ templates or boost, you should NOT enable it.
;; (semantic-load-enable-all-exuberent-ctags-support)
;;   Or, use one of these two types of support.
;;   Add support for new languages only via ctags.
;; (semantic-load-enable-primary-exuberent-ctags-support)
;;   Add support for using ctags as a backup parser.
;; (semantic-load-enable-secondary-exuberent-ctags-support)

;; Enable SRecode (Template management) minor-mode.
;; (global-srecode-minor-mode 1)


…ここまで書いて面倒になった。以下は中クリックで定義部分に飛んで右クリックでもとに戻るなどの設定。
そのうち追記する?

(condition-case nil
    (progn
      (global-semantic-idle-completions-mode 1)
      ;; Enable preparsing many neighboring files.
      (setq semantic-idle-work-parse-neighboring-files-flag t)
      )
  (error nil))

(setf pulse-iterations 0) ;;演出を切る


(require 'semantic-ia)
(require 'semantic-gcc)

;; 標準ヘッダなどの場所(環境依存だと思う、なくても動くかも)
(semantic-add-system-include "/usr/include/c++/4.2" 'c++-mode)
(semantic-add-system-include "/usr/include" 'c++-mode)

;; GNU globalを裏で使う
(setq cedet-global-command "global") ; Change to path of global as needed
(when (cedet-gnu-global-version-check t)  ; Is it ok?
  ;; Configurations for GNU Global and CEDET
     (setq ede-locate-setup-options
           '(ede-locate-global
             ede-locate-base))
     (semanticdb-enable-gnu-global-databases 'c-mode)
     (semanticdb-enable-gnu-global-databases 'c++-mode)
  )

(progn ;; コード間ジャンプの設定。わりともとのコードの意向を無視しているとは思う
  (lexical-let ((mbrs nil))
    (defun semantic-mru-current-bookmark-ring ()
      "コードの種別によってブックマークを分離するようにする。"
      (flet ((assoc-or-create-mbr (key)
	       (or (cadr (assoc key mbrs))
		   (cadar (push (list key
				    (semantic-bookmark-ring (format "%S-Ring" key)
							    :ring (make-ring 20)))
				mbrs)))))
       (case major-mode
  	 ((c++-mode c-mode) 
	  (assoc-or-create-mbr 'c/c++))
  	 ((emacs-lisp-mode lisp-interaction-mode)
	  (assoc-or-create-mbr 'elisp))
	 ((scheme-mode)
	  (assoc-or-create-mbr 'scheme))
	 ;; things following are not tested
	 ;; ((python-mode)
	 ;;  (assoc-or-create-mbr 'python))
	 ;; ((awk-mode)
	 ;;  (assoc-or-create-mbr 'awk))
	 ;; ((makefile-mode)
	 ;;  (assoc-or-create-mbr 'makefile))
	 ;; ((html-mode yahtml-mode)
	 ;;  (assoc-or-create-mbr 'html))
	 ;; ((texinfo-mode)
	 ;;  (assoc-or-create-mbr 'texinfo))
	 ;; ((js-mode javascript-mode)
	 ;;  (assoc-or-create-mbr 'javascript))
	 (otherwise
	  (assoc-or-create-mbr 'unknown))))))

  (defun semantic-mrub-pop-from-jump-mouse (&optional evt sbr)
    "ブックマークリングをpopして戻る。マウスイベントに対して設定されることを想定"
    (interactive "e")
    (posn-set-point (event-end evt))	;major-modeはアクティブなフレームで決まるので、
					;アクティブでなかったフレームをクリックした時には一旦
					;そっちにフォーカスを移す
    (when (null sbr) (setf sbr (semantic-mru-current-bookmark-ring)))
    (if (ring-empty-p (oref sbr ring))
	(message "the bookmark stack empty")
      (progn
	(semantic-mrub-visit (ring-ref (oref sbr ring) 0))
	(ring-remove (oref sbr ring) 0))))
  (defun semantic-mrub-pop-from-jump (&optional sbr)
    "ブックマークリングをpopして戻る"
    (interactive)
    (when (null sbr) (setf sbr (semantic-mru-current-bookmark-ring)))
    (if (ring-empty-p (oref sbr ring))
	(message "the bookmark stack empty")
      (progn
	(semantic-mrub-visit (ring-ref (oref sbr ring) 0))
	(ring-remove (oref sbr ring) 0))))

  (defun semantic-ia-fast-mouse-jump% (evt)
    "現在位置をブックマークリングにpushしたのちカーソル位置で参照されているタグへジャンプする。マウスイベントに設定されることを想定。`semantic-ia-fast-mouse-jump'と`semantic-ia-fast-jump'参照"
    (interactive "e")
    (posn-set-point (event-end evt))
    (let ((semantic-mru-bookmark-ring (semantic-mru-current-bookmark-ring)))
      (semantic-ia-fast-jump (point)))
    ;;write-protectedだったりファイルと関連付けられてないバッファに飛ぶと
    ;;semantic-mru-bookmark-modeがonにできないのでここでキーマップを定義してしまう
    (semantic-mru-bookmark-set-keys))
  
  (defun semantic-ia-fast-jump% (point)
    "現在位置をブックマークリングにpushしたのちカーソル位置で参照されているタグへジャンプする。`semantic-ia-fast-mouse-jump'と`semantic-ia-fast-jump'参照"
    (interactive "d")
    (let ((semantic-mru-bookmark-ring (semantic-mru-current-bookmark-ring)))
      (semantic-ia-fast-jump point))
    (semantic-mru-bookmark-set-keys))

  (defadvice semantic-decoration-include-visit (before push-mark-before-visit-include activate)
    ;; includeの部分からジャンプすると元に戻れないのを修正
    (push-mark))

  (defun semantic-mru-bookmark-set-keys ()
    "key settings for push/pop bookmark ring"
    ;; 書き込み禁止やファイルに関連付けられていないバッファでは
    ;; semantic-mru-bookmark-modeが有効にできないので設定を
    ;; 関数として分離する。
    ;; キーバインドを変えるときはこの関数を編集すること
    (local-set-key (kbd "M-*") 'semantic-mrub-pop-from-jump)
    (local-set-key (kbd "M-.") 'semantic-ia-fast-jump%)
    (local-set-key [mouse-3] 'semantic-mrub-pop-from-jump)
    (local-set-key [mouse-2] 'semantic-ia-fast-mouse-jump%))

  (add-hook 'semantic-mru-bookmark-mode-hook
	    'semantic-mru-bookmark-set-keys)
  )

;;(fringe-mode 'left-only)
;;(global-semantic-tag-folding-mode 1)

最後の2行のコメントを外すとコードの折りたたみが有効になります。

参考:
A Gentle introduction to CEDET
http://alexott.net/en/writings/emacs-devenv/EmacsCedet.html
本家
http://cedet.sourceforge.net/

*1:あとYaccJavaとPHP4に対応してるそうな

*2:バージョンによっては(setq gtags-suggested-key-mapping t)とかしないとだめかも

*3:CEDETに依存したライブラリを先に読み込んでしまうemacsに同梱のほうがロードされてしまう