local M = {} -- All configuration is taken from nvim-lspconfig except for may be minor changes -- -- Deno specific functions local buf_cache = function(bufnr, client) local params = {} params['referrer'] = { uri = vim.uri_from_bufnr(bufnr) } params['uris'] = {} client.request_sync('deno/cache', params) end local virtual_text_document_handler = function(uri, res, client) if not res then return nil end local lines = vim.split(res.result, '\n') local bufnr = vim.uri_to_bufnr(uri) local current_buf = vim.api.nvim_buf_get_lines(bufnr, 0, -1, false) if #current_buf ~= 0 then return nil end vim.api.nvim_buf_set_lines(bufnr, 0, -1, nil, lines) vim.api.nvim_buf_set_option(bufnr, 'readonly' , true) vim.api.nvim_buf_set_option(bufnr, 'modified' , false) vim.api.nvim_buf_set_option(bufnr, 'modifiable', false) vim.lsp.buf_attach_client(bufnr, client.id) end local virtual_text_document = function(uri, client) local params = { textDocument = { uri = uri, }, } local result = client.request_sync('deno/virtualTextDocument', params) virtual_text_document_handler(uri, result, client) end local denols_handler = function(err, result, ctx) if not result or vim.tbl_isempty(result) then return nil end local client = vim.lsp.get_client_by_id(ctx.client_id) for _, res in pairs(result) do local uri = res.uri or res.targetUri if uri:match '^deno:' then virtual_text_document(uri, client) res['uri'] = uri res['targetUri'] = uri end end vim.lsp.handlers[ctx.method](err, result, ctx) end -- End of deno specific functions local get_root_directory = function(root_files) local path = vim.fs.find(root_files, { type = "file" }) return vim.fs.dirname(path[1]) end local default_capabilities = vim.lsp.protocol.make_client_capabilities() function M.bashls_config() local root_directory = vim.fn.getcwd() return { name = "bashls", cmd = { 'bash-language-server', 'start' }, capabilities = default_capabilities, root_dir = root_directory, filetypes = { 'sh' }, single_file_support = true, cmd_env = { -- Prevent recursive scanning which will cause issues when opening a file -- directly in the home directory (e.g. ~/foo.sh). -- -- Default upstream pattern is "**/*@(.sh|.inc|.bash|.command)". GLOB_PATTERN = vim.env.GLOB_PATTERN or '*@(.sh|.inc|.bash|.command)', }, } end function M.clangd_config() local root_files = { '.clangd', '.clang-tidy', '.clang-format', 'compile_commands.json', 'compile_flags.txt' } local clangd_cmd = { "clangd", "--background-index", "--pch-storage=memory", "--clang-tidy", "--header-insertion=never" } local root_directory = get_root_directory(root_files) local clangd_caps = { textDocument = { completion = { editsNearCursor = true } }, offsetEncoding = { 'utf-8', 'utf-16' }, } return { name = "clangd", cmd = clangd_cmd, capabilities = clangd_caps, root_dir = root_directory, filetypes = { 'c', 'cpp' }, single_file_support = true, } end function M.deno_config() local root_files = { 'deno.json', 'deno.jsonc' } local file_types = { 'javascript', 'javascriptreact', 'javascript.jsx', 'typescript', 'typescriptreact', 'typescript.tsx' } local root_directory = get_root_directory(root_files) return { cmd = { 'deno', 'lsp' }, capabilities = default_capabilities, filetypes = file_types, root_dir = root_directory, settings = { deno = { enable = true, suggest = { imports = { hosts = { ['https://deno.land'] = true, ['https://crux.land'] = true, ['https://x.nest.land'] = true, }, }, }, }, }, handlers = { ['textDocument/definition'] = denols_handler, ['textDocument/typeDefinition'] = denols_handler, ['textDocument/references'] = denols_handler, ['workspace/executeCommand'] = function(err, result, context) if context.params.command == 'deno.cache' then buf_cache(context.bufnr, vim.lsp.get_client_by_id(context.client_id)) else vim.lsp.handlers[context.method](err, result, context) end end, }, commands = { DenolsCache = { function() local clients = vim.lsp.get_active_clients() for _, client in ipairs(clients) do if client.name == 'denols' then buf_cache(0, client) break end end end, description = 'Cache a module and all of its dependencies.', }, } } end function M.gopls_config() local root_files = { 'go.mod', 'go.work', '.git' } local root_directory = get_root_directory(root_files) return { name = "gopls", cmd = { "gopls" }, capabilities = default_capabilities, root_dir = root_directory, filetypes = { 'go', 'gomod', 'gowork', 'gotmpl' }, } end function M.hls_config() local root_files = { 'hie.yaml', 'stack.yaml', 'cabal.project', '*.cabal', 'package.yaml' } local root_directory = get_root_directory(root_files) return { name = "hls", cmd = { 'haskell-language-server-wrapper', '--lsp' }, capabilities = default_capabilities, root_dir = root_directory, filetypes = { 'haskell' }, single_file_support = true, settings = { haskell = { checkParents = 'CheckOnSave', checkProject = false, formattingProvider = 'ormolu', maxCompletions = 20, plugin = { stan = { globalOn = false } } } }, } end function M.pursls_config() local root_files = { 'bower.json', 'psc-package.json', 'spago.dhall' } local root_directory = get_root_directory(root_files) return { name = "purescriptls", cmd = { 'purescript-language-server', '--stdio' }, capabilities = default_capabilities, root_dir = root_directory, filetypes = { 'purescript' }, single_file_support = false, settings = { purescript = { formatter = 'purs-tidy', } }, } end function M.ra_config() local root_files = { "Cargo.toml" } local root_directory = get_root_directory(root_files) local ra_caps = vim.lsp.protocol.make_client_capabilities() ra_caps.experimental = { serverStatusNotification = true, } return { name = "rust-analyzer", cmd = { "rust-analyzer" }, capabilities = ra_caps, root_dir = root_directory, settings = { ["rust-analyzer"] = { procMacro = { enable = true }, checkOnSave = { command = "clippy" }, }, }, } end function M.ruff_config() local root_files = { 'pyproject.toml', 'setup.py', 'setup.cfg', 'requirements.txt', 'Pipfile' } local root_directory = get_root_directory(root_files) return { name = "ruff", cmd = { "ruff-lsp" }, filetypes = { 'python' }, capabilities = default_capabilities, root_dir = root_directory, single_file_support = true, } end function M.scheme_config() local root_files = { '.git' } local root_directory = get_root_directory(root_files) return { name = "racket-langserver", cmd = { 'racket', '--lib', 'racket-langserver' }, capabilities = default_capabilities, filetypes = { 'scheme', 'racket' }, root_dir = root_directory, single_file_support = true, } end function M.tsserver_config() local root_files = { 'jsconfig.json', 'tsconfig.json', 'package.json' } local file_types = { 'javascript', 'javascriptreact', 'javascript.jsx', 'typescript', 'typescriptreact', 'typescript.tsx' } local root_directory = get_root_directory(root_files) return { name = "tsserver", cmd = { "typescript-language-server", "--stdio" }, -- Support Yarn PnP -- hostInfo with init_options is required to allow yarn pnp sdk to -- identify editor and patch paths appropriately. init_options = { hostInfo = 'neovim' }, capabilities = default_capabilities, root_dir = root_directory, filetypes = file_types, settings = { typescript = { inlayHints = { includeInlayParameterNameHints = 'all', includeInlayParameterNameHintsWhenArgumentMatchesName = false, includeInlayFunctionParameterTypeHints = true, includeInlayVariableTypeHints = true, includeInlayPropertyDeclarationTypeHints = true, includeInlayFunctionLikeReturnTypeHints = true, includeInlayEnumMemberValueHints = true, } }, javascript = { inlayHints = { includeInlayParameterNameHints = 'all', includeInlayParameterNameHintsWhenArgumentMatchesName = false, includeInlayFunctionParameterTypeHints = true, includeInlayVariableTypeHints = true, includeInlayPropertyDeclarationTypeHints = true, includeInlayFunctionLikeReturnTypeHints = true, includeInlayEnumMemberValueHints = true, } } } } end function M.typescript_config() local root_files = { 'jsconfig.json', 'tsconfig.json', 'package.json' } local root_dir = get_root_directory(root_files) -- Look for npm based typescript files first and if found return tsserver -- configuration, else return deno configuration. This allows us to work -- with both npm and deno based typescript projects. if root_dir then return M.tsserver_config() else return M.deno_config() end end return M