feat(typescript): split typescript extra in main, vtsls and tsgo

This commit is contained in:
Folke Lemaitre 2026-03-20 21:40:30 +01:00
parent 1b4be534f1
commit 9029d928d2
No known key found for this signature in database
GPG key ID: 9B52594D560070AB
5 changed files with 319 additions and 229 deletions

View file

@ -348,13 +348,81 @@ function M.init()
M.json.load()
end
---@alias LazyVimDefault {name: string, extra: string, enabled?: boolean, origin?: "global" | "default" | "extra" }
---@alias LazyVimDefault {name: string, group: string, extra: string, import: string, enabled?: boolean, origin?: "global" | "default" | "extra" }
---
local default_extras ---@type table<string, LazyVimDefault>
---@param name string
---@param extras LazyVimDefault[]
function M.register_defaults(name, extras)
assert(default_extras, "defaults should be loaded by now, this should never happen")
local valid = vim.tbl_map(function(extra)
return extra.name
end, extras) --[[@as string[] ]]
local origin = "default"
local ret ---@type LazyVimDefault?
local use ---@type string?
local global = vim.g["lazyvim_" .. name]
if vim.tbl_contains(valid, global) then
origin = "global" -- was set by the user in their config
use = global
else
if global and global ~= "auto" then
vim.notify(
("Invalid value for `vim.g.lazyvim_%s`: `%s`\nValid options are: %s"):format(
name,
global,
table.concat(valid, ", ")
),
vim.log.levels.ERROR,
{ title = "LazyVim" }
)
end
for _, extra in ipairs(extras) do
if LazyVim.has_extra(extra.extra) then
use = extra.name -- was imported by the user in their lazy spec or added by LazyExtras
origin = "extra"
break
end
end
end
use = use or valid[1] -- fallback to the first one if nothing was set
for _, extra in ipairs(extras) do
local import = "lazyvim.plugins.extras." .. extra.extra
extra = vim.deepcopy(extra)
extra.enabled = extra.name == use
extra.import = import
extra.group = name
if extra.enabled then
extra.origin = origin
ret = extra
end
default_extras[import] = extra
end
return assert(ret, "One of the extras should be enabled, this should never happen")
end
---@param group string
---@return LazyVimDefault?
function M.get_default(group)
for _, extra in pairs(M.get_defaults()) do
if extra.group == group and extra.enabled then
return extra
end
end
end
function M.get_defaults()
if default_extras then
return default_extras
end
default_extras = {}
---@type table<string, LazyVimDefault[]>
local checks = {
picker = {
@ -378,36 +446,10 @@ function M.get_defaults()
table.insert(checks.explorer, 1, table.remove(checks.explorer, 2))
end
default_extras = {}
for name, check in pairs(checks) do
local valid = {} ---@type string[]
for _, extra in ipairs(check) do
if extra.enabled ~= false then
valid[#valid + 1] = extra.name
end
end
local origin = "default"
local use = vim.g["lazyvim_" .. name]
use = vim.tbl_contains(valid, use or "auto") and use or nil
origin = use and "global" or origin
for _, extra in ipairs(use and {} or check) do
if extra.enabled ~= false and LazyVim.has_extra(extra.extra) then
use = extra.name
break
end
end
origin = use and "extra" or origin
use = use or valid[1]
for _, extra in ipairs(check) do
local import = "lazyvim.plugins.extras." .. extra.extra
extra = vim.deepcopy(extra)
extra.enabled = extra.name == use
if extra.enabled then
extra.origin = origin
end
default_extras[import] = extra
end
for name, extras in pairs(checks) do
M.register_defaults(name, extras)
end
return default_extras
end

View file

@ -1,11 +1,7 @@
if lazyvim_docs then
-- LSP Server to use for TypeScript.
vim.g.lazyvim_ts_lsp = "vtsls" -- currently the default
-- Set to "tsgo" to use the new typescript-language-server implementation instead of tsserver.
vim.g.lazyvim_ts_lsp = "tsgo"
end
local lsp = vim.g.lazyvim_ts_lsp or "vtsls"
local extra = LazyVim.config.register_defaults("ts_lsp", {
{ name = "vtsls", extra = "lang.typescript.vtsls" },
{ name = "tsgo", extra = "lang.typescript.tsgo" },
})
return {
recommended = function()
@ -22,200 +18,13 @@ return {
})
end,
{ import = extra.import },
-- correctly setup lspconfig
{
"neovim/nvim-lspconfig",
opts = {
-- make sure mason installs the server
servers = {
---@type lspconfig.settings.tsgo
tsgo = {
-- explicitly add default filetypes, so that we can extend
-- them in related extras
filetypes = {
"javascript",
"javascriptreact",
"javascript.jsx",
"typescript",
"typescriptreact",
"typescript.tsx",
},
settings = {
typescript = {
inlayHints = {
parameterNames = {
enabled = "literals",
suppressWhenArgumentMatchesName = true,
},
parameterTypes = { enabled = true },
variableTypes = { enabled = true },
propertyDeclarationTypes = { enabled = true },
functionLikeReturnTypes = { enabled = true },
enumMemberValues = { enabled = true },
},
},
},
},
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",
},
{
"<leader>cM",
LazyVim.lsp.action["source.addMissingImports.ts"],
desc = "Add missing imports",
},
{
"<leader>cD",
LazyVim.lsp.action["source.fixAll.ts"],
desc = "Fix all diagnostics",
},
{
"<leader>cV",
function()
LazyVim.lsp.execute({
title = "Select TypeScript Version",
filter = "vtsls",
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)
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,
},
},
},
{
"neovim/nvim-lspconfig",
opts = function(_, opts)
local lsp = extra.name or "vtsls"
local servers = { "tsserver", "ts_ls", "vtsls", "tsgo", lsp }
for _, server in ipairs(servers) do
opts.servers[server] = opts.servers[server] or {}

View file

@ -0,0 +1,58 @@
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 = {
---@type lspconfig.settings.tsgo
tsgo = {
-- explicitly add default filetypes, so that we can extend
-- them in related extras
filetypes = {
"javascript",
"javascriptreact",
"javascript.jsx",
"typescript",
"typescriptreact",
"typescript.tsx",
},
settings = {
typescript = {
inlayHints = {
parameterNames = {
enabled = "literals",
suppressWhenArgumentMatchesName = true,
},
parameterTypes = { enabled = true },
variableTypes = { enabled = true },
propertyDeclarationTypes = { enabled = true },
functionLikeReturnTypes = { enabled = true },
enumMemberValues = { enabled = true },
},
},
},
},
},
},
},
-- correctly setup lspconfig
{
"neovim/nvim-lspconfig",
opts = {},
},
}

View file

@ -0,0 +1,174 @@
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 = {
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",
},
{
"<leader>cM",
LazyVim.lsp.action["source.addMissingImports.ts"],
desc = "Add missing imports",
},
{
"<leader>cD",
LazyVim.lsp.action["source.fixAll.ts"],
desc = "Fix all diagnostics",
},
{
"<leader>cV",
function()
LazyVim.lsp.execute({
title = "Select TypeScript Version",
filter = "vtsls",
command = "typescript.selectTypeScriptVersion",
})
end,
desc = "Select TS workspace version",
},
},
},
},
setup = {
vtsls = function(_, opts)
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,
},
},
},
-- correctly setup lspconfig
{
"neovim/nvim-lspconfig",
opts = {},
},
}

View file

@ -115,6 +115,13 @@ function M.get_extra(source, modname)
recommended = M.wants(recommended)
end
-- language extras that are disabled because a conflict with another extra is enabled are not recommended
local defaults = LazyVim.config.get_defaults()
local def = defaults[modname]
if def and def.enabled == false and vim.startswith(modname, "lazyvim.plugins.extras.lang.") then
recommended = false
end
---@type LazyExtra
return {
source = source,