Open vemv opened 2 years ago
This is a complex question, with many ramifications. To start off, you have referenced "macros", above, but my thinking is that you are referring to anything that shows up right after a left parenthesis. Sometimes these are things defined by defmacro
, and sometimes by defn
. I tend to refer to those things as "functions", which also includes those things which are actually defined by defmacro
. Not to be pedantic, but just to confirm we are really talking about the same things.
That said, while I have seen the CIDER "indent specs" here and there, as I am not a CIDER user I haven't spent time investigating them, nor have I had many (any?) requests about zprint interpreting them over the years. That doesn't mean that I'm not willing to explore the subject now.
Certainly zprint classifies (what I call) functions, using the :fn-map
in the options map. Basically this is a map of function names (as strings) to function types. The function types are documented here. Over time, the :fn-map
has been enhanced to accept not only a function type, but also a function type associated with an options map enclosed in a vector. You can see the default :fn-map
of zprint if you have it in a REPL by executing (czprint nil :explain)
which will output the current options map, which isn't small. It should be alphabetized, and the :fn-map
is there. Here is a bit of it:
":require" :force-nl-body,
"=" :hang,
"alt" :pair-fn,
"and" :hang,
"apply" :arg1,
"are" [:guided {:style :areguide}],
"as->" :arg2,
"assert-args" :pair-fn,
"assoc" :arg1-pair,
"assoc-in" :arg1,
"binding" :binding,
"case" :arg1-pair-body,
"cat" :force-nl,
"catch" :arg2,
"comment" :flow-body,
"cond" :pair-fn,
"cond->" :arg1-pair-body,
"cond-let" :pair-fn,
"condp" :arg2-pair,
"def" :arg1-body,
"defc" :arg1-mixin,
"defcc" :arg1-mixin,
"defcs" :arg1-mixin,
"defexpect" [:arg1-body
{:list {:respect-nl? true},
:map {:respect-nl? true},
:next-inner-restore [[:list :respect-nl?] [:map :respect-nl?]
[:vector :respect-nl?]
[:set :respect-nl?]],
:set {:respect-nl? true},
:vector {:respect-nl? true}}],
"defmacro" :arg1-body,
"defmethod" :arg2,
"defmulti" :arg1-body,
You can see that for the most part, the :fn-map
simply maps function names to function types. However, in some cases more complex operations happen. Note that are
uses a "guide", which is specific code for are
that justifies some of the elements. Note also that I have defined some complex capabilities for defexpect
, as I use expectations
for my more than 1000 tests, and this formats my testing files readably.
There are two issues with CIDER "indent specs" about which I'm not clear and which have a large effect on how they might fit with zprint:
:indent-only
which will never change lines (and which ignores function types because of that), :respect-nl
which keeps all of the newlines in the source, but might add some if things get wider than allowed by the :width
, and :respect-bl
which is "respect blank lines", where any existing blank lines are kept but otherwise newlines are ignored in favor what what zprint think makes sense.There are several ways that CIDER "indent specs" could be mapped to zprint function types.
{:list {:option-fn (fn [opts n exprs] ...)}}
. The option-fn
is called with the elements of the list, and it can return any options map it pleases (or nil). The returned options map can specify a function type. So if the option-fn could determine the "indent spec" it could access a mapping (or execute arbitrary code to determine a mapping) from "indent spec" to "zprint function type" and then return that.There is plenty of plumbing in zprint to get it to do different things. I could always add more.
To finish this up for now, I am interested in exploring how to enhance zprint to use CIDER "indent specs" to format functions. Whether that is translation or just outright implementation, I don't know. Given zprint could get access to the information, I think that there is probably a pretty good chance that I could figure out how to get zprint do most if not all of the "indent specs", though until I got into it I wouldn't know for sure. The chances of success would certainly be increased if you would be willing to answer the occasional question and try out various alpha releases I could make available to you.
To start off, you have referenced "macros", above, but my thinking is that you are referring to anything that shows up right after a left parenthesis
They can be functions, but it is not customary in clojure-mode/cider to define custom indentation rules for defns. In the end, anything that is not a macro does not define syntax, and therefore generally is not expected to alter a conventional, homogeneous style.
It isn't clear to me whether CIDER (using these "indent specs") will change what line something is on.
No, never. clojure-mode is only concerned with indentation.
However, if a given argument was placed in a non-customary position, it will be indented differently. This informs the user that his choice was odd.
You can see in red the non-customary user choice, and in green the customary user choice for if
:
-(if
- 1
- 2
- 2)
+(if 1
+ 2
+ 3)
In other words, for a macro like if
which indent spec is 1
, it is expected that the 1
first arguments will be in the same line as if
, otherwise those misplaced arguments get that extra indentation.
n.b., precisely because this indentation makes odd choices ugly, people will rarely choose those. So for simplicity, zprint could, instead of generating this "double indentation", ensure that all 1
first arguments are in the same line as if
.
So, for the question It isn't clear to me whether CIDER (using these "indent specs") will change what line something is on
, my answer would be "it doesn't, but it'd welcome if zprint did it"
Will it move something from the line it is on to a subsequent line if it doesn't "fit"? Is there even a concept of "fit", i.e., is there a width that the code is supposed to not go beyond?
(no)
It looks to me like the "indent specs" are held as metadata on the name of the function. Which is certainly convenient and makes a lot of sense, but not something that zprint usually has access to when formatting source. On the other hand, you say that you have translated CIDER "indent specs" to cljfmt "indents", and zprint and cljfmt share a common source parser, so how did you do that?
I invoke cljfmt from the same JVM that runs my main code. So I can easily translate var metadata to cljfmt config at runtime. I envision doing the same for zprint.
The project that does that is called formatting-stack. Another project that invokes cljfmt over the user's jvm is cider-nrepl (see its format ns).
There is plenty of plumbing in zprint to get it to do different things. I could always add more.
Appreciated! From my side I don't have a specific rush. It certainly will be useful medium/long-term and could easily mean that the mentioned projects would feature a zprint integration.
But my priority short-term would be e.g. https://github.com/kkinnear/zprint/issues/223 and other possible follow-up :rod
issues so I wouldn't like to distract you or ask more than I need.
Cheers - V
Thank you for both answering my questions, and clarifying what you care most about. I really appreciate it.
1.2.3
when it comes out. I have a plan for #226, but I haven't started on it yet as I'm still working on another issue. Once I finish #226 I'm expecting to generate the next release, 1.2.3
.I also really appreciate the test you created for #226. It will make testing that aspect of the fix much easier.
clojure-mode and CIDER have the notion of "indent specs" by which macros can declare a specific indentation. Some valid values under its spec are
0
,1
,:defn
, or more complex values like[2 nil nil [:defn]]
(n.b I almost never use such complex values)Quick examples: https://github.com/clojure-emacs/clojure-mode/blob/c339353f9e649b3af084f1bb6ce759e614a2f243/clojure-mode.el#L1651
More info: https://docs.cider.mx/cider/indent_spec.html
I wonder if zprint has a similar notion of mapping macro names to a specific style, and if you've ever faced the problem of translating one syntax into the other?
(This is not necessarily a feature request or such - I'm simply trying to get a full picture)
...With this information I could implement a zprint integration for formatting-stack. Currently it already translates "cider indent spec" to "cljfmt indent". This automatic translation is pretty essential for us since we really like
:style/indent
metadata attached to .clj defmacros.That way one defines intent once, agnostically, and gets similar formatting with a number of tools (clojure-mode, cider, cljfmt, now zprint)
Cheers - V