language/ts: migrate to vim.lsp.servers

This commit is contained in:
Ching Pei Yang 2025-07-11 18:52:56 +02:00
commit 76c031bbda
No known key found for this signature in database
GPG key ID: B3841364253DC4C8

View file

@ -4,73 +4,177 @@
lib, lib,
... ...
}: let }: let
inherit (builtins) attrNames; inherit (builtins) attrNames elem;
inherit (lib.options) mkEnableOption mkOption; inherit (lib.options) mkEnableOption mkOption;
inherit (lib.modules) mkIf mkMerge; inherit (lib.modules) mkIf mkMerge;
inherit (lib.lists) isList;
inherit (lib.meta) getExe; inherit (lib.meta) getExe;
inherit (lib.types) enum either listOf package str bool; inherit (lib.types) enum listOf package bool;
inherit (lib.nvim.lua) expToLua toLuaObject; inherit (lib.generators) mkLuaInline;
inherit (lib.nvim.attrsets) mapListToAttrs;
inherit (lib.nvim.lua) toLuaObject;
inherit (lib.nvim.types) mkGrammarOption diagnostics mkPluginSetupOption; inherit (lib.nvim.types) mkGrammarOption diagnostics mkPluginSetupOption;
inherit (lib.nvim.dag) entryAnywhere; inherit (lib.nvim.dag) entryAnywhere entryBefore;
cfg = config.vim.languages.ts; cfg = config.vim.languages.ts;
defaultServer = "ts_ls"; defaultServers = ["ts_ls"];
servers = { servers = let
ts_ls = { ts_ls = {
package = pkgs.typescript-language-server; cmd = [(getExe pkgs.typescript-language-server) "--stdio"];
lspConfig = '' init_options = {hostInfo = "neovim";};
lspconfig.ts_ls.setup { filetypes = [
capabilities = capabilities, "javascript"
on_attach = function(client, bufnr) "javascriptreact"
attach_keymaps(client, bufnr); "javascript.jsx"
client.server_capabilities.documentFormattingProvider = false; "typescript"
end, "typescriptreact"
cmd = ${ "typescript.tsx"
if isList cfg.lsp.package ];
then expToLua cfg.lsp.package root_markers = ["tsconfig.json" "jsconfig.json" "package.json" ".git"];
else ''{"${cfg.lsp.package}/bin/typescript-language-server", "--stdio"}'' handlers = {
} # handle rename request for certain code actions like extracting functions / types
} "_typescript.rename" = mkLuaInline ''
function(_, result, ctx)
local client = assert(vim.lsp.get_client_by_id(ctx.client_id))
vim.lsp.util.show_document({
uri = result.textDocument.uri,
range = {
start = result.position,
['end'] = result.position,
},
}, client.offset_encoding)
vim.lsp.buf.rename()
return vim.NIL
end
'';
};
on_attach = mkLuaInline ''
function(client, bufnr)
default_on_attach(client, bufnr);
-- ts_ls provides `source.*` code actions that apply to the whole file. These only appear in
-- `vim.lsp.buf.code_action()` if specified in `context.only`.
vim.api.nvim_buf_create_user_command(0, 'LspTypescriptSourceAction', function()
local source_actions = vim.tbl_filter(function(action)
return vim.startswith(action, 'source.')
end, client.server_capabilities.codeActionProvider.codeActionKinds)
vim.lsp.buf.code_action({
context = {
only = source_actions,
},
})
end, {})
end
''; '';
}; };
in {
denols = { inherit ts_ls;
package = pkgs.deno;
lspConfig = ''
vim.g.markdown_fenced_languages = { "ts=typescript" }
lspconfig.denols.setup {
capabilities = capabilities;
on_attach = attach_keymaps,
cmd = ${
if isList cfg.lsp.package
then expToLua cfg.lsp.package
else ''{"${cfg.lsp.package}/bin/deno", "lsp"}''
}
}
'';
};
# Here for backwards compatibility. Still consider tsserver a valid # Here for backwards compatibility. Still consider tsserver a valid
# configuration in the enum, but assert if it's set to *properly* # configuration in the enum, but assert if it's set to *properly*
# redirect the user to the correct server. # redirect the user to the correct server.
tsserver = { tsserver = ts_ls;
package = pkgs.typescript-language-server;
lspConfig = '' denols = {
lspconfig.ts_ls.setup { cmd = [(getExe pkgs.deno) "lsp"];
capabilities = capabilities; cmd_env = {NO_COLOR = true;};
on_attach = attach_keymaps, filetypes = [
cmd = ${ "javascript"
if isList cfg.lsp.package "javascriptreact"
then expToLua cfg.lsp.package "javascript.jsx"
else ''{"${cfg.lsp.package}/bin/typescript-language-server", "--stdio"}'' "typescript"
} "typescriptreact"
} "typescript.tsx"
];
root_markers = ["deno.json" "deno.jsonc" ".git"];
settings = {
deno = {
enable = true;
suggest = {
imports = {
hosts = {
"https://deno.land" = true;
};
};
};
};
};
handlers = {
"textDocument/definition" = mkLuaInline "nvf_denols_handler";
"textDocument/typeDefinition" = mkLuaInline "nvf_denols_handler";
"textDocument/references" = mkLuaInline "nvf_denols_handler";
};
on_attach = mkLuaInline ''
function(client, bufnr)
default_on_attach(client, bufnr)
vim.api.nvim_buf_create_user_command(0, 'LspDenolsCache', function()
client:exec_cmd({
command = 'deno.cache',
arguments = { {}, vim.uri_from_bufnr(bufnr) },
}, { bufnr = bufnr }, function(err, _result, ctx)
if err then
local uri = ctx.params.arguments[2]
vim.api.nvim_err_writeln('cache command failed for ' .. vim.uri_to_fname(uri))
end
end)
end, {
desc = 'Cache a module and all of its dependencies.',
})
end
''; '';
}; };
}; };
denols_handlers = ''
local function nvf_denols_virtual_text_document_handler(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, false, lines)
vim.api.nvim_set_option_value('readonly', true, { buf = bufnr })
vim.api.nvim_set_option_value('modified', false, { buf = bufnr })
vim.api.nvim_set_option_value('modifiable', false, { buf = bufnr })
vim.lsp.buf_attach_client(bufnr, client.id)
end
local function nvf_denols_virtual_text_document(uri, client)
local params = {
textDocument = {
uri = uri,
},
}
local result = client.request_sync('deno/virtualTextDocument', params)
nvf_denols_virtual_text_document_handler(uri, result, client)
end
local function nvf_denols_handler(err, result, ctx, config)
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
nvf_denols_virtual_text_document(uri, client)
res['uri'] = uri
res['targetUri'] = uri
end
end
vim.lsp.handlers[ctx.method](err, result, ctx, config)
end
'';
# TODO: specify packages # TODO: specify packages
defaultFormat = "prettier"; defaultFormat = "prettier";
formats = { formats = {
@ -122,17 +226,10 @@ in {
lsp = { lsp = {
enable = mkEnableOption "Typescript/Javascript LSP support" // {default = config.vim.lsp.enable;}; enable = mkEnableOption "Typescript/Javascript LSP support" // {default = config.vim.lsp.enable;};
server = mkOption { servers = mkOption {
type = singleOrListOf (enum (attrNames servers));
default = defaultServers;
description = "Typescript/Javascript LSP server to use"; description = "Typescript/Javascript LSP server to use";
type = enum (attrNames servers);
default = defaultServer;
};
package = mkOption {
description = "Typescript/Javascript LSP server package, or the command to run as a list of strings";
example = ''[lib.getExe pkgs.jdt-language-server "-data" "~/.cache/jdtls/workspace"]'';
type = either package (listOf str);
default = servers.${cfg.lsp.server}.package;
}; };
}; };
@ -190,8 +287,17 @@ in {
}) })
(mkIf cfg.lsp.enable { (mkIf cfg.lsp.enable {
vim.lsp.lspconfig.enable = true; vim.lsp.servers =
vim.lsp.lspconfig.sources.ts-lsp = servers.${cfg.lsp.server}.lspConfig; mapListToAttrs (name: {
inherit name;
value = servers.${name};
})
cfg.lsp.servers;
})
(mkIf (cfg.lsp.enable && elem "denols" cfg.lsp.servers) {
vim.globals.markdown_fenced_languages = ["ts=typescript"];
vim.luaConfigRC.denols_handlers = entryBefore ["lsp-servers"] denols_handlers;
}) })
(mkIf cfg.format.enable { (mkIf cfg.format.enable {
@ -234,7 +340,7 @@ in {
{ {
assertions = [ assertions = [
{ {
assertion = cfg.lsp.enable -> cfg.lsp.server != "tsserver"; assertion = cfg.lsp.enable -> !(elem "tsserver" cfg.lsp.servers);
message = '' message = ''
As of a recent lspconfig update, the `tsserver` configuration has been renamed As of a recent lspconfig update, the `tsserver` configuration has been renamed
to `ts_ls` to match upstream behaviour of `lspconfig`, and the name `tsserver` to `ts_ls` to match upstream behaviour of `lspconfig`, and the name `tsserver`