Apheleia

repository·main·Indexed 20 days ago

https://github.com/radian-software/apheleia

An Emacs package providing seamless, asynchronous code formatting. It prevents editor lag and cursor jumping during 'format-on-save' by using asynchronous execution and RCS patches. Apheleia supports chaining multiple formatters, dynamic command generation, and smart point adjustment using the Needleman–Wunsch algorithm. It requires Emacs 27 or later and external formatters installed in the system $PATH.

Tokens
1.6K
Snippets
4
Records
10
Agent score
23%

What's inside Apheleia

  1. How Apheleia handles asynchronous formatting and cursor stability

    main

    Apheleia solves the responsiveness and cursor-jumping issues common with 'format-on-save' by using the following approach:

    1. Asynchronous Execution: It runs formatters on the after-save-hook rather than before-save-hook. It performs the operation asynchronously and only applies changes if the buffer has not been modified since the formatting process started.
    2. RCS Patch Application: Instead of replacing the buffer content directly, it generates an RCS patch of the changes. This prevents changes in other parts of the buffer from moving your cursor (point).
    3. Smart Point Adjustment: If a patch affects the cursor's position, Apheleia uses a dynamic programming algorithm (Needleman–Wunsch) to calculate the new position so the cursor remains in the same place relative to its surroundings. It also adjusts scroll positions across all windows displaying the buffer to maintain visual continuity.
  2. Configure dependencies for Apheleia

    main

    Apheleia requires the following environment setup to function correctly:

    • Emacs: Version 27 or later.
    • Formatters: Apheleia does not bundle formatters. You must install your desired formatters (e.g., black, prettier) separately. They must be available in your system $PATH. If a formatter is missing from $PATH, Apheleia will silently skip it. If an installed formatter returns an error, Apheleia will report it when you save the buffer.
    • Bash: Recommended for invoking certain formatters, such as those based on Node.js.
    • Platform Note: Windows support is not guaranteed.
  3. Map modes to formatters with `apheleia-mode-alist`

    main

    The apheleia-mode-alist variable maps major modes and filename regexps to formatter names.

    Chaining multiple formatters

    You can run multiple formatters in sequence by providing a list of symbols in the cdr of an entry. For example, to run isort followed by black in python-mode:

    (setf (alist-get 'isort apheleia-formatters)
          '("isort" "--stdout" "-"))
    (setf (alist-get 'python-mode apheleia-mode-alist)
          '(isort black))

    Warning: If any formatter in a chain fails (e.g., if isort is not installed), Apheleia will skip the entire chain and perform no formatting at all.

    Warning: Avoid using the file keyword in chained formatters. file implies reading from the original file on disk, which prevents the chain from passing intermediate results from one formatter to the next.

  4. Configure formatters with `apheleia-formatters`

    main

    The apheleia-formatters variable is an alist mapping formatter names (symbols like black or prettier) to the commands used to run them.

    Adding command-line options

    You can modify existing formatters using alist-get. For example, to add options to black:

    (setf (alist-get 'black apheleia-formatters)
          '("black" "--option" "..." "-"))

    Dynamic command generation

    You can use Elisp logic within the formatter definition to pass flags conditionally based on buffer state. For example, passing --tab only if indent-tabs-mode is enabled:

    (push '(shfmt . ("beautysh"
                     "-filename" filepath
                     (when-let ((indent (bound-and-true-p sh-basic-offset)))
                       (list "--indent-size" (number-to-string indent))))
                     (when indent-tabs-mode "--tab")
                     "-"))
      apheleia-formatters)

    Formatting unsaved buffers

    Apheleia can format buffers without underlying files. In these cases, file and filepath resolve to the buffer name with filesystem-special characters stripped. To ensure a formatter detects the correct file type for an unsaved buffer, name the buffer with the appropriate extension (e.g., *foo-bar.c* implies a .c file).

  5. Configure Apheleia logging and error reporting

    main

    Apheleia provides several options to manage how errors and logs are handled:

    • apheleia-hide-log-buffers: If non-nil, prepends a space to log buffer names (e.g., *apheleia-cmdname-log*), hiding them from switch-to-buffer unless you type a space.
    • apheleia-log-only-errors: If set to nil, all runs are logged (including successes). By default, only failed runs are logged.
    • apheleia-log-debug-info: Set to non-nil to enable detailed trace information in the *apheleia-debug-log* buffer for debugging internal bugs or performance issues.
  6. Advanced customization with Apheleia hooks

    main

    For advanced users, Apheleia exposes several hooks:

    • apheleia-formatter-exited-hook: Runs after a formatter finishes. Receives two arguments: the symbol for the formatter (or a list if chained) and a boolean indicating if there was an error.
    • apheleia-inhibit-functions: A list of functions checked before enabling apheleia-global-mode. If any returns non-nil, Apheleia is not enabled in that buffer.
    • apheleia-skip-functions: A list of functions checked before each formatter invocation. If any returns non-nil, the formatter is skipped.
  7. Override formatting locally with `apheleia-formatter`

    main

    To specify a specific formatter for a single buffer, use the buffer-local variable apheleia-formatter. This overrides the settings in apheleia-mode-alist.

    You can set this in:

    1. A local variables list.
    2. A .dir-locals.el file (e.g., `((python-mode . ((apheleia-formatter . (isort black)))))").
    3. A custom hook.
  8. Manage Apheleia modes and manual formatting

    main

    Use the following commands to control Apheleia:

    • M-x apheleia-mode: Toggle automatic formatting on save for the current buffer.
    • M-x apheleia-global-mode: Toggle the default setting for all buffers.
    • M-x apheleia-format-buffer: Manually invoke the configured formatter for the current buffer (even if apheleia-mode is disabled).
    • M-x apheleia-format-buffer (with prefix argument): Prompt to choose which formatter to run.
    • M-x apheleia-goto-error: Jump to the error location if a formatter fails.