Feature request: support https://codeberg.org/ideasman42/emacs-elisp-autofmt
Further, when the user has elisp-autofmt installed, apheleia should probably use it by default for elisp; it is much more powerful than lisp-indent .
I'm a bit wary of enabling automatic reformatting for Emacs Lisp by default, as there are not universal style guidelines that everybody agrees on. I might be convinced. In any case, there is nothing wrong with adding a formatter for it, which can be enabled by the user if they want.
I'm a bit wary of enabling automatic reformatting
Despite the name, elisp-autofmt provides commands to be used non-automatically, and used in this way it still has the significant advantage over lisp-indent in that the former does much more formatting.
Right, but if we register the formatter in apheleia-mode-alist by default, then it will indeed become automatic. Like I said I am fine with registering it in apheleia-formatters, though, and letting the user decide if they want it to be run automatically. With the formatter registered, it can always be run manually.
In case it helps anyone, I'm using this setup to integrate elisp-autofmt with apheleia:
(add-to-list
'apheleia-formatters
'(elisp-autofmt
.
(
;; Evaluates to the main external formatter command that would be run (synchronously) by
;; `elisp-autofmt-buffer', as a list of strings. This expression will be evaluated whenever
;; apheleia runs the formatter, and "flattened" into the main command definition.
;;
;; The logic to build the formatting command is rather complicated, so we extract it directly
;; from elisp-autofmt by advising some internals and running a "no-op" formatting pass.
;;
;; The formatting command executes a python script, with a number of flags (to set line width,
;; etc) based on values from the current buffer. It reads the source from stdin and outputs
;; the formatted contents to stdout, as expected by apheleia by default.
(let ((command nil))
(require 'elisp-autofmt)
(elisp-autofmt--with-advice
((
;; elisp-autofmt uses this to (synchronously) invoke formatting and other processes.
#'elisp-autofmt--call-process
:around
;; Intercept the formatting command from elisp-autofmt and store it in the local `command'
;; variable.
(lambda (orig-fn proc-id command-with-args stdin-buffer stdout-buffer)
;; elisp-autofmt also sometimes needs to generate cache files via external commands. We
;; don't want to interfere with those processes; they need to run before the main
;; apheleia formatter command can succeed.
;;
;; Check if this is the formatting process by looking for a python command which doesn't
;; include a "--gen-defs" argument.
(if (and (string-equal (car command-with-args) elisp-autofmt-python-bin)
(not (member "--gen-defs" command-with-args)))
(progn
;; Store the command so we can pass it to apheleia.
(setq command command-with-args)
;; Fake a "successfully executed" formatting run (response code 2 as expected by
;; elisp-autofmt, and no stderr output).
'(2 . nil))
;; If this isn't the formatting process (i.e. it's a caching process), just let it run
;; normally. This should occur pretty infrequently.
;;
;; Note these cache commands sometimes fail with broken pipe errors, even when using
;; `elisp-autofmt-buffer' directly. But the underlying processes do get triggered, and
;; the formatter seems to fix itself after a few attempts.
(funcall orig-fn proc-id command-with-args stdin-buffer stdout-buffer))))
;; Don't let elisp-autofmt actually write to the buffer at this point, as it
;; would fill it with empty output.
(#'elisp-autofmt--replace-region-contents-wrapper :override (lambda (&rest _))))
;; Now perform a fake formatting pass to intercept/store the formatting command. This
;; should be fast, since the formatting process isn't actually getting run (though note
;; that it's possible for cache processes to be run synchronously here, which may cause
;; temporary broken pipe issues as described above).
(elisp-autofmt-buffer)
(unless (listp command)
(error "Could not determine elisp-autofmt formatting command"))
;; Return the extracted command; apheleia will recognize it as a list of arguments.
command))
;; elisp-autofmt normally forces a non-zero exit code for the formatter's success case, which
;; is handled internally during elisp-autofmt processing. For apheleia we need to restore sane
;; exit code behavior.
"--exit-code=0")))
The basic idea is to intercept the (rather complex) python command that would normally be invoked synchronously during elisp-autofmt-buffer, and allow apheleia to invoke it asynchronously instead. Doing it this way respects the current value of things like fill-column (as opposed to using elisp-autofmt's built-in batch formatting script). It also allows elisp-autofmt to generate cache content when necessary.
Obviously this is pretty dependent on elisp-autofmt's internals, but afaict it's the cleanest way to build the command and leverage all the related elisp-autofmt logic.