Configuration for Emacs Lisp?
Apologies for the off-topic, but as the Clojure and Emacs community have a significant overlap, I was wondering whether anyone is using zprint to format elisp, and would share the configuration?
What an interesting question! I expect the biggest impediment to using zprint on elisp would be the rewrite-clj parser used by zprint. I would be amazed if it parsed a large enough subset of elisp to be useful, but perhaps you have passed your elisp to zprint already and found that it, at least, parsed correctly?
If you get that far, I'd be happy to help you configure zprint to format your elisp the way that you want it formatted. It would help if you sent me the elisp in question formatted the way that you want zprint to format it.
What an interesting question! I expect the biggest impediment to using zprint on elisp would be the
rewrite-cljparser used by zprint. I would be amazed if it parsed a large enough subset of elisp to be useful, but perhaps you have passed your elisp to zprint already and found that it, at least, parsed correctly?
Correct. The only thing it did not like was multiline strings, i.e.:
(error "[run-command] Experiment `%S' does not exist, \
please remove from `run-command-experiments'" experiment-name))))
Which, at least to me, isn't important at all. I did, however, only try with my own code. I'll do some more testing with Emacs's own sources, to see how far this gets.
If you get that far, I'd be happy to help you configure zprint to format your elisp the way that you want it formatted. It would help if you sent me the elisp in question formatted the way that you want zprint to format it.
That's very kind of you! Thank you. I'll follow up here after checking the above.
I've collected some samples at https://github.com/bard/elisp-zprintrc in the form of failing tests, and I'm a little less optimistic.
On the parsing front, there are some issues with escapes (e.g. https://github.com/bard/elisp-zprintrc/blob/d4f6f66e0ead519d8bea1c6baee9d06148a300be/parse-tests/edt-user.el#L31) and unusual function names (e.g. https://github.com/bard/elisp-zprintrc/blob/d4f6f66e0ead519d8bea1c6baee9d06148a300be/parse-tests/calc-alg.el#L20). But perhaps they could all be taken care of by preprocessing (cue jokes about regular expressions...) or just avoiding the forms. Output of the parse test run: https://github.com/bard/elisp-zprintrc/blob/master/parse-tests.out
On the formatting front, I could get some of the behavior I was looking for. I didn't find a way to e.g. have this wrap on a single line:
(defun foo (bar) 1)
Or have the first branch of an if indent more:
(if foo
bar
baz)
Output of the full format test run: https://github.com/bard/elisp-zprintrc/blob/master/format-tests.out
Do you think there's hope, or is Emacs Lisp style just too quirky?
I don't know if there is hope or not for this approach. I haven't looked at the parsing issues at all, but from the formatting aspect I can make four of your format tests and one of your examples from above to format correctly. But I don't know if this is just the tip of the iceberg or not.
zprint.core=> (czprint i197 {:parse-string? true :fn-map {"cond" :none "defcustom" :arg2 "defun" :arg2 "if" [:guided {:guide [:element :element :newline :spaces 3 :element :newline :spaces 1 :element]}] "pcase" :arg1-body :default [:none {:list {:hang? false}}] "t" [:none {:list {:hang? true}}] "error" [:none {:list {:hang? true}}] "_" [:none {:list {:hang? true}}] "save-excursion" :none-body} :list {:indent-arg 1}})
(cond
((eq arg '-) (comment-kill nil))
(arg
(comment-normalize-vars)
(save-excursion
(beginning-of-line)
(comment-search-backward)
(beginning-of-line)
(goto-char (comment-search-forward (line-end-position)))
(setq comment-column (current-column))
(message "Comment column set to %d" comment-column))
(comment-indent))
(t (setq comment-column (current-column))
(message "Comment column set to %d" comment-column)))
nil
zprint.core=> (czprint i197a {:parse-string? true :fn-map {"cond" :none "defcustom" :arg2 "defun" :arg2 "if" [:guided {:guide [:element :element :newline :spaces 3 :element :newline :spaces 1 :element]}] "pcase" :arg1-body :default [:none {:list {:hang? false}}] "t" [:none {:list {:hang? true}}] "error" [:none {:list {:hang? true}}] "_" [:none {:list {:hang? true}}] "save-excursion" :none-body} :list {:indent-arg 1}})
(defcustom pcmpl-unix-group-file "/etc/group"
"If non-nil, a string naming the group file on your system."
:type '(choice file (const nil))
:group 'pcmpl-unix)
nil
zprint.core=> (czprint i197b {:parse-string? true :fn-map {"cond" :none "defcustom" :arg2 "defun" :arg2 "if" [:guided {:guide [:element :element :newline :spaces 3 :element :newline :spaces 1 :element]}] "pcase" :arg1-body :default [:none {:list {:hang? false}}] "t" [:none {:list {:hang? true}}] "error" [:none {:list {:hang? true}}] "_" [:none {:list {:hang? true}}] "save-excursion" :none-body} :list {:indent-arg 1}})
(defun greet (name) (message "Hello, %s!" name))
nil
zprint.core=> (czprint i197c {:parse-string? true :fn-map {"cond" :none "defcustom" :arg2 "defun" :arg2 "if" [:guided {:guide [:element :element :newline :spaces 3 :element :newline :spaces 1 :element]}] "pcase" :arg1-body :default [:none {:list {:hang? false}}] "t" [:none {:list {:hang? true}}] "error" [:none {:list {:hang? true}}] "_" [:none {:list {:hang? true}}] "save-excursion" :none-body} :list {:indent-arg 1}})
(if (equal value "Hello, world!")
(message "it's a greeting")
(message "it's something else"))
nil
zprint.core=> (czprint i197d {:parse-string? true :fn-map {"cond" :none "defcustom" :arg2 "defun" :arg2 "if" [:guided {:guide [:element :element :newline :spaces 3 :element :newline :spaces 1 :element]}] "pcase" :arg1-body :default [:none {:list {:hang? false}}] "t" [:none {:list {:hang? true}}] "error" [:none {:list {:hang? true}}] "_" [:none {:list {:hang? true}}] "save-excursion" :none-body} :list {:indent-arg 1}})
(pcase run-command-completion-method
('helm (run-command--helm))
(_ (error "[run-command] Unrecognized completion method: %s"
run-command-completion-method)))
nil
zprint.core=> (czprint i197e {:parse-string? true :fn-map {"cond" :none "defcustom" :arg2 "defun" :arg2 "if" [:guided {:guide [:element :element :newline :spaces 3 :element :newline :spaces 1 :element]}] "pcase" :arg1-body :default [:none {:list {:hang? false}}] "t" [:none {:list {:hang? true}}] "error" [:none {:list {:hang? true}}] "_" [:none {:list {:hang? true}}] "save-excursion" :none-body} :list {:indent-arg 1}})
(defun foo (bar) 1)
nil
All of the examples above use the same options map, just so you aren't trying to compare them,
The formatting for if uses a newly created capability (that I have shipped in 1.1.2) that might not be fully general, though if is a pretty constrained function, actually.
I don't understand your point:
On the formatting front, I could get some of the behavior I was looking for. I didn't find a way to e.g. have this wrap on a single line:
(defun foo (bar) 1)
The i197e example above seems to me to be what you are looking for, but if not, please show me what you want.
I can't quite figure out which functions are supposed to allow hangs and which are not. So I've had to put a lot of stuff in the options map for the functions that allow hangs based on your examples. Alternatively, if you are open to using :respect-nl (respect new lines) as a style, the formatting is a lot easier -- since every newline you have in the input will be replicated in the output. So that would remove most of the issues where I had to add :list {:hang? true} to a lot of functions and make things much easier. But that may not be what you want, I don't know.
Having done all of this (which was the work of about 40 minutes, just FYI), I don't know if this is 5% of what you need or 75% of what you need from a formatting standpoint, I'd be more than glad to do another 5-15 of these, but if we are talking 40 or 50, I'm probably going to run out of steam. And as I said, I haven't dealt at all with the parsing issues. I don't have any realistic ability (or, to be more accurate, desire) to modify the parser to handle elisp better. If you can find a way to pre and post process things to get the parser to deal with it, then I think there might be a path to get what you want from a formatting standpoint.
Thanks for having a go. I didn't mention changing zprint as an options for addressing the parsing issues because I don't think it's reasonable to expect a tool for Clojure to bend all the way to support elisp. The reason why I tested a wider selection of sources is that there would be little point to the exercise if parsing issues were affecting very common forms.
Your examples are a great starting point, I'll play with them. I get the impression that a significant chunk of the work here is getting indentation right, so maybe leaving the indentation to Emacs as a post-processing step makes the most sense.
With regard to :respect-nl bit, would it cause something like this:
(defun foo (bar)
(message "hello"
)
)
To not be formatted into something like this?
(defun foo (bar)
(message "hello"))
In that case it might be a no-go. But from your examples it looks like zprint allows finer granularity, so maybe it can be told "leave just the first line of a defun/defcustom/defvar/etc alone".
As it happens, :respect-nl will indeed respect all newlines, so it would leave this:
(defun foo (bar)
(message "hello"
)
)
alone. There is not (yet?) a ":smart-respect-nl` which would only respect the important newlines and ignore the ones that weren't important.
However, as most formatting in Clojure is focused on the first line, there are plenty of ways to influence the first line of a function. There isn't one which says ":respect-first-line-newline" or something, but if you know how many elements you want on the first line of a particular named function, I can quite easily get zprint to do that for all occurrences of that particular function.
Likewise, if you know the names of the specific functions that should format with what I call a "hang":
(like this
foo
bar
baz)
or with what I call a "flow"
[like
this
foo
bar
baz)
Then I can make one the default and set up the formatting for the others to be specific.
I didn't put much of any effort into getting the indentation right. I don't think of hang and flow as indentation issues, since what happens to the second element in the list is the important thing. You may be right about letting emacs do the indentation, but I'm not ready to give up on zprint doing that right yet.
Let me know what I can do to help.
I'm guessing that you are not going to pursue this, but if I'm wrong, please reopen this issue! Thanks!
Hey, yes, sorry for neglecting to update the issue myself, in the mean time I started using https://codeberg.org/ideasman42/emacs-elisp-autofmt and it fit the bill.