Make setup easier and outline eclipse.jdt.ls installation

This commit is contained in:
Mathias Fussenegger 2020-09-16 20:24:15 +02:00
parent 7b524f8da7
commit bc1e54f819
4 changed files with 230 additions and 45 deletions

130
README.md
View file

@ -27,51 +27,104 @@ Take a look at [a demo](https://github.com/mfussenegger/nvim-jdtls/issues/3) to
see some of the functionality in action.
## Installation
## Plugin Installation
- Requires [Neovim HEAD/nightly][4]
- nvim-jdtls is a plugin. Install it like any other Vim plugin.
- Call `:packadd nvim-jdtls` if you install `nvim-jdtls` to `'packpath'`.
## LSP Installation
For ``nvim-jdtls`` to work, [eclipse.jdt.ls][3] needs to be installed.
To build eclipse.jdt.ls from source, switch to a folder of your choice and run:
```bash
git clone https://github.com/eclipse/eclipse.jdt.ls.git
cd eclipse.jdt.ls
./mvnw clean verify
```
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.
- `$HOME/workspace` needs to be changed to where you want eclipse.jdt.ls to save
settings for workspaces.
- `/usr/lib/jvm/java-14-openjdk/bin/java` needs to be changed to point to your
Java installation.
If you're using Java < 9, remove the `add-modules` and `-add-opens` options.
```bash
#!/usr/bin/env bash
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-14-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 "$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"}}
```
## Configuration
To use `nvim-jdtls`, you need to setup a LSP client. In your `init.vim` add the
following:
```
if has('nvim-0.5')
packadd nvim-jdtls
lua jdtls = require('jdtls')
augroup lsp
au!
au FileType java lua 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 the `init_options`. See the [eclipse.jdt.ls Wiki][8] for an overview of available options.
## Usage
`nvim-jdtls` doesn't contain logic to spawn a LSP client for [eclipse.jdt.ls][3], see `:help lsp` for information on how to launch a LSP client.
`nvim-jdtls` extends the capabilities of the built-in LSP support in
Neovim, so all the functions mentioned in `:help lsp` will work.
To make use of all the functionality `nvim-jdtls` provides, you need to set some extra capabilities and set a couple of initialization options.
Additional capabilities:
```lua
local capabilities = vim.lsp.protocol.make_client_capabilities()
capabilities.textDocument.codeAction = {
dynamicRegistration = false;
codeActionLiteralSupport = {
codeActionKind = {
valueSet = {
"source.generate.toString",
"source.generate.hashCodeEquals"
};
};
};
}
```
Initialization options:
```lua
config['init_options'] = {
extendedClientCapabilities = require('jdtls').extendedClientCapabilities;
}
```
You may want to create some mappings and commands to make the functionality of `nvim-jdtls` accessible:
`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>
@ -82,11 +135,17 @@ vnoremap crv <Esc><Cmd>lua require('jdtls').extract_variable(true)<CR>
vnoremap crm <Esc><Cmd>lua require('jdtls').extract_method(true)<CR>
-- For nvim-dap
-- If using nvim-dap
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` callback that can be set on the `config` table which is passed to `start_or_attach`.
```
command! -buffer JdtCompile lua require('jdtls').compile()
command! -buffer JdtUpdateConfig lua require('jdtls').update_project_config()
command! -buffer JdtJol lua require('jdtls').jol()
@ -147,3 +206,4 @@ config['init_options'] = {
[5]: https://github.com/mfussenegger/nvim-dap
[6]: https://github.com/microsoft/java-debug
[7]: https://github.com/microsoft/vscode-java-test
[8]: https://github.com/eclipse/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line

View file

@ -873,15 +873,9 @@ function M.setup_dap()
end
M.extendedClientCapabilities = {
classFileContentsSupport = true;
generateToStringPromptSupport = true;
hashCodeEqualsPromptSupport = true;
advancedExtractRefactoringSupport = true;
advancedOrganizeImportsSupport = true;
generateConstructorsPromptSupport = true;
generateDelegateMethodsPromptSupport = true;
};
local setup = require('jdtls.setup')
M.extendedClientCapabilities = setup.extendedClientCapabilities
M.start_or_attach = setup.start_or_attach
M.setup = setup
return M

22
lua/jdtls/path.lua Normal file
View file

@ -0,0 +1,22 @@
local M = {}
local is_windows = vim.loop.os_uname().version:match('Windows')
M.sep = is_windows and '\\' or '/'
if is_windows then
M.is_fs_root = function(path)
return path:match('^%a:$')
end
else
M.is_fs_root = function(path)
return path == '/'
end
end
function M.join(...)
local result = table.concat(vim.tbl_flatten {...}, M.sep):gsub(M.sep .. '+', M.sep)
return result
end
return M

109
lua/jdtls/setup.lua Normal file
View file

@ -0,0 +1,109 @@
local api = vim.api
local lsp = vim.lsp
local uv = vim.loop
local path = require('jdtls.path')
local lsps = {}
local status_callback = vim.schedule_wrap(function(_, _, result)
api.nvim_command(string.format(':echohl Function | echo "%s" | echohl None', result.message))
end)
local function attach_to_active_buf(bufnr, client_name)
for _, buf in pairs(vim.fn.getbufinfo({bufloaded=true})) do
if api.nvim_buf_get_option(buf.bufnr, 'filetype') == 'java' then
local clients = lsp.buf_get_clients(buf.bufnr)
for _, client in ipairs(clients) do
if client.config.name == client_name then
lsp.buf_attach_client(bufnr, client.id)
return true
end
end
end
end
print('No active LSP client found to use for jdt:// document')
return false
end
local function find_root(bufname, markers)
local dirname = vim.fn.fnamemodify(bufname, ':p:h')
while not path.is_fs_root(dirname) do
for _, marker in ipairs(markers) do
if uv.fs_stat(path.join(dirname, marker)) then
return dirname
end
end
dirname = vim.fn.fnamemodify(dirname, ':h')
end
end
local function start_or_attach(config)
assert(config, 'config is required')
assert(
config.cmd and type(config.cmd) == 'table',
'Config must have a `cmd` property and that must be a table. Got: '
.. table.concat(config.cmd, ' ') or 'nil'
)
assert(
tonumber(vim.fn.executable(config.cmd[1])) == 1,
'LSP cmd must be an executable: ' .. config.cmd[1]
)
config.name = config.name or 'jdt.ls'
local bufnr = api.nvim_get_current_buf()
local bufname = api.nvim_buf_get_name(bufnr)
-- Won't be able to get the correct root path for jdt:// URIs
-- So need to connect to an existing client
if vim.startswith(bufname, 'jdt://') then
if attach_to_active_buf(bufnr, config.name) then
return
end
end
config.root_dir = config.root_dir or find_root(bufname, {'.git', 'gradlew', 'mvnw'})
config.callbacks = config.callbacks or {}
config.callbacks['language/status'] = config.callbacks['language/status'] or status_callback
config.capabilities = config.capabilities or lsp.protocol.make_client_capabilities()
config.capabilities.textDocument.codeAction = {
dynamicRegistration = false;
codeActionLiteralSupport = {
codeActionKind = {
valueSet = {
"source.generate.toString",
"source.generate.hashCodeEquals",
"source.organizeImports",
};
};
};
}
local client_id = lsps[config.root_dir]
if not client_id then
client_id = lsp.start_client(config)
lsps[config.root_dir] = client_id
end
lsp.buf_attach_client(bufnr, client_id)
end
local extendedClientCapabilities = {
classFileContentsSupport = true;
generateToStringPromptSupport = true;
hashCodeEqualsPromptSupport = true;
advancedExtractRefactoringSupport = true;
advancedOrganizeImportsSupport = true;
generateConstructorsPromptSupport = true;
generateDelegateMethodsPromptSupport = true;
};
local function add_commands()
api.nvim_command [[command! -buffer -nargs=? JdtCompile lua require('jdtls').compile(<f-args>)]]
api.nvim_command [[command! -buffer JdtUpdateConfig lua require('jdtls').update_project_config()]]
api.nvim_command [[command! -buffer -nargs=* JdtJol lua require('jdtls').jol(<f-args>)]]
api.nvim_command [[command! -buffer JdtBytecode lua require('jdtls').javap()]]
api.nvim_command [[command! -buffer JdtJshell lua require('jdtls').jshell()]]
end
return {
start_or_attach = start_or_attach;
extendedClientCapabilities = extendedClientCapabilities;
add_commands = add_commands;
}