OCaml 的 Neovim 配置方案

Info

更新说明:

  • 2025-03-22: 将 null-ls/none-ls 插件更换为 conform.nvim 插件,因为配置更简单 :)

上一篇博客中,我详细介绍了如何从零开始配置 Neovim,让它更接近 IDE。但我没有讲到的是:

  1. 有的 LSP 本身不支持代码格式化,要如何配置 Neovim 让它可以用第三方的代码格式化工具?
  2. 新增一门编程语言支持的时候,要怎么修改配置文件?

最近在学习函数式编程需要在我的 Mac 上配置 OCaml,正好以它为例,回答以上两个问题

Info

本文假定你已经阅读过我之前的文章,因此对于一些细节问题,我就不再赘述

Info

其他系统的安装方法可以看这里

在 Mac 上安装 OCaml 用 Homebrew 比较方便

sh

$ brew install opam

按照 Homebrew 的输出,我们还需要进行额外的配置,需要修改 ~/.zshrc(如果你用的是 Zsh)或者是 ~/.bashrc(如果你用的是 Bash)

sh

$ opam init
# 按照提示按下 y 确认即可

接下来让我们上一步的配置生效

sh

$ source ~/.zshrc
# $ source ~/.bashrc # if you use bash

最后,我们可以通过如下的命令检查是否安装成功

sh

$ opam switch

输出应该跟下面的差不多

text

#   switch   compiler                   description
->  5.0.0    ocaml-base-compiler.5.0.0  5.0.0
    default  ocaml.4.14.0               default
Tip

接下来需要安装一些包,因为网络原因访问可能会比较慢。加速可以通过换源实现,这里用的是上海交通大学的源,配置命令为

sh

$ opam repo \
    set-url default \
    https://mirrors.sjtug.sjtu.edu.cn/git/opam-repository.git \
    --all --set-default

等待该命令执行完就完成了换源

类似 Python 有 REPL,如 IPython。在 OCaml 里也有类似的工具,叫做 utop

sh

$ opam install utop

接下来尝试使用 utop 看是否安装成功

sh

$ utop

如果一切正常,你应该可以看到如下的输出

text

─────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────
                                                          Welcome to utop version 2.13.1 (using OCaml version 5.0.0)! 
                                                         └─────────────────────────────────────────────────────────────┘

Type #utop_help for help about using utop.

( 10:33:26 )< command 0 >────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────{ counter: 0 }
utop #
┌──────────────┬──────────────┬───────────────┬─────┬───────────┬────┬───┬──────────┬─────┬───────────┬──────┬────────────┬───────┬───────────┬──────────────┬──────────┬────────┐
Afl_instrumentAlias_analysisAllocated_constAnnotApplicativeArchArgArg_helperArrayArrayLabelsAsmgenAsmlibrarianAsmlinkAsmpackagerAssert_failureAst_helperAst_inva
└──────────────┴──────────────┴───────────────┴─────┴───────────┴────┴───┴──────────┴─────┴───────────┴──────┴────────────┴───────┴───────────┴──────────────┴──────────┴────────┘
Tip

如果你跟着我上一篇的教程进行配置,那么你的目录结构应该长下面这样

text

.
├── init.lua
└── lua
    ├── colorscheme.lua
    ├── keymaps.lua
    ├── lsp.lua
    ├── options.lua
    └── plugins.lua

之前的文章里面我们配置 LSP 的时候借助了 mason.nvim。要增加新的编程语言的 LSP 同样可以用这个工具,步骤如下

  1. 打开 Neovim,输入 :Mason,在 (2) LSP 里面找到 ocaml-lsp,将光标放在该行然后按下 i 就可以安装了,稍候片刻☕️
  2. 打开 LSP 配置文件(我放在了 lsp.lua 文件里面),添加如下的内容

lua

... -- rest of the configurations
lspconfig.ocamllsp.setup({
	on_attach = on_attach,
})
Warning

你可能有自己的配置文件而没有跟着我的上一篇 文章操作,所以在你的配置文件里面加上这一段代码并不保证能够 work。你可以选择浏览完整文件 来理解这段代码的作用,和尝试迁移配置到你的配置文件中

修改完 lsp.lua 文件之后,重启 Neovim 之后就生效了

Warning

rust-analyzer 类似,ocaml-lsp 的 LSP 也只支持在项目里启用,无法在单文件下生效

接下来,可以用 dune 新建项目,尝试写一下 OCaml 的代码,检查 LSP 是否正常工作

ocaml-lsp 本身并不支持 OCaml 代码的格式化,在完成如上的配置之后,如果你尝试格式化,会发现 Neovim 报错:

text

[LSP] Format request failed, no matching language servers.

因此,接下来要讲解的就是如何配置 OCaml 的代码格式化功能。

之前你可能已经注意到,在 Neovim 里面输入 :Mason 的时候可以看到,它不仅支持安装 LSP,还可以安装 Linter、Formatter 等。其中 Formatter 就是用于代码的格式化。OCaml 的 Formmater 是 ocamlformat,首先我们在 Mason 的窗口 (5) Formatter 里找到它,将光标停留在该选项,然后按下 i 安装。等待片刻之后就可以看到安装成功了☕️

ocamlformat 本身只是一个 CLI 工具,没法方便地集成到 Neovim 里面。这里我们可以用 conform.nvim 插件,该插件会对接 ocamlformat 等各种 Formatter

打开 plugins.lua 新增如下 Lua 代码,保存并重启后 lazy.nvim 就会自动安装新插件,稍候片刻☕️

lua

... -- rest of the configurations, omit for simplicity
require("lazy").setup({
    {
        "stevearc/conform.nvim",
        opts = {
            formatters_by_ft = {
                ocaml = { "ocamlformat" },
            },

            formatters = {
                ocamlformat = {
                    command = "ocamlformat",
                    args = {
                        "--if-then-else",
                        "vertical",
                        "--break-cases",
                        "fit-or-vertical",
                        "--type-decl",
                        "sparse",
                        -- $FILENAME - absolute path to the file
                        "$FILENAME",
                    },
                },
            },
        },
    }
    ... -- rest of the configurations, omit for simplicity
})

上面的配置看起来可能有点复杂,但其实意思很直白

  • formatters_by_ft 用来指定对应编程语言的 Formatter,这里是 ocaml
  • 如果想要进一步定制化 Formatter 的功能,可以在 formatters 进行进一步定制
Tip

FAQs

  1. 如何确定编程语言用什么 Formatter?用 :help conform-formatters 就可以看到所有 conform.nvim 支持的 Formatter,在 :Mason(5) Formatter 里面找到并安装
  2. 如何往 conform.nvim 里添加 Formatter?在 formatters_by_ft 里面新增 Entry,格式可以参考上面 ocaml 的设置
  3. 如何确定 Formatter 都有什么具体的配置项来定制化?查看各个工具的官方文档

最后,我们还需要修改 lsp.lua 文件,原本跟代码格式化有关的行长这个样子

lua

... -- rest of the configurations, omit for simplicity
vim.keymap.set("n", "<space>f", function()
    vim.lsp.buf.format({ async = true })
end, bufopts)
... -- rest of the configurations, omit for simplicity

现在因为我们使用 conform.nvim,相当于所有的格式化都交给它来做,因此用的是 conform.nvimformat 函数

lua

... -- rest of the configurations, omit for simplicity
vim.keymap.set("n", "<space>f", function()
    require("conform").format({ async = true, lsp_fallback = true })
end, bufopts)
... -- rest of the configurations, omit for simplicity

这里 lsp_fallback 的意思是如果没有对应的 Formatter 就尝试用 LSP 自带的 Formatter(若有)

完成文件编辑之后,重启 Neovim 就生效了

现在对前文的内容进行总结,如果你跟我一样也使用 mason.nvim, mason-lspconfig, conform.nvim 这些插件。那么在添加一门新的编程语言的时候,你可以这样配置:

  1. 首先在你的机器上配置好该编程语言的开发环境,查询官方文档即可
  2. 打开 Neovim,输入 :Mason 安装对应的 LSP,先检查官方文档看有没有自带 Formatter,如果没有就用 :Mason 安装对应的 Formatter
  3. 打开 lsp.lua 文件配置 LSP
  4. 如果是自己单独下载的 Formatter,还需要在 conform.nvim 的配置项里面进行设置

对于本文没有说清楚的地方,你可以查看我的 dotfiles 配置 来了解更多的信息~~