(or emacs irrelevant)

Rule-based multi-line in lispy

Where do one-line expressions come from?

When programming LISP, especially with lispy, it's easy to generate random one-line expressions. This is, of course, because the results of read or eval don't contain any whitespace information: all original newlines are lost.

Just to review the multitude of ways to insert generated code into a buffer using lispy I'll list the shortcuts and the test-based explanations (at around 2000 lines of tests and 54% test coverage, lispy is pretty well tested).

eval-and-insert

E calls lispy-eval-and-insert.

lispy-test-eval-and-insert.png

The image above is generated using the interactive test visualizer lispy-view-test, bound to xv. If you want to explore how a certain command is intended to behave, just find the corresponding test (with the same name as the command) and call xv.

eval-and-replace

xr calls lispy-eval-and-replace. This function evaluates the current expression and replaces it with the result.

lispy-test-eval-and-replace.png

The sequence of actions in the test:

  • e calls lispy-eval to set foo to 42.
  • j calls lispy-down to move to the next sexp.
  • xr calls lispy-eval-and-replace.

Ideally, there should have been "xr" instead of (lispy-eval-and-replace) in the test, but there's a small wrinkle in the lispy-with macro that needs to be fixed before that can happen.

flatten

xf calls lispy-flatten. This function expands in-place the current function or macro call.

lispy-test-flatten.png

In this test, the misleadingly named function square is evaluated and flattened, to see if the &optional and &rest argument passing rules indeed work.

The flatten operation works really well for Elisp and quite well for Clojure. The CL implementation would need to heavily rely on SLIME features (currently absent), since the CL spec doesn't define an equivalent of Elisp's symbol-function. The same applies to Scheme, I guess.

oneline

O calls lispy-oneline. It's not eval-based, it just deletes the newlines. If there are any comments present, they are pushed out.

lispy-test-oneline.png

lispy-alt-multiline Demo 1

In the following image, I just press T once, starting from an unchanged buffer:

lispy-alt-multiline-1.gif

lispy-alt-multiline Demo 2

Flatten push

Start from this code (the cursor is in the CSS, if you don't see it):

(let (res)
  (dotimes (i 10)
    (push i res))
  (nreverse res))

After xf it becomes:

(let (res)
  (dotimes (i 10)
    (setq res
          (cons i res)))
  (nreverse res))

Since push is a macro, macroexpand is used. And since macroexpand doesn't give newline information, pp-to-string is used, and it gives a reasonable result.

Flatten dotimes

Start with the same code, but with cursor on dotimes this time:

(let (res)
  (dotimes (i 10)
    (push i res))
  (nreverse res))

After xf it becomes:

(let (res)
  (cl--block-wrapper
   (catch '--cl-block-nil--
     (let
         ((--dotimes-limit-- 10)
          (i 0))
       (while
           (< i --dotimes-limit--)
         (setq res
               (cons i res))
         (setq i
               (1+ i))))))
  (nreverse res))

This time pp-to-string isn't as good: let and while statements are messed up. Follow this up with T which calls lispy-alt-multiline:

(let (res)
  (cl--block-wrapper
   (catch '--cl-block-nil--
     (let ((--dotimes-limit-- 10)
           (i 0))
       (while (< i
                 --dotimes-limit--)
         (setq res
               (cons i
                     res))
         (setq i
               (1+
                i))))))
  (nreverse res))

Well, at least some parts look better. It could be make perfect by adding a sort of threshold when printing each sub-expression. It's less than, say 15 chars, which (setq i (1+ i)) is, no newlines should be added. I'll add this a bit later.

More on lispy-alt-multiline

lispy-alt-multiline can be used on a LISP expression to re-format it across multiple lines. It doesn't matter in which shape the expression currently is, since all current newlines will be removed before the algorithm starts.

This has to be done with some rules, since a one-line expression can transform to multiple viable multi-line forms. So far, these rules are implemented by customizing these variables:

(defvar lispy--multiline-take-3
  '(defvar defun defmacro defcustom defgroup)
  "List of constructs for which the first 3 elements are on the first line.")

(defvar lispy--multiline-take-2 '(defface define-minor-mode
  condition-case while incf car cdr > >= < <= eq equal incf decf
  cl-incf cl-decf catch require provide setq cons when if unless interactive)
  "List of constructs for which the first 2 elements are on the first line.")

The name suggests that there should be lispy-multiline, and there is, bound to M. The difference between M and T is that M is older and ad-hoc, while T is newer and rule-based. This means that the latter can misbehave, since it's not yet fully tested. However, it has the following built-in check to make sure that it doesn't mess up your code:

The read of the expression before transformation should be equal to the read of the transformed expression.

If the above check fails, no change will be performed on the source code. So your code should be pretty safe. One more cool thing that I want to add to other operations is that it checks if the buffer will be changed after the transformation. If there will be no change, it will just issue a "No change" message, and no change will be performed. This is really cool if you obsess about the buffer changed marker in the mode line like I do.

Outro

The functions bound to O, M, and T apply to the current expression. To be really sure which one, turn on show-paren-mode. You can also call these functions not from special, although it's not very convenient. The typical strategy in that case would be to bind all of them on a prefix map, e.g. C-c. How is it different from special then?

Instead of typing [T you would type C-c T.

But the advantage of the special approach is that [ actually does something (moves point to the start of the current list), instead of just being a useless part of a key combination like C-c. And if you're in special already, there's no need for [.

I hope that you enjoy the new update. If it's needed, variables like lispy--multiline-take-3 can be made buffer-local so that T works appropriately for Clojure, CL and Scheme, instead of just Elisp. If you'd like to add support for your favorite dialect in this way, I'd be happy to explain some details if needed and to merge the PR. Happy hacking!

Paredit emulation in lispy

Intro

I just finished this feature today. Almost all Paredit functionality is available in terms of lispy's own functions. The only functions that I didn't implement were the ones that I didn't find any use for: paredit-backslash (ugh, prompts), paredit-comment-dwim (covered by regular ;), paredit-forward-down and paredit-backward-down (both just move out of special, and throw a lot).

Setting the keymap theme

So if you ever thought that lispy could be a good idea, but the non-special key bindings are weird, and you're too used to Paredit, you can now try this setting:

(lispy-set-key-theme '(special paredit c-digits))

Here's the setting that I'm using, since I love lispy's default bindings:

(lispy-set-key-theme '(oleh special lispy c-digits))

The default setting actually is this one, lispy-mode-map-oleh is an additional map for use with my Xmodmap setup:

(lispy-set-key-theme '(special lispy c-digits))

Each item in the list passed to lispy-set-key-theme designates a keymap to turn on. So in a non-LISP buffer you could turn on just the special map:

(lispy-set-key-theme '(special))

This would mean that zero regular bindings get overridden, but when the point is before or after a paren, you get the lispy bindings. Actually, I've just enabled lispy-mode for markdown-mode, and it's working great. Unfortunately, I haven't yet figured out how to make a minor mode's keymap buffer-local. Maybe someone reading has an idea.

The screencast

I went through all of Paredit tests and made ERT tests out of them, to make sure that the features are actually working. I've recorded a short (<3 minutes) screencast of implementing one of the tests. Have a look, maybe you'll something interesting in my setup. There are two short pauses in the video, that's just me thinking how to write down a function. My ERT and Magit workflows are also shown.

Outro

The new key-theme feature is meant to make lispy super-not-annoying. I think it's useful, since using lispy in Paredit mode was annoying for me, so the opposite has to be true for people that are used to Paredit. So if you notice a bug, please report it, bug reports are very important for my non-annoyance agenda.

Finally, if you're super-devoted to Paredit and don't want to drop it, now you can easily have both lispy-mode and paredit-mode on. Just use this setting:

(lispy-set-key-theme '(special))

Or this one if you additionally want C-1 to show the inline doc, and C-2 to show the inline args:

(lispy-set-key-theme '(special c-digits))

lispy 0.25.0 is out

Seriously, check the release notes if you don't believe me.

Fixes

  • Add minibuffer-inactive-mode to the lispy-elisp-modes list. It means that you can eval there if you want.
  • V (lispy-visit) should turn on projectile-global-mode if it's not on.
  • M (lispy-multiline) works better for Clojure: the regexes for vectors, maps and sets have been improved.
  • C-k should not delete only the string when located at start of string.
  • M will not turn vectors into lists any more.
  • the backquote bug for i and M was fixed.
  • you can flatten Elisp closures as well, at least the plain ones.

New Features

b calls lispy-back

The movement commands, such as:

  • the arrows hjkl (lispy-left, lispy-down etc.)
  • f (lispy-flow)
  • q (lispy-ace-paren)
  • i (lispy-tab), only when called for an active region

will not store each movement in the point-and-mark history. You can press b to go back in history. This is especially useful for h, l, and f, since they are not trivially reversible.

b was previously bound to lispy-store-region-and-buffer, so you could do Ediff with b and B. Now it's bound to xB.

Hungry comment delete

C-d (lispy-delete) when positioned at the start of a comment, and with only whitespace before the start of the line, will delete the whole comment.

If you want to un-comment, just use C-u ; from any point in the comment.

Added flatten operation for Clojure

xf (lispy-flatten) now also works for Clojure, before it was only for Elisp.

Example 1 (flatten a macro):

|(->> [1 2 3 4 5]
     (map sqr)
     (filter odd?))

When you press xf you get this:

|(filter odd? (map sqr [1 2 3 4 5]))

Example 2 (flatten a standard function):

Start with:

|(map odd? [1 2 3 4 5])

After xf:

(let [f odd? coll [1 2 3 4 5]]
  (lazy-seq (when-let [s (seq coll)]
              (if (chunked-seq? s)
                (let [c (chunk-first s)
                      size (int (count c))
                      b (chunk-buffer size)]
                  (dotimes [i size]
                    (chunk-append b (f (.nth c i))))
                  (chunk-cons (chunk b)
                              (map f (chunk-rest s))))
                (cons (f (first s))
                      (map f (rest s)))))))

A bit of a gibberish, but at least we can confirm that map is indeed lazy.

Example 3 (flatten your own function):

Example function:

(defn sqr [x]
  (* x x))

This one requires the function to be properly loaded with C-c C-l (cider-load-file), otherwise Clojure will not know the location of the function.

Example statement:

(+ |(sqr 10) 20)

After xf:

(+ |(let [x 10]
     (* x x)) 20)

Added lax eval for Clojure

This is similar to the lax eval for Elisp. If you mark an expression with a region:

asdf [1 2 3]

and press e, you will actually eval this:

(do (def asdf [1 2 3])
    asdf)

You can do this for let bindings, it's super-useful for debugging. The rule is that if the first element of the region is a symbol, and there's more stuff in the region besides the symbol, a lax eval will be performed.

e will auto-start CIDER

If CIDER isn't live, e will start it and properly eval the current statement.

2F will search for variables first

Since Elisp is a LISP-2, there can be a function and a variable with the same name. F (lispy-follow) prefers functions, but now 2F will prefer variables.

2e will eval and insert the commented result

Starting with:

|(filter odd? (map sqr [1 2 3 4 5]))

Pressing 2e gives:

(filter odd? (map sqr [1 2 3 4 5]))
;; =>
;; (1 9 25)

This works for all dialects, so you can also have:

(symbol-function 'exit-minibuffer)
;; =>
;; (closure (t)
;;          nil "Terminate this minibuffer argument." (interactive)
;;          (setq deactivate-mark nil)
;;          (throw (quote exit)
;;            nil))

or

*MODULES*
;; =>
;; ("SWANK-ARGLISTS" "SWANK-FANCY-INSPECTOR" "SWANK-FUZZY" 
;;                   "SWANK-UTIL" "SWANK-PRESENTATIONS" 
;;                   "SWANK-TRACE-DIALOG" "SB-CLTL2")

To do the last eval you need to be in special. It means that you first have to mark the symbol *MODULES* with a region. A convenient function to mark the current symbol is M-m (lispy-mark-symbol).

y (lispy-occur) now has an ivy back end

lispy-occur launches an interactive search within the current top-level expression, usually a defun. This is useful to see where a variable is used in a function, or to quickly navigate to a statement.

You can customize lispy-occur-backend to either ivy (the default) or helm (if you have it, since it's no longer a dependency of lispy).

Add ivy back end to lispy-completion-method

Now it's the default one for navigating to tags. You can select alternatively helm or ido if you wish.

Remove the dependency on ace-jump-mode

Instead the dependency on ace-window will be re-used. This allows for a lot of code simplifications and better tests.

New custom variables:

  • lispy-avy-style-char: choose where the overlay appears for Q (lispy-ace-char)
  • lispy-avy-style-paren: choose where the overlay appears for q (lispy-ace-paren)
  • lispy-avy-style-symbol: choose where the overlay appears for a (lispy-ace-symbol) and - (lispy-ace-subword) and H (ace-symbol-replace).

There's also lispy-avy-keys, which is a ... z by default.

Add lispy-compat

This is a list of compatibility features with other packages, such as edebug and god-mode. They add overhead, so you might want to turn them off if you don't use the mentioned packages.

F works for Scheme

You can navigate to a symbol definition in Scheme with F. This feature was already in place for Elisp, Clojure and CL.

Outro

Looks like a great batch of features, but I'm the most happy about the stupid backquote bug being fixed.

Up next: pre-defined key binding themes, featuring:

  • the beloved default.
  • the old-school paredit.
  • the special-only minimal.
  • and maybe one more.

Feel free to chime in, if you've got a key setup that you think other people will like. Happy hacking!

An update to my Elisp ERT / Travis CI setup

This post might be interesting to people who use Travis CI to test their Elisp packages.

I've noticed a few days ago that my tests on Travis CI started failing randomly. Turns out that the widely used ppa:cassou/emacs has become deprecated. It still sort of works, but sometimes apt-get update just times out, and I get a build error.

Since it was the only one that I was using, I had to find another one. I went with emacs-snapshot from ppa:ubuntu-elisp.

Here's my current .travis.yml:

language: emacs-lisp
env:
  matrix:
    - emacs=emacs-snapshot

before_install:
  - sudo add-apt-repository -y ppa:ubuntu-elisp
  - sudo apt-get update -qq
  - sudo apt-get install -qq $emacs
  - curl -fsSkL --max-time 10 --retry 10 --retry-delay 10 https://raw.github.com/cask/cask/master/go | python

script:
  - make cask
  - make test

I've adopted one more change in the above code: EMACS is replaced by emacs. The reason for this is that both ansi-term and compile for legacy reasons set EMACS to strange values, like "24.4.91.2 (term:0.96)". So if you have in your Makefile:

EMACS ?= emacs

you're going to have a bad time. So I changed my Makefile to:

emacs ?= emacs

And now I can compile and test while inside Emacs with no issues. The only issue left is that I don't yet know which PPA I should use for a stable Emacs. Maybe someone has a suggestion.

recenter-positions, that's not how gravity works!

Yesterday, I've added a new binding to swiper-map: C-l will now call swiper-recenter-top-bottom. The implementation is really easy, almost nothing to write home about:

(defun swiper-recenter-top-bottom (&optional arg)
  "Call (`recenter-top-bottom' ARG) in `swiper--window'."
  (interactive "P")
  (with-selected-window swiper--window
    (recenter-top-bottom arg)))

An interesting thing that I want to mention though is the customization of the default recenter-top-bottom behavior. This is the default one:

(setq recenter-positions '(middle top bottom))

And this is the logical one that I'm using:

(setq recenter-positions '(top middle bottom))

Try it out, and see if it makes sense to you. For me, when I've just jumped to a function definition, which usually means that the point is on the first line of the function, the first recenter position has to be top, since that will maximize the amount of the function body that's displayed on the screen.

Another use-case is when I'm reading an info or a web page. After a recenter to top, all that I have read is scrolled out of view, and I can continue from the top.