Eclipse to Emacs: Navigating your source tree

Two other things I like about the Eclipse development environment are the ability to jump to a function definition and the ability to open any resource in the workspace. Fortunately, these shortcuts are easy to duplicate in Emacs.

Exuberant Ctags is a utility that builds an index of the function definitions in your source code. You can use this index to jump to any function definition using editors such as vi or emacs. To index your Drupal source code, for example, go to the root of your source directory and use a command like this:

find . -name \*.module -o -name \*.php -o -name \*.inc -o -name \*.install -o -name \*.engine -o -name \*.profile | etags -l php -

To use this index in Emacs, add the following code to your ~/.emacs, changing drupal-project-path as necessary:

(defvar drupal-project-path "~/proj/example" "*Base path for your project")

(require 'etags)
(setq tags-file-name (expand-file-name "TAGS" drupal-project-path))

Evaluate the code. You can then use M-. (find-tag) to jump to the declaration of a function in your project.

To open any resource in your source tree with a few keystrokes, index the files with filecache and use ido to open the file. Ido is well worth learning how to use. Here’s the code I use, taken almost directly from the filecache documentation:

(require 'filecache)
(require 'ido)
(defun file-cache-ido-find-file (file)
  "Using ido, interactively open file from file cache'.
First select a file, matched using ido-switch-buffer against the contents
in `file-cache-alist'. If the file exist in more than one
directory, select directory. Lastly the file is opened."
  (interactive (list (file-cache-ido-read "File: "
                                          (mapcar
                                           (lambda (x)
                                             (car x))
                                           file-cache-alist))))
  (let* ((record (assoc file file-cache-alist)))
    (find-file
     (expand-file-name
      file
      (if (= (length record) 2)
          (car (cdr record))
        (file-cache-ido-read
         (format "Find %s in dir: " file) (cdr record)))))))

(defun file-cache-ido-read (prompt choices)
  (let ((ido-make-buffer-list-hook
	 (lambda ()
	   (setq ido-temp-list choices))))
    (ido-read-buffer prompt)))

(ido-mode t)
;; Change this to filter out your version control files
(add-to-list 'file-cache-filter-regexps "\\.svn-base$")
(if drupal-project-path
    (file-cache-add-directory-using-find drupal-project-path))

(global-set-key (kbd "ESC ESC f") 'file-cache-ido-find-file)

This turns ESC ESC f into a handy shortcut for finding files anywhere in your project tree. Read the source code (ido.el) for more information on ido shortcuts.

Good luck and have fun!

(UPDATE: Added “.” to the find command – two people suggested it! =) )
(UPDATE: Forced etags to detect files as php and added .engine and .profile to the list of extensions)
(UPDATE: Added version control filter for file-cache)

2 Pingbacks/Trackbacks

28 responses to “Eclipse to Emacs: Navigating your source tree”

  1. zzkx says:

    it’s probably just a typo, but you should have a pathname before the expression in ‘find’. e.g. ‘find ~/ -name …’

    1. Sacha Chua says:

      The path is not required with the find command on GNU/Linux, I think – or at least the one that I’ve got installed. =) I should put that in, though, as other systems require it. Thanks!

  2. Nick says:

    It is all right, thank you, but how do I navigate back and forth?
    Say I was in file BfromA.cpp then went to spec of the class in BfromA.hpp by find-tag
    and then to the file A.hpp to the father class, again by find-tag.

    Is there any way to go “back” to BfromA.cpp without mentioning it explicitly – just go “back”
    and then again go “forward” to A.hpp explicitly?

    Thank you very much

    1. marcin says:

      I use M-. with interactive arg (C-u) to find next tag and `pop-tag-mark’ (bound to M-* by default) to get back to previous tag.

  3. Eric Larson says:

    @Nick

    When you go to the other file after using the find-tag, you end up switching buffers. This means the last buffer is simply a C-x b RET away. Unfortunately, it also means that if you go to another definition, then you have to return to the buffer more explicitly.

    With that said, hopefully Sacha will have a good answer to that question :)

    FWIW, I have just started using the etags and find-tag feature, and it is very helpful. It was also extremely fast adding the tag index, which was a pleasant surprise.

    Great post!

  4. Vagn says:

    The same can be achieved with my vps package

    http://ozymandias.dk/emacs/emacs.html#vps

  5. Nick says:

    @Eric
    Hi!
    I know this way, but it falls into “explicit” navigation.
    I think there should be some way to hook find-tag with emacs bookmarks and then
    it is possible to navigate through them.

  6. Voyager says:

    After find-tag you can go back with pop-tag-mark bound to M-* usually. I don’t know of a way to go forwards again other than by M-. on the same keyword. I use etags-select-find-tag-at-point instead of find-tag:
    ___
    M-. runs the command etags-select-find-tag-at-point
    which is an interactive Lisp function in `etags-select.el’.
    It is bound to M-..
    (etags-select-find-tag-at-point)

    Do a find-tag-at-point, and display all exact matches. If only one match is
    found, see the `etags-select-no-select-for-one-match’ variable to decide what
    to do.
    ___

    I also find tags-apropos quite useful.

  7. Rene says:

    You can go back with M-*. There is no forward command, so you use M-. go to the same function again.

  8. Hi Sacha,

    I am absolutely certain that any geek reading this will be using some form of version control in their source trees…. at work I use Subversion and to exclude all the junk svn files ‘find’ will pull in I added the following before the file-cache find command:

    (add-to-list ‘file-cache-filter-regexps “\\.svn-base$”)

    Regards

    Arjen

  9. Sacha Chua says:

    Arjen: Thanks for pointing that out! I ended up adding that line to my ~/.emacs before I read your comment. Much nicer. Now if I can just get it to sort by frequency of access… ;)

  10. I tried the command:
    find -name \*.module -o -name \*.php -o -name \*.inc -o -name \*.install | etags –

    and I get an error:
    etags: Unknown option –

    Was the command truncated from the post or something? what’s the parameter?

    Is there a way to automate the etags generation from within emacs so I don’t have to remember this command every time I start a project?

    1. Sacha Chua says:

      There’s an etags that comes with Emacs, and there’s an etags which comes with Exuberant Ctags. I’m using the one from Exuberant CTags. Don’t know if that makes a difference…

      1. fixed it!
        In my version of exhuberant Ctags i would have to use the command like this:

        find . -name \*.module -o -name \*.php -o -name \*.inc -o -name \*.install | etags -L -

        1. Sacha Chua says:

          With the Exuberant CTags from Sourceforge, I just found out that you should probably make it etags -l php -. =)

        2. With the Exuberant CTags from the Ubuntu Hardy repositories the command is:
          etags –language-force=php -L -

  11. There is actually an error in the post.

    find needs a first argument that says where to look for these files:

    find -name \*.module -o -name \*.php -o -name \*.inc -o -name \*.install | etags –

    should be

    find . -name \*.module -o -name \*.php -o -name \*.inc -o -name \*.install | etags –

    notice the dot between find and the -name,

    1. Sacha Chua says:

      <laugh> Okay, everyone else has a find that requires a path… Updated!

  12. You can also enable flymake to check your PHP source code for errors as you type it, I posted the elisp code for that on my blog:

    http://blog.nethazard.net/2008/07/31/php-syntax-check-as-you-type-with-emacs/

    1. Sacha Chua says:

      Totally! Next, I’m going to go into more detail about Xdebug and GEBEN…

  13. Jason says:

    etags isn’t finding my class methods in my php files. Do you have a regex that you use to find the public class methods?

  14. Val says:

    I’ve got a problem: I use php 5 and I think etags supports only php4. Bad :(

  15. yinglcs says:

    Hi,

    I following the above instruction.
    But when I enable filecache adn ido in .emacs, my emacs goes to 100% for > 5 minutes, for some reason. I have to kill the email using System monitor.

    Can you please tell em how to fix it? I am running on ubuntu.

    1. Sacha Chua says:

      Do you think that you might have some symbolic links that cause a loop? That’s the first thing I think of…

  16. richard says:

    There is some confusion between etags and http://ctags.sourceforge.net/

    or at least they are not the same in Debian.

    I generate my tags using something like

    SRC=~/.emacs.d
    cd $SRC
    ctags-exuberant -e –recurse=yes –links=yes –verbose=no

    No need for find.

    And unlike etags it supports recursion in the one command.

  17. Paul Hobbs says:

    I’d rather use Anything to find files using filecache… that way I also have bookmarks and recent files available from the same command. I bound anything to C-‘ , which is ergonomic on dvorak and qwerty.

    (http://www.emacswiki.org/emacs/Anything)

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>