Emacs: Keyboard shortcuts for navigating code

UPDATE 2014-01-21: Add regexp-quote around current-word.

One of the lines on my list of things I can do in order to make progress on my book is to move my Drupal development environment from Eclipse to Emacs, as immersion would no doubt give me plenty of things to tweak and describe. When you use something every day, you notice the rough edges. With Emacs, you can sand those edges down. I already use Emacs every day, but I had been doing most of my development work in Eclipse because Eclipse packaged a number of useful features I wanted. If I can move my environment to Emacs, though, I’ll be able to customize it a lot more freely.

Take something as simple as navigation, for example. In Eclipse, I can hit Ctrl+K to search for the next instance of the current word, which is a handy way to look for function calls or definitions in the same file. How would you do that in Emacs? The built-in search functions allow me to take text from the buffer, but I wanted something even faster. Here are some of the ways you can tweak navigation, too.

If you haven’t tried it yet, you’ll probably like interactive search (C-s) because you can modify the search and see the results as you type. All you need to do to make it better than Eclipse’s Ctrl+K is to add a function to grab the current word, even if the point in the middle of the word. Add the following code to your ~/.emacs:

(defun sacha/isearch-yank-current-word ()
  "Pull current word from buffer into search string."
  (interactive)
  (save-excursion
    (skip-syntax-backward "w_")
    (isearch-yank-internal
     (lambda ()
       (skip-syntax-forward "w_")
       (point)))))
(define-key isearch-mode-map (kbd "C-x") 'sacha/isearch-yank-current-word)

Type C-s (isearch-forward) to start interactively searching forward, and type C-x to get the current word. Use C-s and C-r to search forward and backward. You can modify your search, too.

Want to make it even faster? Use these functions to bind similar searches to shortcut keys:

(defun sacha/search-word-backward ()
  "Find the previous occurrence of the current word."
  (interactive)
  (let ((cur (point)))
    (skip-syntax-backward "w_")
    (goto-char
     (if (re-search-backward (concat "\\_<" (regexp-quote (current-word)) "\\_>") nil t)
	 (match-beginning 0)
       cur))))

(defun sacha/search-word-forward ()
  "Find the next occurrance of the current word."
  (interactive)
  (let ((cur (point)))
    (skip-syntax-forward "w_")
    (goto-char
     (if (re-search-forward (concat "\\_<" (regexp-quote (current-word)) "\\_>") nil t)
	 (match-beginning 0)
       cur))))
(global-set-key '[M-up] 'sacha/search-word-backward)
(global-set-key '[M-down] 'sacha/search-word-forward)

Feel free to change the keybindings or otherwise improve the code. =) Good luck and have fun!

  • Jason

    Great one. Been an (x)emacs user for years but just kind of settled into my grind quite a few years ago – your blog is making me play with emacs again.

  • rodrigo

    How about C-w while searching, it calls isearch-yank-word-or-char, isn’t that the same?

    • http://sachachua.com Sacha Chua

      It yanks forward, so if you’re in the middle of a word, it only grabs the part that’s in front of the point. =) Mine grabs the entire word, saving you a M-b (backward-word) before you start searching.

  • Cezar

    Very nice !

    How about highlighting the search term in the last two function sacha/search-word-forward and sacha/search-word-backward.

    Cezar

  • http://yassinechaouche.thecoderblogs.com y.chaouche

    Or M-. (you need to generate the tags table first with etags)

  • Elias Pipping

    Hi,

    I use the search-word-* functions all day, thanks a lot for that :)

    There’s only one problem: If you’re in a lisp buffer, variables can contain asterisks,
    and the variable var is different from *var*. If you use

    (re-search-* (concat “\_”)

    where (current-word) returns “*var*”, the asterisks are interpreted by re-search-*…
    As a consequence, search-forward-* will happily jump from *var* to var but not vice versa.

    I don’t know if emacs has a general way of sanitising strings for regex functions (it certainly should). For now,

    https://gist.github.com/pipping/8547615

    addresses this particular situation.

    • http://sachachua.com sachac

      Oh, use (regexp-quote (current-word)) instead of (current-word). =)

      • Elias Pipping

        In retrospect, that seems rather obvious… :)