nvim-jdtls简介
Neovim for eclipse.jdt.ls 中内置 LSP 支持的扩展。
Neovim (>= 0.6.0) 中对eclipse.jdt.ls的内置语言服务器协议支持的扩展。
该项目遵循KISS 原则,面向对 Neovim、Java 及其构建工具 Maven 或 Gradle 有一定经验的用户,他们更喜欢将配置作为代码而不是 GUI 配置。易于使用不是主要优先事项。
纯内置LSP实现的方式可以参考我之前写的文章:
《neovim内置lsp实现Java语言补全》
https://blog.csdn.net/lxyoucan/article/details/123443937
插件安装
需要 Neovim (>= 0.6.0)
nvim-jdtls 是一个插件。像安装任何其他 Vim 插件一样安装它:
如果使用vim-plug:Plug 'mfussenegger/nvim-jdtls'
如果使用packer.nvim:use 'mfussenegger/nvim-jdtls'
项目主页:
https://github.com/mfussenegger/nvim-jdtls
JDK版本选择
这里有一个小坑,就是JDK的版本要选择JDK11及以上版本才行。因为就目前来看,JDK8使用的概率还是非常高的。
如果你使用JDK8,使用java文件会报如下的错误:
Client 1 quit with exit code 1 and signal 0
推荐使用JDK11,因为我实测JDK11是正常使用的,其他版本的JDK我没有一一测试。
我的版本信息如下:
java -version
java version "11.0.10" 2021-01-19 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.10+8-LTS-162)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.10+8-LTS-162, mixed mode)
JDK版本选择
这里有一个小坑,就是JDK的版本要选择JDK11及以上版本才行。因为就目前来看,JDK8使用的概率还是非常高的。
如果你使用JDK8,使用java文件会报如下的错误:
Client 1 quit with exit code 1 and signal 0
推荐使用JDK11,因为我实测JDK11是正常使用的,其他版本的JDK我没有一一测试。
我的版本信息如下:
java -version
java version "11.0.10" 2021-01-19 LTS
Java(TM) SE Runtime Environment 18.9 (build 11.0.10+8-LTS-162)
Java HotSpot(TM) 64-Bit Server VM 18.9 (build 11.0.10+8-LTS-162, mixed mode)
下载解压jdt-language-server
下载jdt-language-server
不同版本下载导航
https://download.eclipse.org/jdtls/milestones/?d
我最终下载的版本是:
https://download.eclipse.org/jdtls/milestones/1.9.0/jdt-language-server-1.9.0-202203031534.tar.gz
以下我的路径是个人喜好,可以根据自己的实际情况修改保存路径:
#创建workspace目录,后面会用到
mkdir -p ~/.local/share/nvim/lsp/jdt-language-server/workspace/folder
cd ~/.local/share/nvim/lsp/jdt-language-server
# 下载jdt-language-server-xxxxx.tar.gz
wget https://download.eclipse.org/jdtls/milestones/1.9.0/jdt-language-server-1.9.0-202203031534.tar.gz
# 解压
tar -zxvf jdt-language-server-1.9.0-202203031534.tar.gz
我的目录结构如下图所示
配置
要配置 nvim-jdtls, 添加以下内容 ftplugin/java.lua 在 neovim 配置基目录 (示例. ~/.config/nvim/ftplugin/java.lua
, 详情见 :help base-directory
)。
nvim ~/.config/nvim/ftplugin/java.lua
编辑文件,并且我的内容如下,请根据自己的实现情况调整。
主要就是文件的路径调整。
local config = {
cmd = {
"java",
"-Declipse.application=org.eclipse.jdt.ls.core.id1",
"-Dosgi.bundles.defaultStartLevel=4",
"-Declipse.product=org.eclipse.jdt.ls.core.product",
"-Dlog.protocol=true",
"-Dlog.level=ALL",
"-Xms1g",
"--add-modules=ALL-SYSTEM",
"--add-opens",
"java.base/java.util=ALL-UNNAMED",
"--add-opens",
"java.base/java.lang=ALL-UNNAMED",
"-jar",
"/home/vnc/.local/share/nvim/lsp/jdt-language-server/plugins/org.eclipse.equinox.launcher_1.6.400.v20210924-0641.jar",
"-configuration",
"/home/vnc/.local/share/nvim/lsp/jdt-language-server/config_linux",
"-data",
"/home/vnc/.local/share/nvim/lsp/jdt-language-server/workspace/folder"
},
root_dir = require("jdtls.setup").find_root({".git", "mvnw", "gradlew"}),
settings = {
java = {}
},
init_options = {
bundles = {}
}
}
require("jdtls").start_or_attach(config)
小坑提醒:
org.eclipse.equinox.launcher_1.6.400.v20210924-0641
.jar这个jar包的小版本号一直在变,不要忘记调整了,我之前就因为这个版本号浪费了好久排错。
为了方便大家理解每行配置的意思,我把配置做了注释,主要源于官方文档的翻译。
当心💀,它表示你必须调整一些东西。
-- 查看 `:help vim.lsp.start_client` 了解支持的 `config` 选项的概述。
local config = {
-- 启动语言服务器的命令
-- See: https://github.com/eclipse/eclipse.jdt.ls#running-from-the-command-line
cmd = {
-- 💀
'java', -- 或者绝对路径 '/path/to/java11_or_newer/bin/java'
-- 取决于 `java` 是否在您的 $PATH 环境变量中以及它是否指向正确的版本。
'-Declipse.application=org.eclipse.jdt.ls.core.id1',
'-Dosgi.bundles.defaultStartLevel=4',
'-Declipse.product=org.eclipse.jdt.ls.core.product',
'-Dlog.protocol=true',
'-Dlog.level=ALL',
'-Xms1g',
'--add-modules=ALL-SYSTEM',
'--add-opens', 'java.base/java.util=ALL-UNNAMED',
'--add-opens', 'java.base/java.lang=ALL-UNNAMED',
-- 💀
--'-jar', '/path/to/jdtls_install_location/plugins/org.eclipse.equinox.launcher_VERSION_NUMBER.jar',
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^
-- 必须指向 修改这里为
-- eclipse.jdt.ls 安装路径 实际版本
'-jar', '/home/vnc/.local/share/nvim/lsp/jdt-language-server/plugins/org.eclipse.equinox.launcher_1.6.400.v20210924-0641.jar',
-- 💀
--'-configuration', '/path/to/jdtls_install_location/config_SYSTEM',
-- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^
-- Must point to the Change to one of `linux`, `win` or `mac`
-- eclipse.jdt.ls installation Depending on your system.
--这里是我们上面解压的jdt-language-server绝对路径,我这里是linux,请根据系统类型调整
'-configuration', '/home/vnc/.local/share/nvim/lsp/jdt-language-server/config_linux',
-- 💀
-- 请参阅 README 中的“数据目录配置”部分
'-data', '/home/vnc/.local/share/nvim/lsp/jdt-language-server/workspace/folder'
},
-- 💀
-- 这是默认设置,如果未提供,您可以将其删除。 或根据需要进行调整。
-- 每个唯一的 root_dir 将启动一个专用的 LSP 服务器和客户端
root_dir = require('jdtls.setup').find_root({'.git', 'mvnw', 'gradlew'}),
-- 这里可以配置eclipse.jdt.ls具体设置
-- See https://github.com/eclipse/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request
-- 选项列表
settings = {
java = {
}
},
-- Language server `initializationOptions`
-- 您需要使用 jar 文件的路径扩展 `bundles`
-- 如果你想使用额外的 eclipse.jdt.ls 插件。
--
-- See https://github.com/mfussenegger/nvim-jdtls#java-debug-installation
--
-- 如果您不打算使用调试器或其他 eclipse.jdt.ls 插件,您可以删除它
init_options = {
bundles = {}
},
}
-- 这将启动一个新的客户端和服务器,
-- 或根据 `root_dir` 附加到现有的客户端和服务器。
require('jdtls').start_or_attach(config)
nvim-lspconfig 和 nvim-jdtls 的区别
nvim-lspconfig 和 nvim-jdtls 都使用 neovim 内置的客户端:
┌────────────┐ ┌────────────────┐
│ nvim-jdtls │ │ nvim-lspconfig │
└────────────┘ └────────────────┘
| |
start_or_attach nvim_lsp.jdtls.setup
│ |
│ setup java filetype hook
│ ┌─────────┐ │
└───►│ vim.lsp │◄─────────────────┘
└─────────┘
.start_client
.buf_attach_client
两者之间的一些区别:
- lspconfig自身
setup
创建一个java
filetype
钩子,并cmd
为config
. nvim-jdtls
将选择何时调用start_or_attach
给用户。nvim-jdtls
添加一些逻辑来处理jdt://
URI。这些是从第三方库或 JDK 加载源代码所必需的。nvim-jdtls
添加一些额外的处理程序并设置相同的额外功能以启用所有扩展。
您可以使用其中任何一个来启动eclipse.jdt.ls
客户端,但建议使用start_or_attachfrom 方法,nvim-jdtls因为它配置了额外的功能以及jdt://URI 处理。
对于 java,您不能同时使用两者。您最终会得到两个客户端和两个语言服务器实例。
用法
nvim-jdtls扩展了 Neovim 中内置 LSP 支持的功能,因此中提到的所有功能:help lsp都可以使用。
nvim-jdtls为您想要创建额外映射的人提供了一些附加功能:
nnoremap <A-o> <Cmd>lua require'jdtls'.organize_imports()<CR>
nnoremap crv <Cmd>lua require('jdtls').extract_variable()<CR>
vnoremap crv <Esc><Cmd>lua require('jdtls').extract_variable(true)<CR>
nnoremap crc <Cmd>lua require('jdtls').extract_constant()<CR>
vnoremap crc <Esc><Cmd>lua require('jdtls').extract_constant(true)<CR>
vnoremap crm <Esc><Cmd>lua require('jdtls').extract_method(true)<CR>
-- If using nvim-dap
-- This requires java-debug and vscode-java-test bundles, see install steps in this README further below.
nnoremap <leader>df <Cmd>lua require'jdtls'.test_class()<CR>
nnoremap <leader>dn <Cmd>lua require'jdtls'.test_nearest_method()<CR>
Lombok支持
使用过Spring Boot开发的工程师,对Lombok应该不陌生吧。这个小插件可以让我们的代码变的简洁。用了以后就回不去的插件。在IDEA中使用都是正常的,用vim开发显示不正常就很难受了。
如下图所示:
cd /home/vnc/.local/share/nvim/lsp/jdt-language-server
#下载lombok.jar
wget https://projectlombok.org/downloads/lombok.jar
最终我们得到的路径是/home/vnc/.local/share/nvim/lsp/jdt-language-server/lombok.jar
我们在-jar参数前面加入以下几行配置:
"-javaagent",
"/home/vnc/.local/share/nvim/lsp/jdt-language-server/lombok.jar",
"-Xbootclasspath/a",
"/home/vnc/.local/share/nvim/lsp/jdt-language-server/lombok.jar",
如下加粗部分
“–add-opens”,
“java.base/java.util=ALL-UNNAMED”,
“–add-opens”,
“java.base/java.lang=ALL-UNNAMED”,
"-javaagent:/home/vnc/.local/share/nvim/lsp/jdt-language-server/lombok.jar",
“-Xbootclasspath/a:/home/vnc/.local/share/nvim/lsp/jdt-language-server/lombok.jar”,
“-jar”,
“/home/vnc/.local/share/nvim/lsp/jdt-language-server/plugins/org.eclipse.equinox.launcher_1.6.400.v20210924-0641.jar”,
一定要在-jar前面加,不然会出错。
参考:
https://github.com/mfussenegger/nvim-jdtls/issues/28
完成以后不报错了,代码简洁。真舒服!!!
我的配置分享
每个人的使用习惯都不相同,我把常用的快捷键进行了映射,供大家参考。
<space>rn
变量重命名<leader>f
代码格式化- 保存自动格式化
<A-o>
自动导入全部缺失的包
等等。
我的配置文件:nvim ~/.config/nvim/ftplugin/java.lua
全部内容如下,仅大家参考:
local config = {
cmd = {
"java",
"-Declipse.application=org.eclipse.jdt.ls.core.id1",
"-Dosgi.bundles.defaultStartLevel=4",
"-Declipse.product=org.eclipse.jdt.ls.core.product",
"-Dlog.protocol=true",
"-Dlog.level=ALL",
"-Xms1g",
"--add-modules=ALL-SYSTEM",
"--add-opens",
"java.base/java.util=ALL-UNNAMED",
"--add-opens",
"java.base/java.lang=ALL-UNNAMED",
--增加lombok插件支持,getter setter good bye
"-javaagent:/home/vnc/.local/share/nvim/lsp/jdt-language-server/lombok.jar",
"-Xbootclasspath/a:/home/vnc/.local/share/nvim/lsp/jdt-language-server/lombok.jar",
"-jar",
"/home/vnc/.local/share/nvim/lsp/jdt-language-server/plugins/org.eclipse.equinox.launcher_1.6.400.v20210924-0641.jar",
"-configuration",
"/home/vnc/.local/share/nvim/lsp/jdt-language-server/config_linux",
"-data",
"/home/vnc/.local/share/nvim/lsp/jdt-language-server/workspace/folder"
},
root_dir = require("jdtls.setup").find_root({".git", "mvnw", "gradlew"}),
settings = {
java = {}
},
init_options = {
bundles = {}
}
}
require("jdtls").start_or_attach(config)
local current_buff = vim.api.nvim_get_current_buf
-- 在语言服务器附加到当前缓冲区之后
-- 使用 on_attach 函数仅映射以下键
local java_on_attach = function(client, bufnr)
local function buf_set_keymap(...)
vim.api.nvim_buf_set_keymap(bufnr, ...)
end
local function buf_set_option(...)
vim.api.nvim_buf_set_option(bufnr, ...)
end
--Enable completion triggered by <c-x><c-o>
buf_set_option("omnifunc", "v:lua.vim.lsp.omnifunc")
-- Mappings.
local opts = {noremap = true, silent = true}
-- See `:help vim.lsp.*` for documentation on any of the below functions
buf_set_keymap("n", "gD", "<Cmd>lua vim.lsp.buf.declaration()<CR>", opts)
buf_set_keymap("n", "gd", "<Cmd>lua vim.lsp.buf.definition()<CR>", opts)
--buf_set_keymap('n', 'K', '<Cmd>lua vim.lsp.buf.hover()<CR>', opts)
buf_set_keymap("n", "gi", "<cmd>lua vim.lsp.buf.implementation()<CR>", opts)
--buf_set_keymap('i', '<C-k>', '<cmd>lua vim.lsp.buf.signature_help()<CR>', opts)
buf_set_keymap("n", "<space>wa", "<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>", opts)
buf_set_keymap("n", "<space>wr", "<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>", opts)
buf_set_keymap("n", "<space>wl", "<cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>", opts)
buf_set_keymap("n", "<space>D", "<cmd>lua vim.lsp.buf.type_definition()<CR>", opts)
--重命名
buf_set_keymap("n", "<space>rn", "<cmd>lua vim.lsp.buf.rename()<CR>", opts)
--智能提醒,比如:自动导包 已经用lspsaga里的功能替换了
buf_set_keymap("n", "<space>ca", "<cmd>lua vim.lsp.buf.code_action()<CR>", opts)
buf_set_keymap("n", "gr", "<cmd>lua vim.lsp.buf.references()<CR>", opts)
buf_set_keymap("n", "<space>e", "<cmd>lua vim.lsp.diagnostic.show_line_diagnostics()<CR>", opts)
--buf_set_keymap('n', '<C-j>', '<cmd>lua vim.lsp.diagnostic.goto_prev()<CR>', opts)
buf_set_keymap("n", "<S-C-j>", "<cmd>lua vim.lsp.diagnostic.goto_next()<CR>", opts)
buf_set_keymap("n", "<space>q", "<cmd>lua vim.lsp.diagnostic.set_loclist()<CR>", opts)
--代码格式化
buf_set_keymap("n", "<leader>f", "<cmd>lua vim.lsp.buf.formatting()<CR>", opts)
buf_set_keymap("n", "<leader>l", "<cmd>lua vim.lsp.buf.formatting()<CR>", opts)
buf_set_keymap("n", "<leader>l", "<cmd>lua vim.lsp.buf.formatting()<CR>", opts)
--自动导入全部缺失的包,自动删除多余的未用到的包
buf_set_keymap("n", "<A-o>", "<cmd>lua require'jdtls'.organize_imports()<CR>", opts)
--引入局部变量的函数 function to introduce a local variable
buf_set_keymap("n", "crv", "<cmd>lua require('jdtls').extract_variable()<CR>", opts)
buf_set_keymap("v", "crv", "<Esc><Cmd>lua require('jdtls').extract_variable(true)<CR>", opts)
--function to extract a constant
buf_set_keymap("n", "crc", "<Cmd>lua require('jdtls').extract_constant()<CR>", opts)
buf_set_keymap("v", "crc", "<Esc><Cmd>lua require('jdtls').extract_constant(true)<CR>", opts)
--将一段代码提取成一个额外的函数function to extract a block of code into a method
buf_set_keymap("v", "crm", "<Esc><Cmd>lua require('jdtls').extract_method(true)<CR>", opts)
-- 代码保存自动格式化formatting
vim.api.nvim_command [[autocmd BufWritePre <buffer> lua vim.lsp.buf.formatting_seq_sync()]]
end
java_on_attach(nil, current_buff)