sexp operations in emacs
Structural editing is very useful for lisp dialect languages, such as emacslisp, 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
downlisp(CMd)
will go down from ❶ to ❷ ❸ ❹ till ❺, and stop at ❺,
because there is no inner place to go.
spdownsexp
from smartparens achieves the same thing as lispdown
.
❶(❷deftest sizetest (❸let [❹ndm (❺nd/newbasemanager)] (is (= 0 (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
backwarduplist(CMu)
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
CMu
.
❶(deftest sizetest ❷(let ❸[ndm ❹(nd/newba❺semanager)] (is (= 0 (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
uplist
is actually forwarduplist
, if compares to backwarduplist~
,
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 sizetest (let [ndm (nd/newba❶semanager)❷]❸ (is (= 0 (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100)))))❹)❺
forwardsexp (CMf)
will move forward across one balanced expression (sexp).
Continue press CMf
, will move point from ❶ to ❷ and ❸, and stop at
❸. While the smartparens version spforwardsexp
, will jump one level
out from ❸.
(deftest sizetest (let [ndm (nd/newbasemanager)] ❶(is (= 0 (nd/size (nd/arange ndm 0 0))))❷ (is (= 100 (nd/size (nd/arange ndm 0 100))))❸))
backwardsexp (CMb)
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.
spbackwardsexp
will jump one level up.
(deftest sizetest (❺let ❹[ndm (nd/newbasemanager)] ❸(is (= 0 (nd/size (nd/arange ndm 0 0)))) ❷(is (= 100 (nd/size (nd/arange ndm 0 100))))❶))
beginningofdefun (CMa)
move backward to the beginning of a
defun. Actually, it moves to the beginning of whatever a top level
parenthesis.
endofdefun (CMe)
move forward to next end of defun, at the next
line of last )
of the defun.
From anywhere between ❶ and ❷, CMa
will move the point to ❶ and
CMe
moves it to ❷:
❶(deftest sizetest (let [ndm (nd/newbasemanager)] (is (= 0 (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100)))))) ❷
2. Mark
marksexp (CMSPC)
can be used to mark next sexp and more if
repeatedly pressed.
If the cursor is at ❶, press CMSPC
once will mark the region from ❶
to ❷, press twice, will mark the region from ❶ to ❸.
spmarksexp
works the same as this function.
(deftest sizetest (let [ndm (nd/newbasemanager)] ❶(is (= 0 (nd/size (nd/arange ndm 0 0))))❷ (is (= 100 (nd/size (nd/arange ndm 0 100))))❸))
3. Kill
killsexp (CMk)
, Kill the sexp (balanced expression) following
point. When the cursor is at ❶:
(deftest sizetest (let [ndm (nd/newbasemanager)] (is (= ❶(+ 0 0) (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
CMk
will remain the code as below:
(deftest sizetest (let [ndm (nd/newbasemanager)] (is (= ❶ (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
spkillsexp(Mk)
does the same operations as killsexp
.
But spkillhybridsexp(Ck)
will kill all expresses after point till
end of the list:
(deftest sizetest (let [ndm (nd/newbasemanager)] (is (= ❶)) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
spbackwardkillsexp
and backwardkillsexp(ESC C<backspace>)
kills
the sexp (balanced expression) preceding point. (+ 0 0)
is killed when
press ESC C<backspace>
:
(deftest sizetest (let [ndm (nd/newbasemanager)] (is (= (+ 0 0)❶ (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
spbackwardcopysexp(CM<backspace>)
, will copy previous balanced
expression. For example, this command will copy (+ 0 0)
to kill ring.
(deftest sizetest (let [ndm (nd/newbasemanager)] (is (= (+ 0 0)❶ (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
spcopysexp(CMw)
does the same operation forwardly comparing to
spbackwardcopysexp
. (nd/size (nd/arange ndm 0 0))
is copied in
below example.
(deftest sizetest (let [ndm (nd/newbasemanager)] (is (= (+ 0 0)❶ (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
4. slurp and barf
spforwardslurpsexp(C<right>)
adds sexp following the current list
in it by moving the closing delimiter. When the point is at ❶:
(deftest sizetest (let [ndm (nd/newbasemanager)] (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 sizetest (let [ndm (nd/newbasemanager)] (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 sizetest (let [ndm (nd/newbasemanager)] (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 sizetest (let [ndm (nd/newbasemanager)] (is (= (+❶ 0 0 (nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100))))❺)))))
spforwardbarfsexp(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 sizetest (let [ndm (nd/newbasemanager)] (is (= (+❺ 0❹ 0❸ (nd/size (nd/arange ndm 0 0))❷ (is (= 100 (nd/size (nd/arange ndm 0 100))))❶)))))
spbackwardslurpsexp(M<left>)
will slurp sexp from left side. When
the point is at ❶:
(deftest sizetest (let [ndm (nd/newbasemanager)] (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 sizetest (let [ndm (nd/newbasemanager)] (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 sizetest (let [ndm (nd/newbasemanager)] ((is (= + 0❶ 0) (nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100))))))))
and is
again:
(deftest sizetest (let [ndm (nd/newbasemanager)] (((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.
spbackwardbarfsexp(M<right>)
is the opposite operation to
spbackwardslurpsexp
.
5. wrap and unwrap
spwraparround
, spwrapcurly
and spwrapsquare
wraps the region
with ()
, {}
or []
.
spunwrapsexp
unwrap next sexp, while spbackwardunwrapsexp
unwraps
previous sexp.
(deftest sizetest (let [ndm (nd/newbasemanager)] (is (= (+ 0 0) ❶ (nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100))))))))
If point is at ❶, spunwrapsexp
will remove parenthesis of next expression.
❿(deftest sizetest ❾(let [ndm (nd/newbasemanager)] ❽(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 ❿.
spbackwardunwrapsexp
will remove parenthesis of previous expression
(+ 0 0)
.
❼(deftest sizetest ❻(let ❹[ndm ❺(nd/newbasemanager)] ❸(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
spsplicesexp
will remove the outer parenthesis, and splice the sexp
from the list. When point is at ❶, this command will remove
parenthesis pair of ❷.
(deftest sizetest (let [ndm (nd/newbasemanager)] (is ❷(= (+ 0 0) ❶ (nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100))))))))
spsplicesexpkillingaround
will keep next expression (nd/size
(nd/arange ndm 0 0))
and kill all other expressions and spaces inside
current list.
(deftest sizetest (let [ndm (nd/newbasemanager)] (is ❶(nd/size (nd/arange ndm 0 0)))))
spsplicesexpkillingforward(M<down>)
will remove parenthesis and
kill following expressions:
(deftest sizetest (let [ndm (nd/newbasemanager)] (is = (+ 0 0)❶)))
spsplicesexpkillingbackward(M<up>)
will remove parenthesis and
kill preceeding expressions:
(deftest sizetest (let [ndm (nd/newbasemanager)] (is ❶(nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100)))))))
7. split and join
spsplitsexp
will add two parenthesis before and after ) (
:
(deftest sizetest (let [ndm (nd/newbasemanager)] (is (= (+ 0 0)) ❶ ((nd/size (nd/arange ndm 0 0)) (is (= 100 (nd/size (nd/arange ndm 0 100))))))))
spjoinsexp
is the reverse operation of spsplitsexp
.
8. convolute
spconvolutsexp
will put proceeding sexp outside of outer proceeding
sexp:
(deftest sizetest (let [ndm (nd/newbasemanager)] ❶(is (= 0 (nd/size (nd/arange ndm 0 0)))) (is (= 100 (nd/size (nd/arange ndm 0 100))))))
(let [ndm (nd/newbasemanager)] (deftest sizetest ❶(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  
CMd 
downlist 
Move forward down one level of parentheses. 
CMu 
backwarduplist 
Move backward out of one level of parentheses. 
CMf 
forwardsexp 
Move forward across one balanced expression (sexp). 
spforwardsexp 
Same as above, will forward up.  
CMb 
backwardsexp 
Move backward across one balanced expression (sexp). 
spbackwardsexp 
Same as above, will backward up.  
CMn 
forwardlist 
Move forward across one balanced group of parentheses. 
CMp 
backwardlist 
Move backward across one balanced group of parentheses. 
CMa 
beginningofdefun 
Move backward to the beginning of a defun. 
CMe 
endofdefun 
Move forward to next end of defun. 
Kill and Yank  
CMk 
killsexp 
Kill the sexp (balanced expression) following point. 
Mk 
spkillsexp 
Kill the balanced expression following point. 
Ck 
spkillhybridsexp 
Kill a line as if with ‘killline’, but respecting delimiters. 
ESC C<backspace> 
backwardkillsexp 
Kill the sexp (balanced expression) preceding point. 
spbackwardkillsexp 
Kill the balanced expression preceding point.  
CM<backspace> 
spbackwardcopysexp 
Copy the previous ARG expressions to the killring. 
CMw 
spcopysexp 
Copy the following ARG expressions to the killring. 
Slurp and Barf  
C<right> 
spforwardslurpsexp 
Add following sexp by moving the closing delimiter. 
C<left> 
spforwardbarfsexp 
Remove the last sexp by moving the closing delimiter. 
M<left> 
spbackwardslurpsexp 
Add preceding sexp by moving the opening delimiter. 
M<right> 
spbackwardbarfsexp 
Remove the first sexp by moving the opening delimiter. 
Wrap and Unwrap  
spwraparround 
Wrap region with () 

spwrapcurly 
Wrap region with {} 

spwrapsquare 
Wrap region with [] 

spunwrapsexp 
remove parenthesis of next expression  
spbackwardunwrapsexp 
Remove parenthesis of previous expression  
Splice, Split, Join  
spsplicesexp 
Remove delimiters  
spsplicesexpkillingaround 
Remove delimiters and keep next expression  
M<down> 
spsplicesexpkillingforward 
Remove delimiters and kill following expressions 
M<up> 
spsplicesexpkillingbackward 
Remove delimiters and kill proceeding expressions 
spsplitsexp 
Add ) and ( 

spjoinsexp 
Remove ) and ( 

spconvolutesexp 
Put proceeding sexp outside of outer proceeding sexp 