merge: pull request #6369 from elken/feature/editor-format-refactor
This commit is contained in:
commit
a234d8e9c0
89 changed files with 526 additions and 728 deletions
|
@ -3,44 +3,37 @@
|
|||
#+created: July 26, 2020
|
||||
#+since: 21.12.0
|
||||
|
||||
#+begin_quote
|
||||
🔨 This module has been scheduled for a rewrite. Its documentation will remain
|
||||
incomplete and edge cases left unpatched in the meantime. A preview of this
|
||||
rewrite can be found [[https://github.com/hlissner/doom-emacs-private/tree/master/modules/editor/format][in my private config]].
|
||||
#+end_quote
|
||||
|
||||
* Description :unfold:
|
||||
This module integrates code formatters into Emacs. Here are some of the
|
||||
formatters that it currently supports:
|
||||
Code style is something that's hotly debated since the beginning of time.
|
||||
|
||||
#+begin_quote
|
||||
asmfmt, black, brittany, cabal-fmt, clang-format, cmake-format, dartfmt, dfmt,
|
||||
dhall format, dockfmt, elm-format, emacs, fish_indent, fprettify, gleam format,
|
||||
gofmt, iStyle, jsonnetfmt, ktlint, latexindent, ledger-mode, lua-fmt, mix
|
||||
format, nixfmt, node-cljfmt, ocp-indent, perltidy, prettier, purty, rufo,
|
||||
rustfmt, scalafmt, script shfmt, snakefmt, sqlformat, styler, swiftformat, tidy
|
||||
#+end_quote
|
||||
Tabs or spaces?
|
||||
2-width or 4-width indentation?
|
||||
|
||||
Which is right? Doom doesn't care, but we will try and make it easy for you to
|
||||
format code within the safety of Emacs.
|
||||
|
||||
At present, the module wraps [[https://github.com/radian-software/apheleia/][apheleia]], which includes some more detail on the
|
||||
internals of the package; but the long and short of it is on-save your code will
|
||||
be formatted and returned to the buffer using
|
||||
[[https://tools.ietf.org/doc/tcllib/html/rcs.html#section4][RCS patching]].
|
||||
|
||||
** Maintainers
|
||||
/This module has no dedicated maintainers./ [[doom-contrib-maintainer:][Become a maintainer?]]
|
||||
- [[doom-user:][@elken]]
|
||||
|
||||
[[doom-contrib-maintainer:][Become a maintainer?]]
|
||||
|
||||
** Module flags
|
||||
- +onsave ::
|
||||
Enable reformatting of a buffer when it is saved. See
|
||||
[[var:+format-on-save-enabled-modes]] to control what major modes to (or not to)
|
||||
[[var:+format-on-save-disabled-modes]] to control what major modes to (or not to)
|
||||
format on save.
|
||||
|
||||
** Packages
|
||||
- [[doom-package:format-all]]
|
||||
- [[doom-package:apheleia]]
|
||||
|
||||
** Hacks
|
||||
- format-all has been heavily modified to suit Doom's goals for this module:
|
||||
- Reformatted text is applied to the buffer by RCS patch, as to reduce its
|
||||
affect on cursor position.
|
||||
- Adds partial formatting, i.e. you can now reformat a subset of the buffer.
|
||||
- Adds the ability to use any arbitrary formatter on the current buffer if you
|
||||
pass the universal argument to [[fn:+format/buffer]] or [[fn:+format/region]] (i.e.
|
||||
removes the major-mode lock on formatters).
|
||||
As of writing this, apheleia doesn't /yet/ support regions or similar kinds of
|
||||
buffers, so there are a couple of hacks to attempt to rectify this.
|
||||
|
||||
** TODO Changelog
|
||||
# This section will be machine generated. Don't edit it by hand.
|
||||
|
@ -51,98 +44,64 @@ rustfmt, scalafmt, script shfmt, snakefmt, sqlformat, styler, swiftformat, tidy
|
|||
|
||||
This module has no direct requirements, but each language will need one of their
|
||||
supported formatter programs in order for this to work. In their absence,
|
||||
[[doom-package:format-all]] will fail silently.
|
||||
[[doom-package:apheleia]] will fail silently.
|
||||
|
||||
Supported formatters:
|
||||
- Angular/Vue (prettier)
|
||||
- Assembly (asmfmt)
|
||||
- Bazel Starlark (buildifier)
|
||||
- BibTeX (emacs)
|
||||
- C/C++/Objective-C (clang-format)
|
||||
- Cabal (cabal-fmt)
|
||||
- Clojure/ClojureScript (node-cljfmt)
|
||||
- CMake (cmake-format)
|
||||
- Crystal (crystal tool format)
|
||||
- CSS/Less/SCSS (prettier)
|
||||
- D (dfmt)
|
||||
- Dart (dartfmt)
|
||||
- Dhall (dhall format)
|
||||
- Dockerfile (dockfmt)
|
||||
- Elixir (mix format)
|
||||
- Elm (elm-format)
|
||||
- Emacs Lisp (emacs)
|
||||
- Fish Shell (fish_indent)
|
||||
- Fortran 90 (fprettify)
|
||||
- Gleam (gleam format)
|
||||
- Go (gofmt)
|
||||
- GraphQL (prettier)
|
||||
- Haskell (brittany)
|
||||
- HTML/XHTML/XML (tidy)
|
||||
- Java (clang-format)
|
||||
- JavaScript/JSON/JSX (prettier)
|
||||
- Jsonnet (jsonnetfmt)
|
||||
- Kotlin (ktlint)
|
||||
- LaTeX (latexindent)
|
||||
- Ledger (ledger-mode)
|
||||
- Lua (lua-fmt)
|
||||
- Markdown (prettier)
|
||||
- Nix (nixfmt)
|
||||
- OCaml (ocp-indent)
|
||||
- Perl (perltidy)
|
||||
- PHP (prettier plugin-php)
|
||||
- Protocol Buffers (clang-format)
|
||||
- PureScript (purty)
|
||||
- Python (black)
|
||||
- R (styler)
|
||||
- Ruby (rufo)
|
||||
- Rust (rustfmt)
|
||||
- Scala (scalafmt)
|
||||
- Shell script (shfmt)
|
||||
- Snakemake (snakefmt)
|
||||
- Solidity (prettier-plugin-solidity)
|
||||
- SQL (sqlformat)
|
||||
- Swift (swiftformat)
|
||||
- Terraform (terraform fmt)
|
||||
- TOML (prettier-plugin-toml)
|
||||
- TypeScript/TSX (prettier)
|
||||
- Verilog (iStyle)
|
||||
- YAML (prettier)
|
||||
To see if a particular mode has a configured formatter, check for the mode in
|
||||
[[var:apheleia-mode-alist]] which corresponds to the list of formatters defined in
|
||||
[[var:apheleia-formatters]]
|
||||
|
||||
* TODO Usage
|
||||
#+begin_quote
|
||||
🔨 This module has no usage documentation yet. [[doom-contrib-module:][Write some?]]
|
||||
#+end_quote
|
||||
* Usage
|
||||
** With +onsave
|
||||
When this flag is enabled, you shouldn't need to do anything other than write
|
||||
code and save it.
|
||||
|
||||
* TODO Configuration
|
||||
#+begin_quote
|
||||
🔨 /This module's configuration documentation is incomplete./ [[doom-contrib-module:][Complete it?]]
|
||||
#+end_quote
|
||||
** Without +onsave
|
||||
Without the flag, formatting will only occur when either =apheleia-format-buffer=
|
||||
or =+format/buffer= is called.
|
||||
|
||||
** Automatic reformatting when saving buffers
|
||||
There are two ways to achieve this. Either through the =+onsave= flag, or by
|
||||
adding ~format-all-mode~ to the hook of each major mode you want this behavior
|
||||
enabled in.
|
||||
* Configuration
|
||||
|
||||
Detailed configuration can be found [[https://github.com/radian-software/apheleia/#user-guide][upstream]], but for most purposes here we
|
||||
provide a simple macro that looks like the below:
|
||||
|
||||
If you choose the former, what modes it applies to can be changed by modifying
|
||||
~+format-on-save-enabled-modes~, which contains a list of major modes. If the
|
||||
first item in the list is the symbol ~not~, the list is negated. This is its
|
||||
default value:
|
||||
#+begin_src emacs-lisp
|
||||
(setq +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))
|
||||
(set-formatter! 'unique-name '("command" "line" "here") :modes '(name-of-major-mode))
|
||||
#+end_src
|
||||
|
||||
If you want to format code when you save a buffer, but want more granular
|
||||
control over which major modes this behavior is enabled in, there is an
|
||||
alternative. Make sure [[doom-module:+onsave]] is disabled before you try this:
|
||||
There are a few bonus symbols that apheleia uses (for example =npx= will be
|
||||
replaced by a correct path to npx) which are all documented in the link above.
|
||||
|
||||
** Disabling formatters
|
||||
*** Permanently
|
||||
To permanently disable a particular formatter with no provided alternative
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(add-hook 'python-mode-hook #'format-all-mode)
|
||||
(add-hook 'js2-mode-hook #'format-all-mode)
|
||||
(setq apheleia-formatters (delq (assoc 'csharpier apheleia-formatters) apheleia-formatters))
|
||||
#+end_src
|
||||
|
||||
*** Per-buffer
|
||||
If you want to save without formatting, this is done by first passing the
|
||||
universal argument thus; =SPC u SPC f s= for evil users, =C-u C-x C-s= for non-evil
|
||||
users.
|
||||
|
||||
If you want to save more than a handful of time, you can set
|
||||
[[var:apheleia-inhibit]] to disable even if =apheleia-global-mode= is on.
|
||||
|
||||
*** Onsave only
|
||||
This behaviour is controlled via [[var:+format-on-save-disabled-modes]] thus;
|
||||
|
||||
#+begin_src emacs-lisp
|
||||
(setq +format-on-save-disabled-modes
|
||||
'(emacs-lisp-mode ; elisp's mechanisms are good enough
|
||||
sql-mode ; sqlformat is currently broken
|
||||
tex-mode ; latexindent is broken
|
||||
latex-mode))
|
||||
#+end_src
|
||||
|
||||
In this case, =emacs-lisp-mode=, =sql-mode=, =tex-mode= and =latex-mode= will not be
|
||||
formatted on save, but can still be formatted by manually invoking the commands
|
||||
=apheleia-format-buffer= or =+format/buffer=.
|
||||
|
||||
** Disabling the LSP formatter
|
||||
If you are in a buffer with ~lsp-mode~ enabled and a server that supports
|
||||
=textDocument/formatting=, it will be used instead of [[doom-package:format-all]]'s formatter.
|
||||
|
@ -151,25 +110,31 @@ If you are in a buffer with ~lsp-mode~ enabled and a server that supports
|
|||
+ To disable this behavior in one mode: ~(setq-hook! 'python-mode-hook
|
||||
+format-with-lsp nil)~
|
||||
|
||||
** TODO Defining your own formatters
|
||||
See the ~set-formatter!~ function.
|
||||
|
||||
** TODO Selecting a specific formatter for a particular buffer
|
||||
** Selecting a specific formatter for a particular buffer
|
||||
Set the buffer-local variable ~+format-with~ to the name of the formatter to
|
||||
use. e.g.
|
||||
#+begin_src emacs-lisp
|
||||
;; Overrides `apheleia-mode-alist`
|
||||
(setq-hook! 'python-mode-hook +format-with 'html-tidy)
|
||||
|
||||
;; Or set it to `:none' to disable formatting
|
||||
(setq-hook! 'python-mode-hook +format-with :none)
|
||||
;; Or set it to `nil' to fallback to `apheleia-mode-alist`
|
||||
(setq-hook! 'python-mode-hook +format-with nil)
|
||||
#+end_src
|
||||
|
||||
Formatters are referred to by the name they were defined with. They can be
|
||||
looked up in the ~format-all-mode-table~ hash table or in format-all's [[https://github.com/lassik/emacs-format-all-the-code/blob/master/format-all.el#L512][source
|
||||
code]].
|
||||
looked up in the ~apheleia-mode-alist~ hash table.
|
||||
|
||||
* Troubleshooting
|
||||
/There are no known problems with this module./ [[doom-report:][Report one?]]
|
||||
There are a few fail-safes apheleia has to prevent accidental code wipe,
|
||||
included silently failing if the command errors or doesn't exist.
|
||||
|
||||
Check that the command you've specified runs fine in a terminal first before
|
||||
reporting this as an issue.
|
||||
|
||||
If any errors are reported from the command, run =apheleia-goto-error= to jump to
|
||||
the error buffer and handle any problems raised there.
|
||||
|
||||
Any issues specific to apheleia should most often be reported upstream [[https://github.com/radian-software/apheleia/issues][here]].
|
||||
|
||||
* Frequently asked questions
|
||||
/This module has no FAQs yet./ [[doom-suggest-faq:][Ask one?]]
|
||||
|
|
|
@ -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,10 @@
|
|||
;;; 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)
|
||||
(cl-defun set-formatter! (name args &key modes)
|
||||
"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 +12,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 +20,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 +32,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 +55,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 +64,25 @@ 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-core
|
||||
(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))))
|
||||
(let ((formatter (cond
|
||||
((listp args) `(,@args))
|
||||
(t args))))
|
||||
(setf (alist-get name apheleia-formatters) formatter))
|
||||
(when modes
|
||||
(dolist (mode modes)
|
||||
(setf (alist-get mode apheleia-mode-alist) name))))))
|
||||
|
|
|
@ -1,18 +1,15 @@
|
|||
;;; 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
|
||||
(defvar +format-on-save-disabled-modes
|
||||
'(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
|
||||
|
@ -21,69 +18,54 @@ 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)
|
||||
(when (modulep! +onsave)
|
||||
(add-hook 'doom-first-file-hook #'apheleia-global-mode))
|
||||
|
||||
(defun +format-enable-on-save-maybe-h ()
|
||||
(defun +format-inhibit-maybe-h ()
|
||||
"Enable formatting on save in certain major modes.
|
||||
This is controlled by `+format-on-save-disabled-modes'."
|
||||
(or (eq major-mode 'fundamental-mode)
|
||||
(string-empty-p (string-trim (buffer-name)))
|
||||
(not (null (memq major-mode +format-on-save-disabled-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))
|
||||
|
||||
(after! apheleia-core
|
||||
(add-to-list 'apheleia-inhibit-functions #'+format-inhibit-maybe-h)))
|
||||
|
||||
;;
|
||||
;;; 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)))))
|
||||
|
||||
;; 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)))
|
||||
(defun +format--refresh-git-gutter-h ()
|
||||
(when (fboundp '+vc-gutter-update-h)
|
||||
(+vc-gutter-init-maybe-h))))
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
;; -*- no-byte-compile: t; -*-
|
||||
;;; editor/format/packages.el
|
||||
|
||||
(package! format-all :pin "47d862d40a088ca089c92cd393c6dca4628f87d3")
|
||||
(package! apheleia :pin "c222927f7086d407dad01b2609ff68768e9adddb")
|
||||
|
|
|
@ -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" ))))
|
Loading…
Add table
Add a link
Reference in a new issue