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:
Ellis Kenyő 2022-09-03 19:22:35 +01:00 committed by GitHub
parent 4d59a9b696
commit 9b745df2fa
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 162 additions and 78 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View 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! ^_^

View file

@ -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)))

View file

@ -0,0 +1 @@
# Nothing to do here, this formatter is pure Emacs!

View file

@ -0,0 +1,5 @@
;; -*- indent-tabs-mode: nil -*-
(if (and (< 3 5)
(= 1 1))
(message "true")
(message "false"))

View file

@ -0,0 +1,5 @@
;; -*- indent-tabs-mode: nil -*-
(if (and (< 3 5)
(= 1 1))
(message "true")
(message "false"))