mirror of
https://github.com/vale981/apheleia
synced 2025-03-04 09:01:42 -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
|
||||
strategy:
|
||||
matrix:
|
||||
emacs_version: [26, 27, "master"]
|
||||
emacs_version: [26, 27, 28, "master"]
|
||||
steps:
|
||||
- name: Checkout
|
||||
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
|
||||
([#101]).
|
||||
* [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
|
||||
[#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
|
||||
[#109]: https://github.com/radian-software/apheleia/issues/109
|
||||
[#110]: https://github.com/radian-software/apheleia/pull/110
|
||||
|
|
23
Makefile
23
Makefile
|
@ -1,3 +1,5 @@
|
|||
SHELL := bash
|
||||
|
||||
VERSION ?=
|
||||
CMD ?=
|
||||
|
||||
|
@ -8,6 +10,7 @@ TAG ?= latest
|
|||
# The order is important for compilation.
|
||||
for_compile := *.el
|
||||
for_checkdoc := *.el
|
||||
for_checkindent := *.el
|
||||
|
||||
.PHONY: help
|
||||
help: ## Show this message
|
||||
|
@ -19,7 +22,7 @@ help: ## Show this message
|
|||
column -t -s'|' >&2
|
||||
|
||||
.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
|
||||
compile: ## Check for byte-compiler errors
|
||||
|
@ -42,6 +45,24 @@ checkdoc: ## Check for missing or poorly formatted docstrings
|
|||
| grep . && exit 1 || true ;\
|
||||
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
|
||||
longlines: ## Check for long lines
|
||||
@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"))
|
||||
(args
|
||||
(append
|
||||
(list (car command) ; argv[0]
|
||||
(not stdin) ; If stdin we don't delete the STDIN
|
||||
; buffer text with
|
||||
; `call-process-region'. Otherwise we
|
||||
; send no INFILE argument to
|
||||
; `call-process'.
|
||||
`(,stdout ,stderr-file) ; stdout buffer and stderr file.
|
||||
; `call-process' cannot capture
|
||||
; stderr into a separate buffer, the
|
||||
; best we can do is save and read
|
||||
; from a file.
|
||||
nil) ; Do not re/display stdout as output
|
||||
; is recieved.
|
||||
(cdr command)))) ; argv[1:]
|
||||
(list
|
||||
;; argv[0]
|
||||
(car command)
|
||||
;; If stdin we don't delete the STDIN buffer text with
|
||||
;; `call-process-region'. Otherwise we send no INFILE
|
||||
;; argument to `call-process'.
|
||||
(not stdin)
|
||||
;; stdout buffer and stderr file. `call-process' cannot
|
||||
;; capture stderr into a separate buffer, the best we can
|
||||
;; do is save and read from a file.
|
||||
`(,stdout ,stderr-file)
|
||||
;; Do not re/display stdout as output is recieved.
|
||||
nil)
|
||||
;; argv[1:]
|
||||
(cdr command))))
|
||||
(unwind-protect
|
||||
(let ((exit-status
|
||||
(cl-letf* ((message (symbol-function #'message))
|
||||
|
@ -656,7 +657,9 @@ See `apheleia--run-formatters' for a description of REMOTE."
|
|||
(clear-files nil)
|
||||
(run-on-remote (and (eq apheleia-remote-algorithm '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)
|
||||
;; Ensure there's a file with the contents of `buffer' on the
|
||||
;; target machine. `fname', if given, refers to an existing
|
||||
|
@ -826,12 +829,12 @@ machine from the machine file is available on"))
|
|||
arg
|
||||
(eval arg)))
|
||||
if val
|
||||
if (and (consp val)
|
||||
(cl-every #'stringp val))
|
||||
append val
|
||||
else if (stringp val)
|
||||
collect val
|
||||
else do (error "Result of command evaluation must be a string \
|
||||
if (and (consp val)
|
||||
(cl-every #'stringp val))
|
||||
append val
|
||||
else if (stringp val)
|
||||
collect val
|
||||
else do (error "Result of command evaluation must be a string \
|
||||
or list of strings: %S" arg)))
|
||||
`(,input-fname ,output-fname ,stdin ,@command))))
|
||||
|
||||
|
@ -928,6 +931,7 @@ being run, for diagnostic purposes."
|
|||
(gofmt . ("gofmt"))
|
||||
(google-java-format . ("google-java-format" "-"))
|
||||
(isort . ("isort" "-"))
|
||||
(lisp-indent . apheleia-indent-lisp-buffer)
|
||||
(ktlint . ("ktlint" "--stdin" "-F"))
|
||||
(latexindent . ("latexindent" "--logfile=/dev/null"))
|
||||
(mix-format . ("mix" "format" "-"))
|
||||
|
@ -1000,6 +1004,26 @@ rather than using this system."
|
|||
(const :tag "Name of temporary file used for output" output)))
|
||||
(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
|
||||
(formatters buffer remote callback &optional stdin)
|
||||
"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)
|
||||
(caml-mode . ocamlformat)
|
||||
(common-lisp-mode . lisp-indent)
|
||||
(css-mode . prettier)
|
||||
(dart-mode . dart-format)
|
||||
(emacs-lisp-mode . lisp-indent)
|
||||
(elixir-mode . mix-format)
|
||||
(elm-mode . elm-format)
|
||||
(fish-mode . fish-indent)
|
||||
|
@ -1070,6 +1096,7 @@ function: %s" command)))
|
|||
(latex-mode . latexindent)
|
||||
(LaTeX-mode . latexindent)
|
||||
(lua-mode . stylua)
|
||||
(lisp-mode . lisp-indent)
|
||||
(nix-mode . nixfmt)
|
||||
(python-mode . black)
|
||||
(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
|
||||
"/in\\([^/]+\\)" "/out\\1" in-file 'fixedcase))
|
||||
(exec-path
|
||||
(append `(,(expand-file-name
|
||||
"scripts/formatters"
|
||||
(file-name-directory
|
||||
(file-truename
|
||||
;; Borrowed with love from Magit
|
||||
(let ((load-suffixes '(".el")))
|
||||
(locate-library "apheleia"))))))
|
||||
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))
|
||||
(append `(,(expand-file-name
|
||||
"scripts/formatters"
|
||||
(file-name-directory
|
||||
(file-truename
|
||||
;; Borrowed with love from Magit
|
||||
(let ((load-suffixes '(".el")))
|
||||
(locate-library "apheleia"))))))
|
||||
exec-path)))
|
||||
(setq stdout-buffer (get-buffer-create
|
||||
(format "*apheleia-ft-stdout-%S" formatter)))
|
||||
(with-current-buffer stdout-buffer
|
||||
(erase-buffer))
|
||||
(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))
|
||||
(if (functionp command)
|
||||
(progn
|
||||
(setq in-temp-file (apheleia-ft--write-temp-file
|
||||
in-text extension))
|
||||
(with-current-buffer (find-file-noselect in-temp-file)
|
||||
(funcall command
|
||||
:buffer (current-buffer)
|
||||
:scratch (current-buffer)
|
||||
:formatter formatter
|
||||
:callback (lambda ()))
|
||||
(copy-to-buffer stdout-buffer (point-min) (point-max))))
|
||||
(progn
|
||||
|
||||
(with-current-buffer stdout-buffer
|
||||
(erase-buffer))
|
||||
(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
|
||||
(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.
|
||||
(when 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