Extensions for the built-in Language Server Protocol support in Neovim (>= 0.5) for eclipse.jdt.ls.
-
organize_imports
function to organize imports -
extract_variable
function to introduce a local variable -
extract_constant
function to extract a constant -
extract_method
function to extract a block of code into a method - Open class file contents
- Code action extensions
- Generate constructors
- Generate
toString
function -
hashCode
andequals
generation. - Extract variables or methods
- Generate delegate methods
- Move package, instance method, static method or type
-
javap
command to show bytecode of current file -
jol
command to show memory usage of current file (jol_path
must be set) -
jshell
command to open up jshell with classpath from project set - Debugger support via nvim-dap
Take a look at a demo to see some of the functionality in action.
- Requires Neovim (>= 0.5)
- nvim-jdtls is a plugin. Install it like any other Vim plugin:
- If using vim-plug:
Plug 'mfussenegger/nvim-jdtls'
- If using packer.nvim:
use 'mfussenegger/nvim-jdtls'
- If using vim-plug:
Install eclipse.jdt.ls using one of the three options:
- Install eclipse.jdt.ls via your package manager (Assuming a package is available).
- Download a pre-built milestone or snapshot and extract the contents of the package.
- Build eclipse.jdt.ls from source:
- Switch to a folder of your choice.
git clone https://github.com/eclipse/eclipse.jdt.ls.git
cd eclipse.jdt.ls
./mvnw clean install -DskipTests
(SetJAVA_HOME
to Java 11 before you run this)
Create a launch script with the following contents. But don't forget to adapt the paths.
$HOME/dev/eclipse
needs to be changed to the folder where you cloned the repository./usr/lib/jvm/java-11-openjdk/bin/java
needs to be changed to point to your Java installation. At least Java 11 is required to run the language server.
#!/usr/bin/env bash
# NOTE:
# This doesn't work as is on Windows. You'll need to create an equivalent `.bat` file instead
#
# NOTE:
# If you're not using Linux you'll need to adjust the `-configuration` option
# to point to the `config_mac' or `config_win` folders depending on your system.
JAR="$HOME/dev/eclipse/eclipse.jdt.ls/org.eclipse.jdt.ls.product/target/repository/plugins/org.eclipse.equinox.launcher_*.jar"
GRADLE_HOME=$HOME/gradle /usr/lib/jvm/java-11-openjdk/bin/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 \
-Xmx2G \
-jar $(echo "$JAR") \
-configuration "$HOME/dev/eclipse/eclipse.jdt.ls/org.eclipse.jdt.ls.product/target/repository/config_linux" \
-data "${1:-$HOME/workspace}" \
--add-modules=ALL-SYSTEM \
--add-opens java.base/java.util=ALL-UNNAMED \
--add-opens java.base/java.lang=ALL-UNNAMED
The script must be placed in a folder that is part of $PATH
. To verify that
the installation worked, launch it in a shell. You should get the following
output:
Content-Length: 126
{"jsonrpc":"2.0","method":"window/logMessage","params":{"type":3,"message":"Sep 16, 2020, 8:10:53 PM Main thread is waiting"}}
To use nvim-jdtls
, you need to setup a LSP client. In your init.vim
add the
following:
if has('nvim-0.5')
augroup lsp
au!
au FileType java lua require('jdtls').start_or_attach({cmd = {'java-lsp.sh'}})
augroup end
endif
java-lsp.sh
needs to be changed to the name of the shell script created earlier.
The argument passed to start_or_attach
is the same config
mentioned in
:help vim.lsp.start_client
. You may want to configure some settings via
init_options
or settings
. See the eclipse.jdt.ls Wiki for an overview
of available options.
You can also find more complete configuration examples in the Wiki.
For the language server to work correctly it is important that the root_dir
in the config
is set correctly. By default start_or_attach
sets the
root_dir
automatically by looking for marker files relative to each file
you're opening. The markers default to .git
, mvnw
and gradlew
. If no
parent directory contains any of the markers, it will fallback to the current
working directory.
nvim-jdtls
contains a find_root
function which you could use to customize the root_dir
:
-- find_root looks for parent directories relative to the current buffer containing one of the given arguments.
require('jdtls').start_or_attach({cmd = {'java-lsp.sh'}, root_dir = require('jdtls.setup').find_root({'gradle.build', 'pom.xml'})})
eclipse.jdt.ls
stores project specific data within the folder set via the
-data
flag in the java-lsp.sh
script. If you're using eclipse.jdt.ls
with
multiple different projects you should use a dedicated data directory per
project. You can do that by adding a second argument to the cmd
property of
the config
passed to start_or_attach
. An example:
start_or_attach({cmd = {'java-lsp.sh', '/home/user/workspace/' .. vim.fn.fnamemodify(vim.fn.getcwd(), ':p:h:t')}})
Warning: You must NOT execute require'nvim_lsp'.jdtls.setup{}
from
nvim-lspconfig if you use nvim-jdtls
.
You can use nvim-lspconfig
for other language servers without problems.
Tip: You can get a better UI for code-actions and other functions by
overriding the jdtls.ui
picker. See UI Extensions.
nvim-jdtls
extends the capabilities of the built-in LSP support in
Neovim, so all the functions mentioned in :help lsp
will work.
nvim-jdtls
provides some extras, for those you'll want to create additional
mappings:
-- `code_action` is a superset of vim.lsp.buf.code_action and you'll be able to
-- use this mapping also with other language servers
nnoremap <A-CR> <Cmd>lua require('jdtls').code_action()<CR>
vnoremap <A-CR> <Esc><Cmd>lua require('jdtls').code_action(true)<CR>
nnoremap <leader>r <Cmd>lua require('jdtls').code_action(false, 'refactor')<CR>
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>
Some methods are better exposed via commands. As a shortcut you can also call
:lua require('jdtls.setup').add_commands()
to declare these.
It's recommended to call add_commands
within the on_attach
handler that can be set on the config
table which is passed to start_or_attach
.
If you use jdtls together with nvim-dap, call add_commands
after setup_dap
to ensure it includes debugging related commands. (More about this is in the debugger setup section further below)
command! -buffer JdtCompile lua require('jdtls').compile()
command! -buffer JdtUpdateConfig lua require('jdtls').update_project_config()
command! -buffer JdtJol lua require('jdtls').jol()
command! -buffer JdtBytecode lua require('jdtls').javap()
command! -buffer JdtJshell lua require('jdtls').jshell()
nvim-jdtls
provides integration with nvim-dap.
Once setup correctly, it enables the following additional functionality:
- Debug applications via explicit configurations
- Debug automatically discovered main classes
- Debug junit tests. Either whole classes or individual test methods
For 1 & 2 to work, eclipse.jdt.ls needs to load the java-debug extension. For 3 to work, it also needs to load the vscode-java-test extension.
For usage instructions once installed, read the nvim-dap help. Debugging junit test classes or methods will be possible via these two functions:
require'jdtls'.test_class()
require'jdtls'.test_nearest_method()
- Clone java-debug
- Navigate into the cloned repository (
cd java-debug
) - Run
./mvnw clean install
- Extend the
initializationOptions
with which you start eclipse.jdt.ls as follows:
config['init_options'] = {
bundles = {
vim.fn.glob("path/to/java-debug/com.microsoft.java.debug.plugin/target/com.microsoft.java.debug.plugin-*.jar")
};
}
You also need to call require('jdtls').setup_dap()
to have it register a
java
adapter.
To do that, extend the configuration for nvim-jdtls
with:
config['on_attach'] = function(client, bufnr)
-- With `hotcodereplace = 'auto' the debug adapter will try to apply code changes
-- you make during a debug session immediately.
-- Remove the option if you do not want that.
require('jdtls').setup_dap({ hotcodereplace = 'auto' })
end
If you also want to discover main classes and create configuration entries for them, you have to call require('jdtls.dap').setup_dap_main_class_configs()
or use the JdtRefreshDebugConfigs
command which is added as part of add_commands()
which is mentioned in the Usage section.
Note that eclipse.jdt.ls needs to have loaded your project before it can discover all main classes and that may take some time. It is best to trigger this deferred or ad-hoc when first required.
See the nvim-dap Adapter Installation Wiki for example configurations in case you're not going to use the main-class discovery functionality of nvim-jdtls.
To be able to debug junit tests, it is necessary to install the bundles from vscode-java-test:
- Clone the repository
- Navigate into the folder (
cd vscode-java-test
) - Run
npm install
- Run
npm run build-plugin
- Extend the bundles in the nvim-jdtls config:
-- This bundles definition is the same as in the previous section (java-debug installation)
local bundles = {
vim.fn.glob("path/to/java-debug/com.microsoft.java.debug.plugin/target/com.microsoft.java.debug.plugin-*.jar"),
};
-- This is the new part
vim.list_extend(bundles, vim.split(vim.fn.glob("/path/to/microsoft/vscode-java-test/server/*.jar"), "\n"))
config['init_options'] = {
bundles = bundles;
}
This can have two reasons:
- The client and server aren't starting up correctly.
You can check if the client is running with :lua print(vim.inspect(vim.lsp.buf_get_clients()))
, it should output a lot of information.
If it doesn't, verify:
- That the language server can be started standalone. (Run the
java-lsp.sh
in a terminal) - That there are no configuration errors. (Run
:set ft=java
and:messages
after opening a Java file) - Check the log files (
:lua print(vim.fn.stdpath('cache'))
lists the path, there should be alsp.log
)
- Eclipse.jdt.ls can't compile your project or it cannot load your project and resolve the class paths.
-
Run
:JdtCompile
for incremental compilation or:JdtCompile full
for full compilation. If there are any errors in the project, it will open the quickfix list with the errors. -
Check the log files (
:lua print(vim.fn.stdpath('cache'))
lists the path, there should be alsp.log
) -
If there is nothing, try changing the log level. See
:help vim.lsp.set_log_level()
Completion requests can be quite expensive on big projects. If you're using some kind of auto-completion plugin that triggers completion requests automatically, consider deactivating it or tuning it so it is less aggressive. Triggering a completion request on each typed character is likely overloading eclipse.jdt.ls.
You can try running :JdtUpdateConfig
to refresh the configuration. If that
doesn't work you'll need to restart the language server.
The language server has its own mental model of which files exists based on what the client tells it. If you modify files outside of neovim, then the language server won't be notified of the changes. This can happen for example if you switch to a different branch with git.
If the language server doesn't get a notification about a new file, you might get errors, telling you that a class cannot be resolved. If that is the case, open the file and save it. Then the language server will be notified about the new file and it should start to recognize the classes within the file.
Try wiping your workspace folder and restart Neovim and the language server.
(the workspace folder is the path you used as argument to -data
in your
java-lsp.sh
).