refactor!(format): simplify & gate lsp/eglot impl behind +lsp
BREAKING CHANGE: This commit introduces two changes, a breaking, one not. The breaking change: `+format-with-lsp` used to control lsp-mode/eglot integration for this module, but it is now gated behind a new +lsp flag. Users will need to add it to their `doom!` blocks (in $DOOMDIR/init.el) to restore the integration. The other change: I've merged the former `lsp` and `eglot` formatters into a single `lsp` formatter that dispatches to the appropriate backend, as well as wrapping this integration in a `+format-with-lsp-mode` minor mode, so it can be toggled at will; mainly to assist in debugging formatter behavior. A unified formatter makes it easier for folks to configure `+format-with` on a per-project/directory/file basis, without needing to know what backend this module uses, and opens us up to integrating other LSP backends in the future (like lsp-bridge).
This commit is contained in:
parent
cca40c0277
commit
fd1941b95f
3 changed files with 70 additions and 59 deletions
|
@ -27,6 +27,9 @@ be formatted and returned to the buffer using
|
||||||
Enable reformatting of a buffer when it is saved. See
|
Enable reformatting of a buffer when it is saved. See
|
||||||
[[var:+format-on-save-disabled-modes]] to disable format on save for certain
|
[[var:+format-on-save-disabled-modes]] to disable format on save for certain
|
||||||
major modes.
|
major modes.
|
||||||
|
- +lsp ::
|
||||||
|
Use ~lsp-mode~'s or ~eglot~'s formatters, instead of Apheleia's, if the active
|
||||||
|
client has the capabilities.
|
||||||
|
|
||||||
** Packages
|
** Packages
|
||||||
- [[doom-package:apheleia]]
|
- [[doom-package:apheleia]]
|
||||||
|
|
|
@ -1,67 +1,86 @@
|
||||||
;;; editor/format/autoload/lsp.el -*- lexical-binding: t; -*-
|
;;; editor/format/autoload/lsp.el -*- lexical-binding: t; -*-
|
||||||
;;;###if (modulep! :tools lsp)
|
;;;###if (modulep! :tools lsp)
|
||||||
|
|
||||||
(defvar +format-with--last nil)
|
(defvar +format-lsp--last nil)
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun +format-enable-lsp-formatter-h ()
|
(define-minor-mode +format-with-lsp-mode
|
||||||
"TODO"
|
"Toggles `lsp-mode'/`eglot' integration with `apheleia-mode' in this buffer.
|
||||||
(when (and +format-with-lsp (null +format-with))
|
|
||||||
(when (or (lsp-feature? "textDocument/formatting")
|
When enabled, it changes `+format-with' to `lsp', and back to its old value when
|
||||||
(lsp-feature? "textDocument/rangeFormatting"))
|
disabled. Use `+format-wtih-lsp-maybe-h' to activate it, to ensure it only
|
||||||
(setq-local +format-with--last +format-with
|
activates if lsp-mode/eglot are on and connected to a valid client, and so this
|
||||||
+format-with 'lsp))))
|
mode won't conflict with pre-existing user config on `+format-with'."
|
||||||
|
:init-value nil
|
||||||
|
(unless (local-variable-p '+format-lsp--last)
|
||||||
|
(setq-local +format-lsp--last +format-with))
|
||||||
|
(setq-local +format-with
|
||||||
|
(if +format-with-lsp-mode
|
||||||
|
(cl-remove-duplicates (cons 'lsp +format-with) :test #'eq)
|
||||||
|
(prog1 (remq 'lsp (ensure-list +format-lsp--last))
|
||||||
|
(kill-local-variable '+format-lsp--last)))))
|
||||||
|
|
||||||
|
(defvar +format--lsp-alist
|
||||||
|
'((lsp-managed-mode . +format--with-lsp-mode)
|
||||||
|
(eglot--managed-mode . +format--with-eglot))
|
||||||
|
"A list of LSP formatter functions to try on `+format-lsp-buffer'.")
|
||||||
|
|
||||||
|
(defun +format--lsp-fn ()
|
||||||
|
(cl-loop for (sym . fn) in +format--lsp-alist
|
||||||
|
if (and (boundp sym) (symbol-value sym))
|
||||||
|
return fn))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun +format-disable-lsp-formatter-h ()
|
(defun +format-with-lsp-toggle-h ()
|
||||||
"TODO"
|
"Toggle `+format-with-lsp-mode' depending on the state of lsp-mode/eglot.
|
||||||
(when (local-variable-p '+format-with--last)
|
|
||||||
(kill-local-variable '+format-with--last)
|
|
||||||
(setq-local +format-with +format-with--last)))
|
|
||||||
|
|
||||||
;;;###autoload
|
Does not activate the mode if `+format-with' is already set. To activate the
|
||||||
(defun +format-toggle-eglot-formatter-h ()
|
mode unconditionally, call `+format-with-lsp-mode' instead."
|
||||||
"TODO"
|
(when (or (null +format-with) +format-with-lsp-mode)
|
||||||
(if (bound-and-true-p eglot--managed-mode)
|
(+format-with-lsp-mode (if (+format--lsp-fn) +1 -1))))
|
||||||
(when (and +format-with-lsp (null +format-with))
|
|
||||||
(when (or (eglot-server-capable :documentFormattingProvider)
|
|
||||||
(eglot-server-capable :documentRangeFormattingProvider))
|
|
||||||
(setq-local +format-with--last +format-with
|
|
||||||
+format-with 'eglot)))
|
|
||||||
(when +format-with--last
|
|
||||||
(kill-local-variable '+format-with--last)
|
|
||||||
(setq-local +format-with +format-with--last))))
|
|
||||||
|
|
||||||
|
|
||||||
;;
|
;;
|
||||||
;;; Apheleia formatters
|
;;; Apheleia formatters
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(cl-defun +format-lsp-buffer (&key buffer scratch callback &allow-other-keys)
|
(cl-defun +format-lsp-buffer (&rest plist &key callback &allow-other-keys)
|
||||||
|
"Format the current buffer with any available lsp-mode or eglot formatter."
|
||||||
|
(if-let* ((fn (+format--lsp-fn))
|
||||||
|
((apply fn plist)))
|
||||||
|
(funcall callback)
|
||||||
|
(funcall callback "LSP server doesn't support formatting")))
|
||||||
|
|
||||||
|
(cl-defun +format--with-lsp-mode (&key buffer scratch &allow-other-keys)
|
||||||
"Format the current buffer with any available lsp-mode formatter."
|
"Format the current buffer with any available lsp-mode formatter."
|
||||||
(with-current-buffer buffer
|
(with-current-buffer buffer
|
||||||
(let ((edits
|
(let ((edits
|
||||||
(cond
|
(cond
|
||||||
((lsp-feature? "textDocument/formatting")
|
((lsp-feature? "textDocument/formatting")
|
||||||
(lsp-request "textDocument/formatting" (lsp--make-document-formatting-params)))
|
(lsp-request "textDocument/formatting"
|
||||||
|
(lsp--make-document-formatting-params)))
|
||||||
((lsp-feature? "textDocument/rangeFormatting")
|
((lsp-feature? "textDocument/rangeFormatting")
|
||||||
(lsp-request "textDocument/rangeFormatting"
|
(lsp-request "textDocument/rangeFormatting"
|
||||||
(lsp--make-document-range-formatting-params
|
(lsp--make-document-range-formatting-params
|
||||||
(point-min) (point-max)))))))
|
(point-min) (point-max))))
|
||||||
(unless (seq-empty-p edits)
|
(:err))))
|
||||||
(with-current-buffer scratch
|
(unless (eq edits :err)
|
||||||
(lsp--apply-text-edits edits 'format))))
|
(unless (seq-empty-p edits)
|
||||||
(funcall callback)))
|
(with-current-buffer scratch
|
||||||
|
(lsp--apply-text-edits edits 'format)))
|
||||||
|
t))))
|
||||||
|
|
||||||
;;;###autoload
|
(cl-defun +format--with-eglot (&key buffer scratch &allow-other-keys)
|
||||||
(cl-defun +format-eglot-buffer (&key buffer scratch callback &allow-other-keys)
|
|
||||||
"Format the current buffer with any available eglot formatter."
|
"Format the current buffer with any available eglot formatter."
|
||||||
(with-current-buffer scratch
|
(with-current-buffer scratch
|
||||||
(setq-local eglot--cached-server
|
(when (setq-local
|
||||||
(with-current-buffer buffer
|
eglot--cached-server
|
||||||
(eglot-current-server)))
|
(with-current-buffer buffer
|
||||||
(let ((buffer-file-name (buffer-file-name buffer)))
|
(when (or (eglot-server-capable :documentFormattingProvider)
|
||||||
(eglot-format-buffer))
|
(eglot-server-capable :documentRangeFormattingProvider))
|
||||||
(funcall callback)))
|
(eglot-current-server))))
|
||||||
|
(let ((buffer-file-name (buffer-file-name buffer)))
|
||||||
|
(eglot-format-buffer))
|
||||||
|
t)))
|
||||||
|
|
||||||
;;; lsp.el ends here
|
;;; lsp.el ends here
|
||||||
|
|
|
@ -12,17 +12,6 @@ If it is t, it is disabled in all modes, the same as if the +onsave flag wasn't
|
||||||
If nil, formatting is enabled in all modes."
|
If nil, formatting is enabled in all modes."
|
||||||
:type '(list symbol))
|
:type '(list symbol))
|
||||||
|
|
||||||
(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, from a project's .dir-locals.el file, or as a file-local
|
|
||||||
variable."
|
|
||||||
:type 'boolean
|
|
||||||
:safe 'booleanp)
|
|
||||||
|
|
||||||
(defvaralias '+format-with 'apheleia-formatter)
|
(defvaralias '+format-with 'apheleia-formatter)
|
||||||
(defvaralias '+format-inhibit 'apheleia-inhibit)
|
(defvaralias '+format-inhibit 'apheleia-inhibit)
|
||||||
|
|
||||||
|
@ -46,19 +35,19 @@ This is controlled by `+format-on-save-disabled-modes'."
|
||||||
(eq +format-on-save-disabled-modes t)
|
(eq +format-on-save-disabled-modes t)
|
||||||
(not (null (memq major-mode +format-on-save-disabled-modes)))))))
|
(not (null (memq major-mode +format-on-save-disabled-modes)))))))
|
||||||
|
|
||||||
;; Use the formatter provided by lsp-mode and eglot, if they are available and
|
;; Use the formatter provided by lsp-mode and eglot, if available.
|
||||||
;; `+format-with-lsp' is non-nil.
|
(when (modulep! +lsp)
|
||||||
(cond ((modulep! :tools lsp +eglot)
|
(add-hook 'eglot-managed-mode-hook #'+format-with-lsp-maybe-h)
|
||||||
(add-hook 'eglot-managed-mode-hook #'+format-toggle-eglot-formatter-h))
|
(add-hook 'lsp-managed-mode-hook #'+format-with-lsp-maybe-h))
|
||||||
((modulep! :tools lsp)
|
|
||||||
(add-hook 'lsp-configure-hook #'+format-enable-lsp-formatter-h)
|
|
||||||
(add-hook 'lsp-unconfigure-hook #'+format-disable-lsp-formatter-h)))
|
|
||||||
|
|
||||||
:config
|
:config
|
||||||
(add-to-list 'doom-debug-variables '(apheleia-log-only-errors . nil))
|
(add-to-list 'doom-debug-variables '(apheleia-log-only-errors . nil))
|
||||||
(add-to-list 'doom-debug-variables '(apheleia-log-debug-info . t))
|
(add-to-list 'doom-debug-variables '(apheleia-log-debug-info . t))
|
||||||
|
|
||||||
(add-to-list 'apheleia-formatters '(eglot . +format-eglot-buffer))
|
;; A psuedo-formatter that dispatches to the appropriate LSP client (via
|
||||||
|
;; `lsp-mode' or `eglot') that is capable of formatting. Without +lsp, users
|
||||||
|
;; must manually set `+format-with' to `lsp' to use it, or activate
|
||||||
|
;; `+format-with-lsp-mode' in the appropriate modes.
|
||||||
(add-to-list 'apheleia-formatters '(lsp . +format-lsp-buffer))
|
(add-to-list 'apheleia-formatters '(lsp . +format-lsp-buffer))
|
||||||
|
|
||||||
(defadvice! +format--inhibit-reformat-on-prefix-arg-a (orig-fn &optional arg)
|
(defadvice! +format--inhibit-reformat-on-prefix-arg-a (orig-fn &optional arg)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue