GNU GLOBAL on Emacsのタグ選択を見易くする

GNU GLOBALは素晴しいタグツールだが表示に問題がある。
Emacsで使うと、複数の候補が見付かったときに *GTAGS SELECT* バッファに候補を表示してくれるのだが、これがとてつもなく見辛い。たとば以下は「M-x gtags-find-with-grep undef」をやった結果表示されたバッファの内容なのだが、検索しているタグが「undef」なのはわかりきってるし、スペースが無駄に多い上、ファイル名がフルパスで表示されているがために、肝心のソースコードが見えなくなっいる!いちいち右スクロールしないといけないんじゃ、正直使いものにならない。

undef             969 /m/home/nobackup/compile/ruby19/array.c            if (rpl =
undef            1794 /m/home/nobackup/compile/ruby19/array.c           rb_ary_spl
undef            2451 /m/home/nobackup/compile/ruby19/array.c            return Qu
undef            2483 /m/home/nobackup/compile/ruby19/array.c            if (v != 
undef             696 /m/home/nobackup/compile/ruby19/bignum.c       #undef MASK_0
undef             697 /m/home/nobackup/compile/ruby19/bignum.c       #undef MASK_3
undef             698 /m/home/nobackup/compile/ruby19/bignum.c       #undef MASK_5
undef             145 /m/home/nobackup/compile/ruby19/blockinlining.c             
undef             174 /m/home/nobackup/compile/ruby19/blockinlining.c             
undef             199 /m/home/nobackup/compile/ruby19/blockinlining.c       if (va
undef             207 /m/home/nobackup/compile/ruby19/blockinlining.c     return Q
undef             245 /m/home/nobackup/compile/ruby19/blockinlining.c             
undef             274 /m/home/nobackup/compile/ruby19/blockinlining.c             
undef             318 /m/home/nobackup/compile/ruby19/blockinlining.c       if (va

そこでgtags.elを調べてみたら、gtags-goto-tag関数にてたんに「global -axg undef」の結果を表示しているだけだとわかった。
そうだとわかれば解決は簡単。-aオプションで「なぜか」フルパスで表示しているので取り除く。なぜフルパスで表示しているのかはっきり言って意味不明だ。だいたいgtags-goto-tag内でgenerate-new-bufferしているけど、default-directoryは作成時点の値が受け継がれるはず。相対パスで十分じゃないか。それなら同じディレクトリにあるファイルはファイル名だけになるから。もしフルパスでないといけない理由があるならば、overlayを使って表示を相対パスにすりゃいいだけの話。
そして、わかりきってるタグ名はいらないので表示しない。その部分をput-text-propertyでinvisibleにしてしまえばよし。そのためにはglobal -xgの結果表示フォーマットを知る必要がある。GNU GLOBALのソースをとってきて調べてみたらlibutil/pathconvert.c内にこんな行を見付けた。なるほど、16桁なんだな。ならば行頭から16桁までを非表示にすればいい。

	case FORMAT_CTAGS_X:
		fprintf(cv->op, "%-16s %4d %-16s %s",
			tag, lineno, convert_pathname(cv, path), rest);

問題点を修正してgtags-goto-tagを再定義したのがコレ。.emacsで「(require 'gtags)」を入れた後に入れてくれ。

(defun gtags-goto-tag (tagname flag)
  (let (save prefix buffer lines)
    (setq save (current-buffer))
    (cond
     ((equal flag "P")
      (setq prefix "(P)"))
     ((equal flag "g")
      (setq prefix "(GREP)"))
     ((equal flag "I")
      (setq prefix "(IDUTILS)"))
     ((equal flag "s")
      (setq prefix "(S)"))
     ((equal flag "r")
      (setq prefix "(R)"))
     (t (setq prefix "(D)")))
    ;; load tag
    (setq buffer (generate-new-buffer (generate-new-buffer-name (concat "*GTAGS SELECT* " prefix tagname))))
    (set-buffer buffer)
    (message "Searching %s ..." tagname)
    (if (not (= 0 (call-process "global" nil t nil (concat "-x" flag) tagname))) ; remove '-a' option
	(progn (message (buffer-substring (point-min)(1- (point-max))))
               (gtags-pop-context))
      (goto-char (point-min))
      (setq lines (count-lines (point-min) (point-max)))
      (cond
       ((= 0 lines)
         (cond
          ((equal flag "P")
           (message "%s: path not found" tagname))
          ((equal flag "g")
           (message "%s: pattern not found" tagname))
          ((equal flag "I")
           (message "%s: token not found" tagname))
          ((equal flag "s")
           (message "%s: symbol not found" tagname))
          (t
           (message "%s: tag not found" tagname)))
	(gtags-pop-context)
	(kill-buffer buffer)
	(set-buffer save))
       ((= 1 lines)
	(message "Searching %s ... Done" tagname)
	(gtags-select-it t))
       (t
        ;; added by rubikitch
        (goto-char (point-min))
        (while (not (eobp))
          (put-text-property (point) (+ 16 (point)) 'invisible t)
          (forward-line 1))
        (goto-char (point-min))
	(switch-to-buffer buffer)
	(gtags-select-mode))))))

(defun gtags-select-it (delete)
  (let (line file)
    ;; get context from current tag line
    (beginning-of-line)
    (if (not (looking-at "[^ \t]+[ \t]+\\([0-9]+\\)[ \t]\\([^ \t]+\\)[ \t]"))
        (gtags-pop-context)
      (setq line (string-to-number (gtags-match-string 1)))
      (setq file (expand-file-name (gtags-match-string 2)))
      (if delete (kill-buffer (current-buffer)))
      ;; move to the context
      (if gtags-read-only (find-file-read-only file) (find-file file))
      (setq gtags-current-buffer (current-buffer))
      (goto-line line)
      (gtags-mode 1))))

この結果 *GTAGS SELECT* は見やすくなった。これで安心してGNU GLOBALを使えるぜ。

  969 array.c            if (rpl == Qundef) {
 1794 array.c        	rb_ary_splice(ary, pos, len, Qundef);	/* Qnil/rb_ary_
 2451 array.c            return Qundef;
 2483 array.c            if (v != Qundef) return v;
  696 bignum.c       #undef MASK_0f
  697 bignum.c       #undef MASK_33
  698 bignum.c       #undef MASK_55
  145 blockinlining.c 		      Qundef);
  174 blockinlining.c 				Qundef));
  199 blockinlining.c 	    if (val == Qundef) {
  207 blockinlining.c     return Qundef;
  245 blockinlining.c 		      Qundef);
  274 blockinlining.c 				Qundef));
  318 blockinlining.c 	    if (val == Qundef) {

追記

フルパス表記をやめたため、gtags-select-itも変更の必要があった。カレントバッファを削除するとdefault-directoryが変わってしまうため、ファイル名を取得ときにexpand-file-nameする必要がでてきた。