I periodically try to use TAGS to navigate round my code in emacs. When it works, it is very convenient. I have not been using them lately, partly because generating the tags and keeping them up-to-date has always been a bit fiddly. In the past, I have tried to get emacs to automatically regenerate my tags when I save changes to a file. I have had solutions that work to a greater or lesser extent, but they are always a bit unsatisfying. Inevitably, I end up having to write an extra shell script to generate the tags. Usually, I need a script because I often work in projects that span more than one repository or cabal project. The simple solutions I have seen for tag management all assume that all of your code lives under one project root.
This time, given that I will probably require some kind of shell
script anyway, I decided to try a more UNIX-y approach. My tag
generation script now uses inotifywait
(from
inotify-tools) to
watch for filesystem changes and rebuild my tags when necessary:
#!/bin/bash
DIRS="*/src"
EVENTS="-e close_write -e move"
inotifywait ${EVENTS} -mr ${DIRS} | while read event ; do
hasktags -e -o TAGS ${DIRS}
done;
This says: whenever a file under src
in my repository is closed
(after some writes) or moved, run
hasktags to rebuild the TAGS
file. So far, it works remarkably well and emacs does not need to
know anything about the process. inotifywait
only works on Linux; I
am still looking for a corresponding utility for OS X.
While this approach produces a nice TAGS file, I still have a small
problem with the find-tag
function in emacs. This function lets you
jump to the tag for the identifier under the cursor. The default
function does not know anything about the qualified module imports
typical of Haskell code, and includes the module qualifier in the
suggested tag name. This does not work, because the TAGS file does
not record the qualifier (and it cannot do so, since you can choose a
different qualifier each time you import the module). You can always
type in whatever name you want when using find-tag
, but having the
default chosen by find-tag
would be ideal (and faster). To get
around this, I stole and adapted a bit of code from clojure-mode that
addresses this very same problem:
(defun find-tag-without-module (next-p)
(interactive "P")
(find-tag (first (last (split-string (symbol-name (symbol-at-point)) "\\.")))
next-p))
(eval-after-load "haskell-mode"
'(progn
(define-key haskell-mode-map (kbd "M-.") 'find-tag-without-module)))
So far, everything is working well.