mfussenegger / nvim-jdtls

Extensions for the built-in LSP support in Neovim for eclipse.jdt.ls
GNU General Public License v3.0
1k stars 62 forks source link

java.lang.NoClassDefFoundError: org/eclipse/jdt/ls/core/internal/hover/JavaElementLabels when trying to debug tests #581

Closed oysandvik94 closed 8 months ago

oysandvik94 commented 8 months ago

LSP client configuration

I have tried both Lunarvim with this configuration: https://github.com/LunarVim/starter.lvim/blob/java-ide/ftplugin/java.lua

As well as my own config:

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 = true,

    -- change this to `true` if you have `nvim-dap`,
    -- `java-test` and `java-debug-adapter` installed
    debugger = true,
}

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('/usr/lib/jvm/java-17-openjdk-amd64'),
        },
        -- {
        --   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()
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', '<leader>lo', "<cmd>lua require('jdtls').organize_imports()<cr>", opts)
    vim.keymap.set('n', '<leader>lev', "<cmd>lua require('jdtls').extract_variable()<cr>", opts)
    vim.keymap.set('x', '<leader>lev', "<esc><cmd>lua require('jdtls').extract_variable(true)<cr>", opts)
    vim.keymap.set('n', '<leader>lec', "<cmd>lua require('jdtls').extract_constant()<cr>", opts)
    vim.keymap.set('x', '<leader>lec', "<esc><cmd>lua require('jdtls').extract_constant(true)<cr>", opts)
    vim.keymap.set('x', '<leader>lem', "<esc><Cmd>lua require('jdtls').extract_method(true)<cr>", 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

        -- Add lsp stuff to cmp
        local ok_cmp, cmp_lsp = pcall(require, 'cmp_nvim_lsp')
        cache_vars.capabilities = ok_cmp and cmp_lsp.default_capabilities()
        -- 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 = {
        '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 = false,
              settings = {
                url = vim.fn.stdpath "config" .. "/lang-servers/intellij-java-google-style.xml",
                profile = "GoogleStyle",
              },
            },
            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.*',
                },
                filteredTypes = {
                    "com.sun.*",
                    "io.micrometer.shaded.*",
                    "java.awt.*",
                    "jdk.*", "sun.*",
                },
                guessMethodArguments = true
            },
            contentProvider = {
                preferred = 'fernflower',
            },
            extendedClientCapabilities = jdtls.extendedClientCapabilities,
            sources = {
                organizeImports = {
                    starThreshold = 9999,
                    staticStarThreshold = 9999,
                }
            },
            codeGeneration = {
                toString = {
                    template = '${object.className}{${member.name()}=${member.value}, ${otherMembers}}',
                },
                hashCodeEquals = {
                    useJava7Objects = true,
                },
                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,
})

return {
    "mfussenegger/nvim-jdtls",
    -- dir = "~/dev/nvim-jdtls",
    dependencies = {
        "rcarriga/nvim-dap-ui",
        "mfussenegger/nvim-dap"
    }
}

dap config:

return {
    "mfussenegger/nvim-dap",
    event = "BufReadPre",
    dependencies = {
        "theHamsta/nvim-dap-virtual-text",
        "rcarriga/nvim-dap-ui",
    },
    config = function()
        local dap_ui = require("dapui")
        local dap = require("dap")
        local jdtls = require("jdtls")
        dap_ui.setup({
            layouts = { {
                elements = { {
                    id = "watches",
                    size = 0.5
                }, {
                    id = "scopes",
                    size = 0.5
                } },
                position = "bottom",
                size = 10
            } },
        })
        require("nvim-dap-virtual-text").setup()

        local function trigger_dap(dapStart)
            dap_ui.open({ reset = true })
            dapStart()
        end

        local function continue()
            if (dap.session()) then
                dap.continue()
            else
                dap_ui.open({ reset = true })
                dap.continue()
            end
        end

        vim.keymap.set('n', '<Leader>dd', function() require('dap').toggle_breakpoint() end,
            { desc = "Toggle breakpoint" })

        vim.keymap.set('n', '<Leader>dD', function()
                vim.ui.input({ prompt = "Condition: " }, function(input)
                    dap.set_breakpoint(input)
                end)
            end,
            { desc = "Toggle breakpoint" })

        vim.keymap.set('n', '<leader>df', function() trigger_dap(require('jdtls').test_class()) end,
            { desc = "Debug test class" })
        vim.keymap.set('n', '<leader>dn', function() trigger_dap(require('jdtls').test_nearest_method()) end,
            { desc = "Debug neartest test method" })
        vim.keymap.set('n', '<leader>dt', function() trigger_dap(jdtls.test_nearest_method) end,
            { desc = 'Debug nearest test' });
        vim.keymap.set('n', '<leader>dT', function() trigger_dap(jdtls.test_class) end, { desc = 'Debug test class' });
        vim.keymap.set('n', '<leader>dp', function() trigger_dap(jdtls.pick_test) end, { desc = 'Choose nearest test' });
        vim.keymap.set('n', '<leader>dl', function() trigger_dap(dap.run_last) end, { desc = 'Choose nearest test' });
        vim.keymap.set('n', '<leader>do', function() dap.step_over() end, { desc = 'Step over' });
        vim.keymap.set('n', '<leader>di', function() dap.step_into() end, { desc = 'Step into' });
        vim.keymap.set('n', '<leader>du', function() dap.step_out() end, { desc = 'Step out' });
        vim.keymap.set('n', '<leader>db', function() dap.step_back() end, { desc = 'Step back' });
        vim.keymap.set('n', '<leader>dh', function() dap.run_to_cursor() end, { desc = 'Run to cursor' });
        vim.keymap.set('n', '<leader>dc', continue, { desc = 'Start debug session, or continue session' });
        vim.keymap.set('n', '<leader>de', function()
            dap.terminate()
            dap_ui.close()
        end, { desc = 'Terminate debug session' });
        vim.keymap.set('n', '<leader>du', function() dap_ui.toggle({ reset = true }) end,
            { desc = 'Reset and toggle ui' });

        vim.api.nvim_set_hl(0, 'DapStopped', { ctermbg = 0, fg = '#1f1d2e', bg = '#f6c177' })
        vim.fn.sign_define('DapStopped', {
            text = '->',
            texthl = 'DapStopped',
            linehl = 'DapStopped',
            numhl = 'DapStopped'
        })

        dap.configurations.java = {
            {
                type = 'java',
                request = 'attach',
                name = "Debug (Attach) - Remote",
                hostName = "127.0.0.1",
                port = 5005,
            },
        }

        dap.adapters.codelldb = {
            type = 'server',
            port = "${port}",
            executable = {
                command = vim.fn.stdpath('data') .. '/mason/bin/codelldb',
                args = { "--port", "${port}" },
            }
        }
        dap.configurations.c = {
            {
                name = 'Launch',
                type = 'codelldb',
                request = 'launch',
                program = function() -- Ask the user what executable wants to debug
                    return vim.fn.input('Path to executable: ', vim.fn.getcwd() .. '/bin/program', 'file')
                end,
                cwd = '${workspaceFolder}',
                stopOnEntry = false,
                args = {},
            },
        }
    end,
}

Eclipse.jdt.ls version

1.29.0

Steps to Reproduce

I have tried multiple projects, running just a simple junit test.

openjdk 17.0.9 2023-10-17

It used to work at some point, then stopped working. Not sure if it is due to jdt.ls version, java, or dap.

Happens when I run methods ".test_nearest_method()" and "test_class()"

Expected Result

Tests should be run.

Actual Result

!ENTRY org.eclipse.jdt.ls.core 4 0 2023-11-09 11:44:29.229 !MESSAGE Error in calling delegate command handler !STACK 0 java.lang.NoClassDefFoundError: org/eclipse/jdt/ls/core/internal/hover/JavaElementLabels at com.microsoft.java.test.plugin.model.builder.JavaTestItemBuilder.build(JavaTestItemBuilder.java:75) at com.microsoft.java.test.plugin.util.TestSearchUtils.findTestItemsInTypeBinding(TestSearchUtils.java:405) at com.microsoft.java.test.plugin.util.TestSearchUtils.findTestTypesAndMethods(TestSearchUtils.java:375) at com.microsoft.java.test.plugin.handler.TestDelegateCommandHandler.executeCommand(TestDelegateCommandHandler.java:60) at org.eclipse.jdt.ls.core.internal.handlers.WorkspaceExecuteCommandHandler$1.run(WorkspaceExecuteCommandHandler.java:230) at org.eclipse.core.runtime.SafeRunner.run(SafeRunner.java:45) at org.eclipse.jdt.ls.core.internal.handlers.WorkspaceExecuteCommandHandler.executeCommand(WorkspaceExecuteCommandHandler.java:220) at org.eclipse.jdt.ls.core.internal.handlers.JDTLanguageServer.lambda$4(JDTLanguageServer.java:616) at org.eclipse.jdt.ls.core.internal.BaseJDTLanguageServer.lambda$0(BaseJDTLanguageServer.java:87) at java.base/java.util.concurrent.CompletableFuture$UniApply.tryFire(CompletableFuture.java:646) at java.base/java.util.concurrent.CompletableFuture$Completion.exec(CompletableFuture.java:483) at java.base/java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:373) at java.base/java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(ForkJoinPool.java:1182) at java.base/java.util.concurrent.ForkJoinPool.scan(ForkJoinPool.java:1655) at java.base/java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1622) at java.base/java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:165) Caused by: java.lang.ClassNotFoundException: org.eclipse.jdt.ls.core.internal.hover.JavaElementLabels cannot be found by com.microsoft.java.test.plugin_0.39.0 at org.eclipse.osgi.internal.loader.BundleLoader.generateException(BundleLoader.java:541) at org.eclipse.osgi.internal.loader.BundleLoader.findClass0(BundleLoader.java:536) at org.eclipse.osgi.internal.loader.BundleLoader.findClass(BundleLoader.java:416) at org.eclipse.osgi.internal.loader.ModuleClassLoader.loadClass(ModuleClassLoader.java:168) at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:525) ... 16 more

mfussenegger commented 8 months ago

See https://github.com/mfussenegger/nvim-jdtls/issues/565