Automatic Arrow Characters in Emacs

Published:

While adding technical writing to my new website using Emacs, I realized I was used to working with software that automatically converted the character - followed by > to the Unicode arrow character . I decided to add the same behavior to my Emacs setup, and I’d like to describe how I did it.

Emacs has a library called quail that allows defining custom character mappings. If the left side of the mapping has multiple characters, and you type them consecutively, Emacs replaces them with the right side.

Quail Setup

(quail-define-package
 "Arrows" "UTF-8" "" nil
 "Arrow input mode"
 nil t t nil nil nil nil nil nil nil t)

(quail-define-rules
 ("->" ?))

This creates a new input method called “Arrows” that converts -> to as you type. I have to caveat that the arguments to quail-define-package are a bit unergonomic, and having quail-define-rules not refer to the “Arrows” method explicitly is a bit odd (it applies its rules to the most recently defined quail input method/package), but it gets the job done.

Markdown Mode Activation

To use this input method specifically in Markdown mode, I have the following:

(defun my-turn-on-arrow-input ()
  "Turn on arrow input mode."
  (interactive)
  (set-input-method 'Arrows))

(add-hook 'markdown-mode-hook #'my-turn-on-arrow-input t)

Handling Indirect Buffers

The above is already enough to accomplish what’s needed, but I’ll explain one other edge case that I cover for Markdown specifically.

I use (setopt markdown-fontify-code-blocks-natively t) to fontify code blocks within Markdown buffers using their native major modes. Previously I used poly-markdown-mode for that, but the built-in markdown-mode approach seems more robust lately.

One precaution that I ported over from polymode is the ability to exclude certain hooks from running in the indirect buffers associated with those code blocks, in case they cause slowdowns or problems. I’ll present those here:

(defun my-in-indirect-md-buffer-p ()
  "Return non-nil if the current buffer is an indirect buffer created from a Markdown buffer."
  (when-let* ((buf (buffer-base-buffer)))
    (and (buffer-live-p buf)
         (with-current-buffer buf
           (derived-mode-p 'markdown-mode)))))

(defun my-inhibit-in-indirect-md-buffers (orig-fun &rest args)
  "Don't run ORIG-FUN (with ARGS) in indirect markdown buffers.
Use this to advise functions that could be problematic."
  (unless (my-in-indirect-md-buffer-p)
    (apply orig-fun args)))

(defun my-around-advice (fun advice)
  "Apply around ADVICE to FUN.
If FUN is a list, apply ADVICE to each element of it."
  (cond ((listp fun)
         (dolist (el fun) (my-around-advice el advice)))
        ((and (symbolp fun)
              (not (advice-member-p advice fun)))
         (advice-add fun :around advice))))

I then ensure that the Arrows input method only activates in the main Markdown buffers, not indirect ones:

(my-around-advice #'my-turn-on-arrow-input
                  #'my-inhibit-in-indirect-md-buffers)

This implementation, along with quite a few other customizations, is also available in my shared .emacs setup.

Some inspiration was taken from this Mastering Emacs post which describes how to make your own emoji input method with Quail.

Keywords: emacs