doomemacs/modules/editor/format/autoload/settings.el
Henrik Lissner 62ee557c32
feat(format): set-formatter!: assign existing formatter to mode(s)
Allows:

  (set-formatter! 'ruff :modes '(python-mode python-ts-mode))

Prior to this, you'd have to supply the whole definition to assign an
existing formatter to new modes.
2025-12-17 02:17:33 -05:00

103 lines
4.1 KiB
EmacsLisp

;;; editor/format/autoload/settings.el -*- lexical-binding: t; -*-
;;;###autodef
(defun set-formatter! (name &rest rest)
"Define (or modify) a formatter named NAME.
NAME is a symbol that identifies this formatter.
ARGS can be a symbol referring to another formatter, a function, string or
nested list.
If a function, it should be a formatter function that
`apheleia--run-formatter-function' will accept.
If a string, it is assumed to be a shell command that the buffer's text will
be piped to (through stdin).
If a list, it should represent a shell command as a list of arguments. Each
element is either a string or list (STRING ARG) where STRING is a format
string and ARG is both a predicate and argument for STRING. If ARG is nil,
STRING will be omitted from the vector.
If you're trying to override this, ensure that you wrap the call in `after!' and
whichever package sets the initial formatter. See the ':editor format' README
for more.
If ARGS isn't used, then the original definition of NAME is used (in
`apheleia-formatters'). Useful for setting what modes to use NAME in.
For more information on how to structure the list to be compatible, see
`apheleia--run-formatter-function'.
MODES is a major mode, a list thereof, or a list of two-element sublists with
the structure: (MAJOR-MODE FORM). FORM is evaluated when the buffer is formatted
and its return value serves two purposes:
1. It is a predicate for this formatter. Assuming the MAJOR-MODE matches the
current mode, if FORM evaluates to nil, the formatter is skipped.
2. It's return value is made available to FORMATTER if it is a function or
list of shell arguments via the `mode-result' variable.
Basic examples:
(set-formatter! \\='asmfmt :modes \\='(asm-mode nasm-mode))
(set-formatter! \\='black \"black -q -\")
(set-formatter! \\='html-tidy \"tidy -q -indent\" :modes \\='(html-mode web-mode))
Advanced examples:
(set-formatter!
\\='clang-format
\\='(\"clang-format\"
(\"-assume-filename=%S\" (or buffer-file-name mode-result \"\")))
:modes
\\='((c-mode \".c\")
(c++-mode \".cpp\")
(java-mode \".java\")
(objc-mode \".m\")
(protobuf-mode \".proto\")))
(set-formatter! \\='html-tidy
\\='(\"tidy\" \"-q\" \"-indent\"
(\"-xml\" (memq major-mode \\='(nxml-mode xml-mode))))
:modes
\\='(html-mode
(web-mode (and (equal \"none\" web-mode-engine)
(car (member web-mode-content-type \\='(\"xml\" \"html\")))))))
(set-formatter! \\='html-tidy ; overwrite predefined html-tidy formatter
\\='(\"tidy\" \"-q\" \"-indent\"
\"--tidy-mark\" \"no\"
\"--drop-empty-elements\" \"no\"
\"--show-body-only\" \"auto\"
(\"--indent-spaces\" \"%d\" tab-width)
(\"--indent-with-tabs\" \"%s\" (if indent-tabs-mode \"yes\" \"no\"))
(\"-xml\" (memq major-mode \\='(nxml-mode xml-mode)))))
(set-formatter! \\='elm-format
\"elm-format --yes --stdin\")
\(fn NAME [ARGS...] [:modes MODES])"
(declare (indent defun))
(cl-check-type name symbol)
(after! apheleia
(let* ((args (if (keywordp (car rest))
t
(pop rest)))
(plist (cl-loop while (keywordp (car rest))
collect (pop rest)
collect (pop rest)))
(modes (plist-get plist :modes)))
(if (null args)
(progn
(setq apheleia-formatters
(assq-delete-all name apheleia-formatters))
(while (rassoc name apheleia-mode-alist)
(setq apheleia-mode-alist
(assq-delete-all (car (rassoc name apheleia-mode-alist)) apheleia-mode-alist))))
(if (eq args t)
(unless (assq name apheleia-formatters)
(error "set-formatter!: unknown formatter (%s) cannot be assigned" name))
(setf (alist-get name apheleia-formatters)
(if (stringp args)
(ensure-list args)
args)))
(dolist (mode (ensure-list modes))
(setf (alist-get mode apheleia-mode-alist) name))))))