After a good bit of digging and learning some features of Emacs Lisp, I managed to make a hack which produces the effect that I desire. I imagine that I'm not the only one who wants to leave trailing whitespace (at least in some scenarios), so here it is:
;; Hack to leave trailing whitespace
(eval-after-load 'electric '(fset 'electric-indent-post-self-insert-function (lambda ()
"Function that `electric-indent-mode' adds to `post-self-insert-hook'.
This indents if the hook `electric-indent-functions' returns non-nil,
or if a member of `electric-indent-chars' was typed; but not in a string
or comment."
;; FIXME: This reindents the current line, but what we really want instead is
;; to reindent the whole affected text. That's the current line for simple
;; cases, but not all cases. We do take care of the newline case in an
;; ad-hoc fashion, but there are still missing cases such as the case of
;; electric-pair-mode wrapping a region with a pair of parens.
;; There might be a way to get it working by analyzing buffer-undo-list, but
;; it looks challenging.
(let (pos)
(when (and
electric-indent-mode
;; Don't reindent while inserting spaces at beginning of line.
(or (not (memq last-command-event '(?\s ?\t)))
(save-excursion (skip-chars-backward " \t") (not (bolp))))
(setq pos (electric--after-char-pos))
(save-excursion
(goto-char pos)
(let ((act (or (run-hook-with-args-until-success
'electric-indent-functions
last-command-event)
(memq last-command-event electric-indent-chars))))
(not
(or (memq act '(nil no-indent))
;; In a string or comment.
(unless (eq act 'do-indent) (nth 8 (syntax-ppss))))))))
;; For newline, we want to reindent both lines and basically behave like
;; reindent-then-newline-and-indent (whose code we hence copied).
(let ((at-newline (<= pos (line-beginning-position))))
(when at-newline
(let ((before (copy-marker (1- pos) t)))
(save-excursion
(unless (or (memq indent-line-function
electric-indent-functions-without-reindent)
electric-indent-inhibit)
;; Don't reindent the previous line if the indentation function
;; is not a real one.
(goto-char before)
(indent-according-to-mode))
;; We are at EOL before the call to indent-according-to-mode, and
;; after it we usually are as well, but not always. We tried to
;; address it with `save-excursion' but that uses a normal marker
;; whereas we need `move after insertion', so we do the
;; save/restore by hand.
(goto-char before))))
(unless (and electric-indent-inhibit
(not at-newline))
(indent-according-to-mode))))))))
This is just a slightly different version of the electric-indent-post-self-insert-function
which is defined in electric.el
. The only difference is that I removed the following code from the hacked version of the function:
...
(when (eolp)
;; Remove the trailing whitespace after indentation because
;; indentation may (re)introduce the whitespace.
(delete-horizontal-space t))...
Here is my hack in action (I'm not running it in emacs -Q
, since that seems to strip away my hack).
emacs -Q
. State, for each step, what you expected to happen and what you saw instead.