refactor(format): replace with apheleia
Initial refactor of format module to replace format-all with apheleia
This commit is contained in:
parent
a44e8d6bfd
commit
4ecd616cd8
9 changed files with 126 additions and 585 deletions
|
@ -1,276 +1,85 @@
|
|||
;;; editor/format/autoload.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar +format-region-p nil
|
||||
"Is non-nil if currently reformatting a selected region, rather than the whole
|
||||
buffer.")
|
||||
|
||||
;;;###autoload
|
||||
(autoload 'format-all--probe "format-all")
|
||||
|
||||
(defun +format--delete-whole-line (&optional arg)
|
||||
"Delete the current line without putting it in the `kill-ring'.
|
||||
Derived from function `kill-whole-line'. ARG is defined as for that
|
||||
function.
|
||||
|
||||
Stolen shamelessly from go-mode"
|
||||
(setq arg (or arg 1))
|
||||
(if (and (> arg 0)
|
||||
(eobp)
|
||||
(save-excursion (forward-visible-line 0) (eobp)))
|
||||
(signal 'end-of-buffer nil))
|
||||
(if (and (< arg 0)
|
||||
(bobp)
|
||||
(save-excursion (end-of-visible-line) (bobp)))
|
||||
(signal 'beginning-of-buffer nil))
|
||||
(cond ((zerop arg)
|
||||
(delete-region (progn (forward-visible-line 0) (point))
|
||||
(progn (end-of-visible-line) (point))))
|
||||
((< arg 0)
|
||||
(delete-region (progn (end-of-visible-line) (point))
|
||||
(progn (forward-visible-line (1+ arg))
|
||||
(unless (bobp)
|
||||
(backward-char))
|
||||
(point))))
|
||||
((delete-region (progn (forward-visible-line 0) (point))
|
||||
(progn (forward-visible-line arg) (point))))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +format--apply-rcs-patch (patch-buffer)
|
||||
"Apply an RCS-formatted diff from PATCH-BUFFER to the current buffer.
|
||||
|
||||
Stolen shamelessly from go-mode"
|
||||
(let ((target-buffer (current-buffer))
|
||||
;; Relative offset between buffer line numbers and line numbers
|
||||
;; in patch.
|
||||
;;
|
||||
;; Line numbers in the patch are based on the source file, so
|
||||
;; we have to keep an offset when making changes to the
|
||||
;; buffer.
|
||||
;;
|
||||
;; Appending lines decrements the offset (possibly making it
|
||||
;; negative), deleting lines increments it. This order
|
||||
;; simplifies the forward-line invocations.
|
||||
(line-offset 0)
|
||||
(column (current-column)))
|
||||
(save-excursion
|
||||
(with-current-buffer patch-buffer
|
||||
(goto-char (point-min))
|
||||
(while (not (eobp))
|
||||
(unless (looking-at "^\\([ad]\\)\\([0-9]+\\) \\([0-9]+\\)")
|
||||
(error "Invalid rcs patch or internal error in +format--apply-rcs-patch"))
|
||||
(forward-line)
|
||||
(let ((action (match-string 1))
|
||||
(from (string-to-number (match-string 2)))
|
||||
(len (string-to-number (match-string 3))))
|
||||
(cond
|
||||
((equal action "a")
|
||||
(let ((start (point)))
|
||||
(forward-line len)
|
||||
(let ((text (buffer-substring start (point))))
|
||||
(with-current-buffer target-buffer
|
||||
(cl-decf line-offset len)
|
||||
(goto-char (point-min))
|
||||
(forward-line (- from len line-offset))
|
||||
(insert text)))))
|
||||
((equal action "d")
|
||||
(with-current-buffer target-buffer
|
||||
(goto-char (point-min))
|
||||
(forward-line (1- (- from line-offset)))
|
||||
(cl-incf line-offset len)
|
||||
(+format--delete-whole-line len)))
|
||||
((error "Invalid rcs patch or internal error in +format--apply-rcs-patch")))))))
|
||||
(move-to-column column)))
|
||||
|
||||
(defun +format--current-indentation ()
|
||||
(save-excursion
|
||||
(goto-char (point-min))
|
||||
(skip-chars-forward " \t\n")
|
||||
(current-indentation)))
|
||||
|
||||
|
||||
;;
|
||||
;; Public library
|
||||
|
||||
(defun +format-completing-read ()
|
||||
"TODO"
|
||||
(require 'format-all)
|
||||
(let* ((fmtlist (mapcar #'symbol-name (hash-table-keys format-all--format-table)))
|
||||
(fmt (completing-read "Formatter: " fmtlist)))
|
||||
(if fmt (intern fmt))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +format-probe-a (fn)
|
||||
"Use `+format-with' instead, if it is set.
|
||||
Prompts for a formatter if universal arg is set."
|
||||
(cond ((or buffer-read-only (eq +format-with :none))
|
||||
(list nil nil))
|
||||
(current-prefix-arg
|
||||
(list (or (+format-completing-read)
|
||||
(user-error "Aborted"))
|
||||
t))
|
||||
(+format-with
|
||||
(list +format-with t))
|
||||
((and +format-with-lsp
|
||||
(bound-and-true-p lsp-managed-mode)
|
||||
(lsp-feature? "textDocument/formatting"))
|
||||
(list 'lsp nil))
|
||||
((and +format-with-lsp
|
||||
(bound-and-true-p eglot--managed-mode)
|
||||
(eglot--server-capable :documentFormattingProvider))
|
||||
(list 'eglot nil))
|
||||
((funcall fn))))
|
||||
|
||||
;;;###autoload
|
||||
(defun +format-buffer-a (formatter mode-result)
|
||||
"Advice that extends `format-all-buffer--with' to:
|
||||
|
||||
1. Enable partial/region reformatting, while preserving leading indentation,
|
||||
2. Applies changes via RCS patch, line by line, to protect buffer markers and
|
||||
reduce cursor movement or window scrolling.
|
||||
|
||||
See `+format/buffer' for the interactive version of this function, and
|
||||
`+format-buffer-h' to use as a `before-save-hook' hook."
|
||||
(cond
|
||||
((eq formatter 'lsp)
|
||||
(call-interactively
|
||||
(if +format-region-p #'lsp-format-region #'lsp-format-buffer)))
|
||||
((eq formatter 'eglot)
|
||||
(call-interactively
|
||||
(if +format-region-p #'eglot-format #'eglot-format-buffer)))
|
||||
((let ((f-function (gethash formatter format-all--format-table))
|
||||
(executable (format-all--formatter-executable formatter))
|
||||
(indent 0)
|
||||
(old-line-number (line-number-at-pos))
|
||||
(old-column (current-column)))
|
||||
(pcase-let*
|
||||
((`(,output ,errput)
|
||||
;; To reliably format regions, rather than the whole buffer, and
|
||||
;; `format-all' (and various formatting functions, like `gofmt') widen
|
||||
;; the buffer, we must copy the region first.
|
||||
(let ((output (buffer-substring-no-properties (point-min) (point-max)))
|
||||
(origin-buffer (or (buffer-base-buffer) (current-buffer)))
|
||||
;; Fixes #5133: some packages (like lsp-mode) can do a bunch
|
||||
;; of complicated stuff in these hooks. Better to not have to
|
||||
;; deal with any of them at all.
|
||||
write-file-functions
|
||||
before-save-hook
|
||||
after-save-hook
|
||||
kill-buffer-query-functions
|
||||
kill-buffer-hook)
|
||||
(with-temp-buffer
|
||||
(with-silent-modifications
|
||||
(insert output)
|
||||
;; 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)
|
||||
in (cl-remove-if-not #'listp (buffer-local-variables origin-buffer))
|
||||
;; Making enable-multibyte-characters buffer-local
|
||||
;; causes an error.
|
||||
unless (eq var 'enable-multibyte-characters)
|
||||
;; Fixes #5133: don't deal with complicated hook
|
||||
;; functionality! This isn't a real buffer anyway.
|
||||
unless (string-match-p (symbol-name var) "-\\(hook\\|functions\\)$")
|
||||
;; Using setq-local would quote var.
|
||||
do (set (make-local-variable var) val))
|
||||
;; 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)))
|
||||
(funcall f-function executable mode-result)))))
|
||||
(`,status
|
||||
(cond ((null output) :error)
|
||||
((eq output t) :already-formatted)
|
||||
(t :reformatted))))
|
||||
(unwind-protect
|
||||
(when (eq status :reformatted)
|
||||
(let ((tmpfile (make-temp-file "doom-format"))
|
||||
(patchbuf (get-buffer-create " *doom format patch*"))
|
||||
(coding-system-for-read coding-system-for-read)
|
||||
(coding-system-for-write coding-system-for-write))
|
||||
(unless IS-WINDOWS
|
||||
(setq coding-system-for-read 'utf-8
|
||||
coding-system-for-write 'utf-8))
|
||||
(unwind-protect
|
||||
(progn
|
||||
(with-current-buffer patchbuf
|
||||
(erase-buffer))
|
||||
(with-temp-file tmpfile
|
||||
(erase-buffer)
|
||||
(insert output)
|
||||
(when (> indent 0)
|
||||
;; restore indentation without affecting new
|
||||
;; indentation
|
||||
(indent-rigidly (point-min) (point-max)
|
||||
(max 0 (- indent (+format--current-indentation))))))
|
||||
(if (zerop (call-process-region (point-min) (point-max) "diff" nil patchbuf nil "-n" "-" tmpfile))
|
||||
(setq status :already-formatted)
|
||||
(+format--apply-rcs-patch patchbuf)
|
||||
(list output errput)))
|
||||
(kill-buffer patchbuf)
|
||||
(delete-file tmpfile))))
|
||||
(format-all--show-or-hide-errors errput)
|
||||
(goto-char (point-min))
|
||||
(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))))))))))
|
||||
(defun +format-region (start end &optional callback)
|
||||
"Format from START to END with `apheleia'."
|
||||
(when-let* ((command (apheleia--get-formatter-command
|
||||
(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)
|
||||
(unless IS-WINDOWS
|
||||
(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)
|
||||
in (cl-remove-if-not #'listp (buffer-local-variables origin-buffer))
|
||||
;; 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)
|
||||
(max 0 (- indent (+format--current-indentation))))))
|
||||
(with-current-buffer cur-buffer
|
||||
(delete-region start end)
|
||||
(insert-buffer-substring-no-properties formatted-buffer)
|
||||
(when callback (funcall callback))
|
||||
(kill-buffer formatted-buffer)))))))
|
||||
|
||||
|
||||
;;
|
||||
;;; Commands
|
||||
|
||||
(defun +format--org-region (beg end)
|
||||
"Reformat the region within BEG and END.
|
||||
If nil, BEG and/or END will default to the boundaries of the src block at point."
|
||||
(let ((element (org-element-at-point)))
|
||||
(save-excursion
|
||||
(let* ((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)))
|
||||
(beg (if beg (max beg block-beg) block-beg))
|
||||
(end (if end (min end block-end) block-end))
|
||||
(lang (org-element-property :language element))
|
||||
(major-mode (org-src-get-lang-mode lang)))
|
||||
(if (eq major-mode 'org-mode)
|
||||
(user-error "Cannot reformat an org src block in org-mode")
|
||||
(+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
|
||||
(defun +format/buffer ()
|
||||
(defun +format/buffer (&optional arg)
|
||||
"Reformat the current buffer using LSP or `format-all-buffer'."
|
||||
(interactive)
|
||||
(+format--buffer))
|
||||
(interactive "P")
|
||||
(call-interactively
|
||||
(if (and +format-with-lsp
|
||||
(bound-and-true-p lsp-mode)
|
||||
(lsp-feature? "textDocument/formatting"))
|
||||
#'lsp-format-buffer
|
||||
#'apheleia-format-buffer)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +format/region (beg end)
|
||||
(defun +format/region (beg end &optional arg)
|
||||
"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."
|
||||
(interactive "rP")
|
||||
(let ((+format-region-p t))
|
||||
(save-restriction
|
||||
(narrow-to-region beg end)
|
||||
(+format--buffer))))
|
||||
(if (and +format-with-lsp
|
||||
(bound-and-true-p lsp-mode)
|
||||
(lsp-feature? "textDocument/rangeFormatting"))
|
||||
(call-interactively #'lsp-format-region)
|
||||
(+format-region beg end)))
|
||||
|
||||
;;;###autoload
|
||||
(defun +format/region-or-buffer ()
|
||||
|
@ -281,13 +90,3 @@ is selected)."
|
|||
(if (doom-region-active-p)
|
||||
#'+format/region
|
||||
#'+format/buffer)))
|
||||
|
||||
|
||||
;;
|
||||
;; Hooks
|
||||
|
||||
;;;###autoload
|
||||
(defalias '+format-buffer-h #'+format/buffer
|
||||
"Format the source code in the current buffer with minimal feedback.
|
||||
|
||||
Meant for `before-save-hook'.")
|
||||
|
|
|
@ -1,97 +1,11 @@
|
|||
;;; editor/format/autoload/settings.el -*- lexical-binding: t; -*-
|
||||
|
||||
;; This must be redefined here because `format-all' only makes it available at
|
||||
;; compile time.
|
||||
(defconst +format-system-type
|
||||
(cl-case system-type
|
||||
(windows-nt 'windows)
|
||||
(cygwin 'windows)
|
||||
(darwin 'macos)
|
||||
(gnu/linux 'linux)
|
||||
(berkeley-unix
|
||||
(save-match-data
|
||||
(let ((case-fold-search t))
|
||||
(cond ((string-match "freebsd" system-configuration) 'freebsd)
|
||||
((string-match "openbsd" system-configuration) 'openbsd)
|
||||
((string-match "netbsd" system-configuration) 'netbsd))))))
|
||||
"Current operating system according to the format-all package.")
|
||||
|
||||
(defun +format--resolve-system (choices)
|
||||
"Get first choice matching `format-all-system-type' from CHOICES."
|
||||
(cl-loop for choice in choices
|
||||
if (atom choice) return choice
|
||||
else if (eql +format-system-type (car choice))
|
||||
return (cadr choice)))
|
||||
|
||||
|
||||
(defun +format--make-command (formatter &rest _)
|
||||
`(format-all--buffer-thunk
|
||||
(lambda (input)
|
||||
(with-silent-modifications
|
||||
(setq buffer-file-name ,(buffer-file-name (buffer-base-buffer))
|
||||
default-directory ,default-directory)
|
||||
(delay-mode-hooks (funcall ',major-mode))
|
||||
(insert input)
|
||||
(condition-case e
|
||||
(progn
|
||||
(doom-log "formatter (commandp) %s" #',formatter)
|
||||
(call-interactively #',formatter)
|
||||
(list nil ""))
|
||||
(error (list t (error-message-string e))))))))
|
||||
|
||||
(defun +format--make-function (formatter &rest _)
|
||||
`(progn
|
||||
(doom-log "formatter (functionp) %s" #',formatter)
|
||||
(format-all--buffer-thunk #',formatter)))
|
||||
|
||||
(defun +format--make-shell-command (command ok-statuses error-regexp)
|
||||
(+format--make-shell-command-list (split-string command " " t)
|
||||
ok-statuses error-regexp))
|
||||
|
||||
(defun +format--make-shell-command-list (command-list ok-statuses error-regexp)
|
||||
`(let (args)
|
||||
(dolist (arg ',command-list)
|
||||
(cond ((stringp arg)
|
||||
(push arg args))
|
||||
((listp arg)
|
||||
(catch 'skip
|
||||
(let (subargs this)
|
||||
(while (setq this (pop arg))
|
||||
(cond ((not (stringp (car arg)))
|
||||
(let ((val (eval (pop arg) t)))
|
||||
(unless val (throw 'skip nil))
|
||||
(push (format this val) subargs)))
|
||||
((stringp this)
|
||||
(push this subargs))))
|
||||
(setq args (append subargs args)))))))
|
||||
(doom-log "formatter (arglist) %s" args)
|
||||
(if ,(and (or ok-statuses error-regexp) t)
|
||||
(apply #'format-all--buffer-hard
|
||||
',ok-statuses ,error-regexp nil
|
||||
(reverse args))
|
||||
(apply #'format-all--buffer-easy (reverse args)))))
|
||||
|
||||
(cl-defun +format--set (name &key function modes unset)
|
||||
(declare (indent defun))
|
||||
(when (and unset (not (gethash name format-all--format-table)))
|
||||
(error "'%s' formatter does not exist to be unset" name))
|
||||
(puthash name function format-all--format-table)
|
||||
(dolist (mode (ensure-list modes))
|
||||
(cl-destructuring-bind (m &optional probe)
|
||||
(ensure-list mode)
|
||||
(if unset
|
||||
(puthash m (assq-delete-all name (gethash key format-all-mode-table))
|
||||
format-all-mode-table)
|
||||
(format-all--pushhash
|
||||
m (cons name (if probe `(lambda () ,probe)))
|
||||
format-all--mode-table)))))
|
||||
|
||||
;;;###autodef
|
||||
(cl-defun set-formatter!
|
||||
(name formatter &key modes filter ok-statuses error-regexp)
|
||||
(name &rest args &key modes filter &allow-other-keys)
|
||||
"Define (or modify) a formatter named NAME.
|
||||
|
||||
Supported keywords: :modes :filter :ok-statuses :error-regexp
|
||||
Supported keywords: :modes :filter
|
||||
|
||||
NAME is a symbol that identifies this formatter.
|
||||
|
||||
|
@ -99,7 +13,7 @@ FORMATTER can be a symbol referring to another formatter, a function, string or
|
|||
nested list.
|
||||
|
||||
If a function, it should be a formatter function that
|
||||
`format-all--buffer-thunk' will accept.
|
||||
`apheleia--run-formatter-function' will accept.
|
||||
If a string, it is assumed to be a shell command that the buffer's text will
|
||||
be piped to (through stdin).
|
||||
If a list, it should represent a shell command as a list of arguments. Each
|
||||
|
@ -107,6 +21,9 @@ nested list.
|
|||
string and ARG is both a predicate and argument for STRING. If ARG is nil,
|
||||
STRING will be omitted from the vector.
|
||||
|
||||
For more information on how to structure the list to be
|
||||
compatible, see `apheleia--run-formatter-function'.
|
||||
|
||||
MODES is a major mode, a list thereof, or a list of two-element sublists with
|
||||
the structure: (MAJOR-MODE FORM). FORM is evaluated when the buffer is formatted
|
||||
and its return value serves two purposes:
|
||||
|
@ -116,27 +33,12 @@ and its return value serves two purposes:
|
|||
2. It's return value is made available to FORMATTER if it is a function or
|
||||
list of shell arguments via the `mode-result' variable.
|
||||
|
||||
FILTER is a function that takes three arguments: the formatted output, any error
|
||||
output and the position of the first change. This function must return these
|
||||
three after making whatever changes you like to them. This might be useful if
|
||||
the output contains ANSI color codes that need to be stripped out (as is the
|
||||
case with elm-format).
|
||||
|
||||
OK-STATUSES and ERROR-REGEXP are ignored if FORMATTER is not a shell command.
|
||||
|
||||
OK-STATUSES is a list of integer exit codes that should be treated as success
|
||||
codes. However, if ERROR-REGEXP is given, and the program's stderr contains that
|
||||
regexp, then the formatting is considered failed even if the exit status is in
|
||||
OK-STATUSES.
|
||||
|
||||
Basic examples:
|
||||
|
||||
(set-formatter! 'asmfmt \"asmfmt\" :modes '(asm-mode nasm-mode))
|
||||
(set-formatter! 'black \"black -q -\")
|
||||
(set-formatter! 'html-tidy \"tidy -q -indent\" :modes '(html-mode web-mode))
|
||||
|
||||
Advanced examples:
|
||||
|
||||
(set-formatter!
|
||||
'clang-format
|
||||
'(\"clang-format\"
|
||||
|
@ -154,9 +56,7 @@ Advanced examples:
|
|||
:modes
|
||||
'(html-mode
|
||||
(web-mode (and (equal \"none\" web-mode-engine)
|
||||
(car (member web-mode-content-type '(\"xml\" \"html\"))))))
|
||||
:ok-statuses '(0 1)
|
||||
:executable \"tidy\")
|
||||
(car (member web-mode-content-type '(\"xml\" \"html\")))))))
|
||||
|
||||
(set-formatter! 'html-tidy ; overwrite predefined html-tidy formatter
|
||||
'(\"tidy\" \"-q\" \"-indent\"
|
||||
|
@ -165,39 +65,22 @@ Advanced examples:
|
|||
\"--show-body-only\" \"auto\"
|
||||
(\"--indent-spaces\" \"%d\" tab-width)
|
||||
(\"--indent-with-tabs\" \"%s\" (if indent-tabs-mode \"yes\" \"no\"))
|
||||
(\"-xml\" (memq major-mode '(nxml-mode xml-mode))))
|
||||
:ok-statuses '(0 1)))
|
||||
(\"-xml\" (memq major-mode '(nxml-mode xml-mode)))))
|
||||
|
||||
(set-formatter! 'elm-format
|
||||
\"elm-format --yes --stdin\"
|
||||
:filter
|
||||
(lambda (output errput first-diff)
|
||||
(list output
|
||||
(format-all--remove-ansi-color errput)
|
||||
first-diff)))"
|
||||
\"elm-format --yes --stdin\")
|
||||
"
|
||||
(declare (indent defun))
|
||||
(cl-check-type name symbol)
|
||||
(after! format-all
|
||||
(if (null formatter)
|
||||
(+format--set name
|
||||
:unset t
|
||||
:modes modes)
|
||||
(let ((fn (funcall (cond ((stringp formatter)
|
||||
#'+format--make-shell-command)
|
||||
((listp formatter)
|
||||
#'+format--make-shell-command-list)
|
||||
((and (commandp formatter)
|
||||
(not (stringp formatter)))
|
||||
#'+format--make-command)
|
||||
((functionp formatter)
|
||||
#'+format--make-function))
|
||||
formatter
|
||||
ok-statuses
|
||||
error-regexp)))
|
||||
(cl-check-type filter (or function null))
|
||||
(+format--set name
|
||||
:function
|
||||
`(lambda (executable mode-result)
|
||||
,(if filter `(apply #',filter ,fn) fn))
|
||||
:modes modes)
|
||||
name))))
|
||||
(after! apheleia
|
||||
(if (null args)
|
||||
(progn
|
||||
(setq apheleia-formatters
|
||||
(assq-delete-all name apheleia-formatters))
|
||||
(while (rassoc name apheleia-mode-alist)
|
||||
(setq apheleia-mode-alist
|
||||
(assq-delete-all (car (rassoc name apheleia-mode-alist)) apheleia-mode-alist))))
|
||||
(setf (alist-get name apheleia-formatters) args)
|
||||
(when modes
|
||||
(dolist (mode modes)
|
||||
(setf (alist-get mode apheleia-mode-alist) name))))))
|
||||
|
|
|
@ -1,89 +1,54 @@
|
|||
;;; editor/format/config.el -*- lexical-binding: t; -*-
|
||||
|
||||
(defvar +format-on-save-enabled-modes
|
||||
'(not emacs-lisp-mode ; elisp's mechanisms are good enough
|
||||
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 reformat the buffer upon saving.
|
||||
|
||||
If this list begins with `not', then it negates the list.
|
||||
If it is `t', it is enabled in all modes.
|
||||
If nil, it is disabled in all modes, the same as if the +onsave flag wasn't
|
||||
used at all.
|
||||
|
||||
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.
|
||||
|
||||
Indentation is always preserved when formatting regions.")
|
||||
|
||||
(defvar-local +format-with nil
|
||||
"Set this to explicitly use a certain formatter for the current buffer.")
|
||||
|
||||
(defvar +format-with-lsp t
|
||||
"If non-nil, format with LSP formatter if it's available.
|
||||
|
||||
This can be set buffer-locally with `setq-hook!' to disable LSP formatting in
|
||||
select buffers.")
|
||||
|
||||
(defvaralias '+format-with 'apheleia-formatter
|
||||
"Set this to explicitly use a certain formatter for the current buffer.")
|
||||
|
||||
|
||||
;;
|
||||
;;; Bootstrap
|
||||
|
||||
(add-to-list 'doom-debug-variables 'format-all-debug)
|
||||
|
||||
(defun +format-enable-on-save-maybe-h ()
|
||||
"Enable formatting on save in certain major modes.
|
||||
|
||||
This is controlled by `+format-on-save-enabled-modes'."
|
||||
(or (cond ((eq major-mode 'fundamental-mode))
|
||||
((string-prefix-p " " (buffer-name)))
|
||||
((and (booleanp +format-on-save-enabled-modes)
|
||||
(not +format-on-save-enabled-modes)))
|
||||
((and (listp +format-on-save-enabled-modes)
|
||||
(if (eq (car +format-on-save-enabled-modes) 'not)
|
||||
(memq major-mode (cdr +format-on-save-enabled-modes))
|
||||
(not (memq major-mode +format-on-save-enabled-modes)))))
|
||||
((not (require 'format-all nil t))))
|
||||
(format-all-mode +1)))
|
||||
|
||||
(when (modulep! +onsave)
|
||||
(add-hook 'after-change-major-mode-hook #'+format-enable-on-save-maybe-h))
|
||||
(add-hook 'doom-first-file-hook #'apheleia-global-mode))
|
||||
|
||||
|
||||
;;
|
||||
;;; Hacks
|
||||
|
||||
;; Allow a specific formatter to be used by setting `+format-with', either
|
||||
;; buffer-locally or let-bound.
|
||||
(advice-add #'format-all--probe :around #'+format-probe-a)
|
||||
(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 (member arg '(nil 1)))))
|
||||
(funcall orig-fn)))
|
||||
|
||||
;; Doom uses a modded `format-all-buffer', which
|
||||
;; 1. Enables partial reformatting (while preserving leading indentation),
|
||||
;; 2. Applies changes via RCS patch, line by line, to protect buffer markers
|
||||
;; and avoid any jarring cursor+window scrolling.
|
||||
(advice-add #'format-all-buffer--with :override #'+format-buffer-a)
|
||||
(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-git-gutter-h ()
|
||||
(when (bound-and-true-p git-gutter-mode)
|
||||
(git-gutter))))
|
||||
|
||||
;; format-all-mode "helpfully" raises an error when it doesn't know how to
|
||||
;; format a buffer.
|
||||
(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 (fn &rest args)
|
||||
:around #'format-all-buffer--from-hook
|
||||
(letf! (defun format-all-buffer--with (formatter mode-result)
|
||||
(when (or (eq formatter 'lsp)
|
||||
(eq formatter 'eglot)
|
||||
(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 fn args)))
|
||||
;;
|
||||
;;; Additional formatters
|
||||
|
||||
(after! apheleia-mode
|
||||
;; TODO html-tidy
|
||||
)
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; editor/format/packages.el
|
||||
|
||||
(package! format-all :pin "47d862d40a088ca089c92cd393c6dca4628f87d3")
|
||||
;; TODO Pin when this is close to finish
|
||||
(package! apheleia)
|
||||
|
|
|
@ -1,103 +0,0 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; editor/format/test/test-format.el
|
||||
|
||||
(load! "../autoload/settings")
|
||||
(load! "../autoload/format")
|
||||
(require! :editor format)
|
||||
(require 'format-all)
|
||||
|
||||
;;
|
||||
(describe "editor/format"
|
||||
:var (format-all--format-table
|
||||
format-all--mode-table)
|
||||
|
||||
(before-each
|
||||
(setq format-all--format-table (make-hash-table)
|
||||
format-all--mode-table (make-hash-table)))
|
||||
|
||||
(describe "set-formatter!"
|
||||
(before-each
|
||||
(set-formatter! 'test (lambda () (interactive))))
|
||||
|
||||
(it "defines a formatter"
|
||||
(set-formatter! 'new (lambda () (interactive)))
|
||||
(expect (gethash 'new format-all--mode-table) :to-equal nil)
|
||||
(expect (functionp (gethash 'new format-all--format-table))))
|
||||
|
||||
(it "defines a formatter with modes"
|
||||
(set-formatter! 'new (lambda () (interactive))
|
||||
:modes '(a-mode (b-mode "x")))
|
||||
(expect (gethash 'a-mode format-all--mode-table)
|
||||
:to-equal '((new)))
|
||||
(expect (gethash 'b-mode format-all--mode-table)
|
||||
:to-equal '((new . (lambda () "x")))))
|
||||
|
||||
(it "replaces a pre-existing formatter"
|
||||
(let ((old-fn (gethash 'test format-all--format-table)))
|
||||
(set-formatter! 'test "echo")
|
||||
(expect (gethash 'test format-all--format-table) :not :to-equal old-fn)))
|
||||
|
||||
(it "unsets a pre-existing formatter"
|
||||
(set-formatter! 'test nil)
|
||||
(expect (gethash 'test format-all--format-table) :to-be nil))
|
||||
|
||||
(it "errors when unsetting non-existent formatter"
|
||||
(expect (set-formatter! 'doesnt-exist nil) :to-throw)))
|
||||
|
||||
|
||||
;; TODO
|
||||
(xdescribe "hooks"
|
||||
(describe "format|enable-on-save-maybe")
|
||||
(describe "format|enable-on-save"))
|
||||
|
||||
|
||||
;; TODO
|
||||
(xdescribe "formatting"
|
||||
(before-each
|
||||
(set-formatter! 'command
|
||||
(lambda ()
|
||||
(interactive)
|
||||
(let ((first-line (car (split-string (buffer-string) "\n"))))
|
||||
(erase-buffer)
|
||||
(insert first-line)))
|
||||
:modes '(text-mode))
|
||||
(set-formatter! 'faulty-command
|
||||
(lambda ()
|
||||
(interactive)
|
||||
(error "This is a test"))
|
||||
:modes '(text-mode))
|
||||
(set-formatter! 'function
|
||||
(lambda (input)
|
||||
(insert (car (split-string input "\n")))
|
||||
(list nil nil))
|
||||
:modes '(text-mode))
|
||||
(set-formatter! 'shellcmd "head -n 1"
|
||||
:modes '(text-mode))
|
||||
(set-formatter! 'cmdlist '("head" "-n" "1")
|
||||
:modes '(text-mode)))
|
||||
|
||||
(describe "with an interactive command"
|
||||
(it "formats a buffer" )
|
||||
(it "formats a region" )
|
||||
(it "no-ops if no change" )
|
||||
(it "doesn't modify the buffer in case of errors" )
|
||||
(it "preserves indentation" ))
|
||||
|
||||
(describe "with a function"
|
||||
(it "formats a buffer" )
|
||||
(it "formats a region" )
|
||||
(it "no-ops if no change" )
|
||||
(it "doesn't modify the buffer in case of errors" )
|
||||
(it "preserves indentation" ))
|
||||
|
||||
(describe "with a shell command")
|
||||
|
||||
(describe "with a shell command list"
|
||||
(it "formats a buffer" )
|
||||
(it "formats a region" )
|
||||
(it "no-ops if no change" )
|
||||
(it "doesn't modify the buffer in case of errors" )
|
||||
(it "preserves indentation" )
|
||||
|
||||
(it "interpolates non-strings into format strings" )
|
||||
(it "conditionally appends sublisted options" ))))
|
|
@ -107,8 +107,6 @@
|
|||
:commands ocamlformat
|
||||
:hook (tuareg-mode-local-vars . +ocaml-init-ocamlformat-h)
|
||||
:config
|
||||
(set-formatter! 'ocamlformat #'ocamlformat
|
||||
:modes '(caml-mode tuareg-mode))
|
||||
;; TODO Fix region-based formatting support
|
||||
(defun +ocaml-init-ocamlformat-h ()
|
||||
(setq +format-with 'ocp-indent)
|
||||
|
|
|
@ -30,7 +30,6 @@
|
|||
(set-docsets! 'php-mode "PHP" "PHPUnit" "Laravel" "CakePHP" "CodeIgniter" "Doctrine_ORM")
|
||||
(set-repl-handler! 'php-mode #'+php/open-repl)
|
||||
(set-lookup-handlers! 'php-mode :documentation #'php-search-documentation)
|
||||
(set-formatter! 'php-mode #'php-cs-fixer-fix)
|
||||
(set-ligatures! 'php-mode
|
||||
;; Functional
|
||||
:lambda "function()" :lambda "fn"
|
||||
|
|
|
@ -17,10 +17,13 @@
|
|||
:config
|
||||
(set-docsets! 'sh-mode "Bash")
|
||||
(set-electric! 'sh-mode :words '("else" "elif" "fi" "done" "then" "do" "esac" ";;"))
|
||||
(set-formatter! 'shfmt
|
||||
'("shfmt" "-ci"
|
||||
("-i" "%d" (unless indent-tabs-mode tab-width))
|
||||
("-ln" "%s" (pcase sh-shell (`bash "bash") (`mksh "mksh") (_ "posix")))))
|
||||
(after! apheleia
|
||||
(setf (alist-get 'shfmt apheleia-formatters)
|
||||
'("shfmt" "-ci"
|
||||
(unless indent-tabs-mode
|
||||
(list "-i" (number-to-string tab-width)))
|
||||
(list "-ln" (pcase sh-shell (`bash "bash") (`mksh "mksh") (_ "posix"))))))
|
||||
|
||||
(set-repl-handler! 'sh-mode #'+sh/open-repl)
|
||||
(set-lookup-handlers! 'sh-mode :documentation #'+sh-lookup-documentation-handler)
|
||||
(set-ligatures! 'sh-mode
|
||||
|
@ -85,10 +88,6 @@
|
|||
;; whatis lookups are exceptionally slow on macOS (#5860)
|
||||
company-shell-dont-fetch-meta IS-MAC))
|
||||
|
||||
(use-package! fish-mode
|
||||
:when (modulep! +fish)
|
||||
:defer t
|
||||
:config (set-formatter! 'fish-mode #'fish_indent))
|
||||
|
||||
(use-package! powershell
|
||||
:when (modulep! +powershell)
|
||||
|
|
|
@ -27,15 +27,15 @@
|
|||
|
||||
;; tidy is already defined by the format-all package. We redefine it to add
|
||||
;; more sensible arguments to the tidy command.
|
||||
(set-formatter! 'html-tidy
|
||||
'("tidy" "-q" "-indent"
|
||||
"--tidy-mark" "no"
|
||||
"--drop-empty-elements" "no"
|
||||
("--show-body-only" "%s" (if +format-region-p "true" "auto"))
|
||||
("--indent-spaces" "%d" tab-width)
|
||||
("--indent-with-tabs" "%s" (if indent-tabs-mode "yes" "no"))
|
||||
("-xml" (memq major-mode '(nxml-mode xml-mode))))
|
||||
:ok-statuses '(0 1))
|
||||
;; (set-formatter! 'html-tidy
|
||||
;; '("tidy" "-q" "-indent"
|
||||
;; "--tidy-mark" "no"
|
||||
;; "--drop-empty-elements" "no"
|
||||
;; ("--show-body-only" "%s" (if +format-region-p "true" "auto"))
|
||||
;; ("--indent-spaces" "%d" tab-width)
|
||||
;; ("--indent-with-tabs" "%s" (if indent-tabs-mode "yes" "no"))
|
||||
;; ("-xml" (memq major-mode '(nxml-mode xml-mode))))
|
||||
;; :ok-statuses '(0 1))
|
||||
|
||||
(setq web-mode-enable-html-entities-fontification t
|
||||
web-mode-auto-close-style 1)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue