Whitespace Cleanup on Diff Lines in Emacs
Some of us are particular about whitespace, especially in files under
version control (PS: you should be using version control,
e.g. git). If you’re like me and you use
emacs, then you can often use
whitespace-cleanup (from
whitespace.el) to keep your
source files pristine.
The trouble comes if a collaborator doesn’t respect your whitespace
wishes. If you just run whitespace-cleanup on your entire buffer,
your revision history and git blame will be useless.
So far, my solution is to sanitize the whitespace only on the lines
that I’m editing. I was doing this by hand, but since I use emacs,
it was time to automate things and learn some rudimentary
elisp in the process.
Without further ado:
(defun buffer-file-git-diff-regions () ""
(or (magit-git-dir) (error "Dir NOT in a git repo: %s" default-directory))
(let ((file (buffer-file-name)))
(or file (error "Buffer \"%s\" does not have a file associated to it" file))
(let* ((command (concat "git diff -U0 " (shell-quote-argument file) "| grep \"^@@\" | cut -d' ' -f3 | tr +, ' '"))
(output (shell-command-to-string command))
(lines (split-string output "\n" t))
(line-pair-helper (lambda (x) (if (not (cadr x)) (list (car x) 1) x)))
(line-maybe-pairs (mapcar (lambda (x) (mapcar 'string-to-number (split-string x " " t))) lines)))
(mapcar line-pair-helper line-maybe-pairs))))
(defun buffer-file-git-diff-regions-apply (func) ""
(interactive "aFunction to apply to dirty regions: ")
(save-excursion
(dolist (line-len (buffer-file-git-diff-regions))
(goto-char (point-min))
(forward-line (1- (car line-len)))
(push-mark)
(forward-line (cadr line-len))
(funcall func (region-beginning) (region-end))
(pop-mark))))
(defun whitespace-cleanup-git-diff-regions () ""
(interactive)
(buffer-file-git-diff-regions-apply 'whitespace-cleanup-region))
The end-user command is whitespace-cleanup-git-diff-regions, which
you can run interactively from a file that’s under git control. Though you can also
use the general function buffer-file-git-diff-regions-apply to run
any function of the type (function region-beginning region-end) on
all the “dirty” regions.
This uses magit, but just to test if we are in a
directory under git control. You could swap this out if desired.
The magic command-line incantation
git diff -U0 <filename> | grep "^@@" | cut -d' ' -f3 | tr +, ' '
will generate a newline-delimited list of “dirty” regions of the form
" lineno numlines". I then run whitespace-cleanup-region on each
of these regions, and we’re done!
So I’ve learned a bit of elisp, and potentially saved my future self
entire minutes of editing. I hope somebody else benefits, too!
Sorry about the lack of documentation. Use at your own risk (which is
basically zilch, because you use git, right?). Ping me if you have
improvements.