Emacs and Pairs

Esperanto | English
Last updated: August 4, 2020

The white noise that beats within the white darkness is the rhythm of life; it is that pulse which never truly left the stage.
—Ergo Proxy, Ergo Proxy

wallhaven-578010

Table of contents

Introduction

In this article, I’ll exclusively talk about smartparens—a package that you wish you should have used, earlier, presuming you don’t use it yet. If you’re new to it, read along; if not, this may be a good refresher.

smartparens is one of those packages that drastically improves, and changes how one uses Emacs. It’s like having cybernetic limbs—it makes you jump higher and punch harder.

Take note, though, that the name is a misnomer, as it not only handles parentheses. It handles just about anything that pairs, and it performs those functions stellarly.

Installation

Installing smartparens is straightforward:

M-x package-install RET smartparens RET

Configuration

Let’s enable smartparens on startup, and hook it with some major hooks:

(use-package smartparens-config
  :ensure smartparens
  :config (progn (show-smartparens-global-mode t)))

(add-hook 'prog-mode-hook 'turn-on-smartparens-strict-mode)
(add-hook 'markdown-mode-hook 'turn-on-smartparens-strict-mode)

Usage

Managing paired characters like parentheses, braces, brackets, quotation marks, angle brackets, and other conceivable pair-able characters has always been a pain. Other packages solve that problem partially. However, they still miss several points.

In the code snippets below, the ^ symbol will be used to represent point:

Basics

In smartparens, when you input a pair-able character:


(defn foo )
          ^

the matching pair gets inserted, too, and point is positioned inside the pair:


(defn foo [])
           ^

Navigation

Starts and ends

If you have the expression:


(let [x "foo bar baz ... blah"])
                         ^

and you want to move point to the start of the string:


(let [x "foo bar baz ... blah"])
         ^

Execute sp-beginning-of-sexp. I bound it to C-M-a.

Conversely, to move point to the end of the expression:


(let [x "foo bar baz ... blah"])
                             ^

Execute sp-end-of-sexp. I bound it to C-M-e.

Traversing lists

If you have the expression:


(defun format-date (format)
  "Insert date with FORMAT specification using a specific locale."
  (let ((system-time-locale "en_US.UTF-8"))
    (insert (format-time-string format)))) ^

and you want to move point to insert:


(defun format-date (format)
  "Insert date with FORMAT specification using a specific locale."
  (let ((system-time-locale "en_US.UTF-8"))
    (insert (format-time-string format))))
     ^

Execute sp-down-sexp. I bound it to C-down.

If you have the expression:


(str "foo" "bar baz qux")
    ^

and you want to move point after ):


(str "foo" "bar baz qux")
                         ^

Execute sp-up-sexp. I bound it to C-up.

If you have the expression:


(defn foo [bar] (let [x 0] x))
                ^

and you want to move point to the preceeding ]:


(defn foo [bar] (let [x 0] x))
              ^

Execute sp-backward-down-sexp. I bound it to M-down.

If you have the expression:


(insert (format-time-string format))
                           ^

and you want to move point to (format:


(insert (format-time-string format))
        ^

Execute sp-backward-up-sexp. I bound it to M-up.

Block movements

If you have the expression:


(:require [clojure.string :as s])
          ^

and you want to move point after ]:


(:require [clojure.string :as s])
                                ^

Execute sp-forward-sexp. I bound it to C-M-f.

Conversely, to move it back to [:


(:require [clojure.string :as s])
          ^

Execute sp-backward-sexp. I bound it to C-M-b.

Top-level-ish traversal

If you have the expression:


(defn blah
  "Returns blah of foo."
  [foo]                 ^
  )

and you want to move point to [:


(defn blah
  "Returns blah of foo."
  [foo]
  ^)

Execute sp-next-sexp. I bound it to C-M-n.

Conversely, to move it back:


(defn blah
  "Returns blah of foo."
  [foo]                 ^
  )

Execute sp-previous-sexp. I bound it to C-M-p.

Free-form movements

If you have the expression:


(defn blah [] (let [x 0 y 1] (+ x 1)))
               ^

and you want to move point to blah:


(defn blah [] (let [x 0 y 1] (+ x 1)))
      ^

Execute sp-backward-symbol. I bound it to C-S-b.

Conversely, if you have the expression:


(defn blah [] (let [x 0 y 1] (+ x 1)))
            ^

and you want to move point just after (let:


(defn blah [] (let [x 0 y 1] (+ x 1)))
                  ^

Execute sp-forward-symbol. I bound it to C-S-f.

What they do is that they navigate around expressions as if delimiters, like parens, brackets, and braces do not exist.

Manipulation

Wrapping

If you have the expression:


var mods = "vars";
           ^

and you want "vars" to be surrounded by [ and ]:


var mods = ["vars"];
            ^

Pressing C-M-Space or ESC C-Space followed by [ will make the whole region become surrounded by matching [ and ]. It also applies to keys like (, {, ", ', *, _, etc, depending on the mode that you’re using.

Alternatively, you may define wrapping functions:

(defmacro def-pairs (pairs)
  "Define functions for pairing. PAIRS is an alist of (NAME . STRING)
conses, where NAME is the function name that will be created and
STRING is a single-character string that marks the opening character.

  (def-pairs ((paren . \"(\")
              (bracket . \"[\"))

defines the functions WRAP-WITH-PAREN and WRAP-WITH-BRACKET,
respectively."
  `(progn
     ,@(loop for (key . val) in pairs
             collect
             `(defun ,(read (concat
                             "wrap-with-"
                             (prin1-to-string key)
                             "s"))
                  (&optional arg)
                (interactive "p")
                (sp-wrap-with-pair ,val)))))

(def-pairs ((paren . "(")
            (bracket . "[")
            (brace . "{")
            (single-quote . "'")
            (double-quote . "\"")
            (back-quote . "`")))

These have the advantage of not requiring a region to operate on. I bound the first three functions to C-c (, C-c [, and C-c {, respectively. So, if you have the expression:


(defn foo args (let [x 0] (inc x)))
          ^

and you want to surround args with [ and ]:


(defn foo [args] (let [x 0] (inc x)))
           ^

Press C-c [.

Sometimes, we inadvertently delete one of the pair characters—this results in an unbalanced expression. smartparens prevents us from doing that. If you hit Backspace in this expression:


var mods = ["vars"];
            ^

Nothing will happen. smartparens saves us a lot of trouble, here.

Unwrapping

If you have the expression:


(foo (bar x y z))
     ^

and you want to unwrap the bar expression, removing the parentheses around foo:


foo (bar x y z)
    ^

Execute sp-backward-unwrap-sexp. I bound it to M-[

Conversely, if you want to unwrap the bar expression, removing the parentheses around bar:


(foo bar x y z)
     ^

Execute sp-unwrap-sexp. I bound it to M-].

Slurp and barf

If you have the expression:


[foo bar] baz
        ^

and you want baz to become part of foo and bar:


[foo bar baz]
        ^

Execute sp-forward-slurp-sexp. I bound it to C-right.

Conversely, if you want to remove baz:


[foo bar] baz
        ^

Execute sp-forward-barf-sexp. I bound it to M-right.

If you have the expression:


blah [foo bar]
             ^

and you want blah to become part of foo and bar:


[blah foo bar]
             ^

Execute sp-backward-slurp-sexp. I bound it to C-left.

Conversely, if you want to remove blah:


blah [foo bar]
             ^

Execute sp-backward-barf-sexp. I bound it to M-left.

Swapping

If you have the expression:


"foo" "bar"
      ^

and you want "foo" and "bar" to trade places:


"bar" "foo"
      ^

Execute sp-transpose-sexp. I bound it to C-M-t.

Killing

If you have the expression:


(let [x "xxx" y "y yy yyy" z 0])
               ^

and you want to kill just "y yy yyy":


(let [x "xxx" y z 0])
               ^

Execute sp-kill-sexp. I bound it to C-M-k.

If you want to kill "y yy yyy" z 0:


(let [x "xxx" y])
               ^

Execute sp-kill-hybrid-sexp. I bound it to C-k.

If you have the expression:


(:require [clojure.string :as s])
                                ^

and you want to kill [clojure.string :as s]:


(:require )
          ^

Execute sp-backward-kill-sexp. I bound it to M-k.

Keys

The following snippet summarizes the key bindings used in this article. I use bind-keys to conveniently map my keys. I discussed about it, in an earlier article.

(defmacro def-pairs (pairs)
  "Define functions for pairing. PAIRS is an alist of (NAME . STRING)
conses, where NAME is the function name that will be created and
STRING is a single-character string that marks the opening character.

  (def-pairs ((paren . \"(\")
              (bracket . \"[\"))

defines the functions WRAP-WITH-PAREN and WRAP-WITH-BRACKET,
respectively."
  `(progn
     ,@(loop for (key . val) in pairs
             collect
             `(defun ,(read (concat
                             "wrap-with-"
                             (prin1-to-string key)
                             "s"))
                  (&optional arg)
                (interactive "p")
                (sp-wrap-with-pair ,val)))))

(def-pairs ((paren . "(")
            (bracket . "[")
            (brace . "{")
            (single-quote . "'")
            (double-quote . "\"")
            (back-quote . "`")))

(bind-keys
 :map smartparens-mode-map
 ("C-M-a" . sp-beginning-of-sexp)
 ("C-M-e" . sp-end-of-sexp)

 ("C-<down>" . sp-down-sexp)
 ("C-<up>"   . sp-up-sexp)
 ("M-<down>" . sp-backward-down-sexp)
 ("M-<up>"   . sp-backward-up-sexp)

 ("C-M-f" . sp-forward-sexp)
 ("C-M-b" . sp-backward-sexp)

 ("C-M-n" . sp-next-sexp)
 ("C-M-p" . sp-previous-sexp)

 ("C-S-f" . sp-forward-symbol)
 ("C-S-b" . sp-backward-symbol)

 ("C-<right>" . sp-forward-slurp-sexp)
 ("M-<right>" . sp-forward-barf-sexp)
 ("C-<left>"  . sp-backward-slurp-sexp)
 ("M-<left>"  . sp-backward-barf-sexp)

 ("C-M-t" . sp-transpose-sexp)
 ("C-M-k" . sp-kill-sexp)
 ("C-k"   . sp-kill-hybrid-sexp)
 ("M-k"   . sp-backward-kill-sexp)
 ("C-M-w" . sp-copy-sexp)
 ("C-M-d" . delete-sexp)

 ("M-<backspace>" . backward-kill-word)
 ("C-<backspace>" . sp-backward-kill-word)
 ([remap sp-backward-kill-word] . backward-kill-word)

 ("M-[" . sp-backward-unwrap-sexp)
 ("M-]" . sp-unwrap-sexp)

 ("C-x C-t" . sp-transpose-hybrid-sexp)

 ("C-c ("  . wrap-with-parens)
 ("C-c ["  . wrap-with-brackets)
 ("C-c {"  . wrap-with-braces)
 ("C-c '"  . wrap-with-single-quotes)
 ("C-c \"" . wrap-with-double-quotes)
 ("C-c _"  . wrap-with-underscores)
 ("C-c `"  . wrap-with-back-quotes))

Closing remarks

The plethora of commands in smartparens may be daunting at first, but the investement in time in learning them, will be minimal compared to benefits that you will reap.

smartparens is the brainchild of Matus Goljer. For more information on smartparens, go here. If you like this project, you may donate here.

Thanks to Andreas Sahlbach for the corrections.