Catatan: Saya berencana untuk terus memperbarui postingan ini jika saya perlu menambahkan lebih banyak konten atau mengubah sesuatu

Saya telah menggunakan Java di Neovim cukup lama di tempat kerja, dan ini merupakan pengalaman yang sangat menyenangkan. Seiring dengan meningkatnya penggunaan Neovim (terutama di kalangan anak muda), saya ingin berbagi bagaimana saya melakukannya.

Saya pikir secara historis hal ini dianggap sebagai pengalaman yang menyakitkan, namun dengan bimbingan, hal ini bisa menjadi sangat mudah!

Saya akan mengawali ini dengan mengatakan bahwa jika Neovim bukan editor utama Anda, Anda harus terlebih dahulu mencoba IDE khusus untuk Java (semuanya harus memiliki plugin Vim):

  • Gerhana
  • IntelliJ
  • Apache Netbean

Jika Neovim adalah editor utama Anda, Anda mungkin benci membuka *masukkan IDE yang mengubah Anda menjadi siput* untuk bahasa tertentu, dan saya juga.

Java memiliki satu opsi LSP untuk Neovim, dan itu adalah JDTLS (Java Development Tools Language Server) oleh Eclipse. Anda harus membaca proyek README untuk gambaran umum tingkat tinggi (termasuk fitur): JDTLS GitHub

Ini adalah LSP yang bagus untuk Java, dan menurut saya hanya itu yang Anda perlukan untuk bekerja dengan proyek Java. Alur kerja pribadi saya biasanya melibatkan satu jendela tmux dengan proyek terbuka dan jendela lain yang menangani kompilasi, pengujian, dll

semua teks

Untuk menggunakan JDTLS di Neovim, ada dua plugin yang dapat Anda pilih, dan yang Anda putuskan bergantung pada preferensi Anda.

Jika Anda senang menerima pengaturan all-in-one yang out-of-the-box, maka nvim-java mungkin cocok untukmu. Ini mencoba untuk menjadi solusi komprehensif dengan default yang populer, dan tidak merepotkan ketika berhubungan dengan LSP, debugging, pengaturan pengujian. Ini tidak sepenuhnya fleksibel, jadi jika Anda memerlukan kontrol lebih besar, Anda harus mencoba opsi berikutnya.

Saya berharap mayoritas cocok di sini, dan nvim-jdtls adalah plugin Java untuk dukungan LSP di Neovim. Anda memiliki akses penuh untuk mengonfigurasi JDTLS, dan saya sangat menyarankan untuk membaca opsi yang tersedia.

Ingatlah untuk menginstal JDTLS melalui Tukang batu.

Terkadang Anda perlu memberikan referensi JAR yang dapat dihubungkan oleh LSP untuk memuat. Saya mengunduh Lombok JAR dan menambahkannya di jalur instalasi JDTLS (Anda akan melihatnya di konfigurasi nvim-jdtls saya di bawah), dan setidaknya saya tahu ini harus dilakukan untuk Playwright di bawah ‘referencedLibraries’.

Debugging dapat dilakukan di dalam Neovim, tapi sekali lagi, perlu diingat bahwa Anda mungkin memiliki pengalaman yang lebih baik dalam IDE yang berfokus pada Java.

Saya sarankan menginstal nvim-dap Dan nvim-dap-ui.

Anda perlu menginstal java-debug-adaptor dari Mason ATAU unduh dan referensikan di konfigurasi lsp (hanya untuk nvim-jdtls).

semua teks

Bekerja dengan tes di dalam Neovim juga dimungkinkan, ikuti pengaturan serupa di atas.

Anda perlu menginstal tes java dari Mason ATAU unduh dan referensikan di konfigurasi lsp (hanya untuk nvim-jdtls).

Saya akan menunjukkan apa yang saya miliki sebagai referensi:

Saya membayangkan Anda juga menggunakan treesitter, lspzero dll.

JDTLS

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',
    'build.sbt'
}

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:
        -- 
        --
        -- This example assume you are using sdkman: 
        {
            name = 'JavaSE-21',
            path = vim.fn.expand('~/.sdkman/candidates/java/21.0.2-tem'),
        },
        {
            name = 'JavaSE-23',
            path = vim.fn.expand('~/.sdkman/candidates/java/23-tem'),
        }

    }

    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)
    --vim.lsp.inlay_hint(bufnr, true)
    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
    -- 

    local opts = { buffer = bufnr }
    vim.keymap.set('n', '', "lua require('jdtls').organize_imports()", opts)
    vim.keymap.set('n', 'crv', "lua require('jdtls').extract_variable()", opts)
    vim.keymap.set('x', 'crv', "lua require('jdtls').extract_variable(true)", opts)
    vim.keymap.set('n', 'crc', "lua require('jdtls').extract_constant()", opts)
    vim.keymap.set('x', 'crc', "lua require('jdtls').extract_constant(true)", opts)
    vim.keymap.set('x', 'crm', "lua require('jdtls').extract_method(true)", opts)
    vim.keymap.set('n', 'pjp', "lua require('jdtls').javap()", opts)
end

local function jdtls_setup(event)
    local jdtls = require('jdtls')
    local extendedClientCapabilities = jdtls.extendedClientCapabilities;
    extendedClientCapabilities.onCompletionItemSelectedCommand = "editor.action.triggerParameterHints"

    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: 
    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"
            --   }
            -- },
            project = {
                referencedLibraries = {
                    -- add any library jars here for the lsp to pick them up
                },
            },
            eclipse = {
                downloadSources = true,
            },
            configuration = {
                updateBuildConfiguration = 'interactive',
                runtimes = path.runtimes,
            },
            maven = {
                downloadSources = true,
            },
            implementationsCodeLens = {
                enabled = true,
            },
            referencesCodeLens = {
                enabled = true,
            },
            references = {
                includeDecompiledSources = true,
            },
            inlayHints = {
                enabled = true,
                --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,
            extendedClientCapabilities = extendedClientCapabilities,
        },
    })
end

vim.api.nvim_create_autocmd('FileType', {
    group = java_cmds,
    pattern = { 'java' },
    desc = 'Setup jdtls',
    callback = jdtls_setup,
})

DAP

local dap = require('dap')

dap.configurations.java = {
    {
        type = 'java',
        request = 'launch',
        name = 'Launch Java Program'
    },
}

vim.fn.sign_define('DapBreakpoint',
    {
        text = '🔴',
        texthl = 'DapBreakpointSymbol',
        linehl = 'DapBreakpoint',
        numhl = 'DapBreakpoint'
    })
vim.fn.sign_define('DapStopped',
    {
        texthl = 'DapStoppedSymbol',
        linehl = 'CursorLine',
        numhl = 'DapBreakpoint'
    })

vim.keymap.set('n', '', function() require('dap').continue() end)
vim.keymap.set('n', '', function() require('dap').step_over() end)
vim.keymap.set('n', '', function() require('dap').step_into() end)
vim.keymap.set('n', '', function() require('dap').step_out() end)
vim.keymap.set('n', 'b', function() require('dap').toggle_breakpoint() end)

local dapui = require('dapui')
dapui.setup()

dap.listeners.before.attach.dapui_config = function()
    dapui.open()
end
dap.listeners.before.launch.dapui_config = function()
    dapui.open()
end
dap.listeners.before.event_terminated.dapui_config = function()
    --dapui.close()
end
dap.listeners.before.event_exited.dapui_config = function()
    --dapui.close()
end

vim.keymap.set('n', 'du', function() dapui.toggle() end)

Saya harap ini membantu Anda mulai bekerja dengan Java di Neovim!

Sumber

Krystian Wiśniewski
Krystian Wiśniewski is a dedicated Sports Reporter and Editor with a degree in Sports Journalism from He graduated with a degree in Journalism from the University of Warsaw. Bringing over 14 years of international reporting experience, Krystian has covered major sports events across Europe, Asia, and the United States of America. Known for his dynamic storytelling and in-depth analysis, he is passionate about capturing the excitement of sports for global audiences and currently leads sports coverage and editorial projects at Agen BRILink dan BRI.