dot-emacs: smarter indentation with tabs and spaces
In XMMS2, the coding guidelines require you to use a mix of tabs and spaces to indent code: tabs indent blocks, while spaces are used to align multi-line statements. It’s all very natural really:
[tab][tab][tab]foo_function (arg1, [tab][tab][tab] arg2, [tab][tab][tab] arg3)
Unfortunately, if you use the default C indentation mode in emacs, what you get (when breaking like with C-j or RET TAB) is the following:
[tab][tab][tab]foo_function (arg1, [tab][tab][tab][tab][tab] arg2, [tab][tab][tab][tab][tab] arg3)
So you mean to tweak the code yourself to fix the tabs. Tedious, annoying. We know better!
I wrote a hook to fix this. The idea is to fix tabs when we’re in an argument list context. It can probably be further improved (please send patches!), but it’s quite useful already.
Just add the following to your .emacs file.
"C mode with adjusted defaults for use with the xmms2."
(interactive)
(c-mode)
(c-set-style "K&R")
(setq tab-width 4)
(setq indent-tabs-mode t)
(setq c-basic-offset 4)
; Align closing paren with opening paren
(c-set-offset ‘arglist-close ‘c-lineup-arglist-intro-after-paren)
(add-hook ‘c-special-indent-hook ’smart-tab-indent-hook))
(defun get-nonempty-context ()
(let ((curr-context (car (c-guess-basic-syntax))))
(if (or (eq (car curr-context) ‘arglist-intro)
(eq (car curr-context) ‘arglist-cont)
(eq (car curr-context) ‘arglist-cont-nonempty)
(eq (car curr-context) ‘arglist-close))
curr-context
nil)))
(defun smart-tab-indent-hook ()
"Fixes indentation to pad with spaces in arglists."
(let ((nonempty-ctx (get-nonempty-context)))
(if nonempty-ctx
(let ((tabbed-columns (+ (point-at-bol)
(/ (c-langelem-col nonempty-ctx t)
tab-width)))
(orig-column (current-column)))
(tabify (point-at-bol) tabbed-columns)
(untabify tabbed-columns (point-at-eol))
; editing tabs screws the pointer position
(move-to-column orig-column)))))
Note: don’t forget the
bit, else it won’t work properly for the last argument before the closing parenthesis. And avoid using
to break lines, it can fail with this hook; use
instead, it’s smarter anyway!
You can follow any responses to this entry through the RSS 2.0 feed. You can leave a response, or trackback from your own site.
July 9, 2009 at 13:42
A more general method is to temporarily change the “tab width” and the “indentation offset” to a very large value while indenting (e.g., via advice). This works because (1) setting the tab width and indentation offset to the same value ensures that all “instances” of the offset are encoded as tabs, (2) using a value that is larger than any amount of alignment forces spaces to be used for the rest.
As the example code below shows, the method doesn’t require us to know very much about the language itself.
(defadvice c-indent-line (around smart-tabs activate)
(setq tab-width tab-width)
(let ((indent-tabs-mode t)
(tab-width fill-column)
(c-basic-offset fill-column))
ad-do-it))
For a different language, say Perl, just replace “c-indent-line” with “cperl-indent-line” and “c-basic-offset” with “cperl-indent-level”. See http://www.emacswiki.org/emacs/SmartTabs for more languages.