:editor format refactor

Just a tracking post for refactor(format): replace with apheleia by elken · Pull Request #6369 · doomemacs/doomemacs · GitHub atm which intends to replace format-all with the much more up-to-date apheleia.

State of set-formatter!

Currently in the PR I mark it as obsolete and replace it with a no-op that advises users to use apheleia vars for formatter customisations as it handles more use cases and is already very capable than a wrapper macro.

There’s nothing we could really add that it doesn’t do already.

Per a comment from Henrik, set-formatter! is still intended to be included.

Planned summary of changes

Remove the tests included

apheleia already includes a comprehensive test suite and any new formatter is required to include tests to be considered. If it looks like we’re going to implement lots of our own rather than upstreaming, this can maybe be revisited.

Replace format-all with apheleia

format-all has been pinned and listed on do-not-pr for some time now while a replacement has been investigated. apheleia seems like a very viable candidate, formatting even very large buffers almost instantly.

Much like some of the complex advise included in the current module, apheleia uses an RCS patching system to only apply what changed to the buffer after it’s been saved (so as not to contest with other tools/hooks). Replacing it means the module becomes greatly simplified.

Refactor the autoloaded functions

apheleia does not yet support formatting regions, so this will either have to be implemented via refactoring the existing functions or upstream into apheleia.

Replace all in-doom invocations

As we have full control of the code, this should be reasonably simple. Defining a formatter per-mode or even per-buffer is extremely simple:

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

The above example creates a new formatter called isort and sets it to run before black during python-mode only. Simpler and more complex configurations also exist.

Per a comment from Henrik, these will be incorporated into set-formatter! still.

Refactor docs

apheleia also has very good documentation (as is expected from raxod), so this should mostly just be stealing borrowing snippets to go here

Default formatters/cmdline

If applicable, the default formatter/cmdline for each module. Ideally filled out by either the module maintainer or Henrik.

:lang agda

Doesn’t seem like there is a linter available.

:lang beancount

Supported upstream (PR)

:lang cc

Using clang-format for this with -assume-filename making a guess on the name of the file or the mode

:lang clojure

Using zprint to handle stdin and return stdout

:lang common-lisp

Using the same format function as emacs-lisp wrapped with sly’s indent function. There is probably an argument for us to suggest a default style but it’s also very subjective…

:lang coq

I tried coqindent and the below function to no avail. If anyone is able to PR a working version, they’re welcome!

(cl-defun +coq-formatter (&key buffer scratch callback &allow-other-keys)
    (with-current-buffer scratch
      (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)))

:lang crystal

Using the builtin crystal tool format

:lang csharp

Using csharpier installed as a local tool (some extra troubleshooting steps RE: libicu need to be documented)

:lang dart

Included upstream, uses dart format

:lang data

Using xmllint included with libxml2 for XML, and nothing for CSVs which can only really be validated (if one is requested then this can be amended

:lang dhall

Using either the included dhall-mode auto-format via +dhall-format or by default using apheleia to call dhall lint

:lang elixir

Included upstream using mix-format

:lang elm

Supported upstream (PR)

:lang emacs-lisp

Supported upstream (PR) but defined locally until it’s merged

:lang erlang

Using efmt

:lang ess

Needs a domain expert

:lang factor

Could not find one

:lang faust

Could not find one

:lang fortran

Using fprettify

:lang fsharp

Included upstream via this PR

:lang fstar

Could not find one

:lang gdscript

Using gdscript-toolkit

:lang go

Included upstream

:lang graphql

Could be handled in a larger PR upstream

:lang haskell

Included upstream

:lang hy

Same function as emacs-lisp uses

:lang idris

Couldn’t find one

:lang java

Included upstream

:lang javascript

  • JS included upstream
  • TS included upstream

:lang json

Included upstream

:lang julia

No-for-now

:lang kotlin

Included upstream and overridden locally.

Optionally include 'android-mode if it is bound and loaded, otherwise everything else should be up to the user

("ktlint" "--stdin" "-F"
               (when (and (boundp 'android-mode)
                          android-mode)
                 "-a"))

:lang latex

Included upstream

:lang lean

Couldn’t find one

:lang ledger

Couldn’t find one

:lang lua

Included upstream

:lang markdown

Could be handled in a larger PR upstream

:lang nim

Using nimfmt)

:lang nix

Included upstream

:lang ocaml

Included upstream

:lang org

AFAIK one does not exist, I would love to be proven wrong though

:lang php

Included upstream

:lang plantuml

Couldn’t find one

:lang purescript

Using purs-tidy

:lang python

Included upstream

:lang qt

Exists via qmllint | Qt Quick 6.5.0 but I struggled to get this working

:lang racket

Using racofmt

:lang raku

Found an old repo but couldn’t get it working

:lang rest

Doesn’t make sense

:lang rst

Using rstfmt

:lang ruby

Included upstream

:lang rust

Included upstream

:lang scala

Using scalafmt

:lang scheme

Using the same function for lisps, but it doesn’t quite seem to format them

:lang sh

  • Fish included upstream
  • sh included upstream and overriden locally

:lang sml

Using smlformat

:lang solidity

Could be handled in a larger PR upstream

:lang swift

Using swiftformat

:lang terra

Included upstream

:lang web

  • CSS included upstream
  • HTML included upstream
  • General web-mode included upstream

:lang yaml

Included upstream

:lang zig

Using the built-in zig fmt

:tools docker

Using dockfmt

Final

This is all WIP, and anything can/will change. Any other improvements/recommendations are welcome here.

Note that this is not the place to report issues unless during active development. After this is merged and live, issues should go to the relevant areas.

9 Likes

Created a discussion that may stall this out for a bit, but I’m still endeavouring to upstream formatters in the meantime.

Do you have any thoughts on https://unibeautify.com/ in that context? It tries doing similar things and might be worth factoring into the “one umbrella” thing.

You’re right that it does, unibeautify was mentioned in the issue thread linked in the discussion (a bit round the houses I know…)

At the end of the day, it doesn’t matter what that umbrella is; just as long as raxod, lassik and purcell can agree to one package :)

An unfortunate downside is the ub emacs package hasn’t been updated in 4y.

Oh, I only skimmed the discussion, so I missed it. We’ll see how it goes, I trust the guys to come up with a good solution.

It seems like UB is somewhat stalled in its development anyway. The reason I was interested in it is basically the same as LSP: keep the formatters definition external to Emacs and use an editor adapter. Wouldn’t be unthinkable to implement an LSP server which would only support textDocument/formatting or something like it.

Similar to null-ls on neovim yes.

It’s a good idea, and hopefully once the discussion starts that can also be proposed.

Maybe I’m misunderstanding, but it doesn’t seem like that discussion is all that related to a Doom PR. It didn’t even seem like purcell was interested in merging his package.

I don’t get why anyone needs to get consensus on merging them, either. Someone can merge them if they want, and people can opt into using it, or the maintainers can opt into aliasing their packages to the merged version, right? Either way, it should be easy enough to modify Doom’s hypothetical Apheleia-based format module to accept the new merged version, since it’ll have all the Apheleia functionality, no?

I’d like for the module to have a set-formatter!, whether or not it adds any value over apheleia-formatters. They exist to simplify cross-module configuration by removing the need for conditional after!'s containing implementation details spread across many :lang modules. They also no-op if their containing module is disabled.

I’m fine with dropping it altogether, frankly. Some formatters have project (or external) side-effects, others operate directly on files—fighting to standardize or isolate their effects is more work than I’m willing to maintain on a module I don’t use.

2 Likes

I’d like for the module to have a set-formatter! , whether or not it adds any value over apheleia-formatters . They exist to simplify cross-module configuration by removing the need for conditional after! 's containing implementation details spread across many :lang modules. They also no-op if their containing module is disabled.

Okay, I wasn’t sure if it was actually needed. I’ll have to spend some time coming up with a macro that handles all the cases apheleia-formatters handles.

I’m fine with dropping it altogether, frankly. Some formatters have project (or external) side-effects, others operate directly on files—fighting to standardize or isolate their effects is more work than I’m willing to maintain on a module I don’t use.

Yeah that’s fair, one idea I did have was creating a temp buffer with the region contents, apheleia-format-buffer it and replace the selection.

1 Like

There’s no point going all-in on adding apheleia if raxod is going to drop it to maintain the “new” project.

Makes sense. I guess I was thinking the point was that the new project is intended to support all the old’s functionality, so it wouldn’t be nearly as much work to move from Apheleia to the new project.

Almost certainly, I’m just poking around to see what the future is :)

1 Like

Also on :ok-statuses, I did attempt to upstream some way to handle that but raxod wasn’t keen so this will have to be advised.

Wait, do you not use the formatting module? How do you format your code then?

Raxod has clarified from the discussion that apheleia will be supported but “at some point” there will be a migration to “something”.

I’ll add myself as a maintainer of this module and keep an eye on that, but he said it’s ages off and not worth waiting for so I won’t be.

A couple more formatters have been upstreamed, but we might want to agree on good defaults for our formatters.

I’m aware not all :lang modules have maintainers, but those that do it would be good to get a list of sane defaults (which Henrik can obviously override), but I’l add a list to the original post.

1 Like

maybe i’m missing something, but running SPC c f on elisp files doesn’t work, it just brings up a chose fomratter prompt. is this a known issue? feels like a natural dogfooding barrier

That indeed is not yet implemented, I was debating how best to handle that but for now I’ll probably just settle on like

(indent-region (point-min) (point-max))

or something

I’m a bit surprised there isn’t an upstream formatter for elisp already

I’ll make it the next one, just for you :)

1 Like

thanks! i was going to try and implement it myself in the near future, so if you don’t get to it let me know and i’ll take a crack at it if i have time