From 909295d8bfb97cfb8e54a8cdd7a6a96b2bc521f2 Mon Sep 17 00:00:00 2001 From: duli Date: Mon, 15 Jun 2026 20:08:49 +0800 Subject: [PATCH] fix(format): restore LSP range formatting The LSP method `textDocument/rangeFormatting` returns a list of TextEdits that are applied to the original buffer. However, `+format-region' formats regions in a temporary buffer containing only the selected text, which causes both eglot and lsp-mode range formatting to fail. Apheleia does not support region formatting directly, so introduce a way for formatters to operate on the original buffer while still receiving the active region boundaries via `+format-region-range'. This also makes it possible for custom Apheleia formatters to implement region-aware formatting. * modules/editor/format/autoload/format.el: - (+format--region-p): Rename and expose `+format--region-p' as `+format-region-range' so custom formatters can access region boundaries. - (+format-region): Rename `+format-region' to `+format-region-via-temp-buffer' and keep a compatibility alias. - (+format-region-in-original-buffer): Add for formatters that need access to the original buffer and region boundaries. - (+format/region): Run formatters in the original buffer when `+format-with-lsp-mode' or `+format-region-force-use-original-buffer' is enabled. * modules/editor/format/autoload/lsp.el (+format--with-lsp): Use explicit capability checks from `lsp--server-capabilities' instead of `lsp-feature?'. `lsp-feature?' may return non-nil even for unsupported methods. --- modules/editor/format/autoload/format.el | 63 ++++++++++++++++++++---- modules/editor/format/autoload/lsp.el | 6 +-- 2 files changed, 57 insertions(+), 12 deletions(-) diff --git a/modules/editor/format/autoload/format.el b/modules/editor/format/autoload/format.el index f3a070d53..b5e09f8a4 100644 --- a/modules/editor/format/autoload/format.el +++ b/modules/editor/format/autoload/format.el @@ -14,11 +14,26 @@ ;;;###autoload (autoload 'apheleia--get-formatters "apheleia-formatters") ;;;###autoload -(defvar +format--region-p nil) +(defvar +format-region-range nil + "A cons cell of the form (BEG . END) set by +`+format-region-in-original-buffer'. + +Custom Apheleia formatters may use this variable to obtain the region +boundaries when called by `+format/region-or-buffer'.") + +;;;###autoload +(defvar-local +format-region-force-use-original-buffer nil + "When non-nil, force `+format/region' to use +`+format-region-in-original-buffer' instead of +`+format-region-via-temp-buffer'.") ;;;###autoload -(defun +format-region (start end &optional callback) - "Format from START to END with `apheleia'." +(defun +format-region-via-temp-buffer (start end &optional callback) + "Format from START to END with `apheleia'. + +The region is copied into a temporary buffer and formatted as if it +were the contents of a standalone file. The formatted result then +replaces the original region." (when-let* ((command (apheleia--get-formatters (if current-prefix-arg 'prompt @@ -49,7 +64,8 @@ (when (> indent 0) (indent-rigidly (point-min) (point-max) (- indent))) ;; - (let ((+format--region-p (cons start end))) + (let ((start start) + (end end)) (apheleia-format-buffer command (lambda () @@ -67,6 +83,32 @@ (when (doom-region-active-p) (setq deactivate-mark t))))) +;;;###autoload +(defalias '+format-region #'+format-region-via-temp-buffer) + +;;;###autoload +(defun +format-region-in-original-buffer (start end &optional callback) + "Format the text between START and END with `apheleia'. + +The formatter is run on the current buffer. Formatters should use +`+format--region-range' to obtain the boundaries of the region being +formatted." + (when-let* ((command (apheleia--get-formatters + (if current-prefix-arg + 'prompt + 'interactive))) + (cur-buffer (current-buffer)) + (indent 0)) + (unwind-protect + (with-current-buffer cur-buffer + (let ((+format-region-range (cons start end))) + (apheleia-format-buffer + command + (lambda () + (with-current-buffer cur-buffer + (when callback (funcall callback))))))) + (when (doom-region-active-p) + (setq deactivate-mark t))))) ;; ;;; Commands @@ -85,11 +127,14 @@ may not always work. Keep your undo keybind handy!" (doom-region-end) current-prefix-arg 'interactive)) - (+format-region - beg end - (lambda () - (when interactive - (message "Region reformatted!"))))) + (let ((format-fn (if (or (bound-and-true-p +format-with-lsp-mode) + +format-region-force-use-original-buffer) + #'+format-region-in-original-buffer + #'+format-region-via-temp-buffer)) + (callback (lambda () + (when interactive + (message "Region reformatted!"))))) + (funcall format-fn beg end callback))) ;;;###autoload (defun +format/region-or-buffer () diff --git a/modules/editor/format/autoload/lsp.el b/modules/editor/format/autoload/lsp.el index 1ffd53129..87dbc6b1e 100644 --- a/modules/editor/format/autoload/lsp.el +++ b/modules/editor/format/autoload/lsp.el @@ -47,7 +47,7 @@ mode unconditionally, call `+format-with-lsp-mode' instead." (cl-defun +format-lsp-buffer (&rest plist &key buffer callback &allow-other-keys) "Format the current buffer with any available lsp-mode or eglot formatter." (if-let* ((fn (with-current-buffer buffer (+format--lsp-fn))) - ((apply fn (car +format--region-p) (cdr +format--region-p) + ((apply fn (car +format-region-range) (cdr +format-region-range) plist))) (funcall callback) (funcall callback "LSP server doesn't support formatting"))) @@ -58,10 +58,10 @@ mode unconditionally, call `+format-with-lsp-mode' instead." Won't forward the buffer to chained formatters if successful." (with-current-buffer buffer (let ((edits - (cond ((and (null beg) (lsp-feature? "textDocument/formatting")) + (cond ((and (null beg) (plist-get (lsp--server-capabilities) :documentFormattingProvider)) (lsp-request "textDocument/formatting" (lsp--make-document-formatting-params))) - ((lsp-feature? "textDocument/rangeFormatting") + ((plist-get (lsp--server-capabilities) :documentRangeFormattingProvider) (lsp-request "textDocument/rangeFormatting" (lsp--make-document-range-formatting-params (or beg (point-min)) (or end (point-max)))))