OCaml 的 Neovim 配置方案
更新说明:
- 2024-09-14. 部分 LSP 本身包含 Formatter,就不需要自己下载 Formatter 再用
null-ls
处理。通过修改vim.lsp.buf.format
的filter
参数就可以让这两种情况并存
前言
在上一篇博客中,我详细介绍了如何从零开始配置 Neovim,让它更接近 IDE。但我没有讲到的是:
- 有的 LSP 本身不支持代码格式化,要如何配置 Neovim 让它可以用第三方的代码格式化工具?
- 新增一门编程语言支持的时候,要怎么修改配置文件?
最近在学习函数式编程需要在我的 Mac 上配置 OCaml,正好以它为例,回答以上两个问题
安装 OCaml
在 Mac 上安装 OCaml 用 Homebrew 比较方便
$ brew install opam
按照 Homebrew 的输出,我们还需要进行额外的配置,需要修改 ~/.zshrc
(如果你用的是 Zsh)或者是 ~/.bashrc
(如果你用的是 Bash)
$ opam init
# 按照提示按下 y 确认即可
接下来让我们上一步的配置生效
$ source ~/.zshrc
# $ source ~/.bashrc # if you use bash
最后,我们可以通过如下的命令检查是否安装成功
$ opam switch
输出应该跟下面的差不多
# switch compiler description
-> 5.0.0 ocaml-base-compiler.5.0.0 5.0.0
default ocaml.4.14.0 default
接下来需要安装一些包,因为网络原因访问可能会比较慢。加速可以通过换源实现,这里用的是上海交通大学的源,配置命令为
$ opam repo \
set-url default \
https://mirrors.sjtug.sjtu.edu.cn/git/opam-repository.git \
--all --set-default
等待该命令执行完就完成了换源
类似 Python 有 REPL,如 IPython。在 OCaml 里也有类似的工具,叫做 utop
$ opam install utop
接下来尝试使用 utop 看是否安装成功
$ utop
如果一切正常,你应该可以看到如下的输出
─────────────────────────────────────────────────────────┬─────────────────────────────────────────────────────────────┬──────────────────────────────────────────────────────────
│ 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_instrument│Alias_analysis│Allocated_const│Annot│Applicative│Arch│Arg│Arg_helper│Array│ArrayLabels│Asmgen│Asmlibrarian│Asmlink│Asmpackager│Assert_failure│Ast_helper│Ast_inva│
└──────────────┴──────────────┴───────────────┴─────┴───────────┴────┴───┴──────────┴─────┴───────────┴──────┴────────────┴───────┴───────────┴──────────────┴──────────┴────────┘
配置 OCaml LSP
如果你跟着我上一篇的教程进行配置,那么你的目录结构应该长下面这样
nvim
├── init.lua
└── lua
├── colorscheme.lua
├── config
│ └── nvim-cmp.lua
├── keymaps.lua
├── lsp.lua
├── options.lua
└── plugins.lua
之前的文章里面我们配置 LSP 的时候借助了 mason.nvim。要增加新的编程语言的 LSP 同样可以用这个工具,步骤如下
- 打开 Neovim,输入
:Mason
,在(2) LSP
里面找到ocaml-lsp
,将光标放在该行然后按下i
就可以安装了,稍候片刻☕️ - 打开 LSP 配置文件(我放在了
lsp.lua
文件里面),添加如下的内容
... -- rest of the configurations
lspconfig.ocamllsp.setup({
on_attach = on_attach,
})
修改完 lsp.lua
文件之后,重启 Neovim 之后就生效了
ocaml-lsp
的 LSP 也只支持在项目里启用,无法在单文件下生效接下来,可以用 dune 新建项目,尝试写一下 OCaml 的代码,检查 LSP 是否正常工作
配置 OCaml Formatter
ocaml-lsp
本身并不支持 OCaml 代码的格式化,在完成如上的配置之后,如果你尝试格式化,会发现 Neovim 报错:
[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 里面。好在还有 none-ls 插件,它可以将 Linter、Formatter 或者命令行等工具暴露成 LSP 给 Neovim
为了进一步减轻配置的负担,我们可以利用 mason-null-ls.nvim 插件,从名字可以看出来,它是 mason.nvim 的开发者开发的。打开 plugins.lua
新增如下 Lua 代码,保存并重启后 lazy.nvim
就会自动安装新插件,稍候片刻☕️
... -- rest of the configurations
require("lazy").setup({
-- Add hooks to LSP to support Linter && Formatter
{
"jay-babu/mason-null-ls.nvim",
event = { "BufReadPre", "BufNewFile" },
dependencies = {
"williamboman/mason.nvim",
"nvimtools/none-ls.nvim",
},
config = function()
-- Note:
-- the default search path for `require` is ~/.config/nvim/lua
-- use a `.` as a path seperator
-- the suffix `.lua` is not needed
require("config.mason-null-ls")
end,
},
... -- rest of the configurations
})
然后在 config
文件夹里新建 mason-null-ls.lua
文件,内容如下
local mason_ok, mason = pcall(require, "mason")
if not mason_ok then
return
end
local null_ls_ok, null_ls = pcall(require, "null-ls")
if not null_ls_ok then
return
end
local mason_null_ls_ok, mason_null_ls = pcall(require, "mason-null-ls")
if not mason_null_ls_ok then
return
end
mason.setup()
mason_null_ls.setup({
-- A list of sources to install if they're not already installed.
-- This setting has no relation with the `automatic_installation` setting.
ensure_installed = {},
-- Run `require("null-ls").setup`.
-- Will automatically install masons tools based on selected sources in `null-ls`.
-- Can also be an exclusion list.
-- Example: `automatic_installation = { exclude = { "rust_analyzer", "solargraph" } }`
automatic_installation = false,
-- Sources found installed in mason will automatically be setup for null-ls.
automatic_setup = true,
handlers = {
-- Hint: see https://github.com/nvimtools/none-ls.nvim/blob/main/doc/BUILTIN_CONFIG.md
-- to see what sources are available
-- Hint: see https://github.com/jose-elias-alvarez/null-ls.nvim/blob/main/doc/BUILTIN_CONFIG.md
-- to check what we can configure for each source
ocamlformat = function(source_name, methods)
null_ls.register(null_ls.builtins.formatting.ocamlformat.with({
-- Add more arguments to a source's defaults
-- Default: { "--enable-outside-detected-project", "--name", "$FILENAME", "-" }
-- type `ocamlformat --help` in your terminal to check more args
extra_args = { "--if-then-else", "vertical" },
}))
end,
},
})
null_ls.setup()
当你开启 automatic_setup = true
的时候,mason-null-ls 会自动帮你进行配置安装了的 Formatter。如果想要修改默认配置,就需要在 handlers
里面进行修改
FAQs:
- 如何找到对应 Formatter 的默认配置?看 这里
- 如何配置
handlers
?看 这里 - 如何知道对应的 Formatter 其他可选配置选项?看对应 LSP 的官方 GitHub 仓库。比如 ocamlformat 就在文档说了,在命令行输入
ocamlformat --help
就可以看到各种配置项
最后,我们还需要修改 lsp.lua
文件,原本跟代码格式化有关的行长这个样子
...
vim.keymap.set("n", "<space>f", function()
vim.lsp.buf.format({ async = true })
end, bufopts)
...
现在因为我们使用 none-ls,相当于所有的格式化都交给它来做,因此,需要新增 filter
参数
... -- rest of the configurations
vim.keymap.set("n", "<space>f", function()
vim.lsp.buf.format({
async = true,
-- Predicate used to filter clients. Receives a client as
-- argument and must return a boolean. Clients matching the
-- predicate are included.
filter = function(client)
return client.name == "null-ls"
end,
})
end, bufopts)
... -- rest of the configurations
完成文件编辑之后,重启 Neovim 就生效了
那如果一个 LSP 本身就包含了 Formatter 呢?比如 Haskell-language-server
,那么我们就不需要让 null-ls
进行格式化,通过修改 filter
参数就可以解决,像下面这样
... -- rest of the configurations
vim.keymap.set("n", "<space>f", function()
vim.lsp.buf.format({
async = true,
-- Predicate used to filter clients. Receives a client as
-- argument and must return a boolean. Clients matching the
-- predicate are included.
filter = function(client)
-- hls stands for Haskell-language-server
return client.name == "null-ls" or client.name == "hls"
end,
})
end, bufopts)
... -- rest of the configurations
总结
现在对前文的内容进行总结,如果你跟我一样也使用 mason.nvim, mason-lspconfig, none-ls.nvim 和 mason-null-ls.nvim 这些插件。那么在添加一门新的编程语言的时候,你可以这样配置:
- 首先在你的机器上配置好该编程语言的开发环境,查询官方文档即可
- 打开 Neovim,输入
:Mason
安装对应的 LSP,先检查官方文档看有没有自带 Formatter,如果没有就用:Mason
安装对应的 Formatter - 打开
lsp.lua
文件配置 LSP - 如果是自己单独下载的 Formatter,还需要打开
mason-null-ls.lua
文件修改handlers
参数
对于本文没有说清楚的地方,你可以查看我的 dotfiles 配置 来了解更多的信息~~