A simple layer to integrate lispy into meow by replacing the INSERT state with a
custom LISPY state in buffers where lispy-cat-mode is active. Previously this
mode was called lispy-meow-mode but this is would make it too prone to confusion
with meow-lispy-mode which is the name that the meow-define-state macro
generates for a state named LISPY.
🎶
Lispy Cat, Lispy Cat
What are they feeding you?
Lispy Cat, Lispy Cat
It’s not your fault
🎵
In LISPY state lispy-mode is active, so you can configure lispy-mode-map
as exepected. There are some features to be aware of however.
Disclaimer: This has just been packaged from an old config that I haven’t used in a while. First testing indicated everything still works, but I can’t make guarantees. I also just learned about meow-shims.el, this code could probably be adapted to be included there if so desired.
The core functionality of lispy-cat is to automatically switch to lispy-mode
when entering insertion state within a major mode that has. This is achieved by
advising core Meow insertion functions to call their lispy-cat counterparts
instead. Some configuration is provided when you want to drop into regular
INSERT instead of LISPY.
- meow-insert, meow-append, meow-change
- These are always adviced to drop you
into
LISPY, disablelispy-cat-modeif you don’t want to uselispy-mode. - meow-open-*
- If
lispy-cat-open-cmds-prefer-lispy-pis non-nil (default:t) these function will enterLISPYstate when opening new lines. - meow-beacon-*
- If
lispy-cat-beacon-prefer-lispy-p(default:nil) is non-nil these function will enterLISPYstate. Note: This is almost never what you want to use, the virtual cursors produced byBEACONare unpredictable.
Meow cancells the selection whenever you enter INSERT state. When entering
LISPY state however any active selection is preserved by default. If you want
to switch back to meow’s regular behaviour unset
lispy-cat-preserve-selection-on-entry-p.
Why this default?
Meow users often use selections for moving point, hence selections are created
without the intend to operate on them. Therefore it makes sense to cancel the
active selection when entering INSERT.
However lispy is built around moving point without selections and they are
created with the intent to be acted upon. On the other hand lispy makes it hard
to enter lispy’s “special-mode” from within a list. This requires bouncing
back from the nearest parens, meaning one has to navigate to parens (via
lispy-backward, lispy-forward, lispy-mark) and then select the item (via
special-lispy-ace-symbol or worse N M-m). But creating a selection will
enter “special-mode”.
Meow offers a excellent selection-making variety, that can be leveraged to mark a selection to be acted upon. So the intention behind the preservation of the selection is to enable a workflow for when point is within a list:
- Create a selection in
NORMAL - Switch to
LISPY - Immediately have
special-lispy-*commands at your disposal.
When lispy-mode is active, this package dynamically
updates the Meow cursor appearance in LISPY state:
- During normal insertion the cursor looks the same as in regular
INSERT(configure vialispy-cat-cursor-type-default) - It switches to a
hollowcursor style when Lispy detects it is operating on a structural boundary (e.g., inside a list or right next to a delimiter) . This provides visual feedack whenspecial-lispy-*commands are active (configure vialispy-cat-cursor-type-special).
Lispy has lispy-kill (C-k) that doesn’t respect the active region and
lispy-delete (C-d) that does, but that doesn’t update the kill-ring. This
package includes a command that does both.
The following user options control the behavior of lispy-cat:
| Variable | Default | Description |
|---|---|---|
lispy-cat-open-cmds-prefer-lispy-p | t | If non-nil, enter LISPY when opening a newline |
lispy-cat-beacon-prefer-lispy-p | nil | If non-nil, enter LISPY when in BEACON state (cursor behaviour unpredictable) |
lispy-cat-preserve-selection-on-entry-p | t | If non-nil, the selection remains active when entering LISPY state |
Be aware that writing this package was the easy part. The hard part is mapping
the mental models of meow and lispy with some consistency, which this package
(currently) doesn’t attempt to do for you.
- Emacs 29.1 or later
- lispy
- meow
The package is available at github.com/ventruvian/lispy-cat. For the
installation and configuration of lispy see github.com/enzuru/lispy.
lispy-cat will src_elisp{(require ‘lispy)}, no need to load it manually.
Add to your packages.el:
(package! lispy :recipe (:host github :repo "enzuru/lispy"))
(package! lispy-cat :recipe (:host github :repo "ventruvian/lispy-cat"))Configure in you config.el / config.org:
(use-package! lispy
:config <<your-lispy-keybindings>>)
(use-package! lispy-cat
:hook
;; Choose whichever modes you want lispy to be active in
((inferior-lisp-mode
lisp-mode emacs-lisp-mode
scheme-mode racket-mode hy-mode
lfe-mode dune-mode clojure-mode
clojure-ts-mode fennel-mode
doom-sandbox-emacs-lisp-mode) . lispy-cat-mode)
:config
;; Choose whatever keybinding fits your config or disregard this command entirely.
;; Lispy still uses the old `define-key' so remapping is done with vector notation.
(lispy-define-key lispy-mode-map [remap lispy-kill] #'lispy-cat-kill-dwim))(use-package lispy
:ensure (:host github :repo "enzuru/lispy")
:config <<your-lispy-keybindings>>)
(use-package lispy-cat
:ensure (:host github :repo "ventruvian/lispy-cat")
:hook
;; Choose whichever modes you want lispy to be active in
((inferior-lisp-mode
lisp-mode emacs-lisp-mode
scheme-mode racket-mode hy-mode
lfe-mode dune-mode clojure-mode
clojure-ts-mode fennel-mode) . lispy-cat-mode)
:config
;; Choose whatever keybinding fits your config or disregard this command entirely.
;; Lispy still uses the old `define-key' so remapping is done with vector notation.
(lispy-define-key lispy-mode-map [remap lispy-kill] #'lispy-cat-kill-dwim))(use-package lispy
:straight (:host github :repo "enzuru/lispy")
:config <<your-lispy-keybindings>>)
(use-package lispy-cat
:straight (:host github :repo "ventruvian/lispy-cat")
:hook
;; Choose whichever modes you want lispy to be active in
((inferior-lisp-mode
lisp-mode emacs-lisp-mode
scheme-mode racket-mode hy-mode
lfe-mode dune-mode clojure-mode
clojure-ts-mode fennel-mode) . lispy-cat-mode)
:config
;; Choose whatever keybinding fits your config or disregard this command entirely.
;; Lispy still uses the old `define-key' so remapping is done with vector notation.
(lispy-define-key lispy-mode-map [remap lispy-kill] #'lispy-cat-kill-dwim))doom-modeline knows already about meow’s other states and to the best of my
knowledge you can’t simply update an existing modeline-segment. Maybe you could
define your own segment, duplicate all the code and use
define-modeline-def-modeline without the old segment and with the new one, but
forking the repo sounds like less of a hassle. For reference and to have all the necessary
changes in one place, this is the advice you’d need if doom-modeline--meow were
a function not a defsubst:
(defface doom-modeline-meow-lispy-state
'((t (:inherit doom-modeline-evil-replace-state)))
"Face for the insert state in meow-edit indicator."
:group 'doom-modeline-faces)
(advice-add 'doom-modeline--meow
:before-until
(defun i.lispy/doom-modeline--meow ()
(when (and (bound-and-true-p meow-mode)
(meow-lispy-mode-p))
(doom-modeline--modal-icon
(substring-no-properties meow--indicator)
'doom-modeline-meow-lispy-state
(symbol-name (meow--current-state))
"nf-md-alpha_l_circle"
"🅛"))))You can also use my fork of the modeline with these changes included if you so
wish. From doom-emacs (add to packages.el and run doom upgrade):
(package! doom-modeline
:recipe (:host github
:repo "ventruvian/doom-modeline"
:branch "lispy-cat")
:pin nil)Contributions welcome! Feel free to check the issues page or submit a pull requests.
This package is licensed under GPLv3. See LICENCE for details.