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

View file

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

View file

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

View file

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

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

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