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 ;)