kkinnear / zprint

Executables, uberjar, and library to beautifully format Clojure and Clojurescript source code and s-expressions.
MIT License
554 stars 47 forks source link

Parinfer compatability #299

Closed kovasap closed 1 year ago

kovasap commented 1 year ago

I like using https://shaunlebron.github.io/parinfer/ when writing code, then running zprint on my final output to clean up the way it looks. This has been working really well for me for a while, but I run into an annoying recurring edge case.

When I have a list in my code where the first element is itself a long list, zprint wants to :flow the rest of the elements below with by default an :indent of 2. However, when this happens, the rest of the elements are indented further than the parens of the long list, leading parinfer to think they are part of the long list! Here is an example (_-_ means 3 spaces):

Without zprint formatting

(defn my-function
  [database things]
  ((apply comp
     (for [thing things]
       (partial my-other-funtion-that-takes-thing-and-database thing)))
_-_database))

With zprint formatting:

(defn my-function
  [database things]
  ((apply comp
     (for [thing things]
       (partial my-other-funtion-that-takes-thing-and-database thing))
_-_-database)))
              ^

A workaround I have is to reduce the indent levels that zprint uses for different forms. However, I kinda like larger indent levels :(. Is there any way to tell zprint to indent less in specific situations only?

My .zprint.edn looks like this:

; For clojure autoformatting.
; https://cljdoc.org/d/zprint/zprint/1.1.2/doc/zprint-reference
{:style [:hiccup :justified]
 :map {:comma? false
       :indent 0}
 ; These :indent settings are required to play nicely with parinfer
 :pair {:indent 0}
 :list {:indent 1}
 :binding {:indent 0}}
kkinnear commented 1 year ago

Thank you for asking! Yes, there is indeed a way to do exactly what (I think) you want. Typically the :fn-map is used to tell zprint how to format a specific function, but it also has several keywords it supports which tell it what to do when a list has a collection as its first element.

For example, I can tell zprint to use an indent of 1 for lists when a list also contains a list as its first element:

; The default

(czprint i299 {:parse-string? true})
(defn my-function
  [database things]
  ((apply comp
     (for [thing things]
       (partial my-other-funtion-that-takes-thing-and-database thing)))
    database))

; If you tell zprint what to do when a list has a list as its first element

(czprint i299 {:parse-string? true :fn-map {:list [:none {:list {:indent 1}}]}})
(defn my-function
  [database things]
  ((apply comp
    (for [thing things]
     (partial my-other-funtion-that-takes-thing-and-database thing)))
   database))

The reference manual says this:

Altering the formatting of lists which begin with collections

In addition to strings containing function names, you can use the keywords: :list, :map, :vector, and :set as keys in the :fn-map. When a list shows up with one of these collections as the first element, the :fn-map entry with that collection type will be used.

My simple trial with a map (and a set and a vector) as the first element of a list doesn't seem like it has a problem in the default case:

(czprint i299b {:parse-string? true})
(defn my-function
  [database things]
  ({:this :is, :a :test, :this :is, :only :a, :test :with, :a :lot, :of :stuff}
   database))

(czprint i299c {:parse-string? true})
(defn my-function
  [database things]
  (#{:this :is :a :test :only :with :lot :of :stuff :in :this :make :it :wide}
   database))

(czprint i299d {:parse-string? true})
(defn my-function
  [database things]
  ([(apply comp
      (for [thing things]
        (partial my-other-funtion-that-takes-thing-and-database thing)))]
   database))

Now you might wonder why maps and sets and vectors don't have an indent of 2 when they are the first element of a list, but lists do? I'm actually not quite sure either -- I can see in the code where lists are special cased to have the "normal" "body" indent of the list, and other collections are set to 1. At this time, I don't know just why I did that, though it was clearly done a good long time ago. The comments indicate some thinking about how a list could evaluate to a function, so that it needed to use the default body indent instead of the indent-arg, since this would be a "body function" instead of an "arg function", in the "community formatting" parlance. Which isn't something I have particularly good instincts for, but which many people (other than myself) use. I will do some more testing, but I'm thinking that this isn't a bug (at least for now).

So, as far as your example goes, I think the :fn-map entry for :list will take care of the specific example you gave me.

I don't know what your thinking behind the :indent values for :map, :binding and :pair is. I'm guessing that is unrelated to this issue with lists having collections as their first element, but rather that you don't like indents for the second thing in a pair to be different from the first thing in a pair. If there is something you would like different in these cases, please let me know and I'll work on it.

Thank you for asking how to do this. I'm glad to be able to offer you some configuration help to make zprint better meet your needs. For what it is worth, I had to look the details up in manual myself, as it has been a long time since I added some of these features.

kovasap commented 1 year ago

Thanks for the detailed response! Your :fn-map solution works perfectly, and also gives me some more insight into how I might solve similar problems like this going forward. Thank you so much!!

WRT to other :indent values I've set. I think I was using them to avoid similar issues, but I don't remember the details now. I'm going to try deleting them and seeing what happens. If I see issues, I'll try using something similar to this :fn-map solution to fix them. I love the detailed configurability of this tool!