editor/format: redesign
This isn't the apheleia rewrite, just a redesign to fix the module's current issues with its +onsave feature. + Rethinks how the formatter dispatches to lsp/eglot's formatter. + Stops format-all from being too imposing with its warnings. + Relies more on format-all-mode to control formatting-on-save. + Sidestep +format-buffer-a hackery when using lsp/eglot formatters. Fixes #5121 Fixes #5128 Fixes #5133
This commit is contained in:
parent
99fbdb1092
commit
68b422c786
2 changed files with 126 additions and 138 deletions
|
@ -102,9 +102,7 @@ Stolen shamelessly from go-mode"
|
||||||
(defun +format-probe-a (orig-fn)
|
(defun +format-probe-a (orig-fn)
|
||||||
"Use `+format-with' instead, if it is set.
|
"Use `+format-with' instead, if it is set.
|
||||||
Prompts for a formatter if universal arg is set."
|
Prompts for a formatter if universal arg is set."
|
||||||
(cond ((or (eq +format-with :none)
|
(cond ((or buffer-read-only (eq +format-with :none))
|
||||||
(doom-temp-buffer-p (current-buffer))
|
|
||||||
(derived-mode-p 'special-mode))
|
|
||||||
(list nil nil))
|
(list nil nil))
|
||||||
(current-prefix-arg
|
(current-prefix-arg
|
||||||
(list (or (+format-completing-read)
|
(list (or (+format-completing-read)
|
||||||
|
@ -112,10 +110,18 @@ Prompts for a formatter if universal arg is set."
|
||||||
t))
|
t))
|
||||||
(+format-with
|
(+format-with
|
||||||
(list +format-with t))
|
(list +format-with t))
|
||||||
|
((and +format-with-lsp
|
||||||
|
(bound-and-true-p lsp-managed-mode)
|
||||||
|
(lsp-feature? "textDocument/rangeFormatting"))
|
||||||
|
(list 'lsp nil))
|
||||||
|
((and +format-with-lsp
|
||||||
|
(bound-and-true-p eglot--managed-mode)
|
||||||
|
(eglot--server-capable :documentFormattingProvider))
|
||||||
|
(list 'eglot nil))
|
||||||
((funcall orig-fn))))
|
((funcall orig-fn))))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun +format-buffer-a (orig-fn formatter mode-result)
|
(defun +format-buffer-a (formatter mode-result)
|
||||||
"Advice that extends `format-all-buffer--with' to:
|
"Advice that extends `format-all-buffer--with' to:
|
||||||
|
|
||||||
1. Enable partial/region reformatting, while preserving leading indentation,
|
1. Enable partial/region reformatting, while preserving leading indentation,
|
||||||
|
@ -124,78 +130,85 @@ Prompts for a formatter if universal arg is set."
|
||||||
|
|
||||||
See `+format/buffer' for the interactive version of this function, and
|
See `+format/buffer' for the interactive version of this function, and
|
||||||
`+format-buffer-h' to use as a `before-save-hook' hook."
|
`+format-buffer-h' to use as a `before-save-hook' hook."
|
||||||
(let ((f-function (gethash formatter format-all--format-table))
|
(cond
|
||||||
(executable (format-all--formatter-executable formatter))
|
((eq formatter 'lsp)
|
||||||
(indent 0)
|
(call-interactively
|
||||||
(old-line-number (line-number-at-pos))
|
(if +format-region-p #'lsp-format-region #'lsp-format-buffer)))
|
||||||
(old-column (current-column)))
|
((eq formatter 'eglot)
|
||||||
(pcase-let*
|
(call-interactively
|
||||||
((`(,output ,errput)
|
(if +format-region-p #'eglot-format #'eglot-format-buffer)))
|
||||||
;; To reliably format regions, rather than the whole buffer, and
|
((let ((f-function (gethash formatter format-all--format-table))
|
||||||
;; `format-all' (and various formatting functions, like `gofmt') widen
|
(executable (format-all--formatter-executable formatter))
|
||||||
;; the buffer, we must copy the region first.
|
(indent 0)
|
||||||
(let ((output (buffer-substring-no-properties (point-min) (point-max)))
|
(old-line-number (line-number-at-pos))
|
||||||
(origin-buffer (or (buffer-base-buffer) (current-buffer))))
|
(old-column (current-column)))
|
||||||
(with-temp-buffer
|
(pcase-let*
|
||||||
(with-silent-modifications
|
((`(,output ,errput)
|
||||||
(insert output)
|
;; To reliably format regions, rather than the whole buffer, and
|
||||||
;; Ensure this temp buffer seems as much like the origin
|
;; `format-all' (and various formatting functions, like `gofmt') widen
|
||||||
;; buffer as possible, in case the formatter is an elisp
|
;; the buffer, we must copy the region first.
|
||||||
;; function, like `gofmt'.
|
(let ((output (buffer-substring-no-properties (point-min) (point-max)))
|
||||||
(cl-loop for (var . val)
|
(origin-buffer (or (buffer-base-buffer) (current-buffer))))
|
||||||
in (cl-remove-if-not #'listp (buffer-local-variables origin-buffer))
|
(with-temp-buffer
|
||||||
;; Making enable-multibyte-characters buffer-local
|
(with-silent-modifications
|
||||||
;; causes an error.
|
(insert output)
|
||||||
unless (eq var 'enable-multibyte-characters)
|
;; Ensure this temp buffer seems as much like the origin
|
||||||
;; Using setq-local would quote var.
|
;; buffer as possible, in case the formatter is an elisp
|
||||||
do (set (make-local-variable var) val))
|
;; function, like `gofmt'.
|
||||||
;; Since we're piping a region of text to the formatter, remove
|
(cl-loop for (var . val)
|
||||||
;; any leading indentation to make it look like a file.
|
in (cl-remove-if-not #'listp (buffer-local-variables origin-buffer))
|
||||||
(setq indent (+format--current-indentation))
|
;; Making enable-multibyte-characters buffer-local
|
||||||
(when (> indent 0)
|
;; causes an error.
|
||||||
(indent-rigidly (point-min) (point-max) (- indent)))
|
unless (eq var 'enable-multibyte-characters)
|
||||||
(funcall f-function executable mode-result)))))
|
;; Using setq-local would quote var.
|
||||||
(`,status
|
do (set (make-local-variable var) val))
|
||||||
(cond ((null output) :error)
|
;; Since we're piping a region of text to the formatter, remove
|
||||||
((eq output t) :already-formatted)
|
;; any leading indentation to make it look like a file.
|
||||||
(t :reformatted))))
|
(setq indent (+format--current-indentation))
|
||||||
(unwind-protect
|
(when (> indent 0)
|
||||||
(when (eq status :reformatted)
|
(indent-rigidly (point-min) (point-max) (- indent)))
|
||||||
(let ((tmpfile (make-temp-file "doom-format"))
|
(funcall f-function executable mode-result)))))
|
||||||
(patchbuf (get-buffer-create " *doom format patch*"))
|
(`,status
|
||||||
(coding-system-for-read coding-system-for-read)
|
(cond ((null output) :error)
|
||||||
(coding-system-for-write coding-system-for-write))
|
((eq output t) :already-formatted)
|
||||||
(unless IS-WINDOWS
|
(t :reformatted))))
|
||||||
(setq coding-system-for-read 'utf-8
|
(unwind-protect
|
||||||
coding-system-for-write 'utf-8))
|
(when (eq status :reformatted)
|
||||||
(unwind-protect
|
(let ((tmpfile (make-temp-file "doom-format"))
|
||||||
(progn
|
(patchbuf (get-buffer-create " *doom format patch*"))
|
||||||
(with-current-buffer patchbuf
|
(coding-system-for-read coding-system-for-read)
|
||||||
(erase-buffer))
|
(coding-system-for-write coding-system-for-write))
|
||||||
(with-temp-file tmpfile
|
(unless IS-WINDOWS
|
||||||
(erase-buffer)
|
(setq coding-system-for-read 'utf-8
|
||||||
(insert output)
|
coding-system-for-write 'utf-8))
|
||||||
(when (> indent 0)
|
(unwind-protect
|
||||||
;; restore indentation without affecting new
|
(progn
|
||||||
;; indentation
|
(with-current-buffer patchbuf
|
||||||
(indent-rigidly (point-min) (point-max)
|
(erase-buffer))
|
||||||
(max 0 (- indent (+format--current-indentation))))))
|
(with-temp-file tmpfile
|
||||||
(if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile))
|
(erase-buffer)
|
||||||
(setq status :already-formatted)
|
(insert output)
|
||||||
(+format--apply-rcs-patch patchbuf)
|
(when (> indent 0)
|
||||||
(list output errput)))
|
;; restore indentation without affecting new
|
||||||
(kill-buffer patchbuf)
|
;; indentation
|
||||||
(delete-file tmpfile))))
|
(indent-rigidly (point-min) (point-max)
|
||||||
(format-all--show-or-hide-errors errput)
|
(max 0 (- indent (+format--current-indentation))))))
|
||||||
(goto-char (point-min))
|
(if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile))
|
||||||
(forward-line (1- old-line-number))
|
(setq status :already-formatted)
|
||||||
(let ((line-length (- (point-at-eol) (point-at-bol))))
|
(+format--apply-rcs-patch patchbuf)
|
||||||
(goto-char (+ (point) (min old-column line-length))))
|
(list output errput)))
|
||||||
(run-hook-with-args 'format-all-after-format-functions formatter status)
|
(kill-buffer patchbuf)
|
||||||
(message (pcase status
|
(delete-file tmpfile))))
|
||||||
(:error "Formatting error")
|
(format-all--show-or-hide-errors errput)
|
||||||
(:already-formatted "Already formatted")
|
(goto-char (point-min))
|
||||||
(:reformatted (format "Reformatted with %s" formatter))))))))
|
(forward-line (1- old-line-number))
|
||||||
|
(let ((line-length (- (point-at-eol) (point-at-bol))))
|
||||||
|
(goto-char (+ (point) (min old-column line-length))))
|
||||||
|
(run-hook-with-args 'format-all-after-format-functions formatter status)
|
||||||
|
(message (pcase status
|
||||||
|
(:error "Formatting error")
|
||||||
|
(:already-formatted "Already formatted")
|
||||||
|
(:reformatted (format "Reformatted with %s" formatter))))))))))
|
||||||
|
|
||||||
|
|
||||||
;;
|
;;
|
||||||
|
@ -221,23 +234,19 @@ If nil, BEG and/or END will default to the boundaries of the src block at point.
|
||||||
(user-error "Cannot reformat an org src block in org-mode")
|
(user-error "Cannot reformat an org src block in org-mode")
|
||||||
(+format/region beg end))))))
|
(+format/region beg end))))))
|
||||||
|
|
||||||
|
(defun +format--buffer ()
|
||||||
|
(if (and (eq major-mode 'org-mode)
|
||||||
|
(org-in-src-block-p t))
|
||||||
|
(+format--org-region (point-min) (point-max))
|
||||||
|
(if (called-interactively-p 'any)
|
||||||
|
(format-all-buffer)
|
||||||
|
(ignore-errors (format-all-buffer)))))
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun +format/buffer ()
|
(defun +format/buffer ()
|
||||||
"Reformat the current buffer using LSP or `format-all-buffer'."
|
"Reformat the current buffer using LSP or `format-all-buffer'."
|
||||||
(interactive)
|
(interactive)
|
||||||
(if (eq major-mode 'org-mode)
|
(+format--buffer))
|
||||||
(when (org-in-src-block-p t)
|
|
||||||
(+format--org-region nil nil))
|
|
||||||
(call-interactively
|
|
||||||
(cond ((and +format-with-lsp
|
|
||||||
(bound-and-true-p lsp-mode)
|
|
||||||
(lsp-feature? "textDocument/formatting"))
|
|
||||||
#'lsp-format-buffer)
|
|
||||||
((and +format-with-lsp
|
|
||||||
(bound-and-true-p eglot--managed-mode)
|
|
||||||
(eglot--server-capable :documentFormattingProvider))
|
|
||||||
#'eglot-format-buffer)
|
|
||||||
(#'format-all-buffer)))))
|
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun +format/region (beg end)
|
(defun +format/region (beg end)
|
||||||
|
@ -247,21 +256,10 @@ WARNING: this may not work everywhere. It will throw errors if the region
|
||||||
contains a syntax error in isolation. It is mostly useful for formatting
|
contains a syntax error in isolation. It is mostly useful for formatting
|
||||||
snippets or single lines."
|
snippets or single lines."
|
||||||
(interactive "rP")
|
(interactive "rP")
|
||||||
(if (and (eq major-mode 'org-mode)
|
(let ((+format-region-p t))
|
||||||
(org-in-src-block-p t))
|
(save-restriction
|
||||||
(+format--org-region beg end)
|
(narrow-to-region beg end)
|
||||||
(cond ((and +format-with-lsp
|
(+format--buffer))))
|
||||||
(bound-and-true-p lsp-mode)
|
|
||||||
(lsp-feature? "textDocument/rangeFormatting"))
|
|
||||||
(call-interactively #'lsp-format-region))
|
|
||||||
((and +format-with-lsp
|
|
||||||
(bound-and-true-p eglot--managed-mode)
|
|
||||||
(eglot--server-capable :documentRangeFormattingProvider))
|
|
||||||
(call-interactively #'eglot-format))
|
|
||||||
((save-restriction
|
|
||||||
(narrow-to-region beg end)
|
|
||||||
(let ((+format-region-p t))
|
|
||||||
(+format/buffer)))))))
|
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun +format/region-or-buffer ()
|
(defun +format/region-or-buffer ()
|
||||||
|
@ -277,11 +275,6 @@ is selected)."
|
||||||
;;
|
;;
|
||||||
;; Hooks
|
;; Hooks
|
||||||
|
|
||||||
;;;###autoload
|
|
||||||
(defun +format-enable-on-save-h ()
|
|
||||||
"Enables formatting on save."
|
|
||||||
(add-hook 'before-save-hook #'+format-buffer-h nil t))
|
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defalias '+format-buffer-h #'+format/buffer
|
(defalias '+format-buffer-h #'+format/buffer
|
||||||
"Format the source code in the current buffer with minimal feedback.
|
"Format the source code in the current buffer with minimal feedback.
|
||||||
|
|
|
@ -34,38 +34,19 @@ select buffers.")
|
||||||
;;
|
;;
|
||||||
;;; Bootstrap
|
;;; Bootstrap
|
||||||
|
|
||||||
(defun +format-enable-for-lsp-on-save-maybe-h ()
|
|
||||||
"Enable LSP formatter when LSP client is available."
|
|
||||||
(remove-hook 'lsp-mode-hook #'+format-enable-for-lsp-on-save-maybe-h 'local)
|
|
||||||
(cond ((not +format-with-lsp) nil)
|
|
||||||
((bound-and-true-p lsp-mode)
|
|
||||||
(when (lsp-feature? "textDocument/formatting")
|
|
||||||
(+format-enable-on-save-h))
|
|
||||||
t)
|
|
||||||
((bound-and-true-p eglot--managed-mode)
|
|
||||||
(when (eglot--server-capable :documentRangeFormattingProvider)
|
|
||||||
(+format-enable-on-save-h))
|
|
||||||
t)
|
|
||||||
((bound-and-true-p lsp--buffer-deferred)
|
|
||||||
(add-hook 'lsp-mode-hook #'+format-enable-for-lsp-on-save-maybe-h
|
|
||||||
nil 'local)
|
|
||||||
t)))
|
|
||||||
|
|
||||||
(defun +format-enable-on-save-maybe-h ()
|
(defun +format-enable-on-save-maybe-h ()
|
||||||
"Enable formatting on save in certain major modes.
|
"Enable formatting on save in certain major modes.
|
||||||
|
|
||||||
This is controlled by `+format-on-save-enabled-modes'."
|
This is controlled by `+format-on-save-enabled-modes'."
|
||||||
(and (not (eq major-mode 'fundamental-mode))
|
(cond ((eq major-mode 'fundamental-mode))
|
||||||
(cond ((booleanp +format-on-save-enabled-modes)
|
((string-prefix-p " " (buffer-name)))
|
||||||
+format-on-save-enabled-modes)
|
((booleanp +format-on-save-enabled-modes)
|
||||||
((eq (car-safe +format-on-save-enabled-modes) 'not)
|
+format-on-save-enabled-modes)
|
||||||
(not (memq major-mode (cdr +format-on-save-enabled-modes))))
|
((if (eq (car-safe +format-on-save-enabled-modes) 'not)
|
||||||
((memq major-mode +format-on-save-enabled-modes))
|
(memq major-mode (cdr +format-on-save-enabled-modes))
|
||||||
((not (require 'format-all nil t))))
|
(not (memq major-mode +format-on-save-enabled-modes))))
|
||||||
(not (+format-enable-for-lsp-on-save-maybe-h))
|
((not (require 'format-all nil t)))
|
||||||
(let (current-prefix-arg) ; never prompt
|
((format-all-mode +1))))
|
||||||
(car (format-all--probe)))
|
|
||||||
(+format-enable-on-save-h)))
|
|
||||||
|
|
||||||
(when (featurep! +onsave)
|
(when (featurep! +onsave)
|
||||||
(add-hook 'after-change-major-mode-hook #'+format-enable-on-save-maybe-h))
|
(add-hook 'after-change-major-mode-hook #'+format-enable-on-save-maybe-h))
|
||||||
|
@ -82,8 +63,22 @@ This is controlled by `+format-on-save-enabled-modes'."
|
||||||
;; 1. Enables partial reformatting (while preserving leading indentation),
|
;; 1. Enables partial reformatting (while preserving leading indentation),
|
||||||
;; 2. Applies changes via RCS patch, line by line, to protect buffer markers
|
;; 2. Applies changes via RCS patch, line by line, to protect buffer markers
|
||||||
;; and avoid any jarring cursor+window scrolling.
|
;; and avoid any jarring cursor+window scrolling.
|
||||||
(advice-add #'format-all-buffer--with :around #'+format-buffer-a)
|
(advice-add #'format-all-buffer--with :override #'+format-buffer-a)
|
||||||
|
|
||||||
;; format-all-mode "helpfully" raises an error when it doesn't know how to
|
;; format-all-mode "helpfully" raises an error when it doesn't know how to
|
||||||
;; format a buffer.
|
;; format a buffer.
|
||||||
(add-to-list 'debug-ignored-errors "^Don't know how to format ")
|
(add-to-list 'debug-ignored-errors "^Don't know how to format ")
|
||||||
|
|
||||||
|
;; Don't pop up imposing warnings about missing formatters, but still log it in
|
||||||
|
;; to *Messages*.
|
||||||
|
(defadvice! +format--all-buffer-from-hook-a (orig-fn &rest args)
|
||||||
|
:around #'format-all-buffer--from-hook
|
||||||
|
(letf! (defun format-all-buffer--with (formatter mode-result)
|
||||||
|
(and (condition-case-unless-debug e
|
||||||
|
(format-all--formatter-executable formatter)
|
||||||
|
(error
|
||||||
|
(message "Warning: cannot reformat buffer because %S isn't installed"
|
||||||
|
(gethash formatter format-all--executable-table))
|
||||||
|
nil))
|
||||||
|
(funcall format-all-buffer--with formatter mode-result)))
|
||||||
|
(apply orig-fn args)))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue