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 by following their Installation instructions.
To use nvim-jdtls
, you need to setup a LSP client. In a ftplugin/java.lua
file, add the following:
local config = {
-- The command that starts the language server
cmd = {
'/path/to/java',
'-Dosgi.bundles.defaultStartLevel=4',
-- ADD REMAINING OPTIONS FROM https://github.com/eclipse/eclipse.jdt.ls#running-from-the-command-line !
},
-- This is the default if not provided, you can remove it. Or adjust as needed.
-- One dedicated LSP server & client will be started per unique root_dir
root_dir = require('jdtls.setup').find_root({'.git', 'mvnw', 'gradlew'})
}
require('jdtls').start_or_attach(config)
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.
start_or_attach
needs to run each time you open a java
file or buffer.
eclipse.jdt.ls
stores project specific data within the folder set via the
-data
flag. If you're using eclipse.jdt.ls
with multiple different projects
you must use a dedicated data directory per project.
An example how you could accomplish that is to infer the workspace directory name from the current working directory.
-- If you started neovim within `~/dev/xy/project-1` this would resolve to `project-1`
local workspace_dir = vim.fn.fnamemodify(vim.fn.getcwd(), ':p:h:t')
-- adjust the `cmd` property of the `config`:
local config = {
cmd = {
...,
-- `..` is string concattenation in Lua
'-data', '/path/to/workspace-root/' .. workspace_dir,
...,
}
}
Be aware that ...
is not valid Lua in this context. It is meant as
placeholder for the other options from the Configuration
section above.)
Both nvim-lspconfig and nvim-jdtls use the client built into neovim:
┌────────────┐ ┌────────────────┐
│ nvim-jdtls │ │ nvim-lspconfig │
└────────────┘ └────────────────┘
| |
start_or_attach nvim_lsp.jdtls.setup
│ |
│ setup java filetype hook
│ ┌─────────┐ │
└───►│ vim.lsp │◄─────────────────┘
└─────────┘
.start_client
.buf_attach_client
Some differences between the two:
- The
setup
of lspconfig creates ajava
filetype hook itself and provides some defaults for thecmd
of theconfig
. nvim-jdtls
delegates the choice when to callstart_or_attach
to the user.nvim-jdtls
adds some logic to handlejdt://
URIs. These are necessary to load source code from third party libraries or the JDK.nvim-jdtls
adds some additional handlers and sets same extra capabilities to enable all the extensions.
You could use either to start the eclipse.jdt.ls
client, but it is
recommended to use the start_or_attach
method from nvim-jdtls
because of
the additional capabilities it configures and because of the jdt://
URI
handling.
You must not use both at the same time for java. You'd end up with two clients and two language server instances.
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 eclipse.jdt.ls)
- 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 config.cmd
)