2023-03-12 10:22:36 +00:00
|
|
|
|
;;; apheleia-formatters.el --- Apheleia formatters -*- lexical-binding: t -*-
|
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
2023-03-25 14:56:54 +00:00
|
|
|
|
;; Formatter helper functions and definitions for `apheleia'.
|
2023-03-12 10:22:36 +00:00
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
2023-03-25 14:56:54 +00:00
|
|
|
|
(require 'cl-lib)
|
|
|
|
|
(require 'subr-x)
|
|
|
|
|
|
|
|
|
|
(defun apheleia-formatters-indent (tab-flag indent-flag indent-var)
|
|
|
|
|
"Set flag for indentation.
|
|
|
|
|
Helper function for `apheleia-formatters' which allows you to supply
|
|
|
|
|
alternating flags based on the current buffers indent configuration. If the
|
|
|
|
|
buffer is indented with tabs then returns TAB-FLAG. Otherwise if INDENT-VAR
|
|
|
|
|
is set in the buffer return INDENT-FLAG and the value of INDENT-VAR. Use this
|
|
|
|
|
to easily configure the indentation level of a formatter."
|
|
|
|
|
(cond
|
|
|
|
|
(indent-tabs-mode tab-flag)
|
|
|
|
|
(indent-var
|
|
|
|
|
(when-let ((indent (and (boundp indent-var)
|
|
|
|
|
(symbol-value indent-var))))
|
|
|
|
|
(list indent-flag (number-to-string indent))))))
|
|
|
|
|
|
|
|
|
|
(defun apheleia-formatters-js-indent (tab-flag indent-flag)
|
|
|
|
|
"Variant of `apheleia-formatters-indent' for JavaScript like modes.
|
|
|
|
|
See `apheleia-formatters-indent' for a description of TAB-FLAG and
|
|
|
|
|
INDENT-FLAG."
|
|
|
|
|
(apheleia-formatters-indent
|
|
|
|
|
tab-flag indent-flag
|
|
|
|
|
(cl-case major-mode
|
|
|
|
|
(json-mode 'js-indent-level)
|
|
|
|
|
(json-ts-mode 'json-ts-mode-indent-offset)
|
|
|
|
|
(js-mode 'js-indent-level)
|
|
|
|
|
(js-jsx-mode 'js-indent-level)
|
|
|
|
|
(js2-mode 'js2-basic-offset)
|
|
|
|
|
(js2-jsx-mode 'js2-basic-offset)
|
|
|
|
|
(js3-mode 'js3-indent-level))))
|
|
|
|
|
|
|
|
|
|
(defcustom apheleia-formatters-respect-fill-column nil
|
|
|
|
|
"Whether formatters should set `fill-column' related flags."
|
|
|
|
|
:type 'boolean
|
|
|
|
|
:group 'apheleia)
|
|
|
|
|
|
|
|
|
|
(defun apheleia-formatters-fill-column (fill-flag)
|
|
|
|
|
"Set flag for wrap column.
|
|
|
|
|
Helper function to set a flag based on `fill-column'. When `fill-column' is set
|
|
|
|
|
and `apheleia-formatters-respect-fill-column' return a list of FILL-FLAG and
|
|
|
|
|
`fill-column'."
|
|
|
|
|
(when (and apheleia-formatters-respect-fill-column
|
|
|
|
|
(bound-and-true-p fill-column))
|
|
|
|
|
(list fill-flag (number-to-string fill-column))))
|
|
|
|
|
|
|
|
|
|
(defun apheleia-formatters-locate-file (file-flag file-name)
|
|
|
|
|
"Set a flag based on a dominating-file.
|
|
|
|
|
Look for a file up recursively from the current directory until FILE-NAME is
|
|
|
|
|
found. If found return a list of FILE-FLAG and the absolute path to the located
|
|
|
|
|
FILE-NAME."
|
|
|
|
|
(when-let ((file (locate-dominating-file default-directory file-name)))
|
|
|
|
|
(list file-flag (concat (expand-file-name file) file-name))))
|
|
|
|
|
|
|
|
|
|
(defun apheleia-formatters-extension-p (&rest exts)
|
|
|
|
|
"Assert whether current buffer has an extension in EXTS."
|
|
|
|
|
(when-let ((name buffer-file-name)
|
|
|
|
|
(ext (file-name-extension name)))
|
|
|
|
|
(cl-find-if (apply-partially #'string-equal ext)
|
|
|
|
|
exts)))
|
|
|
|
|
|
|
|
|
|
(defcustom apheleia-formatters-mode-extension-assoc
|
|
|
|
|
'((c-mode . ".c")
|
|
|
|
|
(c-ts-mode . ".c")
|
|
|
|
|
(c++-mode . ".cpp")
|
|
|
|
|
(c++-ts-mode . ".cpp")
|
|
|
|
|
(glsl-mode . ".glsl")
|
|
|
|
|
(java-mode . ".java")
|
|
|
|
|
(java-ts-mode . ".java"))
|
|
|
|
|
"Association list between major-modes and common file extensions for them."
|
|
|
|
|
:type 'alist
|
|
|
|
|
:group 'apheleia)
|
|
|
|
|
|
|
|
|
|
(defun apheleia-formatters-mode-extension (&optional flag)
|
|
|
|
|
"Get a file-extension based on the current `major-mode'.
|
|
|
|
|
If FLAG is set this function returns a list of FLAG and then the extension.
|
|
|
|
|
Otherwise return the extension only."
|
|
|
|
|
(when-let ((ext
|
|
|
|
|
(alist-get major-mode apheleia-formatters-mode-extension-assoc)))
|
|
|
|
|
(if flag
|
|
|
|
|
(list flag ext)
|
|
|
|
|
ext)))
|
|
|
|
|
|
|
|
|
|
(defun apheleia-formatters-local-buffer-file-name ()
|
|
|
|
|
"Get variable `buffer-file-name' without any remote components."
|
|
|
|
|
(when-let ((name buffer-file-name))
|
|
|
|
|
(let ((remote (file-remote-p name)))
|
|
|
|
|
(if remote
|
|
|
|
|
(substring name (length remote))
|
|
|
|
|
name))))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(defcustom apheleia-formatters
|
|
|
|
|
'((bean-format . ("bean-format"))
|
2023-04-16 17:50:52 +01:00
|
|
|
|
(black . ("black"
|
|
|
|
|
(when (apheleia-formatters-extension-p "pyi") "--pyi")
|
|
|
|
|
(apheleia-formatters-fill-column "--line-length")
|
|
|
|
|
"-"))
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(brittany . ("brittany"))
|
|
|
|
|
(caddyfmt . ("caddy" "fmt" "-"))
|
|
|
|
|
(clang-format . ("clang-format"
|
|
|
|
|
"-assume-filename"
|
|
|
|
|
(or (buffer-file-name)
|
2023-04-16 17:50:52 +01:00
|
|
|
|
(apheleia-formatters-mode-extension)
|
2023-03-12 10:22:36 +00:00
|
|
|
|
".c")))
|
|
|
|
|
(crystal-tool-format . ("crystal" "tool" "format" "-"))
|
|
|
|
|
(dart-format . ("dart" "format"))
|
|
|
|
|
(elm-format . ("elm-format" "--yes" "--stdin"))
|
|
|
|
|
(fish-indent . ("fish_indent"))
|
|
|
|
|
(gofmt . ("gofmt"))
|
|
|
|
|
(gofumpt . ("gofumpt"))
|
|
|
|
|
(goimports . ("goimports"))
|
|
|
|
|
(google-java-format . ("google-java-format" "-"))
|
|
|
|
|
(isort . ("isort" "-"))
|
|
|
|
|
(lisp-indent . apheleia-indent-lisp-buffer)
|
2023-04-16 17:50:52 +01:00
|
|
|
|
(ktlint . ("ktlint" "--log-level=none" "--stdin" "-F" "-"))
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(latexindent . ("latexindent" "--logfile=/dev/null"))
|
|
|
|
|
(mix-format . ("mix" "format" "-"))
|
|
|
|
|
(nixfmt . ("nixfmt"))
|
|
|
|
|
(ocamlformat . ("ocamlformat" "-" "--name" filepath
|
|
|
|
|
"--enable-outside-detected-project"))
|
|
|
|
|
(phpcs . ("apheleia-phpcs"))
|
2023-04-16 17:50:52 +01:00
|
|
|
|
(prettier
|
|
|
|
|
. (npx "prettier" "--stdin-filepath" filepath
|
|
|
|
|
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(prettier-css
|
2023-04-16 17:50:52 +01:00
|
|
|
|
. (npx "prettier" "--stdin-filepath" filepath "--parser=css"
|
|
|
|
|
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(prettier-html
|
2023-04-16 17:50:52 +01:00
|
|
|
|
. (npx "prettier" "--stdin-filepath" filepath "--parser=html"
|
|
|
|
|
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(prettier-graphql
|
2023-04-16 17:50:52 +01:00
|
|
|
|
. (npx "prettier" "--stdin-filepath" filepath "--parser=graphql"
|
|
|
|
|
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(prettier-javascript
|
2023-04-16 17:50:52 +01:00
|
|
|
|
. (npx "prettier" "--stdin-filepath" filepath "--parser=babel-flow"
|
|
|
|
|
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(prettier-json
|
2023-04-16 17:50:52 +01:00
|
|
|
|
. (npx "prettier" "--stdin-filepath" filepath "--parser=json"
|
|
|
|
|
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(prettier-markdown
|
2023-04-16 17:50:52 +01:00
|
|
|
|
. (npx "prettier" "--stdin-filepath" filepath "--parser=markdown"
|
|
|
|
|
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(prettier-ruby
|
2023-04-16 17:50:52 +01:00
|
|
|
|
. (npx "prettier" "--stdin-filepath" filepath "--parser=ruby"
|
|
|
|
|
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(prettier-scss
|
2023-04-16 17:50:52 +01:00
|
|
|
|
. (npx "prettier" "--stdin-filepath" filepath "--parser=scss"
|
|
|
|
|
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
|
2023-03-23 17:00:51 -04:00
|
|
|
|
(prettier-svelte
|
2023-04-16 17:50:52 +01:00
|
|
|
|
. (npx "prettier" "--stdin-filepath" filepath "--parser=svelte"
|
|
|
|
|
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(prettier-typescript
|
2023-04-16 17:50:52 +01:00
|
|
|
|
. (npx "prettier" "--stdin-filepath" filepath "--parser=typescript"
|
|
|
|
|
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(prettier-yaml
|
2023-04-16 17:50:52 +01:00
|
|
|
|
. (npx "prettier" "--stdin-filepath" filepath "--parser=yaml"
|
|
|
|
|
(apheleia-formatters-js-indent "--use-tabs" "--tab-width")))
|
2023-03-29 23:35:02 +02:00
|
|
|
|
(purs-tidy . (npx "purs-tidy" "format"))
|
2023-04-16 17:50:52 +01:00
|
|
|
|
(shfmt . ("shfmt"
|
|
|
|
|
"-filename" filepath
|
|
|
|
|
"-ln" (cl-case (bound-and-true-p sh-shell)
|
|
|
|
|
(sh "posix")
|
|
|
|
|
(t "bash"))
|
|
|
|
|
"-i" (number-to-string
|
|
|
|
|
(cond
|
|
|
|
|
(indent-tabs-mode 0)
|
|
|
|
|
((boundp 'sh-basic-offset)
|
|
|
|
|
sh-basic-offset)
|
|
|
|
|
(t 4)))
|
|
|
|
|
"-"))
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(stylua . ("stylua" "-"))
|
|
|
|
|
(rustfmt . ("rustfmt" "--quiet" "--emit" "stdout"))
|
|
|
|
|
(terraform . ("terraform" "fmt" "-")))
|
|
|
|
|
"Alist of code formatting commands.
|
|
|
|
|
The keys may be any symbols you want, and the values are shell
|
|
|
|
|
commands, lists of strings and symbols, or a function symbol.
|
|
|
|
|
|
|
|
|
|
If the value is a function, the function will be called with
|
|
|
|
|
keyword arguments (see the implementation of
|
|
|
|
|
`apheleia--run-formatter-function' to see which). It should use
|
|
|
|
|
`cl-defun' with `&allow-other-keys' for forward compatibility.
|
|
|
|
|
|
|
|
|
|
Otherwise in Lisp code, the format of commands is similar to what
|
|
|
|
|
you pass to `make-process', except as follows.
|
|
|
|
|
|
|
|
|
|
Normally, the contents of the current buffer are passed to the
|
|
|
|
|
command on stdin, and the output is read from stdout. However, if
|
|
|
|
|
you use the symbol `file' as one of the elements of commands,
|
|
|
|
|
then the filename of the current buffer is substituted for
|
|
|
|
|
it. (Use `filepath' instead of `file' if you need the filename of
|
|
|
|
|
the current buffer, but you still want its contents to be passed
|
|
|
|
|
on stdin.)
|
|
|
|
|
|
|
|
|
|
If you instead use the symbol `input' as one of the elements of
|
|
|
|
|
commands, then the contents of the current buffer are written to
|
|
|
|
|
a temporary file and its name is substituted for `input'. Also,
|
|
|
|
|
if you use the symbol `output' as one of the elements of
|
|
|
|
|
commands, then it is substituted with the name of a temporary
|
|
|
|
|
file. In that case, it is expected that the command writes to
|
|
|
|
|
that file, and the file is then read into an Emacs buffer.
|
|
|
|
|
|
|
|
|
|
If you use the symbol `inplace' as one of the elements of the
|
|
|
|
|
list, then the contents of the current buffer are written to a
|
|
|
|
|
temporary file and its name is substituted for `inplace'.
|
|
|
|
|
However, unlike `input', it is expected that the formatter write
|
|
|
|
|
the formatted file back to the same file in place. In other
|
|
|
|
|
words, `inplace' is like `input' and `output' together.
|
|
|
|
|
|
|
|
|
|
If you use the symbol `npx' as one of the elements of commands,
|
|
|
|
|
then the first string element of the command list is resolved
|
|
|
|
|
inside node_modules/.bin if such a directory exists anywhere
|
|
|
|
|
above the current `default-directory'.
|
|
|
|
|
|
|
|
|
|
Any list elements that are not strings and not any of the special
|
|
|
|
|
symbols mentioned above will be evaluated when the formatter is
|
|
|
|
|
invoked, and spliced into the list. A form can evaluate either to
|
|
|
|
|
a string or to a list of strings.
|
|
|
|
|
|
|
|
|
|
The \"scripts/formatters\" subdirectory of the Apheleia source
|
|
|
|
|
repository is automatically prepended to $PATH (variable
|
|
|
|
|
`exec-path', to be specific) when invoking external formatters.
|
|
|
|
|
This is intended for internal use. If you would like to define
|
|
|
|
|
your own script, you can simply place it on your normal $PATH
|
|
|
|
|
rather than using this system."
|
|
|
|
|
:type '(alist
|
|
|
|
|
:key-type symbol
|
|
|
|
|
:value-type
|
|
|
|
|
(choice
|
|
|
|
|
(repeat
|
|
|
|
|
(choice
|
|
|
|
|
(string :tag "Argument")
|
|
|
|
|
(const :tag "Look for command in node_modules/.bin" npx)
|
|
|
|
|
(const :tag "Name of file being formatted" filepath)
|
|
|
|
|
(const :tag "Name of real file used for input" file)
|
|
|
|
|
(const :tag "Name of temporary file used for input" input)
|
|
|
|
|
(const :tag "Name of temporary file used for output" output)))
|
|
|
|
|
(function :tag "Formatter function")))
|
|
|
|
|
:group 'apheleia)
|
|
|
|
|
|
|
|
|
|
(defcustom apheleia-mode-alist
|
|
|
|
|
'(;; php-mode has to come before cc-mode
|
|
|
|
|
(php-mode . phpcs)
|
|
|
|
|
;; json-mode has to come before javascript-mode (aka js-mode)
|
|
|
|
|
(json-mode . prettier-json)
|
|
|
|
|
(json-ts-mode . prettier-json)
|
|
|
|
|
;; rest are alphabetical
|
|
|
|
|
(bash-ts-mode . shfmt)
|
|
|
|
|
(beancount-mode . bean-format)
|
|
|
|
|
(c++-ts-mode . clang-format)
|
|
|
|
|
(caddyfile-mode . caddyfmt)
|
|
|
|
|
(cc-mode . clang-format)
|
|
|
|
|
(c-mode . clang-format)
|
|
|
|
|
(c-ts-mode . clang-format)
|
|
|
|
|
(c++-mode . clang-format)
|
|
|
|
|
(caml-mode . ocamlformat)
|
|
|
|
|
(common-lisp-mode . lisp-indent)
|
|
|
|
|
(crystal-mode . crystal-tool-format)
|
|
|
|
|
(css-mode . prettier-css)
|
|
|
|
|
(css-ts-mode . prettier-css)
|
|
|
|
|
(dart-mode . dart-format)
|
|
|
|
|
(elixir-mode . mix-format)
|
|
|
|
|
(elixir-ts-mode . mix-format)
|
|
|
|
|
(elm-mode . elm-format)
|
|
|
|
|
(fish-mode . fish-indent)
|
|
|
|
|
(go-mode . gofmt)
|
|
|
|
|
(go-mod-ts-mode . gofmt)
|
|
|
|
|
(go-ts-mode . gofmt)
|
|
|
|
|
(graphql-mode . prettier-graphql)
|
|
|
|
|
(haskell-mode . brittany)
|
|
|
|
|
(html-mode . prettier-html)
|
|
|
|
|
(java-mode . google-java-format)
|
|
|
|
|
(java-ts-mode . google-java-format)
|
|
|
|
|
(js3-mode . prettier-javascript)
|
|
|
|
|
(js-mode . prettier-javascript)
|
|
|
|
|
(js-ts-mode . prettier-javascript)
|
|
|
|
|
(kotlin-mode . ktlint)
|
|
|
|
|
(latex-mode . latexindent)
|
|
|
|
|
(LaTeX-mode . latexindent)
|
|
|
|
|
(lua-mode . stylua)
|
|
|
|
|
(lisp-mode . lisp-indent)
|
|
|
|
|
(nix-mode . nixfmt)
|
2023-03-29 23:35:02 +02:00
|
|
|
|
(purescript-mode . purs-tidy)
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(python-mode . black)
|
|
|
|
|
(python-ts-mode . black)
|
|
|
|
|
(ruby-mode . prettier-ruby)
|
|
|
|
|
(ruby-ts-mode . prettier-ruby)
|
|
|
|
|
(rustic-mode . rustfmt)
|
|
|
|
|
(rust-mode . rustfmt)
|
|
|
|
|
(rust-ts-mode . rustfmt)
|
|
|
|
|
(scss-mode . prettier-scss)
|
2023-03-23 17:00:51 -04:00
|
|
|
|
(svelte-mode . prettier-svelte)
|
2023-03-12 10:22:36 +00:00
|
|
|
|
(terraform-mode . terraform)
|
|
|
|
|
(TeX-latex-mode . latexindent)
|
|
|
|
|
(TeX-mode . latexindent)
|
|
|
|
|
(tsx-ts-mode . prettier-typescript)
|
|
|
|
|
(tuareg-mode . ocamlformat)
|
|
|
|
|
(typescript-mode . prettier-typescript)
|
|
|
|
|
(typescript-ts-mode . prettier-typescript)
|
|
|
|
|
(web-mode . prettier)
|
|
|
|
|
(yaml-mode . prettier-yaml)
|
|
|
|
|
(yaml-ts-mode . prettier-yaml))
|
|
|
|
|
"Alist mapping major mode names to formatters to use in those modes.
|
|
|
|
|
This determines what formatter to use in buffers without a
|
|
|
|
|
setting for `apheleia-formatter'. The keys are major mode
|
|
|
|
|
symbols (matched against `major-mode' with `derived-mode-p') or
|
|
|
|
|
strings (matched against value of variable `buffer-file-name'
|
|
|
|
|
with `string-match-p'), and the values are symbols with entries
|
|
|
|
|
in `apheleia-formatters' (or equivalently, they are allowed
|
|
|
|
|
values for `apheleia-formatter'). Values can be a list of such
|
|
|
|
|
symnols causing each formatter in the list to be called one after
|
|
|
|
|
the other (with the output of the previous formatter).
|
|
|
|
|
Earlier entries in this variable take precedence over later ones.
|
|
|
|
|
|
|
|
|
|
Be careful when writing regexps to include \"\\'\" and to escape
|
|
|
|
|
\"\\.\" in order to properly match a file extension. For example,
|
|
|
|
|
to match \".jsx\" files you might use \"\\.jsx\\'\".
|
|
|
|
|
|
|
|
|
|
If a given mode derives from another mode (e.g. `php-mode' and
|
|
|
|
|
`cc-mode'), then ensure that the deriving mode comes before the mode
|
|
|
|
|
to derive from, as the list is interpreted sequentially."
|
|
|
|
|
:type '(alist
|
|
|
|
|
:key-type
|
|
|
|
|
(choice (symbol :tag "Major mode")
|
|
|
|
|
(string :tag "Buffer name regexp"))
|
|
|
|
|
:value-type
|
|
|
|
|
(choice (symbol :tag "Formatter")
|
|
|
|
|
(repeat
|
|
|
|
|
(symbol :tag "Formatter"))))
|
|
|
|
|
:group 'apheleia)
|
|
|
|
|
|
|
|
|
|
(provide 'apheleia-formatters)
|
|
|
|
|
|
|
|
|
|
;;; apheleia-formatters.el ends here
|