Neovim + Java LSP 在 Play Framework sbt 项目中的缺失指南
I’m ready to translate the article for you, but I need the full text of the post (the content after the source line) in order to do so. Could you please paste the article’s body here? Once I have it, I’ll provide a Simplified‑Chinese translation while keeping the source link, formatting, markdown, and code blocks exactly as they are.
简短回答
sbt-eclipse 生成 Eclipse 项目文件,JDTLS(Eclipse 的 Java 语言服务器)会读取这些文件。它们就是桥梁,其他都是配置细节。
如果你看到这里,可能已经尝试过 Metals,遇到瓶颈,搜索后失望而归。下面给出实际可行的配置方案。
项目背景
- Play Framework 3.x,Java(非 Scala)
- sbt 包含 7 个子模块,约 3 000 个源文件
- 大量代码生成(OpenAPI、Avro、WSDL)
- 团队使用 IntelliJ;你使用 Neovim
Metals 原生支持 sbt,但其 Java 支持极少(没有补全、悬停或组织导入)。一位 Metals 维护者甚至说:
“我无法想象有人会在完整的 Java 项目上使用 Metals。”
JDTLS 完整支持 Java,但只识别 Maven、Gradle 和 Eclipse 项目格式——它不认识 build.sbt。解决办法是让 sbt 生成 JDTLS 所需的文件:.classpath 和 .project。
我通过艰难经历学到的三件事
| 坑点 | 原因 |
|---|---|
不要使用 ThisBuild / 范围 | sbt‑eclipse 在 project 级别读取键,而不是 ThisBuild。放在 ThisBuild 下的设置会被静默忽略。 |
ManagedSrc 至关重要 | 如果没有它,生成的源代码(如 Play 路由、OpenAPI 代码生成、Avro)不会出现在 .classpath 中。仅有 ManagedClasses 和 ManagedResources 仍不足。 |
preTasks := Seq(Compile / compile) | 代码生成必须在创建 Eclipse 文件 之前 运行;否则受管的源目录尚未在磁盘上存在。 |
sbt‑eclipse 配置
全局添加插件
// ~/.sbt/1.0/plugins/plugins.sbt
addSbtPlugin("com.github.sbt" % "sbt-eclipse" % "6.2.0")配置插件
// ~/.sbt/1.0/global.sbt
import com.typesafe.sbteclipse.core.EclipsePlugin.EclipseKeys
EclipseKeys.projectFlavor := EclipseProjectFlavor.Java
EclipseKeys.skipParents := false
EclipseKeys.withSource := true
EclipseKeys.preTasks := Seq(Compile / compile)
EclipseKeys.createSrc := EclipseCreateSrc.ValueSet(
EclipseCreateSrc.Unmanaged,
EclipseCreateSrc.Source,
EclipseCreateSrc.Resource,
EclipseCreateSrc.ManagedSrc,
EclipseCreateSrc.ManagedClasses,
EclipseCreateSrc.ManagedResources
)生成 Eclipse 文件
sbt eclipse此命令首先编译项目(触发代码生成),随后在根目录及每个子模块中创建 .classpath 和 .project。
将生成的文件添加到 .gitignore
.classpath
.project
.settings/通过 Mason 安装 JDTLS
:MasonInstall jdtlsNeovim 的根目录检测配置
在一个多模块的 sbt 项目中,每个子模块都有自己的 build.sbt 和 .project。如果这些文件出现在 root_markers 中,JDTLS 会把该子模块当作工作区根目录,从而导致跨模块导入失效。使用 .git 作为主要锚点:
vim.lsp.config("jdtls", {
root_markers = {
{ "mvnw", "gradlew", "settings.gradle", "settings.gradle.kts", ".git" },
{ "pom.xml", "build.gradle", "build.gradle.kts", "build.xml" },
},
})
vim.lsp.enable("jdtls")注意: 嵌套数组需要 Neovim 0.11.3+。匹配到的第一个分组将被采用。
如果你更改了根目录检测方式或模块未被发现,请清除 JDTLS 的缓存:
rm -rf ~/.cache/nvim/jdtls/workspace/然后重新启动 Neovim;JDTLS 将从头重新导入所有内容。
在 Neovim 中的完整 Java IDE 功能
- 代码补全
- 跳转到定义(跨模块)
- 查找引用
- 诊断
- 整理导入
- 重命名重构
运行/调试 Play 仍然通过终端中的 sbt (sbt run) 完成,因为 Play 的开发模式不使用标准的 main 方法。
类路径更新 – 在 build.sbt 中更改依赖后,重新运行 sbt eclipse 并重启 JDTLS。实际上,这种情况每月只会发生几次。日常编码、生成代码的更改以及切换分支(依赖相同)都不需要额外操作。
资源与工具
- sbt‑eclipse – 使此成为可能的桥梁
- nvim‑lspconfig – Neovim 的 JDTLS 配置
- Mason.nvim – LSP 服务器安装器
- nvim‑metals#503 – 讨论确认 Metals 在此用例下不可用
- metals#1815 – Metals 团队对仅 Java 项目支持的立场