2012/05/10: Indentation in emacs

One of the convenient features of emacs is its support of indentation. For most common languages it comes with the standard installation, but the actual flexibility comes from the fact that you can extend it yourself. And, there's not much to know; essentially, you only have to remember that indentation (as, e.g., done bei the tab-key) is done by calling indent-line-function. So setting an appropriate (buffer local) value suffices.

So, let's do an example. Say, you want support for indentation of pseudo-code looking something like the following.


fun gcd(a,b)
  
  if a == b
    return a
  else
    
    // substract the smaller number
    // from the bigger  
    if a < b
      return gcd(a, b-a)
    else
      return gcd(b, a-b)
    fi
  
  fi
nuf

The task is, as to define a function that correctly indents the current line. The actual indenting can be done by indent-line-to, hence we only need to compute what the correct indentation should be.

There are several ways of doing this. The one I personally prefer is to assume that everything up to the current line, hence, in particular the previous line, is already indented correctly. Starting from the indentation of the previous line, which can be obtained as (current-indentation) after moving to it with (forward-line -1) it is only a small offset to add (or subtract).

Looking back only as little as necessary keeps auto-indentation still useful, even if at some point in the document you want to defer from the usual indentation. Usually, we care more about indentation relative to neighbouring lines, than in absolute numbers.

The full code looks as following.



(defvar pseudocode-indent 2
  "Standard indentation for pseudo code")

(defvar pseudocode-emptyline
  "^\\s-*$"
  "Regular expression matching an empty line")

(defvar pseudocode-opening
  "^\\s-*\\(fun\\|if\\|else\\)"
  "Regular expression matching lines starting an indentation level in pseudo code")

(defvar pseudocode-closing
  "^\\s-*\\(nuf\\|fi\\|else\\)"
  "Regular expression matching lines ending an indentation level in pseudo code")

(defun pseudocode-indent-line ()
  "Indent current line according to pseudo code style"
  (interactive)
  (save-excursion
    (indent-line-to
     (max
      0
      (catch 'indent
        (save-excursion
          (beginning-of-line)
          (if (bobp) (throw 'indent 0))
          (let ((outdent (if (looking-at pseudocode-closing)
                             (- pseudocode-indent) 
                           0)))
            (forward-line -1)
            (while (looking-at pseudocode-emptyline)
              (if (bobp) (throw 'indent 0))
              (forward-line -1))
            (if (looking-at pseudocode-opening)
                (throw 'indent (+ (current-indentation) pseudocode-indent outdent)))
            (throw 'indent (+ (current-indentation) outdent)))))))))
            
                             
(defun pseudocode-mode ()
  "Mode for editing pseudo-code files (incomplete, for demonstrating indentation only)"
  (interactive)
  (set (make-local-variable 'indent-line-function) 'pseudocode-indent-line))
download

There are a few subtle points.