July 27, 2008

Emacs: Keyboard shortcuts for navigating code

July 27, 2008 - Categories: development, emacs

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."
    (skip-syntax-backward "w_")
     (lambda ()
       (skip-syntax-forward "w_")
(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."
  (let ((cur (point)))
    (skip-syntax-backward "w_")
     (if (re-search-backward (concat "\\_<" (regexp-quote (current-word)) "\\_>") nil t)
	 (match-beginning 0)

(defun sacha/search-word-forward ()
  "Find the next occurrance of the current word."
  (let ((cur (point)))
    (skip-syntax-forward "w_")
     (if (re-search-forward (concat "\\_<" (regexp-quote (current-word)) "\\_>") nil t)
	 (match-beginning 0)
(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!

Morning pages from Ottawa

July 27, 2008 - Categories: life

In one of her books, Julia Cameron suggested writing morning pages as a way to get one’s creative juices flowing – at least three pages of writing about whatever comes to mind. I still don’t know exactly how pages translate into blog posts, but I thought it would be nice to break up a string of Emacs-related posts with proof that I do, too, have a life. ;) (Well, a life outside Emacs, at least.)

That statement would probably have been more powerful had W- and I not chosen to skip the Ottawa Linux Symposium’s closing party in favor of working on our computers. He’s been trying to figure out how to get kubuntu up and running with all the usual IBM stuff, and I’ve been trying to figure out how to give Emacs all the features I like in Eclipse and more. So yes, that was what we were doing on a Saturday night: learning more about our tools while sharing a Killaloe sunrise beavertail. (No actual beavers harmed; it’s fried dough.)

We’re heading back to Toronto today. I’m looking forward to having oatmeal for breakfast and preparing a simple, Sacha-sized meal for lunch and dinner. A full week of eating out for every single meal has left me craving the kitchen. Yes, cooking takes more time, but I don’t have to decide between feeling guilty about leaving food on my plate and feeling satiated, I don’t have to take a chance on whether or not the restaurant will be closed (as was the case with many of our choices on Saturday evening), and I learn a lot more in the process. This is one of the reasons why I don’t like traveling for an extended period of time. I miss the kitchen. I miss turning meals from just fuel or explorations of tastes into experiments that’ll teach me something interesting along the way.

And yes, I’m also looking forward to getting back to the gym. It’s funny how you miss it. I’ve been making up with lots of walking, but still. I miss what I feel like after a good workout: bedraggled but triumphant.

I’ve enjoyed my brief stay in Ottawa, though. I didn’t get to meet the people I had been looking forward to seeing, but I did go to some of the birds-of-a-feather get-togethers. I checked out the Sparks pedestrian mall, the Rideau Centre, the Bytown museum, and I saw the cat sanctuary near Parliament (awwww!). I figured out some of the pieces that I’ll need to improve if I want to telecommute effectively from the Philippines. VOIP is a big one, and a backup calling card won’t hurt, either. I also need to figure out the timezone thing. =)

It’s been a good trip.

Eclipse to Emacs: Navigating your source tree

July 27, 2008 - Categories: emacs

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: "
                                           (lambda (x)
                                             (car x))
  (let* ((record (assoc file file-cache-alist)))
      (if (= (length record) 2)
          (car (cdr record))
         (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)