return { recommended = function() return LazyVim.extras.wants({ ft = { "javascript", "javascriptreact", "javascript.jsx", "typescript", "typescriptreact", "typescript.tsx", }, root = { "tsconfig.json", "package.json", "jsconfig.json" }, }) end, -- correctly setup lspconfig { "neovim/nvim-lspconfig", opts = { -- make sure mason installs the server servers = { --- @deprecated -- tsserver renamed to ts_ls but not yet released, so keep this for now --- the proper approach is to check the nvim-lspconfig release version when it's released to determine the server name dynamically tsserver = { enabled = false, }, ts_ls = { enabled = false, }, vtsls = { -- explicitly add default filetypes, so that we can extend -- them in related extras filetypes = { "javascript", "javascriptreact", "javascript.jsx", "typescript", "typescriptreact", "typescript.tsx", }, settings = { complete_function_calls = true, vtsls = { enableMoveToFileCodeAction = true, autoUseWorkspaceTsdk = true, experimental = { maxInlayHintLength = 30, completion = { enableServerSideFuzzyMatch = true, }, }, }, typescript = { updateImportsOnFileMove = { enabled = "always" }, suggest = { completeFunctionCalls = true, }, inlayHints = { enumMemberValues = { enabled = true }, functionLikeReturnTypes = { enabled = true }, parameterNames = { enabled = "literals" }, parameterTypes = { enabled = true }, propertyDeclarationTypes = { enabled = true }, variableTypes = { enabled = false }, }, }, }, keys = { { "gD", function() local win = vim.api.nvim_get_current_win() local params = vim.lsp.util.make_position_params(win, "utf-16") LazyVim.lsp.execute({ command = "typescript.goToSourceDefinition", arguments = { params.textDocument.uri, params.position }, open = true, }) end, desc = "Goto Source Definition", }, { "gR", function() LazyVim.lsp.execute({ command = "typescript.findAllFileReferences", arguments = { vim.uri_from_bufnr(0) }, open = true, }) end, desc = "File References", }, { "co", LazyVim.lsp.action["source.organizeImports"], desc = "Organize Imports", }, { "cM", LazyVim.lsp.action["source.addMissingImports.ts"], desc = "Add missing imports", }, { "cu", LazyVim.lsp.action["source.removeUnused.ts"], desc = "Remove unused imports", }, { "cD", LazyVim.lsp.action["source.fixAll.ts"], desc = "Fix all diagnostics", }, { "cV", function() LazyVim.lsp.execute({ command = "typescript.selectTypeScriptVersion" }) end, desc = "Select TS workspace version", }, }, }, }, setup = { --- @deprecated -- tsserver renamed to ts_ls but not yet released, so keep this for now --- the proper approach is to check the nvim-lspconfig release version when it's released to determine the server name dynamically tsserver = function() -- disable tsserver return true end, ts_ls = function() -- disable tsserver return true end, vtsls = function(_, opts) if vim.lsp.config.denols and vim.lsp.config.vtsls then ---@param server string local resolve = function(server) local markers, root_dir = vim.lsp.config[server].root_markers, vim.lsp.config[server].root_dir vim.lsp.config(server, { root_dir = function(bufnr, on_dir) local is_deno = vim.fs.root(bufnr, { "deno.json", "deno.jsonc" }) ~= nil if is_deno == (server == "denols") then if root_dir then return root_dir(bufnr, on_dir) elseif type(markers) == "table" then local root = vim.fs.root(bufnr, markers) return root and on_dir(root) end end end, }) end resolve("denols") resolve("vtsls") end Snacks.util.lsp.on({ name = "vtsls" }, function(buffer, client) client.commands["_typescript.moveToFileRefactoring"] = function(command, ctx) ---@type string, string, lsp.Range local action, uri, range = unpack(command.arguments) local function move(newf) client:request("workspace/executeCommand", { command = command.command, arguments = { action, uri, range, newf }, }) end local fname = vim.uri_to_fname(uri) client:request("workspace/executeCommand", { command = "typescript.tsserverRequest", arguments = { "getMoveToRefactoringFileSuggestions", { file = fname, startLine = range.start.line + 1, startOffset = range.start.character + 1, endLine = range["end"].line + 1, endOffset = range["end"].character + 1, }, }, }, function(_, result) ---@type string[] local files = result.body.files table.insert(files, 1, "Enter new path...") vim.ui.select(files, { prompt = "Select move destination:", format_item = function(f) return vim.fn.fnamemodify(f, ":~:.") end, }, function(f) if f and f:find("^Enter new path") then vim.ui.input({ prompt = "Enter move destination:", default = vim.fn.fnamemodify(fname, ":h") .. "/", completion = "file", }, function(newf) return newf and move(newf) end) elseif f then move(f) end end) end) end end) -- copy typescript settings to javascript opts.settings.javascript = vim.tbl_deep_extend("force", {}, opts.settings.typescript, opts.settings.javascript or {}) end, }, }, }, { "mfussenegger/nvim-dap", optional = true, dependencies = { { "mason-org/mason.nvim", opts = function(_, opts) opts.ensure_installed = opts.ensure_installed or {} table.insert(opts.ensure_installed, "js-debug-adapter") end, }, }, opts = function() local dap = require("dap") for _, adapterType in ipairs({ "node", "chrome", "msedge" }) do local pwaType = "pwa-" .. adapterType if not dap.adapters[pwaType] then dap.adapters[pwaType] = { type = "server", host = "localhost", port = "${port}", executable = { command = "js-debug-adapter", args = { "${port}" }, }, } end -- Define adapters without the "pwa-" prefix for VSCode compatibility if not dap.adapters[adapterType] then dap.adapters[adapterType] = function(cb, config) local nativeAdapter = dap.adapters[pwaType] config.type = pwaType if type(nativeAdapter) == "function" then nativeAdapter(cb, config) else cb(nativeAdapter) end end end end local js_filetypes = { "typescript", "javascript", "typescriptreact", "javascriptreact" } local vscode = require("dap.ext.vscode") vscode.type_to_filetypes["node"] = js_filetypes vscode.type_to_filetypes["pwa-node"] = js_filetypes for _, language in ipairs(js_filetypes) do if not dap.configurations[language] then local runtimeExecutable = nil if language:find("typescript") then runtimeExecutable = vim.fn.executable("tsx") == 1 and "tsx" or "ts-node" end dap.configurations[language] = { { type = "pwa-node", request = "launch", name = "Launch file", program = "${file}", cwd = "${workspaceFolder}", sourceMaps = true, runtimeExecutable = runtimeExecutable, skipFiles = { "/**", "node_modules/**", }, resolveSourceMapLocations = { "${workspaceFolder}/**", "!**/node_modules/**", }, }, { type = "pwa-node", request = "attach", name = "Attach", processId = require("dap.utils").pick_process, cwd = "${workspaceFolder}", sourceMaps = true, runtimeExecutable = runtimeExecutable, skipFiles = { "/**", "node_modules/**", }, resolveSourceMapLocations = { "${workspaceFolder}/**", "!**/node_modules/**", }, }, } end end end, }, { "jay-babu/mason-nvim-dap.nvim", optional = true, opts = { -- chrome adapter is deprecated, use js-debug-adapter instead automatic_installation = { exclude = { "chrome" } }, }, }, -- Filetype icons { "nvim-mini/mini.icons", opts = { file = { [".eslintrc.js"] = { glyph = "󰱺", hl = "MiniIconsYellow" }, [".node-version"] = { glyph = "", hl = "MiniIconsGreen" }, [".prettierrc"] = { glyph = "", hl = "MiniIconsPurple" }, [".yarnrc.yml"] = { glyph = "", hl = "MiniIconsBlue" }, ["eslint.config.js"] = { glyph = "󰱺", hl = "MiniIconsYellow" }, ["package.json"] = { glyph = "", hl = "MiniIconsGreen" }, ["tsconfig.json"] = { glyph = "", hl = "MiniIconsAzure" }, ["tsconfig.build.json"] = { glyph = "", hl = "MiniIconsAzure" }, ["yarn.lock"] = { glyph = "", hl = "MiniIconsBlue" }, }, }, }, }