How to use ctags in Emacs effectively
Exuberant Ctags is a code navigation tool. It supports many language and could be integrated into Emacs well.
Please read EmacsWiki for basic usage.
I will talk about how I manage my ctags.
Basically ctags will produce a index file with file name TAGS. The full path of TAGS will be stored in a global list "tags-table-list".
An example of tags-table-list:
(setq tags-table-list '("~/wxWidgets-master/TAGS" "~/projs/Loris/src/desktop/TAGS"))
Every time we you "M-x find-tag", the TAGS file in above list will be read from the scratch to locate the definition of the symbol under cursor.
Here is my strategy to manage TAGS automatically:
- I hard coded full path of TAGS in .emacs because I usually don't change project path.
- In major mode hook like c++-mode-hook or js2-mode-hook I will check the directory path of current file. If it contains certain string, I suppose the file belong to certain project.
- Then I will create TAGS for that project if needed
- Every time when I save the file, I may update TAGS according to the value of tags-table-list.
Here is the code:
(defun my-project-name-contains-substring (REGEX) (let ((dir (if (buffer-file-name) (file-name-directory (buffer-file-name)) ""))) (string-match-p REGEX dir))) (defun my-create-tags-if-needed (SRC-DIR &optional FORCE) "return the full path of tags file" (let ((dir (file-name-as-directory (file-truename SRC-DIR)) ) file) (setq file (concat dir "TAGS")) (when (or FORCE (not (file-exists-p file))) (message "Creating TAGS in %s ..." dir) (shell-command (format "ctags -f %s -e -R %s" file dir)) ) file )) (defvar my-tags-updated-time nil) (defun my-update-tags () (interactive) "check the tags in tags-table-list and re-create it" (dolist (tag tags-table-list) (my-create-tags-if-needed (file-name-directory tag) t) )) (defun my-auto-update-tags-when-save () (interactive) (cond ((not my-tags-updated-time) (setq my-tags-updated-time (current-time))) ((< (- (float-time (current-time)) (float-time my-tags-updated-time)) 300) ;; < 300 seconds ;; do nothing ) (t (setq my-tags-updated-time (current-time)) (my-update-tags) (message "updated tags after %d seconds." (- (float-time (current-time)) (float-time my-tags-updated-time))) ) )) (defun my-setup-develop-environment () (when (my-project-name-contains-substring "Loris") (cond ((my-project-name-contains-substring "src/desktop") ;; C++ project don't need html tags (setq tags-table-list (list (my-create-tags-if-needed (concat (file-name-as-directory (getenv "WXWIN")) "include")) (my-create-tags-if-needed "~/projs/Loris/loris/src/desktop"))) ) ((my-project-name-contains-substring "src/html") ;; html project donot need C++ tags (setq tags-table-list (list (my-create-tags-if-needed "~/projs/Loris/loris/src/html"))) )))) (add-hook 'after-save-hook 'my-auto-update-tags-when-save) (add-hook 'js2-mode-hook 'my-setup-develop-environment) (add-hook 'web-mode-hook 'my-setup-develop-environment) (add-hook 'c++-mode-hook 'my-setup-develop-environment) (add-hook 'c-mode-hook 'my-setup-develop-environment)
UPDATE: There is some discussion at Google Plus about using ctags. Kaushal Modi recommended three emacs plugins:
I tried these three plugins. ctags-update and etags-table duplicate my above elisp code. I prefer my own code because it's simpler and totally controllable. For example, the fact that I need only care about only one global variable tags-table-list makes my code shorter.
But I do like etags-select, it provide better UI for finding tag and I will use it from now on.