mirror of
https://github.com/vale981/apheleia
synced 2025-03-05 09:31:40 -05:00
Add emacs-lisp formatting (#102)
* feat: add emacs-lisp formatting * Disable indent-tabs-mode * Add stub file for installation * Fix lint errors * fix: correctly format based on previous mode * Formatting * Fix weird indent * Add checkindent target * Update changelog * Long line * Empty commit * fix ci * revert changelog reformatting * more changelog * more Co-authored-by: Radon Rosborough <radon.neon@gmail.com> Co-authored-by: Radon Rosborough <radon@intuitiveexplanations.com>
This commit is contained in:
parent
4d59a9b696
commit
9b745df2fa
9 changed files with 162 additions and 78 deletions
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
|
@ -5,7 +5,7 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
emacs_version: [26, 27, "master"]
|
emacs_version: [26, 27, 28, "master"]
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v2
|
uses: actions/checkout@v2
|
||||||
|
|
|
@ -21,9 +21,11 @@ The format is based on [Keep a Changelog].
|
||||||
* [bean-format](https://github.com/beancount/beancount) for Beancount
|
* [bean-format](https://github.com/beancount/beancount) for Beancount
|
||||||
([#101]).
|
([#101]).
|
||||||
* [stylua](https://github.com/JohnnyMorganz/StyLua) for Lua ([#105]).
|
* [stylua](https://github.com/JohnnyMorganz/StyLua) for Lua ([#105]).
|
||||||
|
* Native Emacs indentation of Emacs Lisp code as a formatter ([#102]).
|
||||||
|
|
||||||
[#100]: https://github.com/radian-software/apheleia/pull/100
|
[#100]: https://github.com/radian-software/apheleia/pull/100
|
||||||
[#101]: https://github.com/radian-software/apheleia/pull/101
|
[#101]: https://github.com/radian-software/apheleia/pull/101
|
||||||
|
[#102]: https://github.com/radian-software/apheleia/pull/102
|
||||||
[#105]: https://github.com/radian-software/apheleia/pull/105
|
[#105]: https://github.com/radian-software/apheleia/pull/105
|
||||||
[#109]: https://github.com/radian-software/apheleia/issues/109
|
[#109]: https://github.com/radian-software/apheleia/issues/109
|
||||||
[#110]: https://github.com/radian-software/apheleia/pull/110
|
[#110]: https://github.com/radian-software/apheleia/pull/110
|
||||||
|
|
23
Makefile
23
Makefile
|
@ -1,3 +1,5 @@
|
||||||
|
SHELL := bash
|
||||||
|
|
||||||
VERSION ?=
|
VERSION ?=
|
||||||
CMD ?=
|
CMD ?=
|
||||||
|
|
||||||
|
@ -8,6 +10,7 @@ TAG ?= latest
|
||||||
# The order is important for compilation.
|
# The order is important for compilation.
|
||||||
for_compile := *.el
|
for_compile := *.el
|
||||||
for_checkdoc := *.el
|
for_checkdoc := *.el
|
||||||
|
for_checkindent := *.el
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help: ## Show this message
|
help: ## Show this message
|
||||||
|
@ -19,7 +22,7 @@ help: ## Show this message
|
||||||
column -t -s'|' >&2
|
column -t -s'|' >&2
|
||||||
|
|
||||||
.PHONY: lint
|
.PHONY: lint
|
||||||
lint: compile checkdoc longlines fmt-lint ## Build project and run all linters
|
lint: compile checkdoc longlines checkindent fmt-lint ## Run all fast linters
|
||||||
|
|
||||||
.PHONY: compile
|
.PHONY: compile
|
||||||
compile: ## Check for byte-compiler errors
|
compile: ## Check for byte-compiler errors
|
||||||
|
@ -42,6 +45,24 @@ checkdoc: ## Check for missing or poorly formatted docstrings
|
||||||
| grep . && exit 1 || true ;\
|
| grep . && exit 1 || true ;\
|
||||||
done
|
done
|
||||||
|
|
||||||
|
.PHONY: checkindent
|
||||||
|
checkindent: ## Ensure that indentation is correct
|
||||||
|
@tmpdir="$$(mktemp -d)"; for file in $(for_checkindent); do \
|
||||||
|
echo "[checkindent] $$file" >&2; \
|
||||||
|
emacs -Q --batch \
|
||||||
|
-l scripts/apheleia-indent.el \
|
||||||
|
--eval "(setq inhibit-message t)" \
|
||||||
|
--eval "(load (expand-file-name \"apheleia.el\") nil t)" \
|
||||||
|
--eval "(find-file \"$$file\")" \
|
||||||
|
--eval "(indent-region (point-min) (point-max))" \
|
||||||
|
--eval "(write-file \"$$tmpdir/$$file\")"; \
|
||||||
|
(diff <(cat "$$file" | nl -v1 -ba | \
|
||||||
|
sed "s/\t/: /" | sed "s/^ */$$file:/") \
|
||||||
|
<(cat "$$tmpdir/$$file" | nl -v1 -ba | \
|
||||||
|
sed "s/\t/: /" | sed "s/^ */$$file:/") ) \
|
||||||
|
| grep -F ">" | grep -o "[a-z].*" | grep . && exit 1 || true; \
|
||||||
|
done
|
||||||
|
|
||||||
.PHONY: longlines
|
.PHONY: longlines
|
||||||
longlines: ## Check for long lines
|
longlines: ## Check for long lines
|
||||||
@scripts/check-line-length.bash
|
@scripts/check-line-length.bash
|
||||||
|
|
69
apheleia.el
69
apheleia.el
|
@ -363,20 +363,21 @@ NO-QUERY, and CONNECTION-TYPE."
|
||||||
(stderr-file (apheleia--make-temp-file run-on-remote "apheleia"))
|
(stderr-file (apheleia--make-temp-file run-on-remote "apheleia"))
|
||||||
(args
|
(args
|
||||||
(append
|
(append
|
||||||
(list (car command) ; argv[0]
|
(list
|
||||||
(not stdin) ; If stdin we don't delete the STDIN
|
;; argv[0]
|
||||||
; buffer text with
|
(car command)
|
||||||
; `call-process-region'. Otherwise we
|
;; If stdin we don't delete the STDIN buffer text with
|
||||||
; send no INFILE argument to
|
;; `call-process-region'. Otherwise we send no INFILE
|
||||||
; `call-process'.
|
;; argument to `call-process'.
|
||||||
`(,stdout ,stderr-file) ; stdout buffer and stderr file.
|
(not stdin)
|
||||||
; `call-process' cannot capture
|
;; stdout buffer and stderr file. `call-process' cannot
|
||||||
; stderr into a separate buffer, the
|
;; capture stderr into a separate buffer, the best we can
|
||||||
; best we can do is save and read
|
;; do is save and read from a file.
|
||||||
; from a file.
|
`(,stdout ,stderr-file)
|
||||||
nil) ; Do not re/display stdout as output
|
;; Do not re/display stdout as output is recieved.
|
||||||
; is recieved.
|
nil)
|
||||||
(cdr command)))) ; argv[1:]
|
;; argv[1:]
|
||||||
|
(cdr command))))
|
||||||
(unwind-protect
|
(unwind-protect
|
||||||
(let ((exit-status
|
(let ((exit-status
|
||||||
(cl-letf* ((message (symbol-function #'message))
|
(cl-letf* ((message (symbol-function #'message))
|
||||||
|
@ -656,7 +657,9 @@ See `apheleia--run-formatters' for a description of REMOTE."
|
||||||
(clear-files nil)
|
(clear-files nil)
|
||||||
(run-on-remote (and (eq apheleia-remote-algorithm 'remote)
|
(run-on-remote (and (eq apheleia-remote-algorithm 'remote)
|
||||||
remote)))
|
remote)))
|
||||||
(cl-labels ((apheleia--make-temp-file-for-rcs-patch
|
(cl-labels ((;; Weird indentation because of differences in Emacs
|
||||||
|
;; indentation algorithm between 27 and 28
|
||||||
|
apheleia--make-temp-file-for-rcs-patch
|
||||||
(buffer &optional fname)
|
(buffer &optional fname)
|
||||||
;; Ensure there's a file with the contents of `buffer' on the
|
;; Ensure there's a file with the contents of `buffer' on the
|
||||||
;; target machine. `fname', if given, refers to an existing
|
;; target machine. `fname', if given, refers to an existing
|
||||||
|
@ -826,12 +829,12 @@ machine from the machine file is available on"))
|
||||||
arg
|
arg
|
||||||
(eval arg)))
|
(eval arg)))
|
||||||
if val
|
if val
|
||||||
if (and (consp val)
|
if (and (consp val)
|
||||||
(cl-every #'stringp val))
|
(cl-every #'stringp val))
|
||||||
append val
|
append val
|
||||||
else if (stringp val)
|
else if (stringp val)
|
||||||
collect val
|
collect val
|
||||||
else do (error "Result of command evaluation must be a string \
|
else do (error "Result of command evaluation must be a string \
|
||||||
or list of strings: %S" arg)))
|
or list of strings: %S" arg)))
|
||||||
`(,input-fname ,output-fname ,stdin ,@command))))
|
`(,input-fname ,output-fname ,stdin ,@command))))
|
||||||
|
|
||||||
|
@ -928,6 +931,7 @@ being run, for diagnostic purposes."
|
||||||
(gofmt . ("gofmt"))
|
(gofmt . ("gofmt"))
|
||||||
(google-java-format . ("google-java-format" "-"))
|
(google-java-format . ("google-java-format" "-"))
|
||||||
(isort . ("isort" "-"))
|
(isort . ("isort" "-"))
|
||||||
|
(lisp-indent . apheleia-indent-lisp-buffer)
|
||||||
(ktlint . ("ktlint" "--stdin" "-F"))
|
(ktlint . ("ktlint" "--stdin" "-F"))
|
||||||
(latexindent . ("latexindent" "--logfile=/dev/null"))
|
(latexindent . ("latexindent" "--logfile=/dev/null"))
|
||||||
(mix-format . ("mix" "format" "-"))
|
(mix-format . ("mix" "format" "-"))
|
||||||
|
@ -1000,6 +1004,26 @@ rather than using this system."
|
||||||
(const :tag "Name of temporary file used for output" output)))
|
(const :tag "Name of temporary file used for output" output)))
|
||||||
(function :tag "Formatter function"))))
|
(function :tag "Formatter function"))))
|
||||||
|
|
||||||
|
(cl-defun apheleia-indent-lisp-buffer
|
||||||
|
(&key buffer scratch callback &allow-other-keys)
|
||||||
|
"Format a Lisp BUFFER.
|
||||||
|
Use SCRATCH as a temporary buffer and CALLBACK to apply the
|
||||||
|
transformation.
|
||||||
|
|
||||||
|
For more implementation detail, see
|
||||||
|
`apheleia--run-formatter-function'."
|
||||||
|
(with-current-buffer scratch
|
||||||
|
(setq-local indent-line-function
|
||||||
|
(buffer-local-value 'indent-line-function buffer))
|
||||||
|
(setq-local lisp-indent-function
|
||||||
|
(buffer-local-value 'lisp-indent-function buffer))
|
||||||
|
(funcall (with-current-buffer buffer major-mode))
|
||||||
|
(goto-char (point-min))
|
||||||
|
(let ((inhibit-message t)
|
||||||
|
(message-log-max nil))
|
||||||
|
(indent-region (point-min) (point-max)))
|
||||||
|
(funcall callback)))
|
||||||
|
|
||||||
(defun apheleia--run-formatters
|
(defun apheleia--run-formatters
|
||||||
(formatters buffer remote callback &optional stdin)
|
(formatters buffer remote callback &optional stdin)
|
||||||
"Run one or more code formatters on the current buffer.
|
"Run one or more code formatters on the current buffer.
|
||||||
|
@ -1054,8 +1078,10 @@ function: %s" command)))
|
||||||
(c-mode . clang-format)
|
(c-mode . clang-format)
|
||||||
(c++-mode . clang-format)
|
(c++-mode . clang-format)
|
||||||
(caml-mode . ocamlformat)
|
(caml-mode . ocamlformat)
|
||||||
|
(common-lisp-mode . lisp-indent)
|
||||||
(css-mode . prettier)
|
(css-mode . prettier)
|
||||||
(dart-mode . dart-format)
|
(dart-mode . dart-format)
|
||||||
|
(emacs-lisp-mode . lisp-indent)
|
||||||
(elixir-mode . mix-format)
|
(elixir-mode . mix-format)
|
||||||
(elm-mode . elm-format)
|
(elm-mode . elm-format)
|
||||||
(fish-mode . fish-indent)
|
(fish-mode . fish-indent)
|
||||||
|
@ -1070,6 +1096,7 @@ function: %s" command)))
|
||||||
(latex-mode . latexindent)
|
(latex-mode . latexindent)
|
||||||
(LaTeX-mode . latexindent)
|
(LaTeX-mode . latexindent)
|
||||||
(lua-mode . stylua)
|
(lua-mode . stylua)
|
||||||
|
(lisp-mode . lisp-indent)
|
||||||
(nix-mode . nixfmt)
|
(nix-mode . nixfmt)
|
||||||
(python-mode . black)
|
(python-mode . black)
|
||||||
(ruby-mode . prettier)
|
(ruby-mode . prettier)
|
||||||
|
|
6
scripts/apheleia-indent.el
Normal file
6
scripts/apheleia-indent.el
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
;; This file has code that is evaluated in CI before the indentation
|
||||||
|
;; of apheleia.el is checked. This is helpful because it allows us to
|
||||||
|
;; ensure that various things are indented correctly if they require
|
||||||
|
;; some setup for Emacs to know how to do the right thing.
|
||||||
|
|
||||||
|
;; Nothing here yet though! ^_^
|
|
@ -218,65 +218,82 @@ environment variable, defaulting to all formatters."
|
||||||
(out-file (replace-regexp-in-string
|
(out-file (replace-regexp-in-string
|
||||||
"/in\\([^/]+\\)" "/out\\1" in-file 'fixedcase))
|
"/in\\([^/]+\\)" "/out\\1" in-file 'fixedcase))
|
||||||
(exec-path
|
(exec-path
|
||||||
(append `(,(expand-file-name
|
(append `(,(expand-file-name
|
||||||
"scripts/formatters"
|
"scripts/formatters"
|
||||||
(file-name-directory
|
(file-name-directory
|
||||||
(file-truename
|
(file-truename
|
||||||
;; Borrowed with love from Magit
|
;; Borrowed with love from Magit
|
||||||
(let ((load-suffixes '(".el")))
|
(let ((load-suffixes '(".el")))
|
||||||
(locate-library "apheleia"))))))
|
(locate-library "apheleia"))))))
|
||||||
exec-path)))
|
exec-path)))
|
||||||
(mapc
|
|
||||||
(lambda (arg)
|
|
||||||
(when (memq arg '(file filepath input output inplace))
|
|
||||||
(cl-pushnew arg syms)))
|
|
||||||
command)
|
|
||||||
(when (or (memq 'file syms) (memq 'filepath syms))
|
|
||||||
(setq in-temp-real-file (apheleia-ft--write-temp-file
|
|
||||||
in-text extension)))
|
|
||||||
(when (or (memq 'input syms) (memq 'inplace syms))
|
|
||||||
(setq in-temp-file (apheleia-ft--write-temp-file
|
|
||||||
in-text extension))
|
|
||||||
(when (memq 'inplace syms)
|
|
||||||
(setq out-temp-file in-temp-file)))
|
|
||||||
(when (memq 'output syms)
|
|
||||||
(setq out-temp-file (apheleia-ft--write-temp-file
|
|
||||||
"" extension)))
|
|
||||||
(setq command
|
|
||||||
(mapcar
|
|
||||||
(lambda (arg)
|
|
||||||
(pcase arg
|
|
||||||
((or `file `filepath)
|
|
||||||
in-temp-real-file)
|
|
||||||
((or `input `inplace)
|
|
||||||
in-temp-file)
|
|
||||||
(`output
|
|
||||||
out-temp-file)
|
|
||||||
(_ arg)))
|
|
||||||
command))
|
|
||||||
(setq command (delq 'npx command))
|
|
||||||
(setq stdout-buffer (get-buffer-create
|
(setq stdout-buffer (get-buffer-create
|
||||||
(format "*apheleia-ft-stdout-%S" formatter)))
|
(format "*apheleia-ft-stdout-%S" formatter)))
|
||||||
(with-current-buffer stdout-buffer
|
(with-current-buffer stdout-buffer
|
||||||
(erase-buffer))
|
(erase-buffer))
|
||||||
(setq exit-status
|
(if (functionp command)
|
||||||
(apply
|
(progn
|
||||||
#'call-process
|
(setq in-temp-file (apheleia-ft--write-temp-file
|
||||||
(car command)
|
in-text extension))
|
||||||
(unless (or (memq 'file syms)
|
(with-current-buffer (find-file-noselect in-temp-file)
|
||||||
(memq 'input syms)
|
(funcall command
|
||||||
(memq 'inplace syms))
|
:buffer (current-buffer)
|
||||||
in-file)
|
:scratch (current-buffer)
|
||||||
(list stdout-buffer stderr-file)
|
:formatter formatter
|
||||||
nil
|
:callback (lambda ()))
|
||||||
(cdr command)))
|
(copy-to-buffer stdout-buffer (point-min) (point-max))))
|
||||||
;; Verify that formatter succeeded.
|
(progn
|
||||||
(unless (zerop exit-status)
|
|
||||||
(with-temp-buffer
|
(with-current-buffer stdout-buffer
|
||||||
(insert-file-contents stderr-file)
|
(erase-buffer))
|
||||||
(princ (buffer-string)))
|
(mapc
|
||||||
(error
|
(lambda (arg)
|
||||||
"Formatter %s exited with status %S" formatter exit-status))
|
(when (memq arg '(file filepath input output inplace))
|
||||||
|
(cl-pushnew arg syms)))
|
||||||
|
command)
|
||||||
|
(when (or (memq 'file syms) (memq 'filepath syms))
|
||||||
|
(setq in-temp-real-file (apheleia-ft--write-temp-file
|
||||||
|
in-text extension)))
|
||||||
|
(when (or (memq 'input syms) (memq 'inplace syms))
|
||||||
|
(setq in-temp-file (apheleia-ft--write-temp-file
|
||||||
|
in-text extension))
|
||||||
|
(when (memq 'inplace syms)
|
||||||
|
(setq out-temp-file in-temp-file)))
|
||||||
|
(when (memq 'output syms)
|
||||||
|
(setq out-temp-file (apheleia-ft--write-temp-file
|
||||||
|
"" extension)))
|
||||||
|
(setq command
|
||||||
|
(mapcar
|
||||||
|
(lambda (arg)
|
||||||
|
(pcase arg
|
||||||
|
((or `file `filepath)
|
||||||
|
in-temp-real-file)
|
||||||
|
((or `input `inplace)
|
||||||
|
in-temp-file)
|
||||||
|
(`output
|
||||||
|
out-temp-file)
|
||||||
|
(_ arg)))
|
||||||
|
command))
|
||||||
|
(setq command (delq 'npx command))
|
||||||
|
(setq stdout-buffer (get-buffer-create
|
||||||
|
(format "*apheleia-ft-stdout-%S" formatter)))
|
||||||
|
(setq exit-status
|
||||||
|
(apply
|
||||||
|
#'call-process
|
||||||
|
(car command)
|
||||||
|
(unless (or (memq 'file syms)
|
||||||
|
(memq 'input syms)
|
||||||
|
(memq 'inplace syms))
|
||||||
|
in-file)
|
||||||
|
(list stdout-buffer stderr-file)
|
||||||
|
nil
|
||||||
|
(cdr command)))
|
||||||
|
;; Verify that formatter succeeded.
|
||||||
|
(unless (zerop exit-status)
|
||||||
|
(with-temp-buffer
|
||||||
|
(insert-file-contents stderr-file)
|
||||||
|
(princ (buffer-string)))
|
||||||
|
(error
|
||||||
|
"Formatter %s exited with status %S" formatter exit-status))))
|
||||||
;; Verify that formatter has not touched original file.
|
;; Verify that formatter has not touched original file.
|
||||||
(when in-temp-real-file
|
(when in-temp-real-file
|
||||||
(let ((in-text-now (apheleia-ft--read-file in-temp-real-file)))
|
(let ((in-text-now (apheleia-ft--read-file in-temp-real-file)))
|
||||||
|
|
1
test/formatters/installers/lisp-indent.bash
Normal file
1
test/formatters/installers/lisp-indent.bash
Normal file
|
@ -0,0 +1 @@
|
||||||
|
# Nothing to do here, this formatter is pure Emacs!
|
5
test/formatters/samplecode/lisp-indent/in.el
Normal file
5
test/formatters/samplecode/lisp-indent/in.el
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
;; -*- indent-tabs-mode: nil -*-
|
||||||
|
(if (and (< 3 5)
|
||||||
|
(= 1 1))
|
||||||
|
(message "true")
|
||||||
|
(message "false"))
|
5
test/formatters/samplecode/lisp-indent/out.el
Normal file
5
test/formatters/samplecode/lisp-indent/out.el
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
;; -*- indent-tabs-mode: nil -*-
|
||||||
|
(if (and (< 3 5)
|
||||||
|
(= 1 1))
|
||||||
|
(message "true")
|
||||||
|
(message "false"))
|
Loading…
Add table
Reference in a new issue