.circleci | ||
scripts | ||
.gitignore | ||
apheleia.el | ||
CHANGELOG.md | ||
Dockerfile | ||
LICENSE.md | ||
Makefile | ||
README.md |
Apheleia
Good code is automatically formatted by tools like Black or Prettier so that you and your team spend less time on formatting and more time on building features. It's best if your editor can run code formatters each time you save a file, so that you don't have to look at badly formatted code or get surprised when things change just before you commit. However, running a code formatter on save suffers from the following two problems:
- It takes some time (e.g. around 200ms for Black on an empty file), which makes the editor feel less responsive.
- It invariably moves your cursor (point) somewhere unexpected if the changes made by the code formatter are too close to point's position.
Apheleia is an Emacs package which solves both of these problems comprehensively for all languages, allowing you to say goodbye to language-specific packages such as Blacken and prettier-js.
The approach is as follows:
- Run code formatters on
after-save-hook
, rather thanbefore-save-hook
, and do so asynchronously. Once the formatter has finished running, check if the buffer has been modified since it started; only apply the changes if not. - After running the code formatter, generate an RCS patch showing the changes and then apply it to the buffer. This prevents changes elsewhere in the buffer from moving point. If a patch region happens to include point, then use a dynamic programming algorithm for string alignment to determine where point should be moved so that it remains in the same place relative to its surroundings. Finally, if the vertical position of point relative to the window has changed, adjust the scroll position to maintain maximum visual continuity. (This includes iterating through all windows displaying the buffer, if there are more than one.) The dynamic programming algorithm runs in quadratic time, which is why it is only applied if necessary and to a single patch region.
User guide
To get started with Apheleia, install it with
straight.el
as follows:
(straight-use-package '(apheleia :host github :repo "raxod502/apheleia"))
Alternatively, you can use another source-based package manager such as Quelpa, El-Get, or Borg. Apheleia is not currently listed on MELPA or GNU ELPA.
To your init-file, add the following form:
(apheleia-global-mode +1)
The autoloading has been configured so that this will not cause Apheleia to be loaded until you save a file.
By default, Apheleia is configured to format with Black, Prettier, and Gofmt on save in all relevant major modes. To configure this, you can adjust the values of the following variables:
apheleia-formatters
: Alist mapping names of formatters (symbols likeblack
andprettier
) to commands used to run those formatters (such as("black" "-")
and(npx "prettier" input)
). See the docstring for more information.apheleia-mode-alist
: Alist mapping major modes and filename regexps to names of formatters to use in those modes and files. See the docstring for more information.apheleia-formatter
: Optional buffer-local variable specifying the formatter to use in this buffer. Overridesapheleia-mode-alist
.
You can run M-x apheleia-mode
to toggle automatic formatting on save
in a single buffer, or M-x apheleia-global-mode
to toggle the
default setting for all buffers. Also, even if apheleia-mode
is not
enabled, you can run M-x apheleia-format-buffer
to manually invoke
the configured formatter for the current buffer. Running with a prefix
argument will cause the command to prompt you for which formatter to
run.
Apheleia does not currently support TRAMP, and is therefore automatically disabled for remote files.
The following user options are also available:
apheleia-post-format-hook
: Normal hook run after Apheleia formats a buffer. Run if the formatting is successful, even when no changes are made to the buffer.
Contributing
Development of Apheleia happens using the provided Makefile. Run make help
for documentation. All commits are automatically tested using
make lint
for all supported Emacs versions on the excellent
CircleCI platform. Please make sure you can
successfully run make lint
before submitting a pull request.
If the CI fails, it may be that your change is not compatible with one of the Emacs versions supported by Apheleia. Suppose that the failure is for Emacs 25.3. To test this locally, you can install Docker and run:
% make docker VERSION=25.3
This will start a shell inside of Docker with the specified version of Emacs available. The Apheleia source code on your filesystem is synchronized with the filesystem inside of Docker, so you need not restart the shell after making changes. To test, simply use the same Makefile targets as usual.
Acknowledgements
I got the idea for using RCS patches to avoid moving point too much from prettier-js, although that package does not implement the dynamic programming algorithm which Apheleia uses to guarantee stability of point even within a formatted region.
Note that despite this inspiration, Apheleia is a clean-room implementation which is free of the copyright terms of prettier-js.