How to use ctags in Emacs effectively

  |   Source

CREATED: <2014-03-11 Tue>

UPDATED: <2020-05-26 Tue>

Exuberant Ctags is used for code completion and code navigation. It supports many programming languages.

Please read EmacsWiki for basic usage.

Ctags creates an index file named "TAGS". The path of TAGS is stored in a Emacs variable tags-table-list.

Example of tags-table-list:

(setq tags-table-list '("~/wxWidgets-master/TAGS" "~/projs/Loris/src/desktop/TAGS"))

When running M-x find-tag, the tag files in tags-table-list is read to find the definition of the symbol under cursor.

Here is my strategy to manage TAGS automatically:

  • I hard coded path of TAGS in .emacs because I don't change project path frequently
  • In major mode hook like c++-mode-hook or js2-mode-hook I check the path current file. If it matches project name, the file is part of the project
  • TAGS for the project is created
  • When saving the file, update TAGS by the value of tags-table-list

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 ()
  "Check the tags in tags-table-list and re-create it."
  (interactive)
  (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: Ctags is discussed at Google Plus. Kaushal Modi recommended three Emacs plugins:

I tried them. My code has same features as ctags-update and etags-table. I prefer my own code because it's simpler and controllable.

But I do like etags-select, it provides better UI for finding tag and I will use it from now on.

UPDATE: I have developed my own ctags solution for Emacs,

Comments powered by Disqus