13

I'm using hl-mode as a minor mode for deft. How do I make the highlighted line change color (e.g. to gray) when the deft window isn't the current window, and then back to the default highlight color when the deft window becomes the current window again?

1

2 Answers 2

5

I've implemented this for hl-line-mode using the buffer-list-update-hook. Here's the code:

(defface hl-line-inactive
  '((t nil))
  "Inactive variant of `hl-line'."
  :group 'hl-line)

(defun hl-line-update-face (window)
  "Update the `hl-line' face in WINDOW to indicate whether the window is selected."
  (with-current-buffer (window-buffer window)
    (when hl-line-mode
      (if (eq (current-buffer) (window-buffer (selected-window)))
          (face-remap-reset-base 'hl-line)
        (face-remap-set-base 'hl-line (face-all-attributes 'hl-line-inactive))))))

(add-hook 'buffer-list-update-hook (lambda () (walk-windows #'hl-line-update-face nil t)))

What this code is doing:

  • Define a new face hl-line-inactive to be used in inactive windows. You can use M-x customize-face to modify the attributes of this face to your taste.
  • Define a function to temporarily remap the highlighting face in inactive windows. A window is considered inactive if it is not displaying the same buffer as the currently selected window.
  • Add a hook to buffer-list-update-hook that calls hl-line-update-face for all the visible windows.

Old answer

The code above (which I'm using in my own init file) is much simpler than what I originally posted. Thanks @Drew for the suggestion to use walk-windows. I also read more about face remapping (see Face Remapping in the Emacs Lisp Manual) and realized I could remove a lot of the code.

For posterity, here's what I originally posted:

;; Define a face for the inactive highlight line.
(defface hl-line-inactive
  '((t nil))
  "Inactive variant of `hl-line'."
  :group 'local)

(defun toggle-active-window-highlighting ()
  "Update the `hl-line' face in any visible buffers to indicate which window is active."
  (let ((dups))
    (mapc
     (lambda (frame)
       (mapc
        (lambda (window)
          (with-current-buffer (window-buffer window)
            (when hl-line-mode
              (make-local-variable 'face-remapping-alist)
              (let ((inactive (rassoc '(hl-line-inactive) face-remapping-alist)))
                (if (eq window (selected-window))
                    (progn
                      (setq dups (get-buffer-window-list nil nil 'visible))
                      (setq face-remapping-alist (delq inactive face-remapping-alist)))
                  (unless (or inactive (memq window dups))
                    (add-to-list 'face-remapping-alist '(hl-line hl-line-inactive))))))))
        (window-list frame)))
     (visible-frame-list))))

(add-hook 'buffer-list-update-hook #'toggle-active-window-highlighting)
4
  • See also function walk-windows, which you can use to iterate over the windows of visible frames, etc. (And +1 for buffer-list-update-hook, which gets called by select-window.)
    – Drew
    Aug 11, 2015 at 20:37
  • +1 for this! Not so much for the hl-line stuff in specific, but I've been looking off and on for years to find a reliable way of shading the backgrounds of inactive windows in a frame. This works! Thanks! Dec 29, 2015 at 22:59
  • Thank you, this is awesome! Unfortunately, it seems to have problems when using global-hl-line-mode. It seems that global-hl-line-mode doesn't load hl-line-mode for each buffer. It uses an overlay instead of using the hl-line face. I've been fiddling with it for a while with no luck. Any suggestions? Should I open a separate question? Nov 21, 2018 at 16:31
  • You may want to use window-state-change-hook instead, because buffer-list-update-hook is call the buffer is changed.
    – updogliu
    Mar 31, 2021 at 12:03
2

You can use window-selection-change-functions. By adding a function locally the function is called each time when the window showing that buffer has been selected/deselected (see the docstring for more info).

This can be used to toggle hl-line-face face for the deft buffer:

(defvar-local deft-hl-line-toggle-cookie+ nil)

(defun deft-toggle-hl-line-face+ (w)
  (cond ((eq (current-buffer) (get-buffer deft-buffer))
         (when deft-hl-line-toggle-cookie+
           (face-remap-remove-relative deft-hl-line-toggle-cookie+)))
        (t
         (with-current-buffer (get-buffer deft-buffer)
           (setq deft-hl-line-toggle-cookie+
                 (face-remap-add-relative
                  hl-line-face `(:inherit ,hl-line-face :background "gray")))))))

(defun deft-hl-line-toggle-setup+ ()
  (add-hook 'window-selection-change-functions
            'deft-toggle-hl-line-face+ nil t))

(add-hook 'deft-mode-hook 'deft-hl-line-toggle-setup+)
1
  • Thanks for answering this question despite it being so old! Your solution of using a buffer-local hook is much cleaner and less resource-intensive than previous solutions.
    – shankar2k
    May 17, 2021 at 9:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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