{ config, pkgs, lib, ... }: let inherit (builtins) attrNames elem; inherit (lib.options) mkEnableOption mkOption literalExpression; inherit (lib.modules) mkIf mkMerge; inherit (lib) genAttrs; inherit (lib.meta) getExe; inherit (lib.types) enum bool listOf; inherit (lib.nvim.attrsets) mapListToAttrs; inherit (lib.nvim.lua) toLuaObject; inherit (lib.nvim.types) mkGrammarOption diagnostics mkPluginSetupOption deprecatedSingleOrListOf enumWithRename; inherit (lib.nvim.dag) entryAnywhere; cfg = config.vim.languages.ts; defaultServers = ["typescript-language-server"]; servers = ["typescript-language-server" "deno" "typescript-go"]; # TODO: specify packages defaultFormat = ["prettier"]; formats = { prettier = { command = getExe pkgs.prettier; }; prettierd = { command = getExe pkgs.prettierd; }; biome = { command = getExe pkgs.biome; }; biome-check = { command = getExe pkgs.biome; }; biome-organize-imports = { command = getExe pkgs.biome; }; }; # TODO: specify packages defaultDiagnosticsProvider = ["eslint_d"]; diagnosticsProviders = { eslint_d = let pkg = pkgs.eslint_d; in { package = pkg; config = { cmd = getExe pkg; required_files = [ "eslint.config.js" "eslint.config.mjs" ".eslintrc" ".eslintrc.cjs" ".eslintrc.json" ".eslintrc.js" ".eslintrc.yml" ]; }; }; biomejs = let pkg = pkgs.biome; in { package = pkg; config = { cmd = getExe pkg; }; }; }; in { _file = ./ts.nix; options.vim.languages.ts = { enable = mkEnableOption "Typescript/Javascript language support"; treesitter = { enable = mkEnableOption "Typescript/Javascript treesitter" // { default = config.vim.languages.enableTreesitter; defaultText = literalExpression "config.vim.languages.enableTreesitter"; }; tsPackage = mkGrammarOption pkgs "typescript"; tsxPackage = mkGrammarOption pkgs "tsx"; jsPackage = mkGrammarOption pkgs "javascript"; }; lsp = { enable = mkEnableOption "Typescript/Javascript LSP support" // { default = config.vim.lsp.enable; defaultText = literalExpression "config.vim.lsp.enable"; }; servers = mkOption { type = listOf (enumWithRename "vim.languages.ts.lsp.servers" servers { ts_ls = "typescript-language-server"; denols = "deno"; tsgo = "typescript-go"; }); default = defaultServers; description = "Typescript/Javascript LSP server to use"; }; }; format = { enable = mkEnableOption "Typescript/Javascript formatting" // { default = config.vim.languages.enableFormat; defaultText = literalExpression "config.vim.languages.enableFormat"; }; type = mkOption { description = "Typescript/Javascript formatter to use"; type = deprecatedSingleOrListOf "vim.language.ts.format.type" (enum (attrNames formats)); default = defaultFormat; }; }; extraDiagnostics = { enable = mkEnableOption "extra Typescript/Javascript diagnostics" // {default = config.vim.languages.enableExtraDiagnostics;}; types = diagnostics { langDesc = "Typescript/Javascript"; inherit diagnosticsProviders; inherit defaultDiagnosticsProvider; }; }; extensions = { ts-error-translator = { enable = mkEnableOption '' [ts-error-translator.nvim]: https://github.com/dmmulroy/ts-error-translator.nvim Typescript error translation with [ts-error-translator.nvim] ''; setupOpts = mkPluginSetupOption "ts-error-translator" { # This is the default configuration behaviour. auto_override_publish_diagnostics = mkOption { description = "Automatically override the publish_diagnostics handler"; type = bool; default = true; }; }; }; }; }; config = mkIf cfg.enable (mkMerge [ {vim.globals.markdown_fenced_languages = ["ts=typescript"];} (mkIf cfg.treesitter.enable { vim.treesitter.enable = true; vim.treesitter.grammars = [ cfg.treesitter.tsPackage cfg.treesitter.tsxPackage cfg.treesitter.jsPackage ]; }) (mkIf cfg.lsp.enable { vim.lsp = { presets = genAttrs cfg.lsp.servers (_: {enable = true;}); servers = genAttrs cfg.lsp.servers (_: { filetypes = [ "typescript" # TODO: move to a React module "typescriptreact" "typescript.tsx" "javascriptreact" "javascript.jsx" # TODO: move to a JavaScript module "javascript" ]; }); }; }) (mkIf cfg.format.enable { vim.formatter.conform-nvim = { enable = true; setupOpts = { formatters_by_ft = { typescript = cfg.format.type; javascript = cfg.format.type; # .tsx/.jsx files typescriptreact = cfg.format.type; }; formatters = mapListToAttrs (name: { inherit name; value = formats.${name}; }) cfg.format.type; }; }; }) (mkIf cfg.extraDiagnostics.enable { vim.diagnostics.nvim-lint = { enable = true; linters_by_ft.typescript = cfg.extraDiagnostics.types; linters_by_ft.typescriptreact = cfg.extraDiagnostics.types; linters = mkMerge (map (name: {${name} = diagnosticsProviders.${name}.config;}) cfg.extraDiagnostics.types); }; }) # Extensions (mkIf cfg.extensions."ts-error-translator".enable { vim.startPlugins = ["ts-error-translator-nvim"]; vim.pluginRC.ts-error-translator = entryAnywhere '' require("ts-error-translator").setup(${toLuaObject cfg.extensions.ts-error-translator.setupOpts}) ''; }) # Warn the user if they have set the default server name to "tsserver" to match upstream (us) # The name "tsserver" has been deprecated, and now should be called "typescript-language-server". { assertions = [ { assertion = cfg.lsp.enable -> !(elem "tsserver" cfg.lsp.servers); message = '' The name `tsserver` has been deprecated, and now should be called `typescript-language-server`. Please set `vim.languages.ts.lsp.server` to `["typescript-language-server" ...]` instead of to `["tsserver" ...]` Please see: - - for more details about this change. ''; } ]; } ]); }