Apheleia
优秀的代码会由诸如 Black 或 Prettier 之类的工具自动格式化,以便你和你的团队 将更少的时间花在格式化上,而将更多的时间用于构建功能。 如果你的编辑器能够在每次保存文件时运行代码格式化工具, 那就再好不过了,这样你就不必查看格式糟糕的代码, 也不必在即将提交时因代码发生变化而感到意外。然而, 在保存时运行代码格式化工具存在以下两个问题:
- 它需要一些时间(例如,Black 在空文件上大约需要 200ms), 这使得编辑器的响应速度感觉变慢了。
- 如果代码格式化工具所做的更改距离光标(点)的位置 太近,它总是会将你的光标(点)移动到某个意想不到的位置。
Apheleia 是一个 Emacs 包, 它全面地解决了所有语言的这两个问题, 让你可以告别诸如 Blacken 和 prettier-js 等特定于语言的包。
方法如下:
- 在
after-save-hook上运行代码格式化器,而不是 在before-save-hook上运行,并且异步执行。一旦格式化器 运行完毕,检查自其启动以来缓冲区是否已被修改; 仅在未修改的情况下应用更改。 - 运行代码格式化器后,生成一个显示更改的 RCS 补丁 并将其应用到缓冲区。这可以防止缓冲区其他位置的更改 移动点。如果某个补丁区域恰好包含点,则使用 用于字符串 对齐的动态规划 算法 来确定点应移动到哪里,以便它相对于周围环境保持在 相同的位置。最后,如果点相对于窗口的垂直位置 发生了变化,则调整滚动位置以保持最大的视觉连续性。(这 包括遍历所有显示该缓冲区的窗口,如果 存在多个窗口。)动态规划算法的运行时间为 二次方时间,这就是为什么它仅在必要时应用于 单个补丁区域。
安装
Apheleia 可在 MELPA 上获取。使用
straight.el 安装它是最简单的:
(straight-use-package 'apheleia)
但是,如果您愿意,也可以使用任何其他包管理器进行安装。
依赖项
支持 Emacs 27 或更高版本。Apheleia 不包含任何
格式化器。您必须单独安装希望使用的任何
格式化器。只要它在 $PATH 上,Apheleia 就会自动
识别它;缺失的格式化器将被静默跳过,但调用已安装
格式化器时产生的错误将在保存缓冲区时报告。
建议安装 Bash,因为 Apheleia 将其用作依赖项 来调用某些格式化器(例如 基于 Node.js 的格式化器)。
由于该平台缺乏对常见 开放标准的支持,Windows 支持不保证。欢迎提交 调整 Apheleia 以改善跨平台可移植性的拉取请求,但 不保证在 Windows 上的稳定性。
用户指南
在您的 init-file 中,添加以下形式:
(apheleia-global-mode +1)
自动加载已配置为,这不会导致 Apheleia 在您保存文件之前被加载。
默认情况下,Apheleia 配置为在保存时使用 Black、 Prettier 和 Gofmt 在所有相关的主要 模式中进行格式化。要配置此行为,您可以调整以下 变量的值:
apheleia-formatters:一个 Alist,将格式化器的名称(符号 如black和prettier)映射到用于运行这些 格式化器的命令(如("black" "-")和(npx "prettier" input))。 有关更多信息,请参阅 docstring。- 您可以使用标准的 Emacs 函数来操作此 alist。 例如,要为 Black 添加一些命令行选项,您可以 使用:
(setf (alist-get 'black apheleia-formatters)
'("black" "--option" "..." "-"))
```
* 存在一组符号,apheleia 在格式化命令时会对其进行特殊解释(示例:`npx`)。任何
在格式化器中不等于这些符号之一的非字符串条目都会被求值并就地替换。这可以
用于根据当前缓冲区的状态向格式化进程传递特定标志。例如:
```elisp
(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)
```
这为 `beautysh`
格式化器向 `apheleia-formatters` 添加了一个条目。经过求值的条目使得 `--tab`
标志仅在 `indent-tabs-mode` 的值为 true 时传递给 `beautysh`。类似地,缩进大小标志
仅在 `sh-basic-offset` 变量被绑定时传递该变量的确切值。请注意,这些求值中
的一个返回标志列表,而另一个返回单个字符串。它们会按预期替换到命令中。
* 您还可以使用 Apheleia 格式化没有底层
文件的缓冲区。在这种情况下,`file` 和 `filepath` 的值将
是当前缓冲区的名称,其中文件系统(例如在 windows 上的 `*`)的特殊字符
会被去除。
这也是确定 apheleia 可能创建的任何临时文件的扩展名的方式。如果您使用的格式化程序根据扩展名确定文件类型,则应命名此类缓冲区,使其以该扩展名结尾。例如,一个名为 `*foo-bar.c*` 且没有关联文件的缓冲区将具有隐式文件名 `foo-bar.c`,并且任何临时文件都将带有 `.c` 扩展名。
* 您可以将格式化程序实现为任意 Elisp 函数,这些函数直接操作缓冲区,而无需调用外部命令。这对于与例如语言服务器集成可能很有用。有关 Elisp 格式化程序预期接口的更多信息,请参阅 docstring。
* `apheleia-mode-alist`:将主要模式和文件名正则表达式映射到在这些模式和文件中使用的格式化程序名称的 Alist。有关更多信息,请参阅 docstring。
* 您可以通过将条目的 `cdr` 设置为要运行的格式化程序列表(而不是单个格式化程序)来使用此变量为同一缓冲区配置多个格式化程序。例如,您可能希望依次运行 `isort` 和 `black`。
```elisp
(setf (alist-get 'isort apheleia-formatters)
'("isort" "--stdout" "-"))
(setf (alist-get 'python-mode apheleia-mode-alist)
'(isort black))
```
这将使 apheleia 在当前缓冲区上运行 `isort`,然后在 `isort` 的结果上
运行 `black`,然后使用最终输出
来格式化当前缓冲区。
**警告**:目前尚未实现智能或可配置的
错误处理。这意味着如果其中一个已配置的
格式化器失败(例如如果未安装 `isort`),那么
apheleia 将完全不对缓冲区进行格式化,即使已安装 `black`。
**警告:** 如果某个格式化器使用 `file`(而不是 `filepath`
或 `input` 或这些关键字都没有),则它不能链接在
另一个格式化器之后,因为 `file` 意味着该格式化器
必须从*原始*文件读取,而不是从中间
临时文件读取。因此,建议通常避免使用
`file`。
* `apheleia-formatter`:可选的缓冲区局部变量,指定
在此缓冲区中使用的
格式化器。覆盖 `apheleia-mode-alist`。
您可以在局部变量列表中设置此变量,或在 `.dir-locals.el`
中设置(例如 `((python-mode . ((apheleia-formatter . (isort black)))))`),
或者在您自己的自定义钩子中条件性地设置该局部变量。
* `apheleia-inhibit`:可选的缓冲区局部变量,如果设置为
非 nil,则即使
`apheleia-global-mode` 已开启,Apheleia 也不会自动开启。
你可以运行 `M-x apheleia-mode` 来切换单个缓冲区在保存时的自动格式化,
或者运行 `M-x apheleia-global-mode` 来切换所有缓冲区的
默认设置。此外,即使 `apheleia-mode` 未
启用,你也可以运行 `M-x apheleia-format-buffer` 来手动调用
当前缓冲区配置的格式化程序。使用前缀
参数运行该命令会提示你选择要运行
哪个格式化程序。
Apheleia 目前不支持 TRAMP,因此
对于远程文件会自动禁用。
如果在格式化过程中发生错误,消息会显示在
回显区域。你可以通过调用 `M-x
apheleia-goto-error` 跳转到错误,或者手动切换到消息中提到的
日志缓冲区。
你可以使用以下用户选项来配置错误报告:
* `apheleia-hide-log-buffers`: 默认情况下,格式化程序的错误
会放在名为 `*apheleia-cmdname-log*` 的缓冲区中。如果你将此
用户选项自定义为非 nil 值,则这些缓冲区的名称
前会添加一个空格,使其在 `switch-to-buffer` 中默认
隐藏(你必须输入一个空格才能看到它们)。
* `apheleia-log-only-errors`: 默认情况下,仅记录失败的格式化程序运行
日志。如果你将此用户选项自定义为 nil,则所有运行
都会被记录,包括它们是否成功。这可能
有助于调试。
以下用户选项也可用:
* `apheleia-post-format-hook`: 在 Apheleia 格式化缓冲区后正常运行的钩子。即使缓冲区未发生更改,只要格式化成功就会运行。
* `apheleia-max-alignment-size`: 使用 Apheleia 的动态规划算法进行点对齐处理时,diff 区域允许的最大字符数。此值不能过大,否则 Emacs 在大型重新格式化操作上会明显挂起,因为该 DP 算法的时间复杂度为二次方。
* `apheleia-mode-lighter`: 在模式行中显示 `apheleia-mode` 的较浅颜色。如果不想显示它,请使用 nil。否则,其值必须是一个字符串。
Apheleia 暴露了一些钩子用于高级自定义:
* `apheleia-formatter-exited-hook`: 在格式化程序完全结束运行缓冲区后运行的异常钩子。如果格式化被中断且未采取任何操作,则不会运行。接收两个参数:所运行的格式化程序的符号(例如 `black`,或者如果链式运行了多个格式化程序,则可能是一个列表),以及一个表示是否发生错误的布尔值。
* `apheleia-inhibit-functions`: 在从 `apheleia-global-mode` 自动启用 Apheleia 之前运行的函数列表。如果其中一个返回非 nil,则 `apheleia-mode` 不会在该缓冲区中启用。
* `apheleia-skip-functions`: 在每次调用 Apheleia 格式化程序之前运行的函数列表。如果其中一个返回非 nil,则即使 `apheleia-mode` 已启用,也不会运行该格式化程序。
### 格式化程序配置
Apheleia 中没有用于配置格式化器行为的配置界面。配置格式化器的方法是编辑它所读取的标准配置文件(例如 `.prettierrc.json`),或设置它所读取的环境变量,或者通过修改 `apheleia-formatters` 中的条目来自定义命令行参数。
有一个例外,即 Apheleia 为内置格式化器提供的默认命令行参数会自动检查 Emacs 中对应主模式的缩进选项,并将该信息传递给格式化器。这样,格式化器应用的缩进(制表符与空格,以及数量)将与 Emacs 中的自动缩进行为保持一致,从而避免在输入时来回切换。
可以通过将 `apheleia-formatters-respect-indent-level` 设置为 nil 来禁用此行为。
## 故障排除
尝试在 Emacs 外部运行您的格式化器,以验证其是否正常工作。检查它在 `apheleia-formatters` 中配置的命令行选项。
要调试内部错误、竞态条件或性能问题,请尝试将 `apheleia-log-debug-info` 设置为非 nil 值,并检查 `*apheleia-debug-log*` 的内容。它将包含关于 Apheleia 执行的大多数操作的详细跟踪信息。
### 已知问题
* `process aphelieia-whatever no longer connected to pipe; closed it`:
这在旧版本的 Emacs 中格式化大小超过 65,536 个字符的缓冲区时发生。除了禁用受影响缓冲区的 `apheleia-mode`,或升级到较新版本的 Emacs 外,没有已知的解决方法。参见
[#20](https://github.com/radian-software/apheleia/issues/20).
## 贡献
请参阅[我的项目的贡献者指南](https://github.com/radian-software/contributor-guide)]以获取
通用信息,以及以下章节中关于 Apheleia 的
具体细节。
此外还有一个[wiki](https://github.com/radian-software/apheleia/wiki),可能需要补充/澄清。任何
改进建议都应作为 issue 提交。
### 添加格式化器
我已尽力使添加格式化器的过程变得简单。您
只需遵循以下步骤:
1. 在您的机器上安装您的格式化器以便进行测试。
2. 在 `apheleia-formatters` 中创建一个条目,说明如何运行它。(参见
该变量的 docstring 以了解可用
关键字的说明。)
3. 在 `apheleia-mode-alist` 中添加相关主要模式的条目。
4. 看看它是否对您有效!
5. 在 `test/formatters/installers/yourformatter.bash` 处添加一个文件,
说明如何在 Ubuntu 上安装该格式化器。这将用于
CI。
6. 使用 `make fmt-build FORMATTERS=yourformatter` 进行
安装,然后使用 `make fmt-docker` 启动一个包含
该格式化器的 shell。验证它在此环境中可以运行。
7. 在 `test/formatters/samplecode/yourformatter/in.whatever` 和
`test/formatters/samplecode/yourformatter/out.whatever` 处添加一个示例输入(格式化前)和输出(格式化后)
文件。
8. 使用 `make fmt-test
FORMATTERS=yourformatter` from inside the `fmt-docker` shell 验证测试是否通过。
9. 提交一个 pull request,CI 现在应该通过了!
## 致谢
我使用 RCS 补丁来避免过多移动点(point)的
想法来自 [prettier-js](https://github.com/prettier/prettier-emacs),
尽管该包并未实现 Apheleia 用于保证点
在格式化区域内稳定性的动态规划
算法。
请注意,尽管受到此启发,Apheleia 是一个干净室实现,不受 prettier-js 版权条款的约束。