evil-cleverparens

by luxbock

Evil normal-state minor-mode for editing lisp-like languages. Work in progress.

128 Stars 23 Forks Last release: Not found 205 Commits 0 Releases

Available items

No Items, yet!

The developer of this repository has not created any items for sale yet. Need a bug fixed? Help with integration? A different license? Create a request here:

evil-cleverparens

evil-cleverparens
is modal-editing optimized for editing Lisp. It works under the following principles:

1) Act like Vim/evil where useful, but prevent actions that would throw the order of your parentheses and other delimiters into question.

2) Make the most out of the combination of structural and modal editing.

3) Provide but don't force additional features on the user.

Installation

MELPA

The recommended way to install is via

elpa
from MELPA. The following should work:

M-x
package-install evil-cleverparens

evil-cleverparens
uses functions from both
smartparens
and
paredit
. Neither one is required by default, but using one of them is highly recommended, as
evil-cleverparens
doesn't provide anything for the
insert-state
. If you are an user of
smartparens
,
smartparens-strict-mode
is also recommended.

To enable

evil-cleverparens
with your favorite lispy-mode use:
(add-hook ' #'evil-cleverparens-mode)

If you are using

evil
with
evil-move-cursor-back
as
t
and
evil-move-beyond-eol
as
nil
(the defaults), I would recommend putting
(setq evil-move-beyond-eol t)
in your config so that certain movement commands don't break at the end of the lines (see issue #29 for details).

Features

Text Objects

evil-smartparens
includes evil text-objects for forms, comments and defuns. Since these may be useful in non-lisp-like modes, they have been separated out into their own file and can be used with:
(require 'evil-cleverparens-text-objects).

in which case you need to give them key-bindings on your own.

When used with

evil-cleverparens
we have:

Form bound to
f

Form is a pair-delimited range as defined by

smartparens
for your major-mode. It's much like if you combined
evil-(a|inner)-(paren|bracket|curly|double-quote)
under a single text-object.

Comment bound to
c

Selecting an outer comment means selecting both the comment delimiter and the comment text, whereas selecting an inner comment means selecting only the text but not the comment delimiters.

Defun bound to
d

Selects the top-level form.

Movement

Part of embracing Lisp and structural editing is learning to love the parentheses. Vim/evil is optimized for moving around by units of text, but for friends of Lisp the parentheses are more than just text. Therefore in addition to the regular evil movement keys, the following set of commands are provided:

| Key | Behavior | |-------|-------------------------------------------| |

H
| Move backward by sexp | |
L
| Move forward by sexp | |
M-h
| Move to the beginning of a top-level form | |
M-l
| Move to the end of the top-level form | |
(
| Move backward up a sexp. | |
)
| Move forward up a sexp. | |
[
| Move to the previous opening parentheses | |
]
| Move to the next closing parentheses | |
{
| Move to the next opening parentheses | |
}
| Move to the previous closing parentheses |

To customize them see

evil-cleverparens-use-additional-movement-keys
to enable/disable them and
evil-cp-additional-movement-keys
for the keys.

Besides parentheses, the other logical unit of structural movement are symbols. Unfortunately the regular Vim/evil movement keys (

w
,
W
,
b
,
B
,
ge
and
gE
) complect this idea by not making a distinction between symbols and delimiters. This distinction is made for you when using the
evil-cp-(forward|backward)-(word|symbol)-(begin|end)
commands, which happily skip past all parentheses and other delimiters.

TODO: Moving by word's by skipping delimiters doesn't work yet.

If you prefer the vanilla Vim way, set

evil-cleverparens-move-skip-delimiters
to
nil
.

There's also an option to swap the

symbol
/
word
behavior of these keys controlled by:
evil-cleverparens-swap-move-by-word-and-symbol
.

Yank / Delete / Change

When it comes to yanking (in the Vim sense) unbalanced expressions, there are two ways to handle this:

  • 1) Ignore the extra parentheses of the yanked region and only store the safe parts.
  • 2) Store everything and fill in the missing delimiters in kill-ring.

evil-cleverparens
supports both of these approaches, where the former is called ignoring and the latter balancing behavior. This defaults to ignoring and can be toggled with
M-T
. The current behavior is indicated in the modeline via
ecp/i
for ignoring and
ecp/b
for balancing.

Because checking and fixing the safety of a region can be computationally expensive, there is a variable

evil-cleverparens-threshold
(defaults to 1500) that controls upto how many characters will be checked before giving up and defaulting the normal
evil
behavior. In practice this is rarely a problem but it is something worth being aware of. This feature is adopted from evil-smartparens.

If for some reason

evil-cleverparens
messes up, or you would just like to torture your program for unspecified reasons, you can skip the protective measures by prepending next command with
M-z
aka
evil-cp-override
.

yy
,
dd
and
cc

  • Should work pretty much as you would expect!
  • dd
    and
    cc
    will leave the point to indentation or to the last unmatched opening delimiter of the line.
  • evil-cleverparens-indent-afterwards
    (defaults to
    t
    ) controls if the surrounding form should be indented after deleting or changing.

Y
,
D
and
C

  • On safe lines the behavior is identical to
    evil
    .
  • On lines with unmatched opening delimiters, the range of the operation extends all the way to the end of those forms, spanning multiple lines.
  • On lines with unmatched closing delimiters, the range of operation extends from point to the end-of-line.

M-y
,
M-d
and
M-c

  • These act from point to the end of the current or next sexp similar to how
    paredit-kill-sexp
    works.
  • Calling them at the end of a form will act on the whole form.
  • Calling them with the
    universal-argument
    affects everything from point until the end of the current surrounding form.

M-Y
,
M-D
and
M-C

  • Similar to the their little cousins, but act on the enclosing form, and in the case of
    delete
    and
    change
    will also perform whitespace clean-up after themselves.
  • Numeric argument increases the enclosing form up by a level.
  • Using a single
    universal-argument
    deletes the top-level form and its surrounding newlines.

Other Vim Related Features

s
and
S

  • s
    is identical to
    evil
    's, but skips over delimiters.
  • S
    is the same as
    C
    but always starts from the beginning of the line and skips over any unmatched delimiters.
  • evil-cleverparens-use-s-and-S
    (defaults to
    t
    ) controls whether s and S will be overridden as described above. If you decide to set this to
    nil
    , do it before loading
    evil-cleverparens
    !

x
deletes and splices

  • The
    x
    key works the same way it does in
    evil
    , but when called on opening or closing delimiters of a form, it will delete both delimiters.

_

  • Since
    ^
    and
    _
    in
    evil
    do the same thing, i.e. bring the point to the first non-blank column,
    evil-cleverparens
    has told
    _
    to bring the point to the first non-blank non-opening instead.

Insert

  • When entering

    insert-state
    via
    i
    in a situation where the point is between a round opening parentheses and a symbol,
    evil-cleverparens
    will automatically insert a space and then move the cursor back for you. The rationale is that when you are in this situation, it's much more likely that you are inserting a new word at the beginning of the list rather than modifying the beginning of the current head of the list, and therefore it would be nice if the two words were already separated so that your auto-completion mode can do its thing. This behavior can be disabled by setting
    evil-cleverparens-use-regular-insert
    to
    t
    .
  • M-i
    or
    evil-cp-insert-at-the-beginning-of-form
    and
    M-a
    or
    evil-cp-insert-at-end-of-form
    are analogous to the
    evil
    commands
    I
    and
    A
    , but instead of working on lines, they bring the cursor to the beginning/end of the current surrounding sexp while entering
    insert-state
    .
  • In the same manner,

    M-o
    or
    evil-cp-open-below-form
    and
    M-O
    or
    evil-cp-open-above-form
    are analogous to
    evil
    's
    o
    and
    O
    , again working on forms instead of lines.

Additional Functionality

Most of the following functionality is controlled by the

evil-cleverparens-use-additional-bindings
setting. The keys can be customized by editing
evil-cp-additional-bindings
(needs to be re-initialized).

Dragging / Transposing

In addition to regular transpose, bound to

M-t
(
sp-transpose-sexp
),
evil-cleverparens
provides additional means of moving sexps around with behavior inspired by the drag-stuff-el mode.
evil-drag-forward
bound to
M-j
and
evil-drag-backward
bound to
M-k
attempt to drag the thing under point forward or backward. The depth of the thing being moved never changes, i.e. dragging is distinct from slurping or barfing.

The thing can be many different things depending on the location of the cursor:

  • On top of a symbol acts on that symbol.
  • Inside or on the delimiters of a form moves the form.
  • If a symbol or a form can't be moved any further, the command acts on its surrounding form.
  • Outside a form on a safe line will move the line.
  • On a top-level comment will move the entire comment-block.

The behavior of the command with respect to top-level sexps and lines can be customized with

evil-cleverparens-drag-ignore-lines
(default
nil
). You can also choose not to treat connected commented lines as singular units by setting
evil-cleverparens-drag-comment-blocks
to
nil
.

Slurping and Barfing

evil-cleverparens
implements slurping and barfing both forwards and backwards using only the
<
and
>
keys by making them act differently when on top of a form delimiter:
  • On an opening delimiter
    <
    will slurp backwards as many times as there are preceding opening delimiters.
  • On a closing delimiter
    <
    will barf forwards. If the form contains only a single sexp inside it then
    evil-cleverparens
    will question your intention and instead do nothing. Perhaps you meant to splice or delete instead?
  • On a opening delimiter
    >
    will barf backwards with the same caveat as above.
  • On a closing delimiter
    >
    will slurp forwards as many times as there are closing delimiters behind.
  • When inside a form
    <
    will barf and
    >
    will slurp forwards.

While the specifics of the dragging, slurping and barfing behavior may seem complicated when spelled out, a lot of thought has been put to make their use feel intuitive and fluid.

Wrapping

While

evil-cleverparens
editing operations work well with evil-surround, the following specialized wrapping commands are also provided:

| Key | Behavior | |-------|----------------------------------------------| |

M-(
| Wrap the next sexp in round parentheses. | |
M-)
| Wrap the previous sexp in round parentheses. | |
M-[
| Wrap the next sexp in square brackets. | |
M-]
| Wrap the previous sexp in square brackets. | |
M-{
| Wrap the next sexp in curly braces. | |
M-}
| Wrap the previous sexp in curly braces. |

Each command can take a numeric argument to determine how many sexps to operate on maxing out at the number of sexps from the cursor until the end of the surrounding form. When called with the

universal-argument
, the operations act on the surrounding form instead. The universal argument can be called multiple times, where each consecutive call will wrap an additional expression from the surrounding form.

Copy and Paste

M-w
aka
evil-cp-evil-copy-paste-form
will copy the surrounding form and insert it below with the proper indentation. If called outside a form it will do the same with the current line instead (as long as its safe). This command can be called with a numeric argument to repeat the paste operation that many times. Calling it with the
universal-argument
will copy-paste the current top-level form and insert newlines between them.

From Smartparens

The following commands have been lifted straight from

smartparens
:

| Key | Behavior | |-------|---------------------| |

M-q
|
sp-indent-defun
| |
M-J
|
sp-join-sexp
| |
M-s
|
sp-splice-sexp
| |
M-S
|
sp-split-sexp
| |
M-t
|
sp-transpose-sexp
| |
M-v
|
sp-convolute-sexp
| |
M-r
|
sp-raise-sexp
|

M-R
bound to
evil-cp-raise-form
acts like
sp-raise-sexp
but on the enclosing form instead of the next one.

Notes and Tips

Universal Argument

Some of the commands in

evil-cleverparens
optionally accept the
universal-argument
(
C-u
in vanilla Emacs), which is not bound by default in
evil
and has no equivalent in Vim. If you wish to use these features, you must bind it to a key, but that alone only allows you to invoke it once, because calling
universal-argument
enables a transient keymap that assumes that you are using
C-u
for the next invocation. You can therefore use e.g.
 C-u C-u
to invoke it three times, or you can bind your own key like this:
(define-key universal-argument-map (kbd ) 'universal-argument-more)

Prefixed Sexps

evil-cleverparens
relies on
smartparens
to identify forms where the opening delimiter contains a prefix, such as quoted lists or the anonymous function literals in Clojure. If you are having problems with such prefixed forms, make sure that the variable
sp-sexp-prefix
is correctly configured for the mode you are using.

Highlighting Parentheses

Using highlight-parentheses.el to highlight three of the closest delimiters from the location of the point with fixed colors makes it very easy to quickly identify the range of many of the operations of

evil-cleverparens.

See Also

evil-cleverparens
is not the first Emacs/evil mode that tries to make structural editing of lisp-like languages easier. You might enjoy checking out the following modes as well:

abo-abo/lispy

Very rich in features but doesn't attempt to conform to the

vim
/
evil
bindings.

roman/evil-paredit

Prevents the user from messing up their parentheses by erroring out.

evil-cleverparens
originally started out as a fork of this project, with the goal of doing something useful instead of throwing an error in situations where it would make sense.

syl20bnr/evil-lisp-state

As the name suggests, this project creates an additional state for editing lisp in

evil
.

expez/evil-smartparens

Had I known of this project when starting out I would have just contributed to it instead of writing a lot of the same functionality on my own, but by the time I discovered it I had already so much code in place that I decided to continue with my own version. Some of the code in

evil-cleverparens
is lifted directly from here, and the modes work roughly the same.
evil-cleverparens
has more features and opinions, and probably more bugs :).

We use cookies. If you continue to browse the site, you agree to the use of cookies. For more information on our use of cookies please see our Privacy Policy.