mirror of
https://github.com/vale981/apheleia
synced 2025-03-04 17:11:40 -05:00
* [#62] Support functions as formatters Closes #62. Lets you use a lisp function as a formatter. This gives apheleia a lot more flexibility in regards to what constitutes a formatter. For example you can now plug an external language server or another tool as a formatter for use with apheleia. Here's a very basic example of using indent-line-function with apheleia after merging this commit. Note: this doesn't take into account any special local variables in the original buffer such as lisp-body-indent. It's really just for demonstration purposes and as a proof of concept. ```lisp (defun apheleia-indent-region+ (orig scratch callback) (with-current-buffer scratch (setq-local indent-line-function (buffer-local-value 'indent-line-function orig)) (indent-region (point-min) (point-max)) (funcall callback scratch))) (push '(indent-region . apheleia-indent-region+) apheleia-formatters) (push '(elisp-mode . indent-region) apheleia-mode-alist) (push '(lisp-interaction-mode . indent-region) apheleia-mode-alist) ``` * Fix misc-bugs + prevent race conditions * Update docstring * Reword a bit * Add to README Co-authored-by: Radon Rosborough <radon.neon@gmail.com>
This commit is contained in:
parent
e700c78a5d
commit
2e98165137
3 changed files with 115 additions and 46 deletions
|
@ -11,6 +11,9 @@ The format is based on [Keep a Changelog].
|
|||
* Support evaluating items in `apheleia-formatters` to make formatter
|
||||
commands more dynamic ([#50], [#55]).
|
||||
* Allow apheleia to format buffers without an underlying file ([#52]).
|
||||
* Support functional formatters ([#62]). You can now use a lisp
|
||||
function as a formatter allowing you to plug more powerful
|
||||
formatters into apheleia such as language servers.
|
||||
|
||||
### Formatters
|
||||
* [ClangFormat](https://clang.llvm.org/docs/ClangFormat.html) for
|
||||
|
@ -54,6 +57,7 @@ The format is based on [Keep a Changelog].
|
|||
[#52]: https://github.com/raxod502/apheleia/issues/52
|
||||
[#54]: https://github.com/raxod502/apheleia/pull/54
|
||||
[#55]: https://github.com/raxod502/apheleia/issues/55
|
||||
[#62]: https://github.com/raxod502/apheleia/issues/62
|
||||
[#64]: https://github.com/raxod502/apheleia/issues/64
|
||||
[#65]: https://github.com/raxod502/apheleia/pull/65
|
||||
|
||||
|
|
|
@ -119,6 +119,11 @@ variables:
|
|||
example a buffer called `*foo-bar.c*` that has no associated
|
||||
file will have an implicit file-name of `foo-bar.c` and any
|
||||
temporary files will be suffixed with a `.c` extension.
|
||||
* You can implement formatters as arbitrary Elisp functions which
|
||||
operate directly on a buffer, without needing to invoke an
|
||||
external command. This can be useful to integrate with e.g.
|
||||
language servers. See the docstring for more information on the
|
||||
expected interface for Elisp formatters.
|
||||
* `apheleia-mode-alist`: Alist mapping major modes and filename
|
||||
regexps to names of formatters to use in those modes and files. See
|
||||
the docstring for more information.
|
||||
|
|
152
apheleia.el
152
apheleia.el
|
@ -520,26 +520,17 @@ sequence unless it's first in the sequence"))
|
|||
or list of strings: %S" arg)))
|
||||
`(,input-fname ,output-fname ,stdin ,@command))))
|
||||
|
||||
(defun apheleia--run-formatters (commands buffer callback &optional stdin)
|
||||
"Run one or more code formatters on the current buffer.
|
||||
The formatter is specified by the COMMANDS list. Each entry in
|
||||
COMMANDS should be a list of strings or symbols (see
|
||||
`apheleia-format-buffer'). BUFFER is the `current-buffer' when
|
||||
this function was first called. Once all the formatters in
|
||||
COMMANDS finish succesfully then invoke CALLBACK with one argument,
|
||||
a buffer containing the output of all the formatters.
|
||||
|
||||
STDIN is a buffer containing the standard input for the first
|
||||
formatter in COMMANDS. This should not be supplied by the caller
|
||||
and instead is supplied by this command when invoked recursively.
|
||||
The stdout of the previous formatter becomes the stdin of the
|
||||
next formatter."
|
||||
(defun apheleia--run-formatter-command (command buffer callback stdin)
|
||||
"Run a formatter using a shell command.
|
||||
COMMAND should be a list of string or symbols for the formatter that
|
||||
will format the current buffer. See `apheleia--run-formatters' for a
|
||||
description of COMMAND, BUFFER, CALLBACK and STDIN."
|
||||
;; NOTE: We switch to the original buffer both to format the command
|
||||
;; correctly and also to ensure any buffer local variables correctly
|
||||
;; resolve for the whole formatting process (for example
|
||||
;; `apheleia--current-process').
|
||||
(with-current-buffer buffer
|
||||
(when-let ((ret (apheleia--format-command (car commands) stdin)))
|
||||
(when-let ((ret (apheleia--format-command command stdin)))
|
||||
(cl-destructuring-bind (input-fname output-fname stdin &rest command) ret
|
||||
(apheleia--make-process
|
||||
:command command
|
||||
|
@ -552,12 +543,7 @@ next formatter."
|
|||
(erase-buffer)
|
||||
(insert-file-contents-literally output-fname))
|
||||
|
||||
(if (cdr commands)
|
||||
;; Forward current stdout to remaining formatters, passing along
|
||||
;; the current callback and using the current formatters output
|
||||
;; as stdin.
|
||||
(apheleia--run-formatters (cdr commands) buffer callback stdout)
|
||||
(funcall callback stdout)))
|
||||
(funcall callback stdout))
|
||||
:ensure
|
||||
(lambda ()
|
||||
(ignore-errors
|
||||
|
@ -566,6 +552,69 @@ next formatter."
|
|||
(when output-fname
|
||||
(delete-file output-fname)))))))))
|
||||
|
||||
(defun apheleia--run-formatter-function (func buffer callback stdin)
|
||||
"Run a formatter using a Lisp function FUNC.
|
||||
See `apheleia--run-formatters' for a description of BUFFER, CALLBACK
|
||||
and STDIN."
|
||||
;; Will be an ugly name if you use a lambda for FUNC, instead of a symbol.
|
||||
(let* ((formatter-name (if (symbolp func) (symbol-name func) "lambda"))
|
||||
(scratch (generate-new-buffer
|
||||
(format " *apheleia-%s-scratch*" formatter-name))))
|
||||
(with-current-buffer scratch
|
||||
;; We expect FUNC to modify scratch in place so we can't simply pass
|
||||
;; STDIN to it. When STDIN isn't nil, it's the output of a previous
|
||||
;; formatter and we want to keep it alive so we can debug any issues
|
||||
;; with it.
|
||||
(insert-buffer-substring (or stdin buffer))
|
||||
(funcall func
|
||||
;; Original buffer being formatted.
|
||||
buffer
|
||||
;; Buffer the formatter should modify.
|
||||
scratch
|
||||
;; Callback after succesfully formatting.
|
||||
(lambda ()
|
||||
(unwind-protect
|
||||
(funcall callback scratch)
|
||||
(kill-buffer scratch)))
|
||||
;; Callback when formatting scratch has failed.
|
||||
(apply-partially #'kill-buffer scratch)))))
|
||||
|
||||
(defun apheleia--run-formatters (commands buffer callback &optional stdin)
|
||||
"Run one or more code formatters on the current buffer.
|
||||
The formatter is specified by the COMMANDS list. Each entry in
|
||||
COMMANDS should be a list of strings or symbols or a function
|
||||
\(see `apheleia-format-buffer'). BUFFER is the `current-buffer' when
|
||||
this function was first called. Once all the formatters in COMMANDS
|
||||
finish succesfully then invoke CALLBACK with one argument, a buffer
|
||||
containing the output of all the formatters.
|
||||
|
||||
STDIN is a buffer containing the standard input for the first
|
||||
formatter in COMMANDS. This should not be supplied by the caller
|
||||
and instead is supplied by this command when invoked recursively.
|
||||
The stdout of the previous formatter becomes the stdin of the
|
||||
next formatter."
|
||||
(let ((command (car commands)))
|
||||
(funcall
|
||||
(cond
|
||||
((consp command)
|
||||
#'apheleia--run-formatter-command)
|
||||
((or (functionp command)
|
||||
(symbolp command))
|
||||
#'apheleia--run-formatter-function)
|
||||
(t
|
||||
(error "Formatter must be a shell command or a Lisp \
|
||||
function: %s" command)))
|
||||
command
|
||||
buffer
|
||||
(lambda (stdout)
|
||||
(if (cdr commands)
|
||||
;; Forward current stdout to remaining formatters, passing along
|
||||
;; the current callback and using the current formatters output
|
||||
;; as stdin.
|
||||
(apheleia--run-formatters (cdr commands) buffer callback stdout)
|
||||
(funcall callback stdout)))
|
||||
stdin)))
|
||||
|
||||
(defcustom apheleia-formatters
|
||||
'((black . ("black" "-"))
|
||||
(brittany . ("brittany"))
|
||||
|
@ -582,38 +631,49 @@ next formatter."
|
|||
(terraform . ("terraform" "fmt" "-")))
|
||||
"Alist of code formatting commands.
|
||||
The keys may be any symbols you want, and the values are
|
||||
commands, lists of strings and symbols.
|
||||
shell commands, lists of strings and symbols, or a function
|
||||
symbol.
|
||||
|
||||
In Lisp code, the format of commands is similar to what you pass to
|
||||
`make-process', except as follows. Normally, the contents of the
|
||||
current buffer are passed to the command on stdin, and the output
|
||||
is read from stdout. However, if you use the symbol `file' as one
|
||||
of the elements of commands, then the filename of the current
|
||||
buffer is substituted for it. (Use `filepath' instead of `file'
|
||||
if you need the filename of the current buffer, but you still
|
||||
want its contents to be passed on stdin.) If you instead use the
|
||||
symbol `input' as one of the elements of commands, then the
|
||||
contents of the current buffer are written to a temporary file
|
||||
and its name is substituted for `input'. Also, if you use the
|
||||
symbol `output' as one of the elements of commands, then it is
|
||||
substituted with the name of a temporary file. In that case, it
|
||||
is expected that the command writes to that file, and the file is
|
||||
then read into an Emacs buffer. Finally, if you use the symbol
|
||||
`npx' as one of the elements of commands, then the first string
|
||||
If the value is a function, the function will be called with four
|
||||
arguments to format the current buffer: the original buffer that
|
||||
was being formatted (use this to access any relevent local
|
||||
variables or options that the formatter needs); a clone of the
|
||||
original buffer (that may have been modified by another formatter
|
||||
prior to being passed to the function); a callback that should be
|
||||
called when formatting is finished; and another callback that
|
||||
should be called when an error was raised during formatting.
|
||||
|
||||
Otherwise in Lisp code, the format of commands is similar to what
|
||||
you pass to `make-process', except as follows. Normally, the contents
|
||||
of the current buffer are passed to the command on stdin, and the
|
||||
output is read from stdout. However, if you use the symbol `file' as
|
||||
one of the elements of commands, then the filename of the current
|
||||
buffer is substituted for it. (Use `filepath' instead of `file' if you
|
||||
need the filename of the current buffer, but you still want its
|
||||
contents to be passed on stdin.) If you instead use the symbol `input'
|
||||
as one of the elements of commands, then the contents of the current
|
||||
buffer are written to a temporary file and its name is substituted for
|
||||
`input'. Also, if you use the symbol `output' as one of the elements
|
||||
of commands, then it is substituted with the name of a temporary file.
|
||||
In that case, it is expected that the command writes to that file, and
|
||||
the file is then read into an Emacs buffer. Finally, if you use the
|
||||
symbol `npx' as one of the elements of commands, then the first string
|
||||
element of the command list is resolved inside node_modules/.bin if
|
||||
such a directory exists anywhere above the current
|
||||
`default-directory'."
|
||||
:type '(alist
|
||||
:key-type symbol
|
||||
:value-type
|
||||
(repeat
|
||||
(choice
|
||||
(string :tag "Argument")
|
||||
(const :tag "Look for command in node_modules/.bin" npx)
|
||||
(const :tag "Name of file being formatted" filepath)
|
||||
(const :tag "Name of real file used for input" file)
|
||||
(const :tag "Name of temporary file used for input" input)
|
||||
(const :tag "Name of temporary file used for output" output)))))
|
||||
(choice
|
||||
(repeat
|
||||
(choice
|
||||
(string :tag "Argument")
|
||||
(const :tag "Look for command in node_modules/.bin" npx)
|
||||
(const :tag "Name of file being formatted" filepath)
|
||||
(const :tag "Name of real file used for input" file)
|
||||
(const :tag "Name of temporary file used for input" input)
|
||||
(const :tag "Name of temporary file used for output" output)))
|
||||
(function :tag "Formatter function"))))
|
||||
|
||||
(defcustom apheleia-mode-alist
|
||||
'((cc-mode . clang-format)
|
||||
|
|
Loading…
Add table
Reference in a new issue