ctags使用說明詳解


ctags使用說明詳解

一.ctags是干什么的

ctags的功能:掃描指定的源文件,找出其中所包含的語法元素,並將找到的相關內容記錄下來。

我用的是Exuberant Ctags

二.ctags可以識別哪些語言,是如何識別的

ctags識別很多語言,可以用如下命令來查看:

ctags --list-languages

還可以識別自定義語言,具體沒研究過。

ctags是可以根據文件的擴展名以及文件名的形式來確定該文件中是何種語言,從而使用正確的分析器。可以使用如下命令來查看默認哪些擴展名對應哪些語言:

ctags --list-maps

還可以指定ctags用特定語言的分析器來分析某種擴展名的文件或者名字符合特定模式的文件。例如如下命令告知ctags,以inl為擴展名的文件是c++文件。

ctags --langmap=c++:+.inl –R

並不十分清楚ctags使用何種技術來解析內容,估計包括正則表達式、詞法分析、語法分析等等。但ctags不是編譯器也不是預處理器,它的解析能力是有限的。例如它雖然可以識別宏定義,但對於使用了宏的語句的識別還是有缺陷的,在一些稍微正規點的代碼(例如ACE的庫或VC的頭文件等)中的某些常規的宏使用方式會導致ctags無法識別,或者識別錯誤,從而使得ctags沒有記錄user想記錄的內容,或者記錄下的信息不准確。另一方面ctags也有聰明的一面,例如在cpp文件中掃描到static的全局變量時,ctags會記錄這個變量,而且還會標明說這個變量是局限於本文件的,同樣的定義,如果放在h文件中,ctags則不會標明說這個變量是局限於本文件的,因為ctags認為h文件是頭文件的一種,會被其他文件include,所以在其他文件中可能會用到該h文件里定義的這個全局變量。

三.ctags可以識別和記錄哪些語法元素

可以用如下命令查看ctags可以識別的語法元素:

ctags --list-kinds

或者單獨查看可以識別的c++的語法元素

ctags --list-kinds=c++

ctags識別很多元素,但未必全都記錄,例如“函數聲明”這一語法元素默認是不記錄的,可以控制ctags記錄的語法元素的種類。如下命令要求ctags記錄c++文件中的函數聲明和各種外部和前向聲明:

ctags -R --c++-kinds=+px

四.ctags是怎么記錄的

不管一次掃描多少文件,一條ctags命令把記錄的內容都記到一個文件里去,默認是當前目錄的tags文件,當然這是可以更改的。

每個語法元素對應文件里的一行,學名叫tag entry。

1) 開頭是tag的名字,其實也就是語法元素的名字,例如記錄的是函數的話則tag名就是函數名,記錄的是類的話,tag名就是類名。

2) 接下來是一個tab。

3) 接下來是語法元素所在的文件名。

4) 又是一個tab。

5) 一條“命令”。這個要解釋一下意義:ctags所記錄的內容的一個功能就是要幫助像vi這樣的編輯器快速定位到語法元素所在的文件中去。前面已經記錄了語法元素所在的文件,這條命令的功能就是一旦在vi中打開語法元素所在的文件,並且執行了該“命令”后,vi的光標就能定位到語法元素在文件中的具體位置。所以該“命令”的內容一般分兩種,一種是一個正則表達式的搜索命令,一種是第幾行的指向命令。默認讓ctags在記錄時自行選擇命令的種類,選擇的依據不詳,可以通過命令行參數來強制ctags使用某種命令,這里就不多談了。

6) 對於本tag entry(簡稱tag)所對應的語法元素的描述,例如語法元素的類型等。具體內容和語法元素的種類密切相關。顯示哪些描述,顯示的格式等都是可以在命令行指定的。例如如下命令要求描述信息中要包含:a表示如果語法元素的類的成員的話,要標明其access(即是public的還是private的);i表示如果有繼承,標明父類;K表示顯示語法元素的類型的全稱;S表示如果是函數,標明函數的signature;z表示在顯示語法元素的類型是使用kind:type的格式。

ctags -R --fields=+aiKSz

ctags除了記錄上述的各種內容之外,還可以在tags文件中記錄本次掃描的各個文件,一個文件名對應一個tag entry。默認是不記錄的,要強制記錄要是使用如下命令:

ctags –R --extra=+f

還可以強制要求ctags做這樣一件事情——如果某個語法元素是類的一個成員,當然ctags默認會給其記錄一個tag entry(說白了就是在tags文件里寫一行),可以要求ctags對同一個語法元素再記一行。舉一個例子來說明:假設語法元素是一個成員函數,ctags默認記錄的tag entry中的tag的名字就是該函數的名字(不包括類名作為前綴),而我們強制要求ctags多記的那個tag entry的tag的名字是包含了類明作為前綴的函數的全路徑名。這樣做有什么好處見下文分析。強制ctags給類的成員函數多記一行的命令為:

ctags -R --extra=+q

五. vi大概是怎樣使用ctags生成的tags文件的

估計vi是這樣使用tags文件的:我們使用vi來定位某個tag時,vi根據我們輸入的tag的名字在tags文件中一行行查找,判斷每一行tag entry的tag名字(即每行的開頭)是否和用戶給出的相同,如果相同就認為找到一條記錄,最后vi顯示所有找到的記錄,或者根據這些記錄直接跳轉到對應文件的特定位置。

考慮到ctags記錄的內容和方式,出現同名的tag entry是很常見的現象,例如函數聲明和函數定義的tag名字是一樣的,重載函數的tag名字是一樣的等等。vi只是使用tag名字來搜索,還沒智能到可以根據函數的signature來選擇相應的tag entry。vi只能簡單的顯示tag entry的內容給user,讓user自行選擇。

ctags在記錄成員函數時默認是把函數的名字(僅僅是函數的名字,不帶任何類名和namespace作為前綴)作為tag的名字的,這樣就導致很多不同類但同名的函數所對應的tag entry的名字都是一樣的,這樣user在vi中使用函數名來定位時就會出現暴多選擇,挑選起來十分麻煩。user可能會想在vi中用函數的全路徑名來進行定位,但這樣做會失敗,因為tags文件中沒有對應名字的tag entry。要滿足用戶的這種心思,就要求ctags在記錄時針對類的成員多記錄一條tag entry,該tag entry和已有的tag entry的內容都相同,除了tag的名字不同,該tag entry的名字是類的成員的全路徑名(包括了命名空間和類名)。這就解釋了ctags的--extra=+q這樣一條命令行選項(見四)。

六.我的一條ctags命令

ctags-R --languages=c++ --langmap=c++:+.inl -h +.inl --c++-kinds=+px--fields=+aiKSz --extra=+q --exclude=lex.yy.cc --exclude=copy_lex.yy.cc

命令太長了,折成兩行了,可以考慮把命令的各個參數寫到文件里去了(具體做法就不談了)。

-R

表示掃描當前目錄及所有子目錄(遞歸向下)中的源文件。並不是所有文件ctags都會掃描,如果用戶沒有特別指明,則ctags根據文件的擴展名來決定是否要掃描該文件——如果ctags可以根據文件的擴展名可以判斷出該文件所使用的語言,則ctags會掃描該文件。

--languages=c++

只掃描文件內容判定為c++的文件——即ctags觀察文件擴展名,如果擴展名對應c++,則掃描該文件。反之如果某個文件叫aaa.py(Python文件),則該文件不會被掃描。

--langmap=c++:+.inl

告知ctags,以inl為擴展名的文件是c++語言寫的,在加之上述2中的選項,即要求ctags以c++語法掃描以inl為擴展名的文件。

-h +.inl

告知ctags,把以inl為擴展名的文件看作是頭文件的一種(inl文件中放的是inline函數的定義,本來就是為了被include的)。這樣ctags在掃描inl文件時,就算里面有static的全局變量,ctags在記錄時也不會標明說該變量是局限於本文件的(見第一節描述)。

--c++-kinds=+px

記錄類型為函數聲明和前向聲明的語法元素(見第三節)。

--fields=+aiKSz

控制記錄的內容(見第四節)。

--extra=+q

讓ctags額外記錄一些東西(見第四、五節)。

--exclude=lex.yy.cc --exclude=copy_lex.yy.cc

告知ctags不要掃描名字是這樣的文件。還可以控制ctags不要掃描指定目錄,這里就不細說了。

-f tagfile:指定生成的標簽文件名,默認是tags. tagfile指定為 - 的話,輸出到標准輸出。

七.本文內容來源

Exuberant Ctags附帶的幫助文檔(ctags.html)。

補充===

"ctags"是一個獨立的程序,絕大多數Unix系統上都會預裝這個程序。

1、使用tags

tag是什么?一個位置。它記錄了關於一個標識符在哪里被定義的信息,比如C或C++程序中的一個函數定義。這種tag聚集在一起被放入一個tags文件。這個文件可以讓Vim能夠從任何位置起跳達到tag所指示的位置-標識符被定義的位置。

下面的命令可以為當前目錄下的所有C程序文件生成對應的tags文件:
    (shell command) ctags *.c

現在你在Vim中要跳到一個函數的定義(如startlist)就可以用下面的命令:
   (ex command) :tag startlist

這個命令會帶你到函數"startlist"的定義處,哪怕它是在另一個文件中。

CTRL+] 命令會取當前光標下的word作為tag的名字並直接跳轉。這使得在大量C程序中進行探索更容易一些。假設你正看函數"write block",發現它調用了一個叫"write line"的函數,這個函數是干什么的呢?你可以把光標置於"write_line"上,按下CTRL+] 即可。如果"write_line"函數又調用了 "write_ char".你當然又要知道這個函數又是什么功能。同時,置光標於"write_char"上按下CTRL+]。現在你位於函數"write_char"的定義處。

":tags"命令會列出現在你就已經到過哪些tag了:
   (ex command):tags
   #      TO          tag       FROM line          in file/text
   1       1       write_line        8             write_block.c
   2       1       write_char        7             write_line.c

現在往回走。CTRL+T命令會跳到你前一次的tag處。在上例中它會帶你到調用了"write_char"的"write_line"函數的地方。CTRL+T可以帶一個命令記數, 以此作為往回跳的次數, 你已經向前跳過了,現在正在往回跳,我們再往前跳一次。下面的命令可以直接跳轉到當前tag序列的最后:
   (ex command) :tag

你也可以給它一個前輟, 讓它向前跳指定的步長. 比如":3tag"。CTRL+T也可以帶一個前輟。這些命令可以讓你向下深入一個函數調用樹(使用CTRL+]), 也可以回溯跳轉(使用CTRL+T). 還可以隨時用":tags"看你當前的跳轉歷史記錄。

2、分隔窗口

":tag"命令會在當前窗口中載入包含了目標函數定義的文件。但假設你不僅要查看新的函數定義,還要同時保留當前的上下文呢?你可以在":tag"后使用一個分隔窗口命令":split"。Vim還有一個一舉兩得的命令:
  (ex command) :stag tagname

要分隔當前窗口並跳轉到光標下的tag:
(normal mode command) CTRL+W+]
如果同時還指定了一個命令記數, 它會被當作新開窗口的行高.

3、多個tags文件

如果你的源文件位於多個目錄下,你可以為每個目錄都建一個tags文件。Vim會在使用某個目錄下的tags文件進行跳轉時只在那個目錄下跳轉。

要使用更多tags文件,可以通過改變'tags'選項的設置來引入更多的tags文件。如:
   (ex command) :set tags=./tags, ./../tags, ./*/tags

這樣的設置使Vim可以使用當前目錄下的tags文件,上一級目錄下的tags文件,以及當前目錄下所有層級的子目錄下的tags文件。這樣可能會引入很多的tags文件,但還有可能不敷其用。比如說你正在編輯"/proj/src"下的一個文件,但又想使用"/proj/sub/tags"作為 tags文件。對這種Vim情況提供了一種深度搜索目錄的形式。如下:(ex command) :set tags=~/proj/**/tags

4、單個tags文件

Vim在搜索眾多的tags文件時,你可能會聽到你的硬盤在咔嗒咔嗒拼命地叫。顯然這會降低速度。如果這樣還不如花點時間生成一個大一點的tags文件。這需要一個功能豐富的ctags程序,比如上面提到的那個。它有一個參數可以搜索整個目錄樹:
   (shell command)cd ~/proj
    ctags -R
用一個功能更強的ctags的好處是它能處理多種類型的文件。不光是C和C++源程序,也能對付Eiffel或者是Vim腳本。你可以參考ctags程序的文件調整自己的需要。現在你只要告訴Vim你那一個tags文件在哪就行了:
    (ex command) :set tags=~/proj/tags

5、同名tag

當一個函數被多次重載(或者幾個類里都定義了一些同名的函數),":tag"命令會跳轉到第一個符合條件的。如果當前文件中就有一個匹配的,那又會優先使用它。當然還得有辦法跳轉到其它符合條件的tag去:
  (ex command) :tnext

重復使用這個命令可以發現其余的同名tag。如果實在太多,還可以用下面的命令從中直接選取一個:
(ex command) :tselect tagname
Vim會提供給你一個選擇列表,例如:(Display)

pri kind tag file

1 F f mch_init os_amiga.c
mch_init()
2 F f mch_init os_mac.c
mch_init()
3 F f mch_init os_msdos.c
mch_init(void)
4 F f mch_init os_riscos.c
mch_init()
Enter nr of choice ( to abort):

現在你只需鍵入相應的數字(位於第一欄的)。 其它欄中的信息是為了幫你作出決策的。在多個匹配的tag之間移動,可以使用下面這些命令:
(ex command):tfirst go to first match
:[count]tprevious go to [count] previous match
:[count]tnext go to [count] next match
:tlast go to last match
如果沒有指定[count],默認是1。

6、tag的名字

命令補齊真是避免鍵入一個長tag名的好辦法。只要輸入開頭的幾個字符然后按下制表符:
   (ex command) :tag write_<Tab>

Vim 會為你補全第一個符合的tag名。如果還不合你意,接着按制表符直到找到你要的。有時候你只記得一個tag名的片段,或者有幾個tag開頭相同。這里你可以用一個模式匹配來告訴Vim你要找的tag。

假設你想跳轉到一個包含"block"的tag。首先鍵入命令:(ex command) :tag /block。現在使用命令補齊:按<Tab>。Vim會找到所有包含"block"的tag並先提供給你第一個符合的。"/"告訴Vim下面的名字不是一五一十的tag名,而是一個搜索模式。通常的搜索技巧都可以用在這里。比如你有一個tag以"write "開始:(ex command) :tselect / ^write_,"^"表示這個tag以"write_"開始。不然在半中間出現write的tag也會被搜索到。同樣"$"可以用於告訴Vim要查找的tag如何結束。

7、tags的瀏覽器

CTRL+]可以直接跳轉到以當前光標下的word為tag名的地方去,所以可以在一個tag列表中使用它。下面是一個例子。首先建立一個標識符的列表(這需要一個好的ctags):
  (shell command) ctags --c-types=f -f functions *.c

現在直接啟動Vim, 以一個垂直分隔窗口的編輯命令打開生成的文件:
   (shell command) vim:vsplit functions
這個窗口中包含所有函數名的列表。可能會有很多內容,但是你可以暫時忽略它。用一個":setlocal ts=99"命令清理一下顯示。在該窗口中,定義這樣的一個映射:
    (ex command):nnoremap <buffer> <CR> 0ye<C-W>w:tag <C-R>"<CR>
現在把光標移到你想要查看其定義的函數名上,按下回車鍵,Vim就會在另一個窗口中打開相應的文件並定位到到該函數的定義上。

8、其它相關主題

設置'ignorecase'也可以讓tag名的處理忽略掉大小寫。'tagsearch'選項告訴Vim當前參考的tags文件是否是排序過的。默認情況假設該文件是排序過的,這會使tag的搜索快一些,但如果tag文件實際上沒有排序就會在搜索時漏掉一些tag。

'taglength'告訴Vim一個tag名字中有效部分的字符個數。例:

include <stdio.h>

int very_long_variable_1;
int very_long_variable_2;
int very_long_variable_3;
int very_long_variable_4;
int main()
{
very_long_variable_4 = very_long_variable_1 *
very_long_variable_2;
}

對於上面這段代碼, 4個變量長度都為20, 如果將'taglength'設為10, 則:
   (ex command):tag very_long_variable_4

會匹配到4個tag,而不是1個,光標停留在very_long_variable_1所在行上,因為被搜索的tag部分只有前面的10個字符: "very_long_",相應的顯示是(是gvim中文版的真正顯示,不是翻譯的):
(Display)找到tag: 1/4 或更多

向vim注冊索引文件tags的路徑,
lingd@ubuntu:~/arm/linux-2.6.24.7$ vi ~/.vimrc
在打開文件的最后添加如下內容(當然,具體路徑根據你自己的情況)

"--ctags setting--
" 按下F5重新生成tag文件,並更新taglist
map :!ctags -R --c++-kinds=+p --fields=+iaS --extra=+q . :TlistUpdate
imap :!ctags -R --c++-kinds=+p --fields=+iaS --extra=+q . :TlistUpdate
set tags=tags
set tags+=./tags "add current directory's generated tags file
set tags+=~/arm/linux-2.6.24.7/tags "add new tags file(剛剛生成tags的路徑,在ctags -R 生成tags文件后,不要將tags移動到別的目錄,否則ctrl+]時,會提示找不到源碼文件)

set tags+=./tags表示在當前工作目錄下搜索tags文件
set tags+=/arm/linux-2.6.24.7/tags表示在搜尋tags文件的時候,也要搜尋/arm/linux-2.6.24.7/文件夾下的tags文件。
然后保存並退出vi。這樣,你就可以用vim在任意地方查看有關Linux的函數原形
------------------------------------
tag命令用法:
Ctrl+] 跳到當前光標下單詞的標簽
Ctrl+O 返回上一個標簽
Ctrl+T 返回上一個標簽
:tag TagName 跳到TagName標簽
以上命令是在當前窗口顯示標簽,當前窗口的文件替代為包標簽的文件,當前窗口光標跳到標簽位置。如果不希望在當前窗口顯示標簽,可以使用以下命令:
:stag TagName 新窗口顯示TagName標簽,光標跳到標簽處
Ctrl+W + ] 新窗口顯示當前光標下單詞的標簽,光標跳到標簽處
當一個標簽有多個匹配項時(函數 (或類中的方法) 被多次定義),":tags" 命令會跳轉到第一處。如果在當前文件中存在匹配,那它將會被首先使用。
可以用這些命令在各匹配的標簽間移動:
:tfirst 到第一個匹配
:[count]tprevious 向前 [count] 個匹配
:[count]tnext 向后 [count] 個匹配
:tlast 到最后一個匹配
或者使用以下命令選擇要跳轉到哪一個
:tselect TagName
輸入以上命令后,vim會為你展示一個選擇列表。然后你可以輸入要跳轉到的匹配代號 (在第一列)。其它列的信息可以讓你知道標簽在何處被定義過。
以下命令將在預覽窗口顯示標簽
:ptag TagName 預覽窗口顯示TagName標簽,光標跳到標簽處
Ctrl+W + } 預覽窗口顯示當前光標下單詞的標簽,光標跳到標簽處
:pclose 關閉預覽窗口
:pedit file.h 在預覽窗口中編輯文件file.h(在編輯頭文件時很有用)
:psearch atoi 查找當前文件和任何包含文件中的單詞並在預覽窗口中顯示匹配,在使用沒有標簽文件的庫函數時十分有用。

最簡單的使用方法舉例
用vi在任意目錄寫一個Test.c文件,內容如下:

int main(void)
{
printf("Hello World!\n");
return 0;
}

寫好后末行模式輸入w保存好(不要退出vi),按Esc回到指令模式,把光標停留在printf上
然后按 Ctrl+W + ],vi會自動跳到Linux系統函數printf()處,這時我們能查看printf()的原形
查看完了,按Ctrl+o(回到上一個標簽) 就回到原來的地方

參考http://blog.csdn.net/gangyanliang/article/details/6889860


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM