Tag Archives: lisp

Indent with tabs, align with spaces

There have been countless discussions on whether you should use tabs or spaces to indent your code. Until recently I have been a fan of spaces. But why settle with spaces OR tabs? After some discussions with my good friend Ali some time ago, I agreed with myself that I actually prefer mixing tabs and spaces. The idea is of course to get the best of both worlds: indenting with tabs in order to respect each reader’s/developer’s preferred indentation width, and to align with spaces when expressions expand over multiple lines in order to ensure readability for different tab widths. The image below illustrates where I use tabs and where I uses spaces. This way the source code will stay readable even if it’s written with tabs of width 2 and read in an environment that has tabs width of 8.

Indent with tabs, align with spaces

I assume this is nothing new, and hopefully there is a lot of persons out there using this style. However, I was not able to find editors that can indent the code like this automatically nor any extensions to Emacs that does this. So I sat down and wrote a Lisp snippet in order to make Emacs automatically fill in spaces and tabs just the way I want it upon indentation. It uses functions from the c-mode to help to decide where to put spaces and tabs, so I hope it works for other formatting styles than just mine. (Sorry for the poor syntax highlighting, but there seems to be no support for lisp code.)

;; See http://www.gnu.org/software/emacs/manual/html_node/ccmode/Syntactic-Symbols.html
(defvar c-elements-to-align-with-spaces
  (list 'func-decl-cont
        'topmost-intro-cont
        'arglist-cont
        'arglist-cont-nonempty
        'statement-cont
        'c
        'inher-cont
        'member-init-cont
        'template-args-cont
        'objc-method-args-cont
        'objc-method-call-cont)
  "List of syntactic elements that should be aligned with spaces.
If you find an element you want to align with spaces but is not handled here,
find the syntactic element with C-c C-s or M-x c-show-syntactic-information
and simply add it to the list.")

(defun c-context-continuation-p (context)
  "Returns t if the given context is part of a continuation, i.e.
it should be aligned with spaces. The syntactic elements defined
as being a part of a continuation is defined by the variable
c-elements-to-align-with-spaces."
  (let ((continuation nil))
    (dolist (elem c-elements-to-align-with-spaces continuation)
      (when (assq elem context)
        (setq continuation t)))))

(defun c-indent-align-with-spaces-hook ()
  "If indent-tabs-mode is nil this function does nothing. If
indent-tabs-mode is enabled and if current indentation is an
alignment operation, this function will format the line so that
tabs are used until the indent level of the previous line and use
spaces for the rest (the aligment)."
  (interactive)
  (when indent-tabs-mode
    (let ((context c-syntactic-context)
          (curr-indent (current-indentation))
          (base-indent nil))
      (when (c-context-continuation-p context)
        (save-excursion
          ;; Find indentation of nearest not-continuation context
          (do ()
              ((not (c-context-continuation-p context)))
            (goto-char (c-langelem-pos (car context)))
            (setq context (c-guess-basic-syntax)))
          (setq base-indent (current-indentation)))
        ;; Untabify region between base indent and current indent
        (let ((end (point)))
          (save-excursion
            (while (> (current-column) base-indent)
              (backward-char))
            (untabify (point) end)))
        ;; We might need to adjust the marker to a more correct/practical
        ;; position.
        (when (= (current-column) base-indent)
          (back-to-indentation))))))

;; Activate the new indentation style
(setq c-special-indent-hook nil)
(add-hook 'c-special-indent-hook 'c-indent-align-with-spaces-hook)
Advertisements