mirror of
https://github.com/vale981/apheleia
synced 2025-03-04 17:11:40 -05:00

Hello! I would like to add support for `purs-tidy` - PureScript formatter. Best wishes alternateved
320 lines
12 KiB
EmacsLisp
320 lines
12 KiB
EmacsLisp
;;; apheleia-formatters.el --- Apheleia formatters -*- lexical-binding: t -*-
|
||
|
||
;;; Commentary:
|
||
|
||
;; Formatter helper functions and definitions for `apheleia'.
|
||
|
||
;;; Code:
|
||
|
||
(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))))
|
||
|
||
|
||
|
||
(defcustom apheleia-formatters
|
||
'((bean-format . ("bean-format"))
|
||
(black . ("black" "-"))
|
||
(brittany . ("brittany"))
|
||
(caddyfmt . ("caddy" "fmt" "-"))
|
||
(clang-format . ("clang-format"
|
||
"-assume-filename"
|
||
(or (buffer-file-name)
|
||
(cdr (assoc major-mode
|
||
'((c-mode . ".c")
|
||
(c++-mode . ".cpp")
|
||
(cuda-mode . ".cu")
|
||
(protobuf-mode . ".proto"))))
|
||
".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)
|
||
(ktlint . ("ktlint" "--log-level=none" "--stdin" "-F"))
|
||
(latexindent . ("latexindent" "--logfile=/dev/null"))
|
||
(mix-format . ("mix" "format" "-"))
|
||
(nixfmt . ("nixfmt"))
|
||
(ocamlformat . ("ocamlformat" "-" "--name" filepath
|
||
"--enable-outside-detected-project"))
|
||
(phpcs . ("apheleia-phpcs"))
|
||
(prettier . (npx "prettier" "--stdin-filepath" filepath))
|
||
(prettier-css
|
||
. (npx "prettier" "--stdin-filepath" filepath "--parser=css"))
|
||
(prettier-html
|
||
. (npx "prettier" "--stdin-filepath" filepath "--parser=html"))
|
||
(prettier-graphql
|
||
. (npx "prettier" "--stdin-filepath" filepath "--parser=graphql"))
|
||
(prettier-javascript
|
||
. (npx "prettier" "--stdin-filepath" filepath "--parser=babel-flow"))
|
||
(prettier-json
|
||
. (npx "prettier" "--stdin-filepath" filepath "--parser=json"))
|
||
(prettier-markdown
|
||
. (npx "prettier" "--stdin-filepath" filepath "--parser=markdown"))
|
||
(prettier-ruby
|
||
. (npx "prettier" "--stdin-filepath" filepath "--parser=ruby"))
|
||
(prettier-scss
|
||
. (npx "prettier" "--stdin-filepath" filepath "--parser=scss"))
|
||
(prettier-svelte
|
||
. (npx "prettier" "--stdin-filepath" filepath "--parser=svelte"))
|
||
(prettier-typescript
|
||
. (npx "prettier" "--stdin-filepath" filepath "--parser=typescript"))
|
||
(prettier-yaml
|
||
. (npx "prettier" "--stdin-filepath" filepath "--parser=yaml"))
|
||
(purs-tidy . (npx "purs-tidy" "format"))
|
||
(shfmt . ("shfmt" "-i" "4"))
|
||
(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)
|
||
(purescript-mode . purs-tidy)
|
||
(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)
|
||
(svelte-mode . prettier-svelte)
|
||
(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
|