local misc = require('core.misc') local map, auto, augroup = misc.map, misc.auto, misc.augroup return { 'williamboman/mason-lspconfig.nvim', requires = { 'williamboman/mason.nvim', 'mfussenegger/nvim-jdtls', 'neovim/nvim-lspconfig' }, function() local util = require('lspconfig.util') -- configure lsp when attached local function lsp_attach(client, bufnr) -- helper function(s) local function set_lsp_sign(name, text) vim.fn.sign_define(name, { text = text, texthl = name }) end set_lsp_sign("DiagnosticSignError", "x") set_lsp_sign("DiagnosticSignWarn" , "!") set_lsp_sign("DiagnosticSignInfo" , "i") set_lsp_sign("DiagnosticSignHint" , "h") local opts = { buffer = bufnr } -- LSP actions map('n', 'K', vim.lsp.buf.hover, opts) map('n', 'gD', vim.lsp.buf.definition, opts) -- map('n', 'gD', 'lua vim.lsp.buf.declaration()') map('n', 'gI', vim.lsp.buf.implementation, opts) map('n', 'gY', vim.lsp.buf.type_definition, opts) map('n', 'gR', vim.lsp.buf.references, opts) map('n', '', vim.lsp.buf.signature_help, opts) map('n', 'lr', vim.lsp.buf.rename, opts) map('n', '', vim.lsp.buf.rename, opts) map('n', 'gA', vim.lsp.buf.code_action, { buffer = bufnr, desc = 'check code actions', }) map('n', '', vim.lsp.buf.code_action, { buffer = bufnr, desc = 'check code actions' }) -- Diagnostics map('n', '[d', vim.diagnostic.goto_prev) map('n', ']d', vim.diagnostic.goto_next) end -- setup lsp capabilities local capabilities = vim.lsp.protocol.make_client_capabilities() capabilities.textDocument.completion.completionItem = { documentationFormat = { "markdown", "plaintext" }, snippetSupport = true, preselectSupport = true, insertReplaceSupport = true, labelDetailsSupport = true, deprecatedSupport = true, commitCharactersSupport = true, tagSupport = { valueSet = { 1 } }, resolveSupport = { properties = { "documentation", "detail", "additionalTextEdits" } } } -- setup language servers require('mason-lspconfig').setup { ensure_installed = { "lua_ls", "clangd", "jdtls", "tsserver", "phpactor", "html", "cssls", "bashls", "zls" -- "asm-lsp", -- seems to be broken } } require('mason-lspconfig').setup_handlers { function(server_name) require('lspconfig')[server_name].setup { on_attach = lsp_attach, capabilities = capabilities } end, -- setup luals ["lua_ls"] = function(server_name) local root_files = { '.luarc.json', '.luarc.jsonc', '.luacheckrc', '.stylua.toml', 'stylua.toml', 'selene.toml', 'selene.yml', 'README.md' } -- FIXME: for some reason luals randomly resets the indentation of code -- when pressing o or O. Right now this is a minor annoyance I will deal -- with in exchange for really good lua lsp support. -- -- FIXME: luals also seems to start up twice and sends back twice the -- completions (one configured with the below settings and one without) require('lspconfig')[server_name].setup { on_attach = lsp_attach, settings = { Lua = { diagnostics = { globals = { "vim", 'mp' } }, runtime = { version = 'LuaJIT' }, format = { enable = false }, workspace = { checkThirdParty = false, library = { vim.env.VIMRUNTIME } } } }, root_dir = function(fname) local root = util.root_pattern(unpack(root_files))(fname) if root and root ~= vim.env.HOME then return root end root = util.root_pattern('lua/')(fname) if root then return root end return util.find_git_ancestor(fname) end } end, -- setup clangd ["clangd"] = function(server_name) require('lspconfig')[server_name].setup { on_attach = function(client, bufnr) lsp_attach(client, bufnr) -- add some clangd specific mappings local opts = { buffer = bufnr } map("n", "o", "ClangdSwitchSourceHeader", opts) end, capabilities = capabilities, cmd = { "clangd", "--background-index", "--clang-tidy", "--header-insertion=iwyu", "--completion-style=detailed", "--function-arg-placeholders", "--fallback-style=llvm" }, init_options = { usePlaceholders = true, clangdFileStatus = true, fallback_flags = { "-xc" -- makes clangd think we're using c instead of c++ } } } end, -- setup jdtls ["jdtls"] = function(server_name) auto("Filetype", { pattern = "java", callback = function() -- must be a java interpreter of version 17 or greater local java = "java" local buffer = {} ---@type function local startlsp -- check if version of java in use is high enough vim.fn.jobstart({ java, vim.fn.stdpath('config').."/extras/JavaVersion.java" }, { stdin = nil, on_stdout = function(_, data, _) table.insert(buffer, table.concat(data)) end, on_exit = function(_, exit_code, _) local v = vim.version.parse(table.concat(buffer)) -- if there's an error, no version info, or the java version is -- less than 17 stop the lsp from starting if exit_code ~= 0 then vim.notify(string.format( "java version check failed: exit code %s", exit_code), vim.log.levels.ERROR, { title = misc.appid }) return elseif not v then vim.notify("no java version info found", vim.log.levels.ERROR, { title = misc.appid }) return elseif v.major < 17 then vim.notify(string.format( "java version %s < 17.0.0 Cannot run jdtls, bailing out", v[1].."."..v[2].."."..v[3]), vim.log.levels.ERROR, { title = misc.appid }) return end startlsp() end }) function startlsp() local ok, jdtls = pcall(require, "jdtls") if not ok then vim.notify("jdtls not found, can't start java lsp", vim.log.levels.ERROR, {}) return end local config = {} config.on_attach = function(client, bufnr) lsp_attach(client, bufnr) -- add some jdtls specific mappings local opts = { buffer = bufnr } map('n', 'cri', jdtls.organize_imports, opts) map('n', 'crv', jdtls.extract_variable, opts) map('n', 'crc', jdtls.extract_constant, opts) map('x', 'crv', "lua require('jdtls').extract_variable(true)", opts) map('x', 'crc', "lua require('jdtls').extract_constant(true)", opts) map('x', 'crm', "lua require('jdtls').extract_method(true)", opts) -- refresh the codelens every time after writing the file local jdtls_cmds = augroup("jdtls_cmds") pcall(vim.lsp.codelens.refresh) auto('BufWritePost', { buffer = bufnr, group = jdtls_cmds, desc = 'refresh codelens', callback = function() pcall(vim.lsp.codelens.refresh) end }) end -- setup path stuff local path = {} local jdtls_install = require('mason-registry').get_package('jdtls'):get_install_path() path.data_dir = vim.fn.stdpath('cache')..'/nvim-jdtls' path.java_agent = jdtls_install..'/lombok.jar' path.launcher_jar = vim.fn.glob(jdtls_install..'/plugins/org.eclipse.equinox.launcher_*.jar') path.platform_config = jdtls_install..'/config_linux' path.bundles = {} -- data dir local data_dir = path.data_dir..'/'..vim.fn.fnamemodify(vim.fn.getcwd(), ':p:h:t') -- enable basic capabilities config.capabilities = capabilities -- enable some extended client capabilities local extendedClientCapabilities = jdtls.extendedClientCapabilities extendedClientCapabilities.resolveAdditionalTextEditsSupport = true -- command to start the lsp server config.cmd = { java, -- this has to be java17 or newer '-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', '-Xmx1G', '--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 } -- settings config.settings = { java = { eclipse = { downloadSources = true }, gradle = { enabled = true }, maven = { downloadSources = true }, implementationsCodeLens = { enabled = true }, referencesCodeLens = { enabled = true }, references = { includeDecompiledSources = true }, symbols = { includeSourceMethodDeclarations = true }, inlayHints = { parameterNames = { enabled = "all" } }, 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.*" }, importOrder = { "java", "javax", "com", "org" } }, sources = { organizeImports = { starThreshold = 9999, staticStarThreshold = 9999 } }, codeGeneration = { toString = { template = '${object.className}{${member.name()}=${member.value}, ${otherMembers}}' }, hashCodeEquals = { useJava7Objects = true }, useBlocks = true, } } } config.signatureHelp = { enabled = true } config.flags = { allow_incremental_sync = true } -- disable all messages from printing config.handlers = { ['language/status'] = function() end } config.init_options = { extendedClientCapabilities = extendedClientCapabilities, } config.root_dir = vim.fs.root(0, { ".git", "mvnw", ".gradle", "gradlew" }) -- start it up jdtls.start_or_attach(config) end end }) end } end }