-
Notifications
You must be signed in to change notification settings - Fork 119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix #45: Thread first changes the indentation of the inner forms #344
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the PR. This obviously needs to be configurable before it can be merged. Ideally this would happen through an indentation rule, e.g. :thread
to complement :block
, though that might be a little tricky.
I think I need to take another look at the indentation logic anyway. I don't have a lot of time over the next couple of weeks, but I'll see if I can squeeze in a few hours to go over the indentation system.
cljfmt/src/cljfmt/core.cljc
Outdated
(defn- adjust-idx [zloc idx context] | ||
(cond-> idx | ||
(and (parent-is-thread-first? zloc context) (not (is-first-parameter? zloc))) | ||
(some-> dec (max 0)))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Instead of adjust-idx
which doesn't convey why the index is being adjusted, perhaps instead we just split out the predicate:
(defn- in-thread-macro? [zloc context]
(and (form-matches-thread-macro? zloc) (not (first-argument? zloc))))
Then we could write:
(cond-> idx (in-thread-macro? zloc context) dec)
I'm unsure why you have (some-> dec (max 0))
instead of just dec
. Under what circumstances would the index be less than or equal to zero?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't idx
be zero in this case, if I configured {cond-> [[:block 0]]}
?
There is a check for (nil? idx)
in inner-indent
already. Isn't it nil when there is no indentation rule, like (-> x foo bar)
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The index is the position of the child in the parent form, IIRC. So, come to thing about it, we could change (not (first-argument? zloc))
to (> idx 1)
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't the index come from the :indents as specified here?
So for :block
it uses default indenting until we reach the index. So if we are in a thread, we should basically hit that index one spot before (ie. decrease the index). If the index is 0, we should keep it at 0.
For :inner
when index is set, it is restricted to that particular index. So that should also occur one spot earlier (ie. decrease the index). If the index is 0, then we should avoid using this indentation rule, since the spot it was meant for is not present.
I also oversaw the depth
argument of inner. Here we should check whether the parent of the top
zloc is wrapped in a thread. I'll commit a fix.
Hope it makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, yes, I believe you're right. I need to refresh myself on the indentation logic as it's been a while since I've looked at it.
As I see it, the tricky part about having a |
cljfmt/src/cljfmt/core.cljc
Outdated
(defn- form-matches-thread-macro? [zloc context] | ||
(let [possible-sym (form-symbol zloc) | ||
fully-qualified-sym (fully-qualified-symbol possible-sym context) | ||
sym-without-namespace (remove-namespace possible-sym)] | ||
(some #(or (symbol-matches-key? fully-qualified-sym %) | ||
(symbol-matches-key? sym-without-namespace %)) | ||
#{'-> 'as-> 'some-> 'cond->}))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about doing it this way:
(defn- form-symbol-matches? [zloc pred context]
(let [possible-sym (form-symbol zloc)]
(or (pred (fully-qualified-symbol possible-sym context))
(pred (remove-namespace possible-sym)))))
(defn- form-matches-key? [zloc key context]
(form-symbol-matches? zloc #(symbol-matches-key? % key) context))
(defn- form-matches-thread-macro? [zloc context]
(form-symbol-matches? zloc #{'-> 'as-> 'some-> 'cond->} context))
Yes, from a user perspective, we'd expect indentation rules to be all in one place, but threaded indentation is tricky because it depends on both the indentation rules for the current form and the indentation rules for the outer form. It gets even trickier when we consider a form like |
All right, I thought a bit about it and gave it another shot.
I haven't written any documentation yet, since I wanted to hear your take on the approach @weavejester, before doing that work. Let me now what you think. |
I hope this test (the last one) might reflect the recursion:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I need to give this some thought on how best to approach this. The abstraction for how indentation is applied needs to be changed somehow. Apologies for being slow on this; I haven't had a lot of time to delve into it deeply.
(defn- get-zipper-position [zloc] | ||
(loop [idx 0 | ||
loop-zloc (z/leftmost zloc)] | ||
(cond | ||
(= zloc loop-zloc) idx | ||
(= loop-zloc (z/rightmost zloc)) nil | ||
:else (recur (inc idx) (z/right loop-zloc))))) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How does this differ from index-of
?
(defmethod indenter-fn :thread [_ _ _] | ||
(constantly nil)) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to rethink how the indentation functions are applied, as the abstraction has clearly failed if we have an indenter function that does nothing.
No worries, I postponed this fix for several weeks. I also thought about an approach, where the indentation happens by going through the AST recursively. Feel free to reach out, if you get time at some point and envision a solution that I can help with. |
Here is an attempt to solve issue #45.
The concrete issue is nesting a
->
with acond->
inside. Becausecond->
has indent[[:block 1]]
, the parameters will not be indented in pairs as intended.As mentioned in this comment, the indentation of an inner form within a
->
does not take the threading into account.The proposed solution is to take the parent node into account, when determining the index of the indentation of the current node.
The check is:
I just made this fix for thread-first macros in Clojure core. We could maybe do a regex instead?
Please let me know, if I need to do any documentation or something else is missing.