Emacs: Tab Width Woes

Emacs uses the value of the tab-width variable to decide how wide tab characters should be displayed. Setting tab-width is usually one of the first customizations Emacs newbies make:

(setq-default tab-width 2)

Displaying tab characters at whatever width you prefer works well for most applications but eventually fails when files mix tab and space characters and (invariably) make assumptions about what the width of those characters will be.

One particularly conspicuous offender is the Emacs (Lisp) source code. I feel like I’ve recently reached a new level of skill with Emacs–a point where I’m finally effective enough with it that I write Emacs Lisp to improve my workflow as a regular part of my daily workflow. One consequence of this is that I spend a lot of time reading Emacs source code, looking for either implementation clues or undocumented gems. It’s a hell of a lot of fun but unfortunately it means that I spend a lot of time alternating between my preferred tab-width of 2 and 8, the size the Emacs source thinks a tab should be.

My initial solution was to throw together a quick function to toggle between these two values, which I bound to C-c TAB:

(defun camdez/toggle-tab-width ()
  "Toggles `tab-width' between 8 and 2."
  (interactive)
  (setq tab-width
        (if (= tab-width 2)
            8
          2))
  (message "Tab width set to %d" tab-width))

It could definitely use a lot of improvement but that’s the kind of thing that I now tend to hammer out quickly and improve later if the concept survives the prototyping phase. I keep this type of hacks in a file called experimental.el which I load from my Emacs initialization file. I also keep this file in the x register so that I can immediately jump to it with C-x r j x:

(load-library "experimental")
(set-register ?x (cons 'file "~/.emacs.d/experimental.el"))

But I was never thrilled about the idea of having to manually toggle that value, even if it was a couple keystrokes away. I started scheming about ways to analyze source files to determine what tab-width they implied (certainly possible–not a lot of fun) when I realized that the Emacs source was really the only place where I was having this problem.

My next thought, then, was to create a hook that would fire whenever a file was opened, examine the file path, and set the tab-width to 8 if it was part of the Emacs distribution. But hardcoding a file path? That’s a bit distasteful.

I realized today that there’s a wonderfully effective, simple solution called “Directory Variables”. All you have to do is put a file called dir-locals.el in a directory and whenever Emacs visits a file in that directory (or one of it’s subdirectories), those local variables will be loaded and applied. In fact, there’s even a command that will build the file contents for us. Here’s how we can set it up for the Emacs source:

  1. Open a file in the Emacs lisp directory. The easiest way to this is pull up the documentation for a function in the standard distribution (say, occur) with C-h f occur RET, tab to the file name it says it’s defined in, and hit RET to jump to that function’s definition.
  2. Next, run M-x add-dir-local-variable RET.
  3. Hit RET to accept the default of only applying this variable to emacs-lisp-mode.
  4. Enter tab-width as the variable to set.
  5. Enter 8 as the value of the variable.
  6. You’ll now be looking at a new file with contents like this:
;;; Directory Local Variables
;;; See Info node `(emacs) Directory Variables' for more information.

((emacs-lisp-mode
  (tab-width . 8)))

Save the file with C-x C-s and you’re good to go. Now whenever you open a file in the Emacs source code it’ll have the tab-width properly set to 8.