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
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
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.
To view this discussion on the web visit https://groups.google.com/d/msgid/cursive/b1dac126-9638-48f3-8952-de111b530cad%40googlegroups.com.
(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."))
(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.")))
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.
Hi Matthew,
So that’s very concerning, especially since that’s the main bug this integration was designed to fix. A couple of questions:
Cheers,
Colin
- 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.
- 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?
- You say it scrambled all your files - do you mean also files that you didn’t have open in an editor?
- 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?
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.
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.
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.
(let [a b])
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
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.
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.
(let [a ^int 1])
Put the cursor between ^int and 1Press enter
Result:
(let [a ^int])
1
Expected:
(let [a ^int1])(Parinfer reference implementation does the "expected" case)
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
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.
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 :-)
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 :-)
Yup :)