local lspconfig = require("lspconfig") local lsp_filetypes = { -- "vue", "typescript", "json", "javascript", -- "python", -- "rust", "yaml", "bash", -- "lua", } local signs = { Error = " ", Warn = " ", Hint = " ", Info = " " } -- Give floating windows borders vim.lsp.handlers["textDocument/hover"] = vim.lsp.with(vim.lsp.handlers.hover, { border = "rounded" }) -- Configure diagnostic display vim.diagnostic.config({ virtual_text = { -- Only display errors w/ virtual text severity = vim.diagnostic.severity.ERROR, -- Prepend with diagnostic source if there is more than one attached to the buffer -- (e.g. (eslint) Error: blah blah blah) source = "if_many", spacing = 4, prefix = "●", }, float = { severity_sort = true, source = "if_many", border = "solid", header = { "", "LspDiagnosticsDefaultWarning", }, prefix = function(diagnostic) local diag_to_format = { [vim.diagnostic.severity.ERROR] = { "Error", "LspDiagnosticsDefaultError" }, [vim.diagnostic.severity.WARN] = { "Warning", "LspDiagnosticsDefaultWarning" }, [vim.diagnostic.severity.INFO] = { "Info", "LspDiagnosticsDefaultInfo" }, [vim.diagnostic.severity.HINT] = { "Hint", "LspDiagnosticsDefaultHint" }, } local res = diag_to_format[diagnostic.severity] return string.format("(%s) ", res[1]), res[2] end, }, severity_sort = true, }) -- Don't let tsserver or vuels do formatting, they do it wrong local custom_format = function(bufnr) vim.lsp.buf.format({ bufnr = bufnr, filter = function(client) return client.name ~= "tsserver" and client.name ~= "vuels" end, }) end local format_group = vim.api.nvim_create_augroup("LspFormatting", { clear = true }) local format_on_save = function(bufnr) vim.api.nvim_create_autocmd("BufWritePre", { group = format_group, buffer = bufnr, callback = function() custom_format(bufnr) end, }) end local custom_attach = function(client, bufnr) local keymap_opts = { buffer = bufnr, silent = true, noremap = true } -- LSP mappings (only apply when LSP client attached) vim.keymap.set("n", "K", vim.lsp.buf.hover, keymap_opts) -- vim.keymap.set("n", "", vim.lsp.buf.definition, keymap_opts) -- vim.keymap.set("n", "gr", vim.lsp.buf.references, keymap_opts) -- vim.keymap.set("n", "gr", vim.lsp.buf.rename, keymap_opts) -- diagnostics vim.keymap.set("n", "dk", vim.diagnostic.open_float, keymap_opts) -- diagnostic(s) on current line vim.keymap.set("n", "dn", vim.diagnostic.goto_next, keymap_opts) -- move to next diagnostic in buffer vim.keymap.set("n", "dp", vim.diagnostic.goto_prev, keymap_opts) -- move to prev diagnostic in buffer vim.keymap.set("n", "da", vim.diagnostic.setqflist, keymap_opts) -- show all buffer diagnostics in qflist vim.keymap.set("n", "H", vim.lsp.buf.code_action, keymap_opts) -- code actions (handled by telescope-ui-select) vim.keymap.set("n", "F", function() custom_format(bufnr) end, keymap_opts) -- manual formatting, because sometimes null-ls just decides to stop working -- use omnifunc vim.bo[bufnr].omnifunc = "v:lua.vim.lsp.omnifunc" vim.bo[bufnr].formatexpr = "v:lua.vim.lsp.formatexpr" end local web_dev_attach = function(client, bufnr) -- local root_files = vim.fn.readdir(vim.fn.getcwd()) -- local volar = false -- -- TODO: the "right" way to do this would be to check the typescript version, but that seems hard -- for _, fname in ipairs(root_files) do -- if fname == "pnpm-lock.yaml" then volar = true end -- end -- -- disable vuels and tsserver if we're using volar -- if volar and (client.name == "tsserver" or client.name == "vuels") then -- client.stop() -- return false -- end -- -- disable volar if we don't have pnpm -- if not volar and client.name == "volar" then -- client.stop() -- return false -- end format_on_save(bufnr) custom_attach(client, bufnr) return true end -- Set up clients local null_ls = require("null-ls") null_ls.setup({ on_attach = custom_attach, should_attach = function(bufnr) local cur_ft = vim.bo[bufnr].filetype for _, ft in ipairs({ "javascript", "javascriptreact", "javascript.jsx", "typescript", "typescriptreact", "typescript.tsx"}) do if ft == cur_ft then return true end end return false end, sources = { --#formatters null_ls.builtins.formatting.prettierd, null_ls.builtins.formatting.eslint_d, --#diagnostics/linters null_ls.builtins.diagnostics.eslint_d, --#code actions null_ls.builtins.code_actions.eslint_d, }, fallback_severity = vim.diagnostic.severity.WARN, }) -- python -- lspconfig.pyright.setup({ -- on_attach = function(client, bufnr) -- custom_attach(client, bufnr) -- -- 'Organize imports' keymap for pyright only -- vim.keymap.set("n", "ii", "PyrightOrganizeImports", { -- buffer = bufnr, -- silent = true, -- noremap = true, -- }) -- end, -- settings = { -- pyright = { -- disableOrganizeImports = false, -- analysis = { -- useLibraryCodeForTypes = true, -- autoSearchPaths = true, -- diagnosticMode = "workspace", -- autoImportCompletions = true, -- }, -- }, -- }, -- }) -- typescript lspconfig.tsserver.setup({ on_attach = function(client, bufnr) if not web_dev_attach(client, bufnr) then return end -- local ts_utils = require("nvim-lsp-ts-utils") -- ts_utils.setup({ -- update_imports_on_move = false, -- enable_import_on_completion = true, -- }) -- ts_utils.setup_client(client) -- TS specific mappings vim.keymap.set("n", "ii", "TSLspOrganize", { buffer = bufnr, silent = true, noremap = true }) -- organize imports vim.keymap.set("n", "R", "TSLspRenameFile", { buffer = bufnr, silent = true, noremap = true }) -- rename file AND update references to it end, }) -- vue -- lspconfig.vuels.setup({ -- on_attach = web_dev_attach, -- settings = { -- vetur = { -- completion = { -- autoImport = true, -- tagCasing = "kebab", -- useScaffoldSnippets = true, -- }, -- useWorkspaceDependencies = true, -- experimental = { -- templateInterpolationService = true, -- }, -- }, -- format = { -- enable = true, -- options = { -- useTabs = false, -- tabSize = 2, -- }, -- defaultFormatter = { -- ts = "prettier", -- }, -- scriptInitialIndent = false, -- styleInitialIndent = false, -- }, -- validation = { -- template = true, -- script = true, -- style = true, -- templateProps = true, -- interpolation = true, -- }, -- }, -- }) -- lspconfig.volar.setup({ -- on_attach = web_dev_attach, -- -- enable "take over mode" for typescript files as well: https://github.com/johnsoncodehk/volar/discussions/471 -- filetypes = { "typescript", "javascript", "javascriptreact", "typescriptreact", "vue" }, -- init_options = { -- typescript = { -- -- "take over mode" can be weird in monorepos, use a global typescript installation instead -- serverPath = vim.fn.expand("~") .. "/.config/yarn/global/node_modules/typescript/lib/tsserverlibrary.js", -- }, -- }, -- }) -- yaml lspconfig.yamlls.setup({ on_attach = custom_attach, }) -- -- bash -- lspconfig.bashls.setup({ -- on_attach = custom_attach, -- }) -- -- lua -- lspconfig.sumneko_lua.setup({ -- on_attach = function(client, bufnr) -- custom_attach(client, bufnr) -- format_on_save(bufnr) -- end, -- settings = { -- Lua = { -- runtime = { -- -- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim) -- version = "LuaJIT", -- }, -- diagnostics = { -- -- Get the language server to recognize the `vim` global -- globals = { "vim" }, -- }, -- workspace = { -- -- Make the server aware of Neovim runtime files -- library = vim.api.nvim_get_runtime_file("", true), -- }, -- -- Do not send telemetry data containing a randomized but unique identifier -- telemetry = { -- enable = false, -- }, -- }, -- }, -- }) -- json w/ common schemas -- lspconfig.jsonls.setup({ -- on_attach = custom_attach, -- settings = { -- json = { -- schemas = require("schemastore").json.schemas(), -- validate = { enable = true }, -- }, -- }, -- }) -- rust -- lspconfig.rust_analyzer.setup({ -- on_attach = custom_attach, -- }) for type, icon in pairs(signs) do local hl = "DiagnosticSign" .. type vim.fn.sign_define(hl, { text = icon, texthl = hl, numhl = "" }) end return { lsp_filetypes = lsp_filetypes, }