天天看點

用CEDET浏覽和編輯C++代碼

  • 1 前言
  • 2 簡介
    • 2.1 EDE
    • 2.2 Semantic
    • 2.3 SRecode
    • 2.4 Cogre
    • 2.5 Speedbar
    • 2.6 EIEIO
  • 3 安裝
  • 4 semantic配置
    • 4.1 功能介紹
      • 4.1.1 semantic-load-enable-minimum-features
      • 4.1.2 semantic-load-enable-code-helpers
      • 4.1.3 semantic-load-enable-gaudy-code-helpers
      • 4.1.4 semantic-load-enable-excessive-code-helpers
      • 4.1.5 semantic-load-enable-semantic-debugging-helpers
    • 4.2 基本配置
    • 4.3 代碼跳轉
    • 4.4 代碼補全
  • 5 EDE配置
  • 6 其它
    • 6.1 可視化書簽
    • 6.2 pluse
    • 6.3 h/cpp切換
    • 6.4 代碼折疊
      • 6.4.1 semantic-tag-folding
      • 6.4.2 senator-fold-tag
  • 7 Todo

1 前言

網上關于如何用emacs+cedet做C++ IDE的文章已經很多了,可是大都隻列出了配置檔案和效果,沒有講清楚具體的配置過程;一篇講得比較具體的文章(http://alexott.net/en/writings/emacs-devenv/EmacsCedet.html ,這篇文章是被cedet官方推薦的)還是英文的。剛接觸cedet的使用者經常照抄了别人的配置卻發現不能補全,而且配置檔案是别人的想改還無法下手。本文中我盡量詳細解釋各個語句的作用,希望能給初次接觸的人提供點幫助。

cedet于2010年2月26日釋出了1.0pre7,強烈建議以前用過cedet覺得速度慢而放棄的同學試試1.0pre7,這個版本速度比以前有很大很大提高。

2 簡介

cedet是一堆完全用elisp實作的emacs工具的集合,主要有:

2.1 EDE

用來管理項目,它可以把emacs模拟得像一個IDE那樣,把一堆檔案作為一個project來管理。

2.2 Semantic

Semantic應該是cedet裡用得最多的元件了,代碼間跳轉和自動補全這兩大功能都是通過semantic來實作的。

2.3 SRecode

SRecode是一個模闆系統,通過一些預定義的模闆,可以很快地插入一段代碼。個人覺得這個功能跟msf-abbrev和yasnippet的功能有些類似。

2.4 Cogre

全稱叫”Connected Graph Editor”,主要和圖形相關,比如可以用它來為C++類生成UML圖。

2.5 Speedbar

Speedbar可以單獨建立一個frame,用于顯示目錄樹,函數清單等等。這個元件已經包含在emacs官方釋出包中。

2.6 EIEIO

EIEIO是一個底層庫,它為elisp加入了OO支援。cedet的其它元件都依賴于EIEIO。

3 安裝

安裝就不多說了,這兒詳細說明了如何下載下傳安裝。

要注意的是通過cvs下載下傳必須要編譯後才能用,而官方釋出後的包可以直接解壓不編譯也是能用。

安裝完後首先當然要load它(確定安裝的路徑已經在load-path中了):

? View Code LISP

(require 'cedet)      

4 semantic配置

先介紹一下最常用的semantic。

4.1 功能介紹

一般裝插件的思路,都是先load然後enable某個minormode。cedet基本上也遵循這個規則,不過有點差別是semantic定義了很多個mode,要是挨個去enable,使用者可能就要罵娘了,是以cedet的作者Eric定義了幾個友善使用的函數,這些函數會自動幫你enable某些minor mode,大概有這麼幾個:

? View Code LISP

1
2
3
4
5
      
(semantic-load-enable-minimum-features)
(semantic-load-enable-code-helpers)
(semantic-load-enable-guady-code-helpers)
(semantic-load-enable-excessive-code-helpers)
(semantic-load-enable-semantic-debugging-helpers)      

簡單介紹一下各個函數的功能:

4.1.1 semantic-load-enable-minimum-features

這個函數開啟了最基本的三個特性:

  • semantic-idle-scheduler-mode

    enable這個mode讓cedet在emacs空閑的時候自動分析buffer内容,比如正在編輯的buffer内容改變後。這個mode一般應該是需要enable的,如果沒有enable這個mode,那隻有手工觸發才會讓cedet重新分析。

  • semanticdb-minor-mode

    semanticdb是semantic用來儲存分析後的内容的,是以也是應該enable的。

  • semanticdb-load-ebrowse-caches

    這個feature我不是很确定,大概的意思好像是semantic可以利用ebrowse的結果。這個feature大概就是把ebrowse生成的檔案load給semantic使用。(要是誰了解這個feature具體意義請告訴我下)

4.1.2 semantic-load-enable-code-helpers

這個函數除enable semantic-load-enable-minimum-features外,還包括:

  • imenu

    這個feature可以讓imenu顯示semantic分析出的類,函數等tags。如圖:

    用CEDET浏覽和編輯C++代碼
    imenu顯示semantic分析出的類
  • semantic-idle-summary-mode

    打開這個mode之後,光标停留在一個類/函數等tag上時,會在minibuffer顯示出這個函數原型,如圖:

    用CEDET浏覽和編輯C++代碼
    用ssemantic在minibuffer顯示函數原型
  • senator-minor-mode

    senator開啟之後,會在emacs上增加一個senator的菜單,可以通過菜單在目前檔案的各個tag之間前後移動,跳轉;還可以在裡面友善地打開/關閉某個feature;還有另外一些實用的功能,看看菜單大概就能明白:

    用CEDET浏覽和編輯C++代碼
    senator菜單
  • semantic-mru-bookmark-mode

    cedet有tag跳轉的功能,但是經常跳轉完後還需要跳回剛才的位置,這時候就需要mru-bookmark-mode了。打開這個mode之後,每次跳轉semantic都會把位置當作書簽一樣記錄下來,以後可以通過M-xsemantic-mrub-switch-tags(綁定到按鍵C-x B上)來選擇跳回以前的任意一個位置。

4.1.3 semantic-load-enable-gaudy-code-helpers

這個函數除enable semantic-load-enable-code-helpers之外,還包括:

  • semantic-stickyfunc-mode

    這個mode會根據光标位置把目前函數名顯示在buffer頂上,如圖:

    用CEDET浏覽和編輯C++代碼

    在head-line上顯示函數名

    這個mode我覺得用處不大,因為基本上可以用which-func-mode代替。而且我習慣打開tabbar-mode,這個mode會覆寫tabbar-mode,是以我是不打開它的。

  • semantic-decoration-mode

    打開這個mode後,semantic會在類/函數等tag上方加一條藍色的線,源檔案很大的時候用它可以提示出哪些是類和函數的頭。如圖:

    用CEDET浏覽和編輯C++代碼
    semantic标記函數頭
  • semantic-idle-completions-mode

    這個mode打開後,光标在某處停留一段時間後,semantic會自動提示此處可以補全的内容。比如下面這段代碼:

    用CEDET浏覽和編輯C++代碼

    semantic自動補全目前光标内容

    如果把光标停留在”this->”的後面,稍隔一會會提示:

    用CEDET浏覽和編輯C++代碼

    semantic自動補全目前光标内容

    如果提示的函數不是需要的,按TAB鍵可以在各個可能的函數之間循環,按回車就可以确定了。

4.1.4 semantic-load-enable-excessive-code-helpers

這個函數除enable semantic-load-enable-gaudy-code-helpers之外,還包括:

  • semantic-highlight-func-mode

    打開這個mode的話,semantic會用灰的底色把光标所在函數名高亮顯示,如下圖中,函數Delete被高亮了,而LexicalCast沒被高亮:

    用CEDET浏覽和編輯C++代碼
    semantic高亮目前函數
  • semantic-idle-tag-highlight-mode

    用過XCode或eclipse的人應該會喜歡高亮光标處變量的功能:就是在函數内部,光标停留在一個變量上,整個函數内部用這個變量的地方都高亮了。在emacs裡隻要打開semantic-idle-tag-highlight-mode,光标在變量處停留一會,就會把相同的變量全都高亮,比如下圖中的變量mAddr:

    用CEDET浏覽和編輯C++代碼

    semantic智能高亮目前符号

    semantic的這個tag-highlight雖然智能,可是我感覺它顯示得太慢了,是以我是用另一個插件highlight-symbol來高亮的,這兒有它的介紹。

  • semantic-decoration-on-*-members

    把private和protected的函數用顔色辨別出來,如圖:

    用CEDET浏覽和編輯C++代碼
    semantic用顔色區分方法的通路權限
  • which-func-mode

    這個其實就是emacs自帶的which-function-mode,把光标目前所在的函數名顯示在mode-line上。

4.1.5 semantic-load-enable-semantic-debugging-helpers

這個函數會enable幾個和調試semantic相關的特性:

  • semantic-highlight-edits-mode

    打開這個mode後,emacs會把最近修改過的内容高亮出來,如下圖中begin就是剛輸入的,是以用灰底色高亮了:

    用CEDET浏覽和編輯C++代碼

    semantic高亮最近修改

    隔一段時間後高亮會自動取消,不會一直高亮着讓整個buffer看起來混亂。

    其實emacs自帶也有高亮修改内容的mode:highlight-changes-mode,它會用紅色的字型高亮所有修改的内容,但是不會自動取消,是以修改多了整個buffer就會亂七八糟糕。用semantic這個就好多了。

  • semantic-show-unmatched-syntax-mode

    這個mode會把semantic解析不了的内容用紅色下劃線辨別出來,比如下面這個檔案是從emacs源代碼中來的:

    用CEDET浏覽和編輯C++代碼
    semantic用紅色下劃線标記不比對的文法
  • semantic-show-parser-state-mode

    打開這個mode,semantic會在modeline上顯示出目前解析狀态,這是關閉mode的樣子:

    用CEDET浏覽和編輯C++代碼
    這是打開mode的樣子:
    用CEDET浏覽和編輯C++代碼

    能看出modeline上檔案名前的橫線多了一條,其實倒數第二條就是用來顯示目前semantic解析狀态的:未解析時顯示為”!”,正在解析時顯示”@”,解析完後顯示”-”,如果buffer修改後未重新解析顯示為”^”。

    semantic會在空閑時自動解析,另外可以打開senator-minor-mode,按[C-c , ,]或者在senator菜單中選[Force Tag Refresh]強制它馬上解析。

4.2 基本配置

了解了上面這些feature,就可以根據需要配置了,為了使用semantic,至少需要開啟semantic-load-enable-minimum-features定義的三個基礎feature,其餘的feature就可以根據自己的需要開啟了。比如我的配置是:

? View Code LISP

1
2
3
4
5
      
;; (semantic-load-enable-minimum-features)
(semantic-load-enable-code-helpers)
;; (semantic-load-enable-guady-code-helpers)
;; (semantic-load-enable-excessive-code-helpers)
(semantic-load-enable-semantic-debugging-helpers)      

因為imenu,idle-summary-mode,senator-mode,mru-bookmark-mode都是我需要的。特别是senator,有時候我會碰到semantic等很久也不自動解析檔案的問題,這時候就需要在senator菜單裡[Force TagRefresh]一下了,并且senator還可以通過菜單友善地打開和關閉某些mode,用起來還是很友善的。

(semantic-load-enable-guady-code-helpers)和(semantic-load-enable-excessive-code-helpers)定義的那些feature,對我來說用處不大,而且我感覺打開的話還會讓emacs反應變慢,是以我就不啟用了。

(semantic-load-enable-semantic-debugging-helpers)的幾個feature我都比較喜歡,是以我也啟用了。

有了這些基本配置,在emacs打開C和C++檔案的時候,semantic就會自動解析檔案。不過有個問題,一個cpp檔案中肯定會include很多頭檔案,要想解析這個cpp的内容,頭檔案的資訊是必要的;但是頭檔案可能和cpp放在一起,也可能放在系統某個目錄下,semantic怎麼才能找到這個頭檔案一起解析呢?

semantic是這樣處理的:1、如果目前目錄中能找到,就直接在目前檔案中讀取頭檔案。2、如果目前目錄下沒有,就上系統INCLUDE目錄中去找(在Linux下,我們一般使用gcc編譯器,semantic會自動調用gcc,取得gcc的INCLUDE目錄,比如/usr/include,/usr/local/include等,但是Windows下就不行了)。

BTW:很多文檔中提到需要load semantic-gcc,不過我沒有load它,在Linux下semantic仍然能自動把gcc的INCLUDE目錄加進來。

semantic這種找法肯定會造成大量的頭檔案找不到的(找不到頭檔案還怎麼解析啊),有兩個問題需要解決:1、很多工程中都會把頭檔案和實作檔案分開放置,比如頭檔案放在include(或者inc,public,common等)目錄中,實作檔案放在src目錄中,這些目錄semantic是不能自己找的;2、在Windows下怎麼能讓semantic去找編譯器的INCLUDE目錄。

既然semantic不能自動查找找,那就隻能我們告訴semantic了,辦法是調用semantic-add-system-include函數,這個函數會根據mode把路徑加入到semantic-dependency-system-include-path裡去。下面是我的配置:

? View Code LISP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
      
;; (setq semanticdb-project-roots (list (expand-file-name "/")))
(defconst cedet-user-include-dirs
  (list ".." "../include" "../inc" "../common" "../public"
        "../.." "../../include" "../../inc" "../../common" "../../public"))
(defconst cedet-win32-include-dirs
  (list "C:/MinGW/include"
        "C:/MinGW/include/c++/3.4.5"
        "C:/MinGW/include/c++/3.4.5/mingw32"
        "C:/MinGW/include/c++/3.4.5/backward"
        "C:/MinGW/lib/gcc/mingw32/3.4.5/include"
        "C:/Program Files/Microsoft Visual Studio/VC98/MFC/Include"))
(require 'semantic-c nil 'noerror)
(let ((include-dirs cedet-user-include-dirs))
  (when (eq system-type 'windows-nt)
    (setq include-dirs (append include-dirs cedet-win32-include-dirs)))
  (mapc (lambda (dir)
          (semantic-add-system-include dir 'c++-mode)
          (semantic-add-system-include dir 'c-mode))
        include-dirs))      

因為我在Windows下可能用MinGW和VC6,是以我把它們的include目錄都加進來了,要是你用别的編譯器,就改成自己的目錄好了。

另外,我找了一些一般項目中經常用到的頭檔案目錄名(include,inc,common,public),把它們也加進來了,這樣對于一般的項目來說基本上都能解析正确(比如我們在項目中見到頭檔案放在include目錄實作檔案放在src目錄的方式,對src目錄下一個cpp檔案,通過“../include”這個路徑就能找到對應的頭檔案)。如果你的項目中還用了其它一些目錄名,也可以配置在這兒。

上面配置中那一行(require ’semantic-c nil‘noerror)是必須的,因為semantic的大部分功能是autoload的,如果不在這兒loadsemantic-c,那打開一個c檔案時會自動loadsemantic-c,它會把semantic-dependency-system-include-path重設為/usr/include,結果就造成前面自定義的include路徑丢失了。

順便說一下semanticdb-project-roots的配置,很多地方都說要把它配置成”/”,但是我在Linux/Mac/Windows都試驗過,不配這一行并沒什麼影響。

解析檔案是semantic基本進階功能的基礎,正确地解析了檔案我們才能實作:代碼跳轉和代碼補全。

4.3 代碼跳轉

有了前面的配置,semantic自動就解析c/c++檔案,解析完後跳轉就容易了:光标放在函數上,執行M-xsemantic-ia-fast-jump,馬上就跳轉到函數的定義上了。如果跳不過去,那就檢查一下前面配置的INCLUDE路徑,是不是目前檔案include的所有頭檔案都能在INCLUDE中找到。如果檢查了很多遍都不好用,那就換個項目或者别的檔案試試,确實存在semantic對某些檔案支援不太好的情況,比如boost。

semantic-ia-fast-jump這個功能如此常用,我就把它綁定到f12上去了。

? View Code LISP

(global-set-key [f12] 'semantic-ia-fast-jump)      

另外,前面我們說過跳轉過去了我們還需要跳回來,在打開mru-bookmark-mode的情況下,按[C-x B],emacs會提示你跳回到哪個地方,一般預設的就是上一次semantic-ia-fast-jump的位置,是以回車就可以回去了。

不過看代碼時候我經常需要跳轉後馬上就跳回來,要按[C-x B] [RET]這麼多鍵實在有點麻煩,是以我寫了個函數不提示直接就跳回上次的位置,并把它綁定到shift+f12上了:

? View Code LISP

1
2
3
4
5
6
7
8
9
10
11
12
      
(global-set-key [S-f12]
                (lambda ()
                  (interactive)
                  (if (ring-empty-p (oref semantic-mru-bookmark-ring ring))
                      (error "Semantic Bookmark ring is currently empty"))
                  (let* ((ring (oref semantic-mru-bookmark-ring ring))
                         (alist (semantic-mrub-ring-to-assoc-list ring))
                         (first (cdr (car alist))))
                    (if (semantic-equivalent-tag-p (oref first tag)
                                                   (semantic-current-tag))
                        (setq first (cdr (car (cdr alist)))))
                    (semantic-mrub-switch-tags first))))      

除了semantic-ia-fast-jump可以跳轉之外,其實semantic中還有兩個函數也有類似的功能:

  • semantic-complete-jump-local
  • semantic-complete-jump

看名字很容易看出來,前一個隻能在目前buffer内跳轉,後一個可以跳轉到其它檔案。不過這兩個指令都需要使用者手工輸入要跳轉的Tag名,不能像semantic-ia-fast-jump那樣自動識别目前光标處單詞,是以浏覽代碼時還是semantic-ia-fast-jump舒服。

cedet還有個功能在函數和聲明和實作間跳轉,一般的,函數聲明放在h檔案中,函數的實作放在cpp檔案中,光标在函數體的時候通過M-xsemantic-analyze-proto-impl-toggle可以跳到函數聲明去,在聲明處再執行的話就會再跳回函數體,我把它綁定到M-S-F12上了:

? View Code LISP

(define-key c-mode-base-map [M-S-f12] 'semantic-analyze-proto-impl-toggle)      

不是這個功能不是十分準确,一般在cpp中函數實作處想跳到函數聲明處正常,但是從聲明處跳到實作處的話cedet不一定能找到cpp檔案的位置。

4.4 代碼補全

semantic中有4個用來代碼補全的指令:

  • senator-complete-symbol
  • senator-completion-menu-popup
  • semantic-ia-complete-symbol
  • semantic-ia-complete-symbol-menu

senator-complete-symbol和semantic-ia-complete-symbol這兩個函數是新開一個buffer提示可能的補全内容;而senator-completion-menu-popup和semantic-ia-complete-symbol-menu會彈出一個補全菜單。

至于功能,以senator開頭的兩個函數是調用senator補全,另外兩個是調用semantic-ia補全。至于senator和semantic-ia的差別,http://alexott.net/en/writings/emacs-devenv/EmacsCedet.html#sec9是這樣解釋的:

“semantic-ia調用semantic-analyze-possible-completions函數來取得可能的補全内容,它能為使用者提供精确的補全清單;而senator用了一個更簡單的的函數來擷取補全内容,是以有可能會提供錯誤的結果。”

也就是說semantic-ia的補全更智能一些。

至于semantic-ia這兩個補全選哪一樣就看各人喜好了,我喜歡用semantic-ia-complete-symbol-menu,因為看起來更直覺一些,像這樣:

用CEDET浏覽和編輯C++代碼

semantic的補全菜單

我喜歡把它綁定到[Alt+n]上:

? View Code LISP

(define-key c-mode-base-map (kbd "M-n") 'semantic-ia-complete-symbol-menu)      

不過semantic-ia-complete-symbol-menu隻能用于GUI下,要是在終端下,就隻能用semantic-ia-complete-symbol了。(終端下想要semantic-ia-complete-symbol一樣的結果可以用别的插件,比如auto-complete或者company-mode)

如果啟用了semantic-idle-completions-mode,不用按鍵隻需要光标在.或者->後面停一會semantic就會自動開始補全了。

如果你用cedet不能補全,檢查一下semantic是不是已經啟用了,我的emacs上經常出現第一次打開c++-mode時semantic沒自動啟用的情況。看semantic是否正常有個直覺的方法就是senator,如果啟用了senator-minor-mode,打開c++檔案時emacs會出現Senator菜單,如果沒有Senator菜單你可以關掉再重新打開試試,要是仍然不出現菜單那就得檢查配置是不是有問題。

如果确認semantic啟用了仍然不能補全,就需要檢查INCLUDE路徑的配置,通過C-h vsemantic-dependency-system-include-pathRET檢查INCLUDE路徑,確定目前cpp中直接或間接include的頭檔案都能在INCLUDE路徑中找到。

5 EDE配置

EDE是用來管理project的工具,用下面的代碼啟用它:

? View Code LISP

(global-ede-mode t)      

EDE會在emacs中加一個叫做“Project”的菜單:

用CEDET浏覽和編輯C++代碼

EDE的Project菜單

通過菜單可以建立project,往project裡添加/移除檔案;還可以編譯project,不過好像隻能通過已有的Makefile編譯。

另外EDE還可以通過Speedbar顯示整個project的目錄樹(見右邊的Speedbar):

用CEDET浏覽和編輯C++代碼

用Speedbar顯示project的目錄樹

EDE可以支援四種類型的project:

  • Automake
  • 手工寫的Makefile
  • C++ Root project
  • Simple project

并且EDE能解析Autoconf/Automake,如果打開一個檔案時在目前或者上級目錄中能找到Makefile.am檔案,EDE會自動解析檔案(認為這是一個Automake的project),識别出Makefile.am中定義的target和編譯需用到的檔案;打開目錄樹的話EDE能由Makefile.am中涉及到的檔案生成目錄樹(上圖的目錄樹就是EDE通過Makefile.am自動生成的)。

為了讓semantic找到C/C++的頭檔案,前面是通過調用semantic-add-system-include把系統中可能出現的INCLUDE目錄都告訴semantic的來實作的。其實semantic還可以通過EDE識别project中特定的INCLUDE目錄,方法是在.emacs檔案中定義C++ Root project,比如:

? View Code LISP

1
2
3
4
5
6
7
8
9
10
11
      
(setq libutil-project
      (ede-cpp-root-project "libutil"
                            :file "~/projects/libutil/configure.in"
                            :system-include-path '("/home/meteor1113/projects/include"
                                                   "/home/meteor1113/projects/common"
                                                   "/home/meteor1113/projects/libutil/pub")))
(setq test-project
      (ede-cpp-root-project "test"
                            :file "~/test/Makefile"
                            :system-include-path '("/test/include"
                                                   "/usr/include/boost-1.42")))      

上面定義了兩個project,并且設定了各個project各自的INCLUDE目錄。

不過這種方式有兩個缺點:

  • 不能支援常見的Makefile/Makefile.am型project。
  • 我不願意為每個project都定義這樣一個project,對于每天都要自己寫代碼的項目生成個C++ Root project還可以接受,有時候隻是臨時閱讀一下其它項目,要是還要為它寫個EDE的project配置就太麻煩了。

是以這個功能我也一直沒用過,有問題的請參考官方文檔。我覺得把所有可能的目錄都加進system-include裡更友善。

6 其它

6.1 可視化書簽

emacs有自帶的書簽功能(c-x r m, c-x r b, c-x r l),不過對于用了多年VC6的我來說還是更習慣讓一個書簽能高亮顯示出來。cedet裡就帶了一個可視化的書簽,通過下面的語句可以啟用它:

? View Code LISP

(enable-visual-studio-bookmarks)      

之後就可以通過下面幾個按鍵操作書簽了:

  • F2 在目前行設定或取消書簽
  • C-F2 查找下一個書簽
  • S-F2 查找上一個書簽
  • C-S-F2 清空目前檔案的所有書簽

看這個效果:

用CEDET浏覽和編輯C++代碼

cedet的可視化标簽

有點遺憾的是這個書簽功能隻能在目前buffer的書簽間跳轉。

6.2 pluse

使用semantic-ia-fast-jump跳轉時,cedet有個很酷的效果:在跳轉到的行上實作一個淡入淡出的效果。具體的分析和使用看這兒。

6.3 h/cpp切換

cedet的contrib目錄下有一些實用的小功能,比如eassist.el就提供了一個在C++的頭檔案和實作檔案間跳轉的小功能。

要使用這個功能首先要load它:

? View Code LISP

(require 'eassist nil 'noerror)      

之後就可以通過指令M-x eassist-switch-h-cpp來切換了,我喜歡把它綁定到M-F12上:

? View Code LISP

(define-key c-mode-base-map [M-f12] 'eassist-switch-h-cpp)      

這個功能是依賴semantic的,也就是說通過cpp找頭檔案時它也會上配置好的INCLUDE路徑中去查找,不過如果通過頭檔案找cpp檔案,好像隻能找和頭檔案所在的同一目錄了。

eassist-switch-h-cpp有個BUG:它是通過檔案擴充名來比對的(通過eassist-header-switches可配置),預設它能識别h/hpp/cpp/c/C/H/cc這幾個擴充名的檔案;但是C++的擴充名還可能會有别的,比如c++,cxx等,對一個擴充名為cxx的檔案調用eassist-switch-h-cpp的話,它會建立一個新buffer顯示錯誤資訊。是以我把eassist-header-switches配置為:

? View Code LISP

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
      
(setq eassist-header-switches
      '(("h" . ("cpp" "cxx" "c++" "CC" "cc" "C" "c" "mm" "m"))
        ("hh" . ("cc" "CC" "cpp" "cxx" "c++" "C"))
        ("hpp" . ("cpp" "cxx" "c++" "cc" "CC" "C"))
        ("hxx" . ("cxx" "cpp" "c++" "cc" "CC" "C"))
        ("h++" . ("c++" "cpp" "cxx" "cc" "CC" "C"))
        ("H" . ("C" "CC" "cc" "cpp" "cxx" "c++" "mm" "m"))
        ("HH" . ("CC" "cc" "C" "cpp" "cxx" "c++"))
        ("cpp" . ("hpp" "hxx" "h++" "HH" "hh" "H" "h"))
        ("cxx" . ("hxx" "hpp" "h++" "HH" "hh" "H" "h"))
        ("c++" . ("h++" "hpp" "hxx" "HH" "hh" "H" "h"))
        ("CC" . ("HH" "hh" "hpp" "hxx" "h++" "H" "h"))
        ("cc" . ("hh" "HH" "hpp" "hxx" "h++" "H" "h"))
        ("C" . ("hpp" "hxx" "h++" "HH" "hh" "H" "h"))
        ("c" . ("h"))
        ("m" . ("h"))
        ("mm" . ("h"))))      

基本上所有C/C++的擴充名都包含了,同時ObjectiveC也可以用了。

6.4 代碼折疊

6.4.1 semantic-tag-folding

從我開始用emacs開始就聽大蝦們說hs-minor-mode可以實作代碼折疊,是以我的.emacs裡一直把hs-minor-mode打開的,可是用了5年之後我發現還是不習慣它的按鍵,另外也不是很喜歡它顯示的樣子,5年來Hide/Show這個菜單對我來說基本上是個擺設。

我期待像eclipse那樣可以通過滑鼠在直接點選就可以打開和折疊代碼,這個功能在cedet也實作了(可惜這麼長時間一直沒發現它),就是semantic-tag-folding.el(也在cedet的contrib目錄下)。

? View Code LISP

1
2
      
(require 'semantic-tag-folding nil 'noerror)
(global-semantic-tag-folding-mode 1)      

看這個圖:

用CEDET浏覽和編輯C++代碼

cedet的代碼折疊

隻要用滑鼠點選左側的小三角圖示就可以打開或折疊代碼了。箭頭向下的空心三角表示這段代碼可以被折疊,箭頭向右的實心三角表示這段代碼被打折過了。

為了友善鍵盤操作,我把按鍵綁定到了[C-c , -]和[C-c , +]上(綁定這麼複雜的

按鍵主要是為了和senator相容,後面會講到senator實作代碼折疊):

? View Code LISP

1
2
      
(define-key semantic-tag-folding-mode-map (kbd "C-c , -") 'semantic-tag-folding-fold-block)
(define-key semantic-tag-folding-mode-map (kbd "C-c , +") 'semantic-tag-folding-show-block)      

同時它還提供了兩個函數可以同時打開和折疊整個buffer的所有代碼,分别是

semantic-tag-folding-fold-all和semantic-tag-folding-show-all,我把它們

綁定到了[C-_]和[C-+]上:

? View Code LISP

1
2
      
(define-key semantic-tag-folding-mode-map (kbd "C-_") 'semantic-tag-folding-fold-all)
(define-key semantic-tag-folding-mode-map (kbd "C-+") 'semantic-tag-folding-show-all))      

打開semantic-tag-folding-mode後,用gdb調試時不能點左側的fringe切換斷點了,是以我把C-?定義為semantic-tag-folding-mode的切換鍵,在gdb調試時臨時把semantic-tag-folding關掉:

? View Code LISP

(global-set-key (kbd "C-?") 'global-semantic-tag-folding-mode)      

不過,semantic-tag-folding在終端下會有一點點小問題:終端下semantic-tag-folding在函數前面加了個“+”或“-”号,看下面這個圖:

用CEDET浏覽和編輯C++代碼

終端下的semantic-tag-folding

雖然功能不受影響(除了不能用滑鼠操作外,快捷鍵和GUI下是一樣的),不過代碼不能對齊了還是令我有些不爽,是以終端下我是禁用semantic-tag-folding的,最終我的配置如下:

? View Code LISP

1
2
3
4
5
6
7
      
(when (and window-system (require 'semantic-tag-folding nil 'noerror))
  (global-semantic-tag-folding-mode 1)
  (global-set-key (kbd "C-?") 'global-semantic-tag-folding-mode)
  (define-key semantic-tag-folding-mode-map (kbd "C-c , -") 'semantic-tag-folding-fold-block)
  (define-key semantic-tag-folding-mode-map (kbd "C-c , +") 'semantic-tag-folding-show-block)
  (define-key semantic-tag-folding-mode-map (kbd "C-_") 'semantic-tag-folding-fold-all)
  (define-key semantic-tag-folding-mode-map (kbd "C-+") 'semantic-tag-folding-show-all))      

需要注意的是,semantic-tag-folding依賴于文法解析,也就是說必須等semantic解析完檔案之後才能使用。如果找開檔案在fringe處找不到空心三角,可以[Force Tag Refresh]下,或者檢查下semantic是否配置正确。

6.4.2 senator-fold-tag

終端下不用semantic-tag-folding了,最好能有替代方案吧:首先可以用回hs-minor-mode,此外cedet的senator也提供了一種代碼折疊方案。

隻要啟用了senator-minor-mode(emacs中會出現Senator菜單),就可以通過M-x senator-fold-tag和M-x senator-unfold-tag來折疊和打開代碼了,GUI和終端下都可以使用。

預設地,senator-fold-tag綁定到[C-c , -],senator-unfold-tag綁定到[C-c ,+]上(是以前面我把semantic折疊的快捷鍵也綁定到這兩個鍵上,這樣GUI和終端下快捷鍵就一緻了)。不過senator裡好像沒有對應的fold-all和show-all方法。

7 Todo

以上隻是cedet裡我用到的一些功能,其實cedet還有很多優秀的功能,比如通過模闆自動生成代碼(SRecode);通過代碼畫UML圖以及通過UML圖生成代碼(Cogre)等;另外semantic除了可以自己解析代碼外還可以借助ctags,global,ebrowse來解析。更多的功能需要進一步發掘。

最後,歡迎參觀我的cedet配置: http://github.com/meteor1113/dotemacs/blob/master/init-site.el