(or emacs irrelevant)

ace-window display mode

Intro

Today, I'll describe a quite recent addition to ace-window, which comes from the idea by @deftsp:

Why not show the ace-window dispatch keys in the mode line all the time?

Certainly, there's no reason not to have this option, and it actually makes the whole interface better, since it becomes less feedback-based:

  • You glance at a window that you want.
  • You know which key you have to press before you call ace-window
  • You make a single-step call instead of:
    • press ace-window shortcut,
    • read the dispatch char,
    • press the dispatch char.

The feature is implemented as ace-window-display-mode - a minor mode that you can toggle on or off whenever you feel like it.

Here's how it looks like:

ace-window-display-mode.png

As you can see, I've made aw-keys list short on purpose, just to show you that the full path will be displayed. See, for instance, the *scratch* window: its path is gf.

Implementation

Since the implementation is quite short, I'll post it here and go over a few things that might interest people who write Elisp (on some level, please don't mock me for being obvious).

As per request of a curious reader, I've updated the code with the comments from below. Don't try this at home, excessive commenting is bad style.

;; Something that modifies the Emacs behavior should
;; preferrably be implemented as a minor mode.
;;;###autoload
(define-minor-mode ace-window-display-mode
    "Minor mode for showing the ace window key in the mode line."
  ;; And since this minor mode isn't tied to a particular
  ;; buffer, I declare it as global.
  ;;
  ;; An interesting quirk is that I have to put *something*
  ;; between the docstring and the body, otherwise it won't
  ;; work.
  :global t
  ;; Dispatch on the variable symbol of the mode -
  ;; `ace-window-display-mode`.  `define-minor-mode' will
  ;; define both a variable and function symbol.
  (if ace-window-display-mode
      (progn
        ;; Update the window parameters
        (aw-update)
        ;; Since `mode-line-format' is a buffer-local
        ;; variable, I set it with `set-default', in order
        ;; for the change to not just happen in the current
        ;; buffer.
        (set-default
         'mode-line-format
         `((ace-window-display-mode
            (:eval (window-parameter (selected-window)
                                     'ace-window-path)))
           ,@(default-value 'mode-line-format)))
        (force-mode-line-update t)
        ;; Each time a window is created or deleted, Emacs
        ;; will run the `window-configuration-change-hook' -
        ;; exactly what I need to update `mode-line-format'.
        (add-hook 'window-configuration-change-hook 'aw-update))
    (set-default
     'mode-line-format
     (assq-delete-all
      'ace-window-display-mode
      (default-value 'mode-line-format)))
    (remove-hook 'window-configuration-change-hook 'aw-update)))

(defun aw-update ()
  "Update ace-window-path window parameter for all windows."
  (avy-traverse
   (avy-tree (aw-window-list) aw-keys)
   (lambda (path leaf)
     ;; Use `set-window-parameter' to store a variable for
     ;; each window.  Buffer local variables would not work
     ;; here, since one buffer can be displayed in multiple
     ;; windows, and those would need a different key each.
     (set-window-parameter
      leaf 'ace-window-path
      (propertize
       (apply #'string (reverse path))
       'face 'aw-mode-line-face)))))

Since I'm writing something that modifies Emacs behavior when it's enabled, I first try to implement it as a minor mode. And since this minor mode isn't tied to a particular buffer, I declare it as global.

An interesting quirk is that I have to put something between the docstring and the body, otherwise it won't work. In this case, I put :global t.

Next, follows that standard dispatch on the variable symbol of the mode - ace-window-display-mode. The define-minor-mode macro will make both variable and function definition for the symbol it's given:

  • the variable is used to check if the mode is on.
  • the function is used to turn the mode on / off.

Note the use of assq-delete-all: this is for when some other package modifies the mode-line-format after ace-window does. In that case, ace-window's entry wouldn't be the first one any more.

Since mode-line-format is a buffer-local variable, I set it with set-default, in order for the change to not just happen in the current buffer.

Each time a window is created or deleted, Emacs will run the window-configuration-change-hook - exactly what I need to update mode-line-format.

One final trick is to use set-window-parameter to store a variable for each window. Buffer local variables would not work here, since one buffer can be displayed in multiple windows, and those would need a different key each.

I really like how the avy-tree / avy-traverse interface ended up as: the same functions are used for selecting window and setting mode-line-format.

Outro

I hope that you enjoy the update, and keep those interesting ideas coming!