Parinfer v3 in Cursive!

1,314 views
Skip to first unread message

Colin Fleming

unread,
Feb 8, 2018, 3:49:29 AM2/8/18
to Cursive

Hi everyone,

I’m pleased to announce that there’s a dev build of Cursive available containing the new integration of Parinfer, which has been a long time in the making. It’s released as a snapshot build, so no-one will get it by default, you’ll have to download it manually if you’d like to try it out. You can do this by going to the plugin repo page here, scrolling down a bit and then clicking on the “dev” link to see dev builds. Then choose the version of 1.7.0-snapshot1 which corresponds to your IntelliJ version and click the download link to manually download the plugin zip. You can then install it by going to Settings->Plugins->Install from disk… and pointing it at the downloaded archive.

There are two main changes to the integration. The first is that it now implements Parinfer v3, which implements “Smart mode”. One of the issues with Parinfer was always that you had to switch between Paren and Indent modes, and that was confusing for new users. I was convinced that it should be possible to create a hybrid mode, so I never actually exposed paren mode to Cursive users. This made Parinfer quirky in Cursive, and of course I never actually got around to developing a hybrid mode. However Ryan De La Torre and (in particular) Shaun LeBron have done some really amazing work, and Smart mode is now more or less what I always envisioned Parinfer should be. So Cursive continues to have a single Parinfer mode, which is now Smart mode.

Briefly, what Smart mode does is adjust the indentation of forms which begin on the line that you’re editing and continue onto subsequent lines, so that their nesting remains correct. However I’m hopeful that users won’t have to care what Parinfer is doing any more, and it should just do what you would intuitively expect. If that’s not the case, Shaun and I would love to know about it.

The second change is in how Parinfer is integrated into Cursive. One issue with Parinfer has always been that Indent mode requires files to be indented according to its invariants in order to work correctly. In order to ensure this, the Cursive integration (and others) would re-indent files using Paren mode when they were opened, which would make the minimal changes required to ensure that the file could be correctly processed using Indent mode. This often resulted in numerous indentation changes to working code, which was unacceptable to a lot of users in a work environment. Cursive had an additional problem in that it turns out that this cannot be done reliably in IntelliJ (see here for some really gory details if you’re interested). This meant that very occasionally Cursive would not re-indent a file when opened, and then Indent mode would break it.

The new integration no longer tries to do this. Instead, it highlights in the file where it is incorrectly indented, and doesn’t run Parinfer at all until the user has corrected the indentation manually. This means that files will no longer be re-indented just by looking at them, which is a big improvement. However if you do need to edit the file, currently you’ll need to fix the indentation everywhere in the file in order to be able to edit with Parinfer working. I’m going to try to make another improvement which would run Parinfer per top-level form, which would then mean that you would only need to correct the indentation locally in the part of the file you’re actually working on.

If you like Parinfer, please try it out and let us know how it goes!

Cheers,
Colin



Mark Addleman

unread,
Feb 8, 2018, 9:28:44 AM2/8/18
to Cursive
Fantastic!  I'm downloading now to give it a try

Colin Fleming

unread,
Feb 8, 2018, 10:35:56 PM2/8/18
to Cursive

I’ve just pushed 1.7.0-snapshot2 which fixes a rather embarrassing bug where Parinfer only worked in CLJ files, not CLJS, CLJC or EDN. If you’ve been having problems with it, give this build a try!

Cheers,
Colin

--
You received this message because you are subscribed to the Google Groups "Cursive" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cursive+u...@googlegroups.com.
To post to this group, send email to cur...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/cursive/etPan.5a7c0f14.10e252d8.1b1%40cursive-ide.com.
For more options, visit https://groups.google.com/d/optout.

Mark Addleman

unread,
Feb 9, 2018, 11:14:17 AM2/9/18
to Cursive
Hi Colin -

Some early feedback:  I really enjoy the new experience.  Throughout all of my typical editing operations, the new behavior matches my intuition.  I'll continue to play with it heavily over the weekend.

Very rarely, I've noticed the entire IDE UI lock up for several seconds.  No input events seem to be processed - keystrokes do nothing, the mouse moves but mouse cursor doesn't change shape.  I haven't noticed this before the new Cursive update and I believe it's only happened twice so far.  The files I'm editing are pretty small, normally less than a couple hundred lines.  I haven't captured any CPU data or stack snapshots yet.  Now that I know the pause can occur, I'll be ready.

Colin Fleming

unread,
Feb 9, 2018, 10:35:03 PM2/9/18
to cur...@googlegroups.com, Mark Addleman

Great, thanks Mark! I’m glad the new behaviour matches your intuition, that’s been my experience too.

Re: the performance issues, a CPU snapshot would be great if you can catch it. There’s some doc here about how to capture that in case you haven’t done it before.

Matthew Chadwick

unread,
Feb 10, 2018, 3:04:48 AM2/10/18
to cur...@googlegroups.com
I tried the latest ParInfer yesterday and somehow it instantly scrambled all my files. What it did was move seemingly random function bodies into their parameter vectors like this:

(defn qwe
  ([x y z]
    (* x y z)))

would become

(defn qwe
  ([x y z (* x y z)]))

I reverted everything and turned it off again

Wes Morgan

unread,
Feb 10, 2018, 12:27:26 PM2/10/18
to Cursive
First of all: Parinfer v3 is just 😃🤯🙌🏻🙇🏼‍♂️🌟💖!!! Thank you for all of your hard work on this. I have been wanting it to work this way since the first day I tried it, and I know it was a long road to get here. But wow, this is so good.

The first minor issue I've encountered is that except for the first line (which works), multi-line docstrings don't follow their containing form's indentation as it changes.

So for example, this form:

(defn foobar
"This is really the best function.
All other functions are sad.
If you want to call the best function,
you should just call this one."
[argh]
(println "You can't call this function with" argh ". It's too good."))

becomes this form:

  (defn foobar
"This is really the best function.
All other functions are sad.
If you want to call the best function,
you should just call this one."
[argh]
(println "You can't call this function with" argh ". It's too good.")))

...if you indent the opening paren before "defn".

Colin Fleming

unread,
Feb 11, 2018, 5:59:34 PM2/11/18
to cur...@googlegroups.com, Wes Morgan

Great, I’m glad it’s working well for you!

And yes, parinfer won’t reindent multi-line docstrings, as you’ve discovered. The whole docstring is really a single form as far as Clojure is concerned, and the only thing that matters is the indentation of the first line, so that’s what parinfer will adjust. Right now, the only solution is to indent those manually.

--
You received this message because you are subscribed to the Google Groups "Cursive" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cursive+u...@googlegroups.com.
To post to this group, send email to cur...@googlegroups.com.

Colin Fleming

unread,
Feb 11, 2018, 6:37:12 PM2/11/18
to cur...@googlegroups.com, Matthew Chadwick

Hi Matthew,

So that’s very concerning, especially since that’s the main bug this integration was designed to fix. A couple of questions:

  1. Firstly, are you sure you had the snapshot build installed (i.e. you downloaded it manually and installed it by hand)? This has actually been surprisingly confusing for some users due to a bug in the plugin repo display.
  2. If you definitely had that installed, did you enable parinfer with the status bar widget when you had the file open in the editor, or did you already have it enabled when you opened the file?
  3. You say it scrambled all your files - do you mean also files that you didn’t have open in an editor?
  4. That modification in particular looks really strange because it’s deleted a newline, which parinfer should never do. It definitely made those modifications with no other interaction in the editor?

Cheers,
Colin

Matthew Chadwick

unread,
Feb 12, 2018, 4:23:26 AM2/12/18
to cur...@googlegroups.com
here’s what I remember:


  1. Firstly, are you sure you had the snapshot build installed (i.e. you downloaded it manually and installed it by hand)? This has actually been surprisingly confusing for some users due to a bug in the plugin repo display.

I upgraded from setttings>plugins so no not manually - then I restarted IntelliJ and my previous projects were open...

  1. If you definitely had that installed, did you enable parinfer with the status bar widget when you had the file open in the editor, or did you already have it enabled when you opened the file?

…and I enabled parinfer from the status bar menu, it definitely wasn’t enabled before that

  1. You say it scrambled all your files - do you mean also files that you didn’t have open in an editor?

hmm don’t know about files I didn’t have open, but what I do remember is that several cljc and cljs files were transformed in that same way, with function bodies moved into the end of parameter vectors

  1. That modification in particular looks really strange because it’s deleted a newline, which parinfer should never do. It definitely made those modifications with no other interaction in the editor?

sorry I was really busy that morning so I just reverted everything, disabled parinfer and continued work, didn’t have time to reproduce the problem

Wes Morgan

unread,
Feb 12, 2018, 5:58:23 PM2/12/18
to Cursive
That's somewhat surprising. It seems like the whole point (or at least the headline feature) of Parinfer's new smart mode is to indent single Clojure forms together no matter how many lines they span. Why doesn't this apply to docstrings too?

If this is something that will be improved someday, then no problem. I might have misinterpreted your response as more of a "wontfix."

At any rate, it's pretty minor in the grand scheme of things.

Colin Fleming

unread,
Feb 12, 2018, 7:15:01 PM2/12/18
to cur...@googlegroups.com, Matthew Chadwick

I upgraded from setttings>plugins so no not manually

Well that’s a relief - that means you were still using the older parinfer integration. Since the newer one is still experimental, you have to install it manually as described in the first email in this thread.

--
You received this message because you are subscribed to the Google Groups "Cursive" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cursive+u...@googlegroups.com.
To post to this group, send email to cur...@googlegroups.com.

Colin Fleming

unread,
Feb 12, 2018, 7:20:00 PM2/12/18
to cur...@googlegroups.com, Wes Morgan

More than a “wontfix”, I think it’s out of scope for the Cursive integration part, it’s more something that you’d have to convince Shaun to get into Parinfer proper.

I don’t want to put words in his mouth, but I think Parinfer is mostly concerned with semantics rather than aesthetics. For example, Paren mode doesn’t actually indent code correctly as a formatter like Cursive’s or clj-format would do, it just tries to preserve what’s there and make the minimum changes required to meet Indent mode’s constraints. Indenting docstrings as you suggest only affect aesthetics, the code isn’t broken without doing that.

I’ll ping him and see if he’d like to comment over here.

Shaun Williams

unread,
Feb 12, 2018, 7:44:40 PM2/12/18
to Cursive
Hi Wes!  We have to preserve structure in front of a change, so we can’t shift multiline string indentation without breaking strings—changing the indentation inside a multiline string would change its value.  Mind you, this would be fine with docstrings because people don't really care there.  But there could be strings where that is not okay, so I can't let Parinfer touch them.

Hope that makes sense. Some new Lisp—can't seem to find it now—it ignores whitespace characters to the left of the opening quote on subsequent lines, and actually throws an error if it finds non-whitespace there.  That is the only thing that would make it safe for Parinfer to shift multiline strings like everything else.  Alas, we can't have this in Clojure.

Mark Addleman

unread,
Feb 13, 2018, 1:51:30 PM2/13/18
to Cursive
After some heavy editing over the past few days, I have not seen any problems.  I haven't seen a repeat of the CPU problem, either.  I'm just about willing to chalk that up to some weird X issues running on my Chromebook's chroot.  

All-in-all, the experience is great!

timothypratley

unread,
Feb 20, 2018, 8:20:07 PM2/20/18
to Cursive
Colin, do you have plans to put this in the EAP soon? :)

Colin Fleming

unread,
Feb 21, 2018, 4:14:34 AM2/21/18
to timothypratley, cur...@googlegroups.com

Yes, I do! There haven’t been any serious problems with it so it’ll definitely be in the next EAP. I’m investigating what’s involved in just running it over the affected top-level forms, once I have a clearer idea about that I’ll decide whether that will go in the first EAP or the second.




On 21 February 2018 at 14:20:10, timothypratley (timothy...@gmail.com) wrote:

Colin, do you have plans to put this in the EAP soon? :)
--
You received this message because you are subscribed to the Google Groups "Cursive" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cursive+u...@googlegroups.com.
To post to this group, send email to cur...@googlegroups.com.

Mark Addleman

unread,
Feb 24, 2018, 7:42:19 PM2/24/18
to Cursive
I've run into a minor but irritating problem.  If I have the form
(let [a b])

I want to change a to be {:keys [a]} and I end up with a malformed expression

Steps to reproduce:
  1. Delete the a
  2. Start typing { and the closing brace is automatically inserted
  3. Follow by typing :keys [ The closing bracket is not automatically inserted and I end up with a malformed expression.

Colin Fleming

unread,
Feb 27, 2018, 9:56:11 PM2/27/18
to cur...@googlegroups.com, Mark Addleman

Hi Mark,

So this is something that Shaun has struggled mightily with, and there really isn’t a great solution. The issue is that parinfer is very good at inferring structure from indentation, but in inline cases (i.e. insertion of an opening paren or mismatched closing paren in the middle of a line containing structure) it’s very hard to do because there is no indentation to infer from. Some relevant issues documenting the edge cases are #141 and #131.

Personally I’m not a fan of the current solution (leave broken code if a solution can’t be determined), so I was planning to always insert a matching closing paren when an open paren is typed. This would mean that you couldn’t get the paredit with no hotkeys effect that you can right now, in particular you’d have to remember the hotkeys for wrapping forms (which I don’t think is too big a deal, they’re relatively easy). I still haven’t worked through all the cases in my head for when the user types a close paren though, I’m not sure how that should work yet.

Cheers,
Colin

timothypratley

unread,
Apr 17, 2018, 2:23:55 AM4/17/18
to Cursive
Here is a common error I am making with parinfer:

(for [a (something)])

I've highlighted the closing square ] in red
I've just finished typing `something)`
My cursor is just before the red closing square ]
I type ) closing parenthesis in error.
I guess I meant to type ]... my intention is to put something in the body.
Now I get

(for [a (something))

If I press ctrl-Z to undo I get a blank line!
All my hard work typing is undone :(
I only wanted my closing ] back.

Notably this behaves quite different to an existing form

(for [a (something)] a)

In this case it INSERTS a ) but keeps the ] and undo works as expected.



Colin Fleming

unread,
Apr 17, 2018, 7:42:06 PM4/17/18
to cur...@googlegroups.com

I’ve investigated this a bit, and the closing paren mangling is a bug in the Cursive parinfer integration. The undo thing is very strange, but I don’t think I can do anything about that - IntelliJ handles all of that. Hopefully after fixing the closing paren issue that won’t be such a problem.

--
You received this message because you are subscribed to the Google Groups "Cursive" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cursive+u...@googlegroups.com.
To post to this group, send email to cur...@googlegroups.com.

Matthew Chadwick

unread,
Apr 18, 2018, 5:48:36 AM4/18/18
to cur...@googlegroups.com
Couple of odd ones here:

why does my repl not offer completions for new functions following a namespace reload ?

also, the repl repeatedly suggests that it can require a namespace it has already required several times

timothypratley

unread,
Apr 19, 2018, 1:54:42 PM4/19/18
to Cursive
Another weird parinfer behaviour:

Starting with

(let [[a] (foo bar)])

go to just after `a` and type {:keys [

Expected:

(let [[a {:keys []}] (foo bar)])

Actual:

Unclosed

timothypratley

unread,
Apr 23, 2018, 6:13:54 PM4/23/18
to Cursive
Hi Colin,


Here's another parinfer oddity:

(let [a ^int 1])

Put the cursor between ^int and 1
Press enter

Result:
(let [a ^int])
1

Expected:
(let [a ^int
      1])

(Parinfer reference implementation does the "expected" case)

Colin Fleming

unread,
May 3, 2018, 11:19:12 PM5/3/18
to timothypratley, cur...@googlegroups.com

Hi Timothy,

Ok, I’ve reworked the parinfer implementation a bit internally to make it easier to maintain it in lockstep with the reference implementation. Your (for [a (something)]) case is fixed as a result.

The (let [[a] (foo bar)]) is just the way the new parinfer implementation works, the reference implementation does the same thing. There’s a debate to be had about how this should work, I’m planning to write this up and do a poll, see #1964 for some discussion about this.

Your (let [a ^int 1]) case is actually not parinfer related, it’s something to do with the Cursive formatter - you can turn structural editing off altogether and it still does weird things. It’s something to do with the metadata hint I think.

Cheers,
Colin

--
You received this message because you are subscribed to the Google Groups "Cursive" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cursive+u...@googlegroups.com.
To post to this group, send email to cur...@googlegroups.com.

Timothy Pratley

unread,
May 4, 2018, 12:02:09 AM5/4/18
to Colin Fleming, cur...@googlegroups.com
Hi Colin,
 

Ok, I’ve reworked the parinfer implementation a bit internally to make it easier to maintain it in lockstep with the reference implementation. Your (for [a (something)]) case is fixed as a result.


nice :)
 

The (let [[a] (foo bar)]) is just the way the new parinfer implementation works, the reference implementation does the same thing. There’s a debate to be had about how this should work, I’m planning to write this up and do a poll, see #1964 for some discussion about this.

There is a subtle difference at play here... the new parinfer implementation produces correctly closed code at every stage when I use it on the website...
Whereas Cursive produces unbalanced code.

To illustrate; starting with (let [[a] (foo bar)])
and adding {:keys []} to get to:
(let [[a {:keys []}] (foo bar)])
Parinfer produces an intermediate stage
(let [[a {:keys (foo bar)}]])
Cursive produces an intermediate stage
(let [[a {:keys] (foo bar)])
As you can see the cursive form is "broken" but the parinfer form is correctly closed.
Both seem to successfully reach the end state!
The "broken" state feels like an error because it gets highlighted in red,
whereas the parinfer "closed" state feels like the normal parinfer behavior where every edit produces ballanced forms.
That said... they both result in the same final form when you type the closing symbols... so maybe it's just a tree falling in a forrest!

 

Your (let [a ^int 1]) case is actually not parinfer related, it’s something to do with the Cursive formatter - you can turn structural editing off altogether and it still does weird things. It’s something to do with the metadata hint I think.

Aha, wierd!


Regards,
Timothy 

Colin Fleming

unread,
May 4, 2018, 12:18:35 AM5/4/18
to Timothy Pratley, cur...@googlegroups.com

Just to be clear, you’re talking about the official demo site, right? I only see what you’re seeing there with the forceBalance option checked - without it, parinfer proper also creates a broken form. This is basically the tradeoff in the issue I linked which I’m planning to write up - forceBalance means that code will always be balanced, but with some counterintuitive behaviour. For example, in your intermediate state (let [[a {:keys (foo bar)}]]) I don’t think anyone would have expected the closing ] from [a] to be pushed out beyond (foo bar). See this comment for links to the relevant parinfer discussions.

Currently Atom has forceBalance on, and Cursive has it off. My reasoning is that when parinfer can’t actually be sure to do something sensible, the user should have the error marked and sort it out manually. But the others in that issue (and you, I think) are arguing that a fundamental premise of parinfer is that things are always balanced, and you’ll tolerate some weirdness to get that. I’d like to create a doc with some of the counterintuitive examples so people can see more clearly what the tradeoff is, and see what everyone thinks. It may just require a config flag, but then I have to document the logic in the doc somewhere, which will require several pages of doc just for one flag :-)

Timothy Pratley

unread,
May 4, 2018, 1:13:35 AM5/4/18
to Colin Fleming, cur...@googlegroups.com

Just to be clear, you’re talking about the official demo site, right?

Yup :)
 

I only see what you’re seeing there with the forceBalance option checked - without it, parinfer proper also creates a broken form.

I don't see any "forceBalance" options to check or uncheck or string matches on the page (http://shaunlebron.github.io/parinfer/index.html); where can I learn more about balancing the force? (joking!)
 

This is basically the tradeoff in the issue I linked which I’m planning to write up - forceBalance means that code will always be balanced, but with some counterintuitive behaviour. For example, in your intermediate state (let [[a {:keys (foo bar)}]]) I don’t think anyone would have expected the closing ] from [a] to be pushed out beyond (foo bar). See this comment for links to the relevant parinfer discussions.

Agreed; the behavior seems wierd to me... I expected the intermediate state to be (let [[a {:keys}] (foo bar)])

It is not clear to me why this is a 'tricky' edit at all... I'm opening a new form inside an existing form, it should be closed.
But I see that's not how it works with or without forceBalance.

The way I see it there are three situations when introducing delimiters:

(a |b c)
=> (a (b c))

(a | b c)
=> (a () b c)

(a b c |)
=> (a b c ())
 
Here the | pipe represents cursor position, and I type ( to start a new form. This seems like a pretty universal approach regardless of the delimiter or nesting... but I'm sure there are several good reasons why I'm not seeing the bigger picture here. It's not up to me how it should work, that's just what seems to make sense to me.

Currently Atom has forceBalance on, and Cursive has it off. My reasoning is that when parinfer can’t actually be sure to do something sensible, the user should have the error marked and sort it out manually. But the others in that issue (and you, I think) are arguing that a fundamental premise of parinfer is that things are always balanced, and you’ll tolerate some weirdness to get that. I’d like to create a doc with some of the counterintuitive examples so people can see more clearly what the tradeoff is, and see what everyone thinks. It may just require a config flag, but then I have to document the logic in the doc somewhere, which will require several pages of doc just for one flag :-)

1) Personally I'm not interested in a flag. I prefer not to customize it and will probably ignore it and leave it at default if it exists :)
2) I don't have an argument or a position; my motivation for reporting it was based on comparing it with what I thought was reference behavior and surprise that imbalances could exist.
3) I did have a notion that parinfer promises to keep things balanced. I blame Shauns impressive mathematical notation and gear animations. But I can also accept the reality that sometimes the world is complicated. o_O
 

Timothy Pratley

unread,
May 4, 2018, 1:18:35 AM5/4/18
to Colin Fleming, cur...@googlegroups.com
Yup :)

Ahhhh..... and by yup I mean nope, I was looking at an entirely different page from the "demo" page you linked
 while I've been looking at
   http://shaunlebron.github.io/parinfer/index.html

Your page has a force checkmark on it! Mine doesn't :(
Hence my confusion.

timothypratley

unread,
May 7, 2018, 6:38:47 PM5/7/18
to Cursive
Pasting does not obey insert rules:

(a)
 |

paste b

Expected (and as per parinfer reference demo with our without force):
(a
 b)

Actual:

(a)
b

Note that typing b instead of pasting b works as expected.

Reply all
Reply to author
Forward
0 new messages