2018-08-25 00:02:53 +02:00
|
|
|
;;; editor/format/autoload.el -*- lexical-binding: t; -*-
|
|
|
|
|
2018-09-04 02:11:07 +02:00
|
|
|
(defun +format--current-indentation ()
|
|
|
|
(save-excursion
|
|
|
|
(goto-char (point-min))
|
|
|
|
(skip-chars-forward " \t\n")
|
|
|
|
(current-indentation)))
|
|
|
|
|
2022-08-16 08:13:55 +01:00
|
|
|
(defun +format-region (start end &optional callback)
|
|
|
|
"Format from START to END with `apheleia'."
|
2023-09-15 07:20:11 +01:00
|
|
|
(when-let* ((command (apheleia--get-formatters
|
2022-08-16 08:13:55 +01:00
|
|
|
(if current-prefix-arg
|
|
|
|
'prompt
|
|
|
|
'interactive)))
|
|
|
|
(cur-buffer (current-buffer))
|
|
|
|
(formatted-buffer (get-buffer-create " *apheleia-formatted*"))
|
|
|
|
(indent 0))
|
|
|
|
(with-current-buffer formatted-buffer
|
|
|
|
(erase-buffer)
|
2023-12-05 17:05:13 -05:00
|
|
|
(unless (featurep :system 'windows)
|
2022-08-16 08:13:55 +01:00
|
|
|
(setq-local coding-system-for-read 'utf-8)
|
|
|
|
(setq-local coding-system-for-write 'utf-8))
|
|
|
|
;; Ensure this temp buffer seems as much like the origin buffer as
|
|
|
|
;; possible, in case the formatter is an elisp function, like `gofmt'.
|
|
|
|
(cl-loop for (var . val)
|
2023-09-15 07:20:11 +01:00
|
|
|
in (cl-remove-if-not #'listp (buffer-local-variables cur-buffer))
|
2022-08-16 08:13:55 +01:00
|
|
|
;; Making enable-multibyte-characters buffer-local causes an
|
|
|
|
;; error.
|
|
|
|
unless (eq var 'enable-multibyte-characters)
|
|
|
|
;; Using setq-local would quote var.
|
|
|
|
do (set (make-local-variable var) val))
|
|
|
|
;;
|
|
|
|
(insert-buffer-substring-no-properties cur-buffer start end)
|
|
|
|
;; Since we're piping a region of text to the formatter, remove any
|
|
|
|
;; leading indentation to make it look like a file.
|
|
|
|
(setq indent (+format--current-indentation))
|
|
|
|
(when (> indent 0)
|
|
|
|
(indent-rigidly (point-min) (point-max) (- indent)))
|
|
|
|
;;
|
|
|
|
(apheleia-format-buffer
|
|
|
|
command
|
|
|
|
(lambda ()
|
|
|
|
(with-current-buffer formatted-buffer
|
|
|
|
(when (> indent 0)
|
|
|
|
;; restore indentation without affecting new
|
|
|
|
;; indentation
|
|
|
|
(indent-rigidly (point-min) (point-max)
|
2023-09-15 07:20:11 +01:00
|
|
|
(max 0 (- indent (+format--current-indentation)))))
|
|
|
|
(set-buffer-modified-p nil))
|
2022-08-16 08:13:55 +01:00
|
|
|
(with-current-buffer cur-buffer
|
|
|
|
(delete-region start end)
|
|
|
|
(insert-buffer-substring-no-properties formatted-buffer)
|
|
|
|
(when callback (funcall callback))
|
|
|
|
(kill-buffer formatted-buffer)))))))
|
2018-08-25 00:02:53 +02:00
|
|
|
|
|
|
|
|
|
|
|
;;
|
2020-03-10 01:48:20 -04:00
|
|
|
;;; Commands
|
2018-08-25 00:02:53 +02:00
|
|
|
|
|
|
|
;;;###autoload
|
2022-08-16 08:13:55 +01:00
|
|
|
(defun +format/buffer (&optional arg)
|
2020-05-08 16:45:58 -04:00
|
|
|
"Reformat the current buffer using LSP or `format-all-buffer'."
|
2022-08-16 08:13:55 +01:00
|
|
|
(interactive "P")
|
2024-02-14 17:44:21 -05:00
|
|
|
(or (run-hook-with-args-until-success '+format-functions (point-min) (point-max) 'buffer)
|
|
|
|
(call-interactively #'apheleia-format-buffer)))
|
2018-08-25 00:02:53 +02:00
|
|
|
|
|
|
|
;;;###autoload
|
2022-08-16 08:13:55 +01:00
|
|
|
(defun +format/region (beg end &optional arg)
|
2018-08-25 00:02:53 +02:00
|
|
|
"Runs the active formatter on the lines within BEG and END.
|
|
|
|
|
|
|
|
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
|
|
|
|
snippets or single lines."
|
2018-09-08 23:36:24 -04:00
|
|
|
(interactive "rP")
|
2024-02-14 17:44:21 -05:00
|
|
|
(or (run-hook-with-args-until-success '+format-functions beg end 'region)
|
|
|
|
(+format-region beg end)))
|
2018-08-25 00:02:53 +02:00
|
|
|
|
|
|
|
;;;###autoload
|
2019-04-02 00:51:29 -04:00
|
|
|
(defun +format/region-or-buffer ()
|
2018-08-25 00:02:53 +02:00
|
|
|
"Runs the active formatter on the selected region (or whole buffer, if nothing
|
|
|
|
is selected)."
|
2019-04-02 00:51:29 -04:00
|
|
|
(interactive)
|
|
|
|
(call-interactively
|
2020-05-06 16:29:32 -04:00
|
|
|
(if (doom-region-active-p)
|
2020-05-08 16:45:58 -04:00
|
|
|
#'+format/region
|
|
|
|
#'+format/buffer)))
|
2024-02-14 17:44:21 -05:00
|
|
|
|
|
|
|
|
|
|
|
;;
|
|
|
|
;;; Specialized formatters
|
|
|
|
|
|
|
|
;;;###autoload
|
|
|
|
(defun +format-with-lsp-fn (beg end op)
|
|
|
|
"Format the region/buffer using any available lsp-mode formatter.
|
|
|
|
|
|
|
|
Does nothing if `+format-with-lsp' is nil or the active server doesn't support
|
|
|
|
the requested feature."
|
2024-02-14 18:09:15 -05:00
|
|
|
(and +format-with-lsp
|
|
|
|
(bound-and-true-p lsp-mode)
|
|
|
|
(pcase op
|
|
|
|
('buffer (condition-case _
|
|
|
|
;; Avoid lsp-feature? checks for this, since
|
|
|
|
;; `lsp-format-buffer' does its own, and allows clients
|
|
|
|
;; without formatting support (but with rangeFormatting,
|
|
|
|
;; for some reason) to work.
|
|
|
|
(always (lsp-format-buffer))
|
|
|
|
('lsp-capability-not-supported nil)))
|
|
|
|
('region (if (lsp-feature? "textDocument/rangeFormatting")
|
|
|
|
(always (lsp-format-region beg end))))
|
|
|
|
(_ (error "Invalid formatter operation: %s" op)))))
|
2024-02-14 17:44:21 -05:00
|
|
|
|
2024-02-14 17:44:38 -05:00
|
|
|
;;;###autoload
|
|
|
|
(defun +format-with-eglot-fn (beg end op)
|
|
|
|
"Format the region/buffer using any available eglot formatter.
|
|
|
|
|
|
|
|
Does nothing if `+format-with-lsp' is nil or the active server doesn't support
|
|
|
|
the requested feature."
|
2024-02-14 18:09:15 -05:00
|
|
|
(and +format-with-lsp
|
2024-03-11 20:46:02 -04:00
|
|
|
(bound-and-true-p eglot--managed-mode)
|
2024-02-14 18:09:15 -05:00
|
|
|
(pcase op
|
|
|
|
('buffer (if (eglot--server-capable :documentFormattingProvider)
|
|
|
|
(always (eglot-format-buffer))))
|
|
|
|
('region (if (eglot--server-capable :documentRangeFormattingProvider)
|
|
|
|
(always (eglot-format beg end))))
|
|
|
|
(_ (error "Invalid formatter operation: %s" op)))))
|
2024-02-14 17:44:38 -05:00
|
|
|
|
2024-02-14 17:44:21 -05:00
|
|
|
;;;###autoload
|
|
|
|
(defun +format-in-org-src-blocks-fn (beg end _op)
|
2024-02-16 02:10:26 -05:00
|
|
|
"Reformat org src blocks with apheleia as if they were independent buffers."
|
2024-02-14 17:44:21 -05:00
|
|
|
(when (derived-mode-p 'org-mode)
|
|
|
|
(goto-char beg)
|
|
|
|
(while (re-search-forward org-babel-src-block-regexp end t)
|
|
|
|
(let* ((element (org-element-at-point))
|
|
|
|
(block-beg (save-excursion
|
|
|
|
(goto-char (org-babel-where-is-src-block-head element))
|
|
|
|
(line-beginning-position 2)))
|
|
|
|
(block-end (save-excursion
|
|
|
|
(goto-char (org-element-property :end element))
|
|
|
|
(skip-chars-backward " \t\n")
|
|
|
|
(line-beginning-position)))
|
2024-02-16 02:09:45 -05:00
|
|
|
(beg (max beg block-beg))
|
|
|
|
(end (min end block-end))
|
2024-02-14 17:44:21 -05:00
|
|
|
(lang (org-element-property :language element))
|
|
|
|
(major-mode (org-src-get-lang-mode lang)))
|
|
|
|
(save-excursion
|
|
|
|
(if (eq major-mode 'org-mode)
|
|
|
|
(user-error "Cannot reformat an org src block in org-mode")
|
|
|
|
;; Determine formatter based on language and format the region
|
|
|
|
(let ((formatter (apheleia--get-formatters 'interactive)))
|
|
|
|
(unless formatter
|
|
|
|
(setq formatter (apheleia--get-formatters 'prompt))
|
|
|
|
(unless formatter
|
|
|
|
(user-error "No formatter configured for language: %s" lang)))
|
|
|
|
(let ((apheleia-formatter formatter))
|
2024-02-16 02:09:45 -05:00
|
|
|
(+format-region beg end)))))))
|
|
|
|
t))
|