diff --git a/README.md b/README.md index 8c59bff..549f8e7 100644 --- a/README.md +++ b/README.md @@ -181,6 +181,21 @@ local opts = { }, }, }, + -- Extra configuration for the `mason-lspconfig.nvim` plugin + mason_lspconfig = { + -- Allow registering more Mason packages as language servers for autodetection/setup + servers = { + -- The key is the lspconfig server name to register a package for + nextflow_ls = { + -- The Mason package name to register to the language server + package = "nextflow-language-server", + -- The filetypes that apply to the package and language server + filetypes = { "nextflow" }, + -- (Optional) any default configuration changes that may need to happen (can be a table or a function that returns a table) + config = { cmd = { "nextflow-language-server" } } + } + } + } -- A list like table of servers that should be setup, useful for enabling language servers not installed with Mason. servers = { "dartls" }, -- A custom `on_attach` function to be run after the default `on_attach` function, takes two parameters `client` and `bufnr` (`:h lspconfig-setup`) @@ -217,12 +232,16 @@ local opts = { { "williamboman/mason-lspconfig.nvim", -- MUST be set up before `nvim-lspconfig` dependencies = { "williamboman/mason.nvim" }, - opts = function() - return { - -- use AstroLSP setup for mason-lspconfig - handlers = { function(server) require("astrolsp").lsp_setup(server) end }, - } - end, + opts = { + -- use AstroLSP setup for mason-lspconfig + handlers = { function(server) require("astrolsp").lsp_setup(server) end }, + }, + config = function(_, opts) + -- Optionally tell AstroLSP to register new language servers before calling the `setup` function + -- this enables the `mason-lspconfig.servers` option in the AstroLSP configuration + require("astrolsp.mason-lspconfig").register_servers() + require("mason-lspconfig").setup(opts) + end }, }, config = function() diff --git a/lua/astrolsp/config.lua b/lua/astrolsp/config.lua index d827527..ebf815a 100644 --- a/lua/astrolsp/config.lua +++ b/lua/astrolsp/config.lua @@ -47,6 +47,16 @@ ---@field hover vim.lsp.buf.hover.Opts? control the default options for `vim.lsp.buf.hover()` (`:h vim.lsp.buf.hover.Opts`) ---@field signature_help vim.lsp.buf.signature_help.Opts? control the default options for `vim.lsp.buf.signature_help()` (`:h vim.lsp.buf.signature_help.Opts`) +---@class AstroLSPMasonLspconfigServer +---@field public package string the Mason package name +---@field filetypes string|string[] the filetype(s) that the server applies to +---@field config? table|(fun(): table) extensions tothe default language server configuration + +---@alias AstroLSPMasonLspconfigServers { [string]: AstroLSPMasonLspconfigServer } + +---@class AstroLSPMasonLspconfigOpts +---@field servers AstroLSPMasonLspconfigServers? a table of servers to register with mason-lspconfig.nvim + ---@class AstroLSPOpts ---Configuration of auto commands ---The key into the table is the group name for the auto commands (`:h augroup`) and the value @@ -253,6 +263,21 @@ ---} ---``` ---@field mappings AstroLSPMappings? +---Extra options for the `mason-lspconfig.nvim` plugin such as registering new packages as language servers. +---Example: +-- +---```lua +---mason_lspconfig = { +--- servers = { +--- nextflow_ls = { +--- package = "nextflow-language-server", +--- filetypes = "nextflow", +--- config = { cmd = { "nextflow-language-server" } } +--- } +--- } +---} +---``` +---@field mason_lspconfig AstroLSPMasonLspconfigOpts? ---A list like table of servers that should be setup, useful for enabling language servers not installed with Mason. ---Example: -- @@ -292,6 +317,7 @@ local M = { handlers = {}, lsp_handlers = {}, mappings = {}, + mason_lspconfig = {}, servers = {}, on_attach = nil, } diff --git a/lua/astrolsp/mason-lspconfig.lua b/lua/astrolsp/mason-lspconfig.lua new file mode 100644 index 0000000..3f988e5 --- /dev/null +++ b/lua/astrolsp/mason-lspconfig.lua @@ -0,0 +1,60 @@ +---AstroNvim mason-lspconfig Utilities +--- +---Utilities for working with mason-lspconfig.nvim +--- +---This module can be loaded with `local astro_mason_lspconfig = require "astrocore.mason-lspconfig"` +--- +---copyright 2025 +---license GNU General Public License v3.0 +---@class astrocore.mason-lspconfig +local M = {} + +local function resolve_config() return require("astrolsp").config.mason_lspconfig or {} end + +--- Register a new language server with mason-lspconfig +---@param server string the server name in lspconfig +---@param spec AstroLSPMasonLspconfigServer the details for registering the server +function M.register_server(server, spec) + local filetype_mappings_avail, filetype_mappings = pcall(require, "mason-lspconfig.mappings.filetype") + local server_mappings_avail, server_mappings = pcall(require, "mason-lspconfig.mappings.server") + + if not (filetype_mappings_avail and server_mappings_avail) then + vim.notify("Unable to properly load required `mason-lspconfig` modules", vim.log.levels.ERROR) + end + + -- register server in the filetype maps + local filetypes = spec.filetypes + if type(filetypes) ~= "table" then filetypes = { filetypes } end + for _, filetype in ipairs(filetypes) do + if not filetype_mappings[filetype] then filetype_mappings[filetype] = {} end + table.insert(filetype_mappings, server) + end + -- register the mappings between lspconfig server name and mason package name + server_mappings.lspconfig_to_package[server] = spec.package + server_mappings.package_to_lspconfig[spec.package] = server + -- if a config is provided, set up a mason-lspconfig server configuration module + if spec.config then + local module = spec.config + if type(module) == "table" then + local orig_function = module + module = function() return orig_function end + end + local module_name = "mason-lspconfig.server_configurations." .. server + if package.loaded[module_name] == nil then + package.preload[module_name] = function() return module end + else + package.loaded[module_name] = module + end + end +end + +--- Register multiple new language servers with mason-lspconfig +---@param server_specs? AstroLSPMasonLspconfigServers +function M.register_servers(server_specs) + if not server_specs then server_specs = resolve_config().servers or {} end + for server, spec in pairs(server_specs) do + M.register_server(server, spec) + end +end + +return M