Closing all parentheses at once

October 19, 2017

While watching interesting presentation called Inspiring a future Clojure editor with forgotten Lisp UX, I've noticed author mentioned one really cool feature I was looking for some time - Interlisp's super-paren.

In short, Interlisp had this unique super-paren option, bound to ] key, that would close all opened parentheses at once.

To my knowledge, Emacs doesn't have something like this out of the box, unless you use Allegro CL mode or SLIME, but let's see how would it be hard to implement it.

First Google hit is this, which brings really nice implementation from Gilles. Copied code is down below:

(defun close-all-parentheses ()
  (interactive "*")
  (let ((closing nil))
    (save-excursion
      (while (condition-case nil
         (progn
           (backward-up-list)
           (let ((syntax (syntax-after (point))))
             (case (car syntax)
               ((4) (setq closing (cons (cdr syntax) closing)))
               ((7 8) (setq closing (cons (char-after (point)) closing)))))
           t)
           ((scan-error) nil))))
    (apply #'insert (nreverse closing))))

The function is using backward-up-list to find open parentheses for current block and get matching closing parentheses using syntax-table. Unique feature of this implementation is that target language doesn't have to have parentheses only. It works with Clojure, Java, C/C++, Javascript...

Assuming this function is bound to C-c ] in Emacs, let's see how it works out of the box with nested block in Clojure:

(defn my-func []
  {:key
    [(fun1) (fun2) (fun3 {:key2 
                           {:a1 "str"
                            :a2 (call [1 2 3 
                                            ^
                                            cursor is here

When you place the cursor where caret sign is (^) and press C-c ], everything will be nicely balanced and closed:

(defn my-func []
  {:key
    [(fun1) (fun2) (fun3 {:key2 
                           {:a1 "str"
                            :a2 (call [1 2 3])}})]})

Even better, it works correctly when open parentheses is found in comments:

;; testing ([{[[[
(defn my-func []
  {:key
    [(fun1) (fun2) (fun3 {:key2 
                           {:a1 "str"
                            :a2 (call [1 2 3])}})]})

Where it doesn't work is inside blocks where broader knowledge of surrounding structure is required. For example:

(defn my-fun []
  (let [a (call [1 2
                    ^
    (println a)))

and when you try to close the parentheses where caret is, it will yield:

(defn my-fun []
  (let [a (call [1 2])]))
    (println a)))

In short, it will close the whole block! However, to keep things fairly simple without adding complex modes like paren-mode, smartparens and etc. I'm pretty much fine with this.

Use it with other languages

Let's be adventurous and try this this facility for other modes, specifically editing C code (this should be applicable for C++, Java, Javascript...).

Nested C code example:

if (myfunc()) {
  if (v1) {
    if (v2) {
      call(); 
             ^

again, cursor is when caret is placed. Pressing C-c ] will yield a surprise:

if (myfunc()) {
  if (v1) {
    if (v2) {
      call();}}}

which is kind of too lispy for ordinary C developer. Let's fix that by adding formatting function and optional argument for calling it:

(defun close-all-parentheses (arg)
  (interactive "P")
  (let ((closing nil))
    (save-excursion
      (while (condition-case nil
         (progn
           (backward-up-list)
           (let ((syntax (syntax-after (point))))
             (case (car syntax)
               ((4) (setq closing (cons (cdr syntax) closing)))
               ((7 8) (setq closing (cons (char-after (point)) closing)))))
           t)
           ((scan-error) nil))))

    ;; changed part - call (newline-and-indent) when C-u prefix is given
    (dolist (token (nreverse closing))
      (when arg
        (newline-and-indent))
      (insert token))))

Now, when you go to above C code example and press C-u C-c ] (argument is set to true), it will insert remaining braces with correct formatting.

if (myfunc()) {
  if (v1) {
    if (v2) {
      call();
      }
    }
  }

Close, but not perfect. c-mode is using c-electric-brace to insert braces with proper formatting and something like this will make it near perfect. Here is a modified chunk of above code:

;; ...
    (dolist (token (nreverse closing))
      (if arg
          (progn
            (let ((last-command-event ?}))
              (newline)
              (c-electric-brace nil)))
        (insert token))

which will yield this:

if (myfunc()) {
  if (v1) {
    if (v2) {
        call();
    }
  }
}

Notice that calling (insert token) is not necessary: setting last-command-event to } in combination with c-electric-brace will actually insert } in buffer and place it with correct indentation.

This starts to show all complexities of c-mode that one should tackle with and I even didn't touch c++-mode with templates (implementing proper completion for <> should not be that hard).

Now, let's make close-all-parentheses implementation a bit more generic, so caller can provide own formatting function if necessary.

;; internal function which does most of the job

(defun close-all-parentheses* (indent-fn)
  (let* ((closing nil)
         ;; by default rely on (newline-and-indent)
         (local-indent-fn (lambda (token)
                            (newline-and-indent)
                            (insert token)))
         (indent-fn (if indent-fn
                      indent-fn
                      local-indent-fn)))
    (save-excursion
      (while (condition-case nil
         (progn
           (backward-up-list)
           (let ((syntax (syntax-after (point))))
             (case (car syntax)
               ((4) (setq closing (cons (cdr syntax) closing)))
               ((7 8) (setq closing (cons (char-after (point)) closing)))))
           t)
           ((scan-error) nil))))
    (dolist (token (nreverse closing))
      (if arg
        (funcall indent-fn token)
        (insert token)))))

Formatting function is expected to be in form:

(defun my-formatter (token)
  ;; do some formatting if necessary
  ;; and finally insert a token
  (insert token))

where (insert token) will do actual matched character insertion. Emacs supports number of ways to insert a character, so this is optional approach.

Here is the final implementation of close-all-parentheses:

(defun close-all-parentheses (arg)
  (interactive "P")
  (let ((my-format-fn (lambda (token)
                        ;; 125 is codepoint for '}'
                        (if (and (= token 125)
                                 ;; C, C++ and Java
                                 (member major-mode '(c-mode c++-mode java-mode)))
                            (let ((last-command-event ?}))
                              (newline)
                              (c-electric-brace nil))
                          (insert token)))))
    (close-all-parentheses* my-format-fn)))

Default formatting is using newline-and-indent, which will be enough for most cases. For specialized modes, close-all-parentheses can be a starting point. Note however that my implementation of c-mode formatting isn't perfect: mixing braces and brackets will easily confuse it so there is a bit room for improvements.

Again, if you prefer simplicity like I do, use original implementation ;)