How do I make Emacs highlight whitespace at EOF, eg. any whitespace after >?\n for PHP?

I think the general rule would be:

After the last non-whitespace character, accept one newline, then highlight all whitespace after.

I know about delete-trailing-whitespace, but I want to highlight, not delete. A web search didn't provide any solutions. Is this not supported out of the box? My current settings are:

(setq whitespace-style '(face trailing lines tabs big-indent))
  • 1
    By the way, you can also set indicate-empty-lines for a more subtle indication. – npostavs Mar 25 '18 at 16:04

Is this not supported out of the box?

It is; see (emacs) Useless Whitespace.

My current settings are:

(setq whitespace-style '(face trailing lines tabs big-indent))

As per the aforementioned manual node, you're only missing an empty element for your whitespace-style setting. Quoth C-hvwhitespace-styleRET:

empty  empty lines at beginning and/or end of buffer
       are visualized via faces.
       It has effect only if ‘face’ (see above)
       is present in ‘whitespace-style’.

In fact, empty is present in whitespace-style by default. So, you just need to modify your setting to include it again:

(setq whitespace-style '(face trailing tabs lines empty big-indent))

I think the general rule would be:

After the last non-whitespace character, accept one newline, then highlight all whitespace after.

Note that the empty setting for whitespace-style is slightly more general than this, in that it also applies to lines at the beginning of the buffer.

A relevant setting which only applies to the buffer's end, but only in the fringe of graphical frames, rather than by highlighting buffer contents, is the buffer-local user option indicate-empty-lines, which is also documented in the aforementioned manual node.

  • I can't believe I missed this! Maybe because it's documented as just "Highlight empty lines" at gnu.org/software/emacs/manual/html_node/emacs/… so I thought it highlighted all empty lines. This should obviously be changed to "Highlight empty lines at start and end of file". I also found out at emacswiki.org/emacs/WhiteSpace that whitespace-cleanup can do the same job as delete-trailing-whitespace, and then some. I just knew that Emacs had a built-in function for highlighting trailing whitespace. – forthrin Mar 25 '18 at 15:19
  • @forthrin Indeed, that's a shortcoming of the manual (usually the manual is more verbose and enlightening than docstrings) and ought to be reported as a bug via M-x report-emacs-bug RET or similar; see (efaq) Reporting bugs. If you would rather not have to do this yourself, let me know and I can submit a report in your name or anonymously. – Basil Mar 25 '18 at 16:47
  • I'd appreciate if you report this. I'd do it myself if the bug reporter wasn't a mailing list. – forthrin Mar 25 '18 at 17:09
  • @forthrin Will do. Would you like to have your name mentioned in the bug report? You can get my email address from my GitHub profile which is linked to my SE profile, if you want to send that in private. As for the mailing list - for better or worse, email is still the greatest common factor for online communication/collaboration on software projects. – Basil Mar 25 '18 at 17:16
  • I needn't be mentioned. Just happy to help. – forthrin Mar 25 '18 at 19:34

The following lisp code defines a new minor mode hl-ws-eob-mode. If you have installed the code in your init files you can try it by M-x hl-ws-eob-mode RET.

It works for buffers that are fontified by font-lock-mode. I think buffers with php-mode as major mode belong to this class. The final lonely newline is not highlighted if the option hl-ws-eob-accept-final-newline is non-nil.

If you think hl-ws-eob-mode does what you want you can install it in the mode hooks of the appropriate major modes, e.g.,

(defcustom hl-ws-eob-accept-final-newline 'skip
  "How to treat the newline on the last non-empty line.
t: Do not highlight a final newline at end of buffer
if there are no further newlines and whitespaces in front of it.
'skip: Don't highlight the newline on the last non-empty line.
nil: Treat the newline on the last non-empty line as trailing whitespace."
  :group 'whitespace
  :type '(choice (const :tag "Don not highlight newline on last non-empty line." skip)
         (const :tag "Do not highlight the final newline at end of buffer." t)
         (const :tag "Interpret the newline on the last non-empty line as trailing whitespace." nil)))

(defun hl-ws-eob-search ()
  "Search for the trailing whitespace of the accessible part of the buffer.
Return nil if point is not within
the trailing whitespace of the accessible part of the buffer."
  (unless ;; Do nothing if we don't reach end of buffer via whitespaces and newlines:
    (skip-chars-forward "[:space:]\n")
    (null (eobp)))
    (skip-chars-backward "[:space:]\n")
    (unless (and
         (eq hl-ws-eob-accept-final-newline t)
         (eq (char-after) ?\n)
         (let ((ch (char-before)))
           (and ch
            (null (eq (char-syntax ch) ?\s))
            (/= ch ?\n)))
         (eq (point) (1- (point-max))))
      (when (and
         (eq hl-ws-eob-accept-final-newline 'skip)
         (eq (char-after) ?\n))
      (unless (eobp)

;; defined in library font-lock:
(defvar font-lock-beg)
(defvar font-lock-end)

(defun hl-ws-eob-region ()
  "Expand font lock region to full trailing whitespace at end of buffer."
    (goto-char font-lock-end)
    (when (hl-ws-eob-search)
      (let (changed)
        (when (< (match-beginning 0) font-lock-beg)
          (setq font-lock-beg (match-beginning 0)
            changed t))
        (when (> (match-end 0) font-lock-end)
          (setq font-lock-end (match-end 0)
            changed t))

(defun hl-ws-eob-fun (end)
  "Function for highlighting trailing ws at buffer end via font-lock.
It returns the part of the region between `point' and END
that belongs to whitespace at the end of buffer."
  ;; Let us see how far we get with whitespaces starting at END:
  (when (save-excursion
        (goto-char end)
    ;; It may be that match-beginning is before END
    ;; but it cannot be after since in that case `hl-ws-eob-search'
    ;; would return nil.
    (set-match-data (list
             (max (point) (match-beginning 0))
             (min end (match-end 0))
    (goto-char (match-end 0))))

(defcustom hl-ws-eob-keywords '((hl-ws-eob-fun (0 'whitespace-trailing t)))
  "Keywords to add for mode `hl-ws-eob-mode'."
  :group 'whitespace
  :type '(repeat sexp))

(define-minor-mode hl-ws-eob-mode
  "Highlight whitespace at end of buffer."
  " $"
  (if hl-ws-eob-mode
        (require 'whitespace)
    (add-hook 'font-lock-extend-region-functions
          #'hl-ws-eob-region t t))
    (font-lock-remove-keywords nil hl-ws-eob-keywords)
    (remove-hook 'font-lock-extend-region-functions
          #'hl-ws-eob-region t))

Tested with emacs-version 25.3.1.

  • Wow! I guess you can say it doesn't come out of the box. So Emacs has no built-in functionality for this? That's strange with all the other whitespace detection it has. And particularly in PHP, trailing whitespace at EOF will make the script echo whitespace, so it's a priority to prevent it. Anyway, I put your code in .emacs, but couldn't see any highlighting at EOF. Do I need to add some extra lines to activate the code? My version is the same as yours. – forthrin Mar 23 '18 at 19:09
  • @forthrin I have added usage instructions in the introductory text. – Tobias Mar 23 '18 at 21:23
  • It partially works, but not quite. PHP: Erroneously highlights the line with ?>\n. Reproduction: Place cursor right of >. Then press space. Then three newlines. Then delete the space. The line will still be highlighted. Python: Only highlights the very last surplus newline. C: No highlighting at all. PS! Would it be possible to build this quite essential function into the existing whitespace-mode, so that it could be committed to GNU for inclusion in Emacs? PPS! Would it be possible to reuse code (or methodology) from delete-trailing-white, because that function gets it right. – forthrin Mar 24 '18 at 8:00
  • @forthrin 1: PHP: Option hl-ws-eob-accept-final-newline allows now better fine-adjustment. Set it to skip to get what you want. The old value t means to suppress highlighting trailing whitespace if it only consists of a newline. 2: I cannot reproduce the problems with c-mode (c-mode-common-hook) and python-mode (python-mode-hook). 3: It is much more difficult to get something into emacs than into melpa. 4: Deleting trailing whitespace and highlighting it are completely different things. The requirements for highlighting are much harder. 5: I extended the region to be highlighted. – Tobias Mar 25 '18 at 2:39

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.