Barf/slurp modify indentation of nested forms
Barfing and slurping have this colateral effect of re-indenting nested forms. For example:
(if (neg? i)
())
(cons (get s i)
(rec s (dec i)))
After ~~barfing~~ slurping at the if form:
(if (neg? i)
()
(cons (get s i)
(rec s (dec i)))) ; <-- re-indented
It would be valuable for these structural editing operations to preserve existing code layout as much as they can, both for:
- the case of intentional deviations from the formatting rules currently set at the IDE, and
- for the maintenance of existing code for which we don't want to introduce collateral formatting changes.
Automatic re-indents may be explicitly done when actually needed. (In my experience this is an extremely rare operation).
There could be a toggle setting to enable/disable reformat on structural editing.
Question is: how to define what should happen to the piece of code modified by the structural editing command just performed? From observation I think it's currently as simple as: 1. carry out structural editing 2. reformat affected code region. This would be more complicated with the introduction of the requested feature as far as I see.
Actually, I don't see the need for an configuration option. For the reasons above, I don't find value in performing that additional code reformatting. I never need it when performing these operations, and, if I ever need it, I can just invoke it explicitly. But it has little relation with my intent when doing barf or slurp.
I don't find value in performing that additional code reformatting. I never need it when performing these operations
You don't. I usually do and I don't think any of us are alone with our opinions.
I don't think any of us are alone with our opinions.
Of course we aren't, sorry if it sounded as if I'd want to impose anything. I'm just extrapolating from my own experience, and providing my context and reasons. In your case, do you find situations that don't fit with my assumptions above? When do you find yourself wanting to barf/slurp and having to apply re-indent to the nested form?
I normally format code according to rules set globally in Cursive or within the project so in most cases I'm happy with the current behavior. It allows me to not have to worry about how the context being modified used to be formatted as it'll be formatted automatically after the structural operation.
That being said I could see it being useful to be able to turn it off for specific projects where formatting is not as standardized as in the ones I most often work on.
Understood. I also usually format code in a uniform way, and only deviate from it when I find a better way to indent, worth enough introducing that deviation.
But this is the point: Clojure code, once written, is usually the way we want it. It will therefore not benefit from additional reformatting; it will basically have no effect. But, when it does have an effect, it will mess with the existing format, which most probably was intentional. Even when the reformatting was wanted, it is questionable if that it'd be tied to a barf/slurp operation, which seems more of an unexpected effect.
This holds for maintaining existing code. For introducing a change, I usually don't want to reformat colateral code, which is more likely in foreign codebases. If I actually want to reformat code, I have the possibility to easily do it, but that is not tied to my intent of barfing or slurping.
I consider this to be an accidental complection (I mean adding a reformat step to the barf and slurp operations). For instance, when using Paredit, if you rebalance forms by inserting or removing parentheses, the nested forms are not reformatted. This is an asymmetry between structural editing operations.
That being said I could see it being useful to be able to turn it off for specific projects where formatting is not as standardized as in the ones I most often work on.
The more standardized, the less effect will have to keep it turned off. It won't be noticeable.
Clojure code, once written, is usually the way we want it.
I find this a rather bold statement. Perhaps it's true for some piece of code at some point in time. I also normally don't want to worry about formatting at all, just want autoformat to happen all the time.
standardized, the less effect will have to keep it turned off. It won't be noticeable.
One exception are parts that for some reason do not adhere to the standards but I'd still like to have formatted once I check them in. So unless Colin is strongly opinionated in favour of your suggestion, I'd still like to have the option to keep the current behaviour.
Formatting the code after structural editing operations will always be the default in Cursive. I'm a firm believer in auto-formatting code, and I think with tools like gofmt, black and parinfer it's the way the industry is moving. I'll consider adding a flag for those that don't want it, though.
Thank you Colin for considering!
As said, I find it excessive as an option, but I'll love to be able to set it off! :) I think it will be valuable for everybody, if maybe not for their own codebases, at least for contributing to other codebases wihtout introducing unwanted changes.
In terms of auto-formatting, I find IDE capabilites great, and warnings and such, but I actually find it harmful to enforce, or automatically apply formatting without the programmer's intervention. At least in Clojure, I think there is value in applying certain exceptional formats occasionally for a more meaningful layout. But well, this would be a different discussion.
Just to be clear, after slurping in your example, you'd expect:
(if (neg? i)
()
(cons (get s i)
(rec s (dec i))))
Is that correct?
No, I would expect the nested form to be indented as a whole, but the relative indentation inside the nested form to be preserved as is, (so no autoformat applied to the nested form internals):
(if (neg? i)
()
(cons (get s i)
(rec s (dec i))))
The problem with that is that you do want some indentation, but not all of it. That gets very complicated to implement in all the cases that structural editing can throw up. Currently the implementation is basically "move the form inside the bracket and reformat" which is very easy. All the IntelliJ infrastructure is designed around keeping the code formatted according to the current settings.
It's an explicit non-goal for Cursive to support code as poetry. The way things are implemented at the moment, this change would introduce a lot of complexity for very marginal benefit.
I see, I had the suspicion that that might be the current implementation. So, this would require an ad hoc effort.
Not sure why you are introducing the "poetry" thing, as it hasn't been part of this conversation so far.
By poetry, I mean non-standard code layout for aesthetic reasons.
I've looked into this a bit, and there's something in the IntelliJ API that might support this, I'll try it out.
Thank you for persisting!
To be clear, I'm not raising this issue for aesthetic reasons, and I am also not thinking about it just from my personal use. (The indentation I use is pretty standard).
As an additional note about the value of this: In my experience I have seen unwanted re-indents, when unnoticed, being a common source of unwanted diffs (seemingly not a big deal, but they are when having to resolve conflicts) and lost indentation by the original developer (which effects vary from losing some info that the original developer wanted to convey, to getting a totally messed up format, or sometimes getting an illegal paredit/parinfer format).
Having more diffs than strictly required for a particular change is an issue, and would be the main reason I'd consider this change.
What is a non-goal for Cursive is to optimise for conveying information through formatting. I think that this is something that people think they will miss, but once they start using tools like black it quickly becomes a non-issue. For example, from Kent Beck writing about his experimental editor Prune:
We didn't miss formatting. We are both fussy about code formatting, but almost as soon as we were constrained to what the pretty-printer (esprima) gave us, we didn't waste any more thought on it.
In my experience that is typical.
Reformatting causing a totally messed up format, or resulting in an illegal state, should be reported as a bug and (hopefully) fixed accordingly. I don't think this is common at all, and I would definitely like to know about cases in which it happens.
Hi Colin, thanks for your response.
In this request, there isn't an ask to "optimize", rather to allow the programmer decide when to apply the automatic formatting options configured at the IDE. Otherwise, the developer needs to:
- Assume that there will be a deterministic format applied to arbitrary code in arbitrary operations, and undo as needed. As said, Clojure programmers may introduce occasional format deviations in the code. Also, there may be codebases where different styles are followed. In some cases, you will want to edit your application and some libraries from inside the same Intellij Project.
- Make sure that the IDE is fully setup for the code at hand, including all used macros and functions. I think this shouldn't be a precondition to using structured editing. This becomes more daunting the more straneous the code is.
Regarding experience, I can just share mine: I frequently have to undo these changes when working on my code (it could get better if I configured functions/macros according to my preference, but I'm ok with applying specific indentations on my own, and well, that would not cover exceptions). I have also found these issues when collaborating on real codebases where other developers have inadvertently made massive reindents that made resolution of conflicts much harder. That was detected in code already committed, so I guess it was frequently encountered when modifying code.
I'm not sure how to understand your objections above. You say that it's a non-goal to optimise for conveying information through formatting, but that's what we do through indenting: we convey the structure of the code visually. And Cursive goes a great extent to enable that, to the point of offering automatic indentation options for any function/macro depending on the semantics of its parameters. There may be other more ideal situations with other programming languages (you mention Prune), but how do those apply to us?
By the way, I found that it is also the case for the "rename" refactoring: some of the affected code gets reformatted. Did you finally find an alternative for rearranging affected code without fully reformatting it?
... Clojure programmers may introduce occasional format deviations in the code
This is what I'm saying Cursive will not optimise for. You can do it if you like, but you are likely to have to fix it up manually.
... there may be codebases where different styles are followed
Cursive allows you to have project-specific settings for this case.
I think this shouldn't be a precondition to using structured editing.
This is very unlikely to change.
... real codebases where other developers have inadvertently made massive reindents that made resolution of conflicts much harder
IntelliJ will allow you to only reformat lines which have been modified to try to minimise these conflicts. However, it doesn't always work since sometimes operations require more context to work (e.g. Cursive generally has to reformat an entire map if the user has map value alignment on).
Cursive goes a great extent to enable that, to the point of offering automatic indentation options for any function/macro depending on the semantics of its parameters.
It does, and the idea is that you decide how you want your code to look, and then Cursive will help you keep it like that.
... I found that it is also the case for the "rename" refactoring
Yes, this will be the case for many operations in Cursive.
Did you finally find an alternative for rearranging affected code without fully reformatting it?
Not one that will work for your example code in the issue description, no. There is an operation that will just adjust line indents rather than fully reformatting the block. But that will not help in your case, since it would indent the second line of the cons according to the current code indentation settings. It would preserve formatting internal to the block, so if the form you had slurped had columns that you had lined up manually, those would be preserved. But in your example case above, the only available solution at the moment is to configure the indentation of cons how you would like it.
It's unfortunate that there isn't a good way to implement this. It would have been nice to remove the autoformatting steps when not strictly needed, which can be silently invasive in some cases. We'll have to revert to just indenting with Parinfer, which doesn't have these colateral effects, when we want to make sure to preserve original indentation.
Thank you Colin for looking into this, and taking the time to follow up with the conversation!