refactor(format): redesign module

Rather than wrap Apheleia in custom formatting logic, I now use
Apheleia's own machinary to integrate into LSP and Eglot, which is less
complexity to maintain. It also makes settings +format-with a more
reliable option for per-project or per-file configuration.

This also adds a +format/org-src-block command, which I'll incorporate
into the org module in a follow-up commit.

Ref: #7685
This commit is contained in:
Henrik Lissner 2024-07-06 19:54:55 -04:00
parent b0e16dc243
commit 8072762de8
No known key found for this signature in database
GPG key ID: B60957CA074D39A3
2 changed files with 90 additions and 142 deletions

View file

@ -1,87 +1,79 @@
;;; editor/format/config.el -*- lexical-binding: t; -*-
(defvar +format-on-save-disabled-modes
(defcustom +format-on-save-disabled-modes
'(sql-mode ; sqlformat is currently broken
tex-mode ; latexindent is broken
latex-mode
org-msg-edit-mode) ; doesn't need a formatter
"A list of major modes in which to not reformat the buffer upon saving.
If it is t, it is disabled in all modes, the same as if the +onsave flag
wasn't used at all.
If nil, formatting is enabled in all modes.
Irrelevant if you do not have the +onsave flag enabled for this module.")
(defvar +format-preserve-indentation t
"If non-nil, the leading indentation is preserved when formatting the whole
buffer. This is particularly useful for partials.
If it is t, it is disabled in all modes, the same as if the +onsave flag wasn't
used at all.
If nil, formatting is enabled in all modes."
:type '(list symbol))
Indentation is always preserved when formatting regions.")
(defvar +format-with-lsp t
(defcustom +format-with-lsp t
"If non-nil, format with LSP formatter if it's available.
LSP formatter is provided by either `lsp-mode' or `eglot'.
This can be set buffer-locally with `setq-hook!' to disable LSP formatting in
select buffers.
This has no effect on the +onsave flag, apheleia will always be used there.")
select buffers, from a project's .dir-locals.el file, or as a file-local
variable."
:type 'boolean
:safe 'booleanp)
(defvaralias '+format-with 'apheleia-formatter
"Set this to explicitly use a certain formatter for the current buffer.")
(defvaralias '+format-with 'apheleia-formatter)
(defvar +format-functions
'(+format-in-org-src-blocks-fn
+format-with-lsp-fn
+format-with-eglot-fn)
"A list of functions to run when formatting a buffer or region.
Each function is given three arguments: the starting point, end point, and a
symbol indicating the type of operation being requested (as a symbol: either
`region' or `buffer').
The first function to return non-nil will abort all functions after it,
including Apheleia itself.")
;;
;;; Bootstrap
(when (modulep! +onsave)
(add-hook 'doom-first-file-hook #'apheleia-global-mode))
(defun +format-maybe-inhibit-h ()
"Check if formatting should be disabled for current buffer.
(use-package! apheleia
:defer t
:init
(when (modulep! +onsave)
(add-hook 'doom-first-file-hook #'apheleia-global-mode)
;; apheleia autoloads `apheleia-inhibit-functions' so it will be immediately
;; available to mutate early.
(add-hook! 'apheleia-inhibit-functions
(defun +format-maybe-inhibit-h ()
"Check if formatting should be disabled for current buffer.
This is controlled by `+format-on-save-disabled-modes'."
(or (eq major-mode 'fundamental-mode)
(string-blank-p (buffer-name))
(eq +format-on-save-disabled-modes t)
(not (null (memq major-mode +format-on-save-disabled-modes)))))
(or (eq major-mode 'fundamental-mode)
(string-blank-p (buffer-name))
(eq +format-on-save-disabled-modes t)
(not (null (memq major-mode +format-on-save-disabled-modes)))))))
(after! apheleia
:config
(add-to-list 'doom-debug-variables '(apheleia-log-only-errors . nil))
(when (modulep! +onsave)
(add-to-list 'apheleia-inhibit-functions #'+format-maybe-inhibit-h)))
;; Use the formatter provided by lsp-mode and eglot, if they are available and
;; `+format-with-lsp' is non-nil.
(cond ((modulep! :tools lsp +eglot)
(add-to-list 'apheleia-formatters '(eglot . +format-eglot-buffer))
(add-hook 'eglot-managed-hook #'+format-toggle-eglot-formatter-h))
((modulep! :tools lsp)
(add-to-list 'apheleia-formatters '(lsp . +format-lsp-buffer))
(add-hook 'lsp-configure-hook #'+format-enable-lsp-formatter-h)
(add-hook 'lsp-unconfigure-hook #'+format-disable-lsp-formatter-h)))
(defadvice! +format--inhibit-reformat-on-prefix-arg-a (orig-fn &optional arg)
"Make it so \\[save-buffer] with prefix arg inhibits reformatting."
:around #'save-buffer
(let ((apheleia-mode (and apheleia-mode (memq arg '(nil 1)))))
(funcall orig-fn)))
;;
;;; Hacks
(defadvice! +format--inhibit-reformat-on-prefix-arg-a (orig-fn &optional arg)
"Make it so \\[save-buffer] with prefix arg inhibits reformatting."
:around #'save-buffer
(let ((apheleia-mode (and apheleia-mode (memq arg '(nil 1)))))
(funcall orig-fn)))
(add-hook!
'apheleia-post-format-hook
;; HACK `web-mode' doesn't update syntax highlighting after arbitrary buffer
;; modifications, so we must trigger refontification manually.
(defun +format--fix-web-mode-fontification-h ()
(when (eq major-mode 'web-mode)
(setq web-mode-fontification-off nil)
(when (and web-mode-scan-beg web-mode-scan-end global-font-lock-mode)
(save-excursion
(font-lock-fontify-region web-mode-scan-beg web-mode-scan-end)))))
(defun +format--refresh-vc-gutter-h ()
(when (fboundp '+vc-gutter-update-h)
(+vc-gutter-update-h))))
;; HACK: Apheleia suppresses notifications that the current buffer has
;; changed, so plugins that listen for them need to be manually informed:
(add-hook!
'apheleia-post-format-hook
(defun +format--update-web-mode-h ()
(when (eq major-mode 'web-mode)
(setq web-mode-fontification-off nil)
(when (and web-mode-scan-beg web-mode-scan-end global-font-lock-mode)
(save-excursion
(font-lock-fontify-region web-mode-scan-beg web-mode-scan-end)))))
(defun +format--update-vc-gutter-h ()
(when (fboundp '+vc-gutter-update-h)
(+vc-gutter-update-h)))))