From 66f718bedf15dd81601f3ea041c127308207dba6 Mon Sep 17 00:00:00 2001 From: ChrisHilborne Date: Fri, 27 Oct 2023 09:14:46 +0200 Subject: [PATCH] jdtls - java language server working --- init.lua | 10 +- lua/custom/configs/jdtls.lua | 285 ++++++++++++++++++++++++++++++++++ lua/custom/init.lua | 1 + lua/custom/plugins/themes.lua | 1 + 4 files changed, 294 insertions(+), 3 deletions(-) create mode 100644 lua/custom/configs/jdtls.lua diff --git a/init.lua b/init.lua index 5586a5a4..ade5b9d1 100644 --- a/init.lua +++ b/init.lua @@ -115,7 +115,7 @@ require('lazy').setup({ }, -- Useful plugin to show you pending keybinds. - { 'folke/which-key.nvim', opts = {} }, + { 'folke/which-key.nvim', opts = {} }, { -- Adds git related signs to the gutter, as well as utilities for managing changes 'lewis6991/gitsigns.nvim', @@ -147,10 +147,10 @@ require('lazy').setup({ }, }, - + { 'mfussenegger/nvim-jdtls' }, -- "gc" to comment visual regions/lines - { 'numToStr/Comment.nvim', opts = {} }, + { 'numToStr/Comment.nvim', opts = {} }, -- Fuzzy Finder (files, lsp, etc) { @@ -441,6 +441,7 @@ local servers = { pyright = {}, rust_analyzer = {}, tsserver = {}, + jdtls = { filetypes = { 'java' } }, html = { filetypes = { 'html', 'twig', 'hbs' } }, lua_ls = { @@ -467,6 +468,9 @@ mason_lspconfig.setup { mason_lspconfig.setup_handlers { function(server_name) + if server_name == 'jdtls' then + return + end require('lspconfig')[server_name].setup { capabilities = capabilities, on_attach = on_attach, diff --git a/lua/custom/configs/jdtls.lua b/lua/custom/configs/jdtls.lua new file mode 100644 index 00000000..f81629ee --- /dev/null +++ b/lua/custom/configs/jdtls.lua @@ -0,0 +1,285 @@ +local java_cmds = vim.api.nvim_create_augroup('java_cmds', { clear = true }) +local cache_vars = {} + +local root_files = { + '.git', + 'mvnw', + 'gradlew', + 'pom.xml', + 'build.gradle', +} + +local features = { + -- change this to `true` to enable codelens + codelens = false, + + -- change this to `true` if you have `nvim-dap`, + -- `java-test` and `java-debug-adapter` installed + debugger = false, +} + +local function get_jdtls_paths() + if cache_vars.paths then + return cache_vars.paths + end + + local path = {} + + path.data_dir = vim.fn.stdpath('cache') .. '/nvim-jdtls' + + local jdtls_install = require('mason-registry') + .get_package('jdtls') + :get_install_path() + + path.java_agent = jdtls_install .. '/lombok.jar' + path.launcher_jar = vim.fn.glob(jdtls_install .. '/plugins/org.eclipse.equinox.launcher_*.jar') + + if vim.fn.has('mac') == 1 then + path.platform_config = jdtls_install .. '/config_mac' + elseif vim.fn.has('unix') == 1 then + path.platform_config = jdtls_install .. '/config_linux' + elseif vim.fn.has('win32') == 1 then + path.platform_config = jdtls_install .. '/config_win' + end + + path.bundles = {} + + --- + -- Include java-test bundle if present + --- + local java_test_path = require('mason-registry') + .get_package('java-test') + :get_install_path() + + local java_test_bundle = vim.split( + vim.fn.glob(java_test_path .. '/extension/server/*.jar'), + '\n' + ) + + if java_test_bundle[1] ~= '' then + vim.list_extend(path.bundles, java_test_bundle) + end + + --- + -- Include java-debug-adapter bundle if present + --- + local java_debug_path = require('mason-registry') + .get_package('java-debug-adapter') + :get_install_path() + + local java_debug_bundle = vim.split( + vim.fn.glob(java_debug_path .. '/extension/server/com.microsoft.java.debug.plugin-*.jar'), + '\n' + ) + + if java_debug_bundle[1] ~= '' then + vim.list_extend(path.bundles, java_debug_bundle) + end + + --- + -- Useful if you're starting jdtls with a Java version that's + -- different from the one the project uses. + --- + path.runtimes = { + -- Note: the field `name` must be a valid `ExecutionEnvironment`, + -- you can find the list here: + -- https://github.com/eclipse/eclipse.jdt.ls/wiki/Running-the-JAVA-LS-server-from-the-command-line#initialize-request + -- + -- This example assume you are using sdkman: https://sdkman.io + -- { + -- name = 'JavaSE-17', + -- path = vim.fn.expand('~/.sdkman/candidates/java/17.0.6-tem'), + -- }, + -- { + -- name = 'JavaSE-18', + -- path = vim.fn.expand('~/.sdkman/candidates/java/18.0.2-amzn'), + -- }, + } + + cache_vars.paths = path + + return path +end + +local function enable_codelens(bufnr) + pcall(vim.lsp.codelens.refresh) + + vim.api.nvim_create_autocmd('BufWritePost', { + buffer = bufnr, + group = java_cmds, + desc = 'refresh codelens', + callback = function() + pcall(vim.lsp.codelens.refresh) + end, + }) +end + +local function enable_debugger(bufnr) + require('jdtls').setup_dap({ hotcodereplace = 'auto' }) + require('jdtls.dap').setup_dap_main_class_configs() + + local opts = { buffer = bufnr } + vim.keymap.set('n', 'df', "lua require('jdtls').test_class()", opts) + vim.keymap.set('n', 'dn', "lua require('jdtls').test_nearest_method()", opts) +end + +local function jdtls_on_attach(client, bufnr) + if features.debugger then + enable_debugger(bufnr) + end + + if features.codelens then + enable_codelens(bufnr) + end + + -- The following mappings are based on the suggested usage of nvim-jdtls + -- https://github.com/mfussenegger/nvim-jdtls#usage + + local opts = { buffer = bufnr } + vim.keymap.set('n', '', "lua require('jdtls').organize_imports()", opts) + vim.keymap.set('n', 'rv', "lua require('jdtls').extract_variable()", opts) + vim.keymap.set('x', 'rv', "lua require('jdtls').extract_variable(true)", opts) + vim.keymap.set('n', 'rc', "lua require('jdtls').extract_constant()", opts) + vim.keymap.set('x', 'rc', "lua require('jdtls').extract_constant(true)", opts) + vim.keymap.set('x', 'rc', "lua require('jdtls').extract_method(true)", opts) +end + +local function jdtls_setup(event) + local jdtls = require('jdtls') + + local path = get_jdtls_paths() + local data_dir = path.data_dir .. '/' .. vim.fn.fnamemodify(vim.fn.getcwd(), ':p:h:t') + + if cache_vars.capabilities == nil then + jdtls.extendedClientCapabilities.resolveAdditionalTextEditsSupport = true + + local ok_cmp, cmp_lsp = pcall(require, 'cmp_nvim_lsp') + cache_vars.capabilities = vim.tbl_deep_extend( + 'force', + vim.lsp.protocol.make_client_capabilities(), + ok_cmp and cmp_lsp.default_capabilities() or {} + ) + end + + -- The command that starts the language server + -- See: https://github.com/eclipse/eclipse.jdt.ls#running-from-the-command-line + local cmd = { + -- 💀 + '/usr/lib/jvm/java-17-openjdk-amd64/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', + '-javaagent:' .. path.java_agent, + '-Xms1g', + '--add-modules=ALL-SYSTEM', + '--add-opens', + 'java.base/java.util=ALL-UNNAMED', + '--add-opens', + 'java.base/java.lang=ALL-UNNAMED', + + -- 💀 + '-jar', + path.launcher_jar, + + -- 💀 + '-configuration', + path.platform_config, + + -- 💀 + '-data', + data_dir, + } + + local lsp_settings = { + java = { + -- jdt = { + -- ls = { + -- vmargs = "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx1G -Xms100m" + -- } + -- }, + eclipse = { + downloadSources = true, + }, + configuration = { + updateBuildConfiguration = 'interactive', + runtimes = path.runtimes, + }, + maven = { + downloadSources = true, + }, + implementationsCodeLens = { + enabled = true, + }, + referencesCodeLens = { + enabled = true, + }, + -- inlayHints = { + -- parameterNames = { + -- enabled = 'all' -- literals, all, none + -- } + -- }, + format = { + enabled = true, + -- settings = { + -- profile = 'asdf' + -- }, + } + }, + signatureHelp = { + enabled = true, + }, + completion = { + favoriteStaticMembers = { + 'org.hamcrest.MatcherAssert.assertThat', + 'org.hamcrest.Matchers.*', + 'org.hamcrest.CoreMatchers.*', + 'org.junit.jupiter.api.Assertions.*', + 'java.util.Objects.requireNonNull', + 'java.util.Objects.requireNonNullElse', + 'org.mockito.Mockito.*', + }, + }, + contentProvider = { + preferred = 'fernflower', + }, + extendedClientCapabilities = jdtls.extendedClientCapabilities, + sources = { + organizeImports = { + starThreshold = 9999, + staticStarThreshold = 9999, + } + }, + codeGeneration = { + toString = { + template = '${object.className}{${member.name()}=${member.value}, ${otherMembers}}', + }, + useBlocks = true, + }, + } + + -- This starts a new client & server, + -- or attaches to an existing client & server depending on the `root_dir`. + jdtls.start_or_attach({ + cmd = cmd, + settings = lsp_settings, + on_attach = jdtls_on_attach, + capabilities = cache_vars.capabilities, + root_dir = jdtls.setup.find_root(root_files), + flags = { + allow_incremental_sync = true, + }, + init_options = { + bundles = path.bundles, + }, + }) +end + +vim.api.nvim_create_autocmd('FileType', { + group = java_cmds, + pattern = { 'java' }, + desc = 'Setup jdtls', + callback = jdtls_setup, +}) diff --git a/lua/custom/init.lua b/lua/custom/init.lua index 87a0d132..31be9c85 100644 --- a/lua/custom/init.lua +++ b/lua/custom/init.lua @@ -1,2 +1,3 @@ require("custom.configs.set") +require("custom.configs.jdtls") require("custom.statusline.init") diff --git a/lua/custom/plugins/themes.lua b/lua/custom/plugins/themes.lua index b724f728..53f19082 100644 --- a/lua/custom/plugins/themes.lua +++ b/lua/custom/plugins/themes.lua @@ -31,6 +31,7 @@ return { 'lukas-reineke/indent-blankline.nvim', -- Enable `lukas-reineke/indent-blankline.nvim` -- See `:help indent_blankline.txt` + tag = 'v2.20.8', opts = { char = '┊', show_trailing_blankline_indent = false,