Meta Redux: Smarter Form Targeting Is Coming to CIDER

Wait 5 sec.

If I had a dollar for every time someone asked on the Clojurians Slack in#cider why C-x C-e evaluated “the wrong thing”, I’d probably be writing thispost from a yacht. The answer was always the same: the cursor wasn’t whereCIDER expected it to be. The upcoming CIDER 2.1 release changes that - theevaluation commands now figure out which form you mean from where your cursoractually is.A bit of historyEmacs has a very particular tradition when it comes to evaluating code:eval-last-sexp (the venerable C-x C-e) acts on the expression before thecursor. Not the one you’re looking at, not the one you’re inside of - the onethat ends exactly where your cursor stands. SLIME follows this tradition,Emacs Lisp itself follows it, and for the past 15+ years CIDER has followed ittoo.1 If you grew up in Emacs, this rule is in your fingers and you’venever once thought about it.Here’s the thing, though - most people using CIDER didn’t grow up in Emacs.And many of them never programmed in Emacs Lisp and Common Lisp with SLIME.They came to Emacs because of CIDER (or Clojure in general), and for themthe rule is invisible, arbitrary and mildly hostile. You put your cursor on aform, you press the eval key, and CIDER cheerfully evaluates… somethingelse. Meanwhile every other modern Clojure environment - Calva, Conjure, thevarious vim plugins - resolves the form from the cursor position and just doeswhat you meant.2For a long time I resisted changing this, mostly out of respect for the Emacstradition (and my own muscle memory). But at some point I had to admit that Iwas optimizing for the wrong audience. CIDER’s users are mostly casualClojure hackers who happen to use Emacs, not Emacs experts who happen towrite Clojure. The tradition was serving me, and confusing them.What’s actually changingThe evaluation commands (and their macroexpansion, inspection and tappingsiblings) now resolve “the form the cursor indicates”. Concretely, with |marking the cursor:(map inc |(range 10))Pressing C-c C-e here used to evaluate inc - the form before the cursor,which is almost never what you wanted. Now it evaluates (range 10) - theform your cursor is pointing at.(str "hello" " " "world"|)This one used to evaluate "world" (really!), because the last completeexpression before a cursor sitting on the closing paren is the final string.Now it evaluates the whole (str ...) call.Macroexpansion benefits too:(when tru|e (launch-missiles))C-c C-m here used to complain that true is not a macro. Now it expands theenclosing (when ...) call, because expanding a bare symbol is never whatanyone means.And my favorite one - the rich comment workflow is now consistent everywhere:(comment (calculate-all-the-things|))Every defun-level command - eval, pretty-print, inspect, debug - now treatsthe form inside the (comment ...) as the top-level one. Evaluating a wholecomment form returns nil by definition, which has exactly zero uses, soCIDER no longer does that no matter which command you reach for.Why you probably won’t noticeHere’s the part I’m most pleased with: the new behavior agrees with the oldone at every position where “the form before the cursor” made sense. Cursorright after a form? Same result as always. Cursor in the whitespace after aform? Same. Cursor in the middle of a symbol? Same. The two behaviors onlydiverge where the classic answer was something nobody ever wanted - aprevious sibling, a lone trailing atom.So if your muscle memory follows the Emacs tradition, nothing changes foryou. If it doesn’t - CIDER stops punishing you for it. That’s the wholechange.Reverting to the classic behavior (for now)If you do want the traditional rules - maybe you genuinely use“evaluate the previous sibling while standing on an opening paren” - onesetting restores them exactly:(setq cider-form-targeting 'preceding)There’s also a per-session toggle in the eval menu (C-c C-v T) that showsthe active mode in the mode line while you experiment.This option is probably living on borrowedtime, though. I added it back when I planned to keep the classic behavior as thedefault and offer smart targeting as an opt-in. Now that the roles arereversed, an option whose only job is restoring rules almost nobodydeliberately relied on doesn’t really make much sense, and lately I’ve beentrying to trim that kind of clutter from CIDER, not add to it. Don’t besurprised if the option quietly disappears - possibly even before the releaseships. Which is one more reason to speak up now if the classic behaviorgenuinely matters to you.Farewell, “last sexp”This change forced my hand on something I’d been putting off for years - thecommand names. cider-eval-last-sexp is a fine name for a command thatevaluates the last sexp. It’s a lie for a command that evaluates the formyour cursor indicates. So the commands got honest names: cider-eval-last-sexp is now cider-eval-form cider-eval-defun-at-point is now cider-eval-defun (the -at-pointnever carried information) likewise for the pprint/tap/inspect/insert variantsEvery old name keeps working as an alias, so your config and your M-xhabits are safe. But why “form” and not “sexp”? Beyond the targeting change,there’s a Clojure-specific reason: in Clojure a form isn’t always a singlesexp. ^:private x is two sexps but one form; so is #inst "2024-01-01".The commands operate on forms - the reader’s unit of evaluation - and nowthey say so.3 The manual’s evaluation docs got a proper glossaryexplaining all of this.Closing thoughtsAll of this is on master and in the MELPA snapshots today, ahead of thenext stable release. I’d really love for people to play with it before therelease ships - especially if you’re an Emacs veteran whose fingers disagreewith my reasoning, or a newcomer for whom this was supposed to just work. Didwe get the resolution rules right? Does anything still surprise you?Share your feedback on the CIDER discussions board, in#cider on the Clojurians Slack, or just file an issue. This is exactly thekind of change that’s easy to adjust before a release and painful after -and the fate of the compatibility option depends on what I hear.That’s all I have for you today. Keep hacking! CIDER started its life as a SLIME “clone” for Clojure, after all - the tradition runs deep. ↩ Interestingly, Cursive is the only major non-Emacs Clojure environment that kept the classic “form before the caret” model. ↩ This also explains a subtlety Emacs veterans might appreciate: plain forward-sexp movement doesn’t know that Clojure metadata belongs to the form it annotates, which is why clojure-mode has always needed its own “logical sexp” movement functions. The new targeting is built on those, so metadata is never silently dropped from what you evaluate. ↩