s-exp operations in emacs
Structural editing is very useful for lisp dialect languages, such as emacs-lisp, scheme, clojure and so on. Vanilla emacs ships Lisp editing commands for Emacs(lisp.el) and smartparens provides a minor mode for Emacs that deals with parens pairs and tries to be smart about it. If we combine the power of lisp.el and smartparens, we will gain super productivity.
Let’s demo these keys and functions with code examples.
1. Navigation
down-lisp(C-M-d)
will go down from ❶ to ❷ ❸ ❹ till ❺, and stop at ❺,
because there is no inner place to go.
sp-down-sexp
from smartparens achieves the same thing as lisp-down
.
❶(❷deftest size-test (❸let [❹ndm (❺nd/new-base-manager)] (is (= 0 (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
backward-up-list(C-M-u)
will move the point backward, in from of each
parenthesis. If the point starts from ❺, wherever inside the internal
expression, it will move to ❹ ❸ ❷ ❶, respectively on each press of
C-M-u
.
❶(deftest size-test ❷(let ❸[ndm ❹(nd/new-ba❺se-manager)] (is (= 0 (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
up-list
is actually forward-up-list
, if compares to backward-up-list~
,
it will let the point move forward out of one level of parentheses,
from ❶, wherever inside the internal expression, to ❷ ❸ ❹ till ❺. This
function is not bound to any keys as default.
(deftest size-test (let [ndm (nd/new-ba❶se-manager)❷]❸ (is (= 0 (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100)))))❹)❺
forward-sexp (C-M-f)
will move forward across one balanced expression (sexp).
Continue press C-M-f
, will move point from ❶ to ❷ and ❸, and stop at
❸. While the smartparens version sp-forward-sexp
, will jump one level
out from ❸.
(deftest size-test (let [ndm (nd/new-base-manager)] ❶(is (= 0 (nd/size (nd/arange ndm 0 0))))❷ (is (= 100 (nd/size (nd/arange ndm 0 100))))❸))
backward-sexp (C-M-b)
move backward across one balanced expression (sexp).
The point will move from ❶ to ❺, and stop here, because there is no backward sexp at the same level.
sp-backward-sexp
will jump one level up.
(deftest size-test (❺let ❹[ndm (nd/new-base-manager)] ❸(is (= 0 (nd/size (nd/arange ndm 0 0)))) ❷(is (= 100 (nd/size (nd/arange ndm 0 100))))❶))
beginning-of-defun (C-M-a)
move backward to the beginning of a
defun. Actually, it moves to the beginning of whatever a top level
parenthesis.
end-of-defun (C-M-e)
move forward to next end of defun, at the next
line of last )
of the defun.
From anywhere between ❶ and ❷, C-M-a
will move the point to ❶ and
C-M-e
moves it to ❷:
❶(deftest size-test (let [ndm (nd/new-base-manager)] (is (= 0 (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100)))))) ❷
2. Mark
mark-sexp (C-M-SPC)
can be used to mark next sexp and more if
repeatedly pressed.
If the cursor is at ❶, press C-M-SPC
once will mark the region from ❶
to ❷, press twice, will mark the region from ❶ to ❸.
sp-mark-sexp
works the same as this function.
(deftest size-test (let [ndm (nd/new-base-manager)] ❶(is (= 0 (nd/size (nd/arange ndm 0 0))))❷ (is (= 100 (nd/size (nd/arange ndm 0 100))))❸))
3. Kill
kill-sexp (C-M-k)
, Kill the sexp (balanced expression) following
point. When the cursor is at ❶:
(deftest size-test (let [ndm (nd/new-base-manager)] (is (= ❶(+ 0 0) (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
C-M-k
will remain the code as below:
(deftest size-test (let [ndm (nd/new-base-manager)] (is (= ❶ (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
sp-kill-sexp(M-k)
does the same operations as kill-sexp
.
But sp-kill-hybrid-sexp(C-k)
will kill all expresses after point till
end of the list:
(deftest size-test (let [ndm (nd/new-base-manager)] (is (= ❶)) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
sp-backward-kill-sexp
and backward-kill-sexp(ESC C-<backspace>)
kills
the sexp (balanced expression) preceding point. (+ 0 0)
is killed when
press ESC C-<backspace>
:
(deftest size-test (let [ndm (nd/new-base-manager)] (is (= (+ 0 0)❶ (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
sp-backward-copy-sexp(C-M-<backspace>)
, will copy previous balanced
expression. For example, this command will copy (+ 0 0)
to kill ring.
(deftest size-test (let [ndm (nd/new-base-manager)] (is (= (+ 0 0)❶ (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
sp-copy-sexp(C-M-w)
does the same operation forwardly comparing to
sp-backward-copy-sexp
. (nd/size (nd/arange ndm 0 0))
is copied in
below example.
(deftest size-test (let [ndm (nd/new-base-manager)] (is (= (+ 0 0)❶ (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
4. slurp and barf
sp-forward-slurp-sexp(C-<right>)
adds sexp following the current list
in it by moving the closing delimiter. When the point is at ❶:
(deftest size-test (let [ndm (nd/new-base-manager)] (is (= (+❶) 0 0 (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
C-<right>
will slurp the next 0
in the parenthesis. The new position
of the right parenthesis is at ❷.
(deftest size-test (let [ndm (nd/new-base-manager)] (is (= (+❶ 0❷) 0 (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
Continue press C-<right>
, twice, the next 0
and ()
will be slurped:
(deftest size-test (let [ndm (nd/new-base-manager)] (is (= (+❶ 0❷ 0❸ (nd/size (nd/arange ndm 0 0))❹))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
Press C-<right>
again, it will slurp next (is ..)
sexp, and put the
right parenthesis to ❺:
(deftest size-test (let [ndm (nd/new-base-manager)] (is (= (+❶ 0 0 (nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100))))❺)))))
sp-forward-barf-sexp(C-<left>)
does the opposite operation that the
delimiter will move one level inside. When the point is at ❺,
continuously press C-<left>
will move the point from ❶ to ❺:
(deftest size-test (let [ndm (nd/new-base-manager)] (is (= (+❺ 0❹ 0❸ (nd/size (nd/arange ndm 0 0))❷ (is (= 100 (nd/size (nd/arange ndm 0 100))))❶)))))
sp-backward-slurp-sexp(M-<left>)
will slurp sexp from left side. When
the point is at ❶:
(deftest size-test (let [ndm (nd/new-base-manager)] (is (= (+ 0❶ 0) (nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100))))))))
M-<left>
will slurp =
inside left parenthesis:
(deftest size-test (let [ndm (nd/new-base-manager)] (is ((= + 0❶ 0) (nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100))))))))
Next, it will slurp is
:
(deftest size-test (let [ndm (nd/new-base-manager)] ((is (= + 0❶ 0) (nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100))))))))
and is
again:
(deftest size-test (let [ndm (nd/new-base-manager)] (((is = + 0❶ 0) (nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100))))))))
That means the backward slurp will always slurp the nearest sexp, if there is no sexp in the inner parenthesis, it will slurp outer sexp.
sp-backward-barf-sexp(M-<right>)
is the opposite operation to
sp-backward-slurp-sexp
.
5. wrap and unwrap
sp-wrap-arround
, sp-wrap-curly
and sp-wrap-square
wraps the region
with ()
, {}
or []
.
sp-unwrap-sexp
unwrap next sexp, while sp-backward-unwrap-sexp
unwraps
previous sexp.
(deftest size-test (let [ndm (nd/new-base-manager)] (is (= (+ 0 0) ❶ (nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100))))))))
If point is at ❶, sp-unwrap-sexp
will remove parenthesis of next expression.
❿(deftest size-test ❾(let [ndm (nd/new-base-manager)] ❽(is ❼(= (+ 0 0) ❶ nd/size ❷(nd/arange ndm 0 0) ❸(is ❹(= 100 ❺(nd/size ❻(nd/arange ndm 0 100))))))))
If continuously invoke it, emacs will remove parenthesis at ❷, ❸, ❹, ❺ and ❻. And it will continue to remove outer parenthesis ❼, ❽, ❾, and ❿.
sp-backward-unwrap-sexp
will remove parenthesis of previous expression
(+ 0 0)
.
❼(deftest size-test ❻(let ❹[ndm ❺(nd/new-base-manager)] ❸(is ❷(= + 0 0 ❶ (nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100))))))))
If continuously invoked, if will remove ❷, ❸, ❹, ❺, ❻ till ❼. Remind the order of ❹ and ❺, because ❹ is the previous parenthesis when ❸ is removed and ❺ is the previous parenthesis, when ❹ is removed.
6. splice
sp-splice-sexp
will remove the outer parenthesis, and splice the sexp
from the list. When point is at ❶, this command will remove
parenthesis pair of ❷.
(deftest size-test (let [ndm (nd/new-base-manager)] (is ❷(= (+ 0 0) ❶ (nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100))))))))
sp-splice-sexp-killing-around
will keep next expression (nd/size
(nd/arange ndm 0 0))
and kill all other expressions and spaces inside
current list.
(deftest size-test (let [ndm (nd/new-base-manager)] (is ❶(nd/size (nd/arange ndm 0 0)))))
sp-splice-sexp-killing-forward(M-<down>)
will remove parenthesis and
kill following expressions:
(deftest size-test (let [ndm (nd/new-base-manager)] (is = (+ 0 0)❶)))
sp-splice-sexp-killing-backward(M-<up>)
will remove parenthesis and
kill preceeding expressions:
(deftest size-test (let [ndm (nd/new-base-manager)] (is ❶(nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100)))))))
7. split and join
sp-split-sexp
will add two parenthesis before and after ) (
:
(deftest size-test (let [ndm (nd/new-base-manager)] (is (= (+ 0 0)) ❶ ((nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100))))))))
sp-join-sexp
is the reverse operation of sp-split-sexp
.
8. convolute
sp-convolut-sexp
will put proceeding sexp outside of outer proceeding
sexp:
(deftest size-test (let [ndm (nd/new-base-manager)] ❶(is (= 0 (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
(let [ndm (nd/new-base-manager)] (deftest size-test ❶(is (= (+ 0 0) (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
9. Summary
Key | Map | Description |
---|---|---|
Navigation | ||
C-M-d |
down-list |
Move forward down one level of parentheses. |
C-M-u |
backward-up-list |
Move backward out of one level of parentheses. |
C-M-f |
forward-sexp |
Move forward across one balanced expression (sexp). |
sp-forward-sexp |
Same as above, will forward up. | |
C-M-b |
backward-sexp |
Move backward across one balanced expression (sexp). |
sp-backward-sexp |
Same as above, will backward up. | |
C-M-n |
forward-list |
Move forward across one balanced group of parentheses. |
C-M-p |
backward-list |
Move backward across one balanced group of parentheses. |
C-M-a |
beginning-of-defun |
Move backward to the beginning of a defun. |
C-M-e |
end-of-defun |
Move forward to next end of defun. |
Kill and Yank | ||
C-M-k |
kill-sexp |
Kill the sexp (balanced expression) following point. |
M-k |
sp-kill-sexp |
Kill the balanced expression following point. |
C-k |
sp-kill-hybrid-sexp |
Kill a line as if with ‘kill-line’, but respecting delimiters. |
ESC C-<backspace> |
backward-kill-sexp |
Kill the sexp (balanced expression) preceding point. |
sp-backward-kill-sexp |
Kill the balanced expression preceding point. | |
C-M-<backspace> |
sp-backward-copy-sexp |
Copy the previous ARG expressions to the kill-ring. |
C-M-w |
sp-copy-sexp |
Copy the following ARG expressions to the kill-ring. |
Slurp and Barf | ||
C-<right> |
sp-forward-slurp-sexp |
Add following sexp by moving the closing delimiter. |
C-<left> |
sp-forward-barf-sexp |
Remove the last sexp by moving the closing delimiter. |
M-<left> |
sp-backward-slurp-sexp |
Add preceding sexp by moving the opening delimiter. |
M-<right> |
sp-backward-barf-sexp |
Remove the first sexp by moving the opening delimiter. |
Wrap and Unwrap | ||
sp-wrap-arround |
Wrap region with () |
|
sp-wrap-curly |
Wrap region with {} |
|
sp-wrap-square |
Wrap region with [] |
|
sp-unwrap-sexp |
remove parenthesis of next expression | |
sp-backward-unwrap-sexp |
Remove parenthesis of previous expression | |
Splice, Split, Join | ||
sp-splice-sexp |
Remove delimiters | |
sp-splice-sexp-killing-around |
Remove delimiters and keep next expression | |
M-<down> |
sp-splice-sexp-killing-forward |
Remove delimiters and kill following expressions |
M-<up> |
sp-splice-sexp-killing-backward |
Remove delimiters and kill proceeding expressions |
sp-split-sexp |
Add ) and ( |
|
sp-join-sexp |
Remove ) and ( |
|
sp-convolute-sexp |
Put proceeding sexp outside of outer proceeding sexp |