- ta[g][!] tagstring 跳轉到tagstring定義處,其中!表示在文件更改沒保存時進行強制跳轉
- tags 查看跳轉記錄,並用'>'指出當前位置
- [n]ta[g][!] 跳轉到相對當前位置的前第n個tag,n默認為1
- [n]po[p][!] 跳轉到相對當前位置的后第n個tag,n默認為1
- ts[elect][!] [tagstring] 顯示與tagstring的匹配數
- sts[elect][!] [tagstring] 顯示與tagstring的匹配數,選中時以子窗口的形式打開
- [n]tn[ext][!] 跳轉到相對后n個匹配處
- [n]tp[revious][!] 跳轉到相對前n個匹配處
- [n]tr[ewind][!] 跳轉到第n個匹配處
- tl[ast][!] 跳轉到一個匹配處
一. ctags是干什么的
ctags的功能:掃描指定的源文件,找出其中所包含的語法元素,並將找到的相關內容記錄下來。
我用的是Exuberant Ctags,在Windows上使用,就一個可執行文件,非常綠色,可在sourceforge下載。
二. 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
命令太長了,折成兩行了,可以考慮把命令的各個參數寫到文件里去了(具體做法就不談了)。
1.
-R
表示掃描當前目錄及所有子目錄(遞歸向下)中的源文件。並不是所有文件ctags都會掃描,如果用戶沒有特別指明,則ctags根據文件的擴展名來決定是否要掃描該文件——如果ctags可以根據文件的擴展名可以判斷出該文件所使用的語言,則ctags會掃描該文件。
2.
--languages=c++
只掃描文件內容判定為c++的文件——即ctags觀察文件擴展名,如果擴展名對應c++,則掃描該文件。反之如果某個文件叫aaa.py(python文件),則該文件不會被掃描。
3.
--langmap=c++:+.inl
告知ctags,以inl為擴展名的文件是c++語言寫的,在加之上述2中的選項,即要求ctags以c++語法掃描以inl為擴展名的文件。
4.
-h +.inl
告知ctags,把以inl為擴展名的文件看作是頭文件的一種(inl文件中放的是inline函數的定義,本來就是為了被include的)。這樣ctags在掃描inl文件時,就算里面有static的全局變量,ctags在記錄時也不會標明說該變量是局限於本文件的(見第一節描述)。
5.
--c++-kinds=+px
記錄類型為函數聲明和前向聲明的語法元素(見第三節)。
6.
--fields=+aiKSz
控制記錄的內容(見第四節)。
7.
--extra=+q
讓ctags額外記錄一些東西(見第四、五節)。
8.
--exclude=lex.yy.cc --exclude=copy_lex.yy.cc
告知ctags不要掃描名字是這樣的文件。還可以控制ctags不要掃描指定目錄,這里就不細說了。