diff --git a/lua/roslyn/config.lua b/lua/roslyn/config.lua index ab1bb79..dddec2e 100644 --- a/lua/roslyn/config.lua +++ b/lua/roslyn/config.lua @@ -77,7 +77,11 @@ end local roslyn_config = { filewatching = true, exe = default_exe(), - args = { "--logLevel=Information", "--extensionLogDirectory=" .. vim.fs.dirname(vim.lsp.get_log_path()) }, + args = { + "--logLevel=Information", + "--extensionLogDirectory=" .. vim.fs.dirname(vim.lsp.get_log_path()), + "--stdio", + }, ---@diagnostic disable-next-line: missing-fields config = { capabilities = default_capabilities(), @@ -126,6 +130,15 @@ function M.setup(user_config) end end + if not vim.tbl_contains(roslyn_config.args, "--stdio") then + vim.notify( + "roslyn.nvim requires the `--stdio` argument to be present. Please add it to your configuration", + vim.log.levels.WARN, + { title = "roslyn.nvim" } + ) + table.insert(roslyn_config.args, "--stdio") + end + -- HACK: Enable filewatching to later just not watch any files -- This is to not make the server watch files and make everything super slow in certain situations if not roslyn_config.filewatching then diff --git a/lua/roslyn/init.lua b/lua/roslyn/init.lua index 4081fb0..6fb8824 100644 --- a/lua/roslyn/init.lua +++ b/lua/roslyn/init.lua @@ -16,6 +16,9 @@ end local M = {} +---@type boolean +local roslyn_version_verified = false + ---@param config? RoslynNvimConfig function M.setup(config) local roslyn_config = require("roslyn.config").setup(config) @@ -33,6 +36,22 @@ function M.setup(config) return end + if not roslyn_version_verified then + -- TODO: Remove this in a few months or so + -- vim.system will fail with required args not provided if `--stdio` exists as an argument + -- to the version installed, so this should be safe + local cmd = vim.list_extend(vim.deepcopy(roslyn_config.exe), { "--stdio" }) + local stderr = vim.system(cmd):wait().stderr + if stderr and string.find(stderr, "Unrecognized command or argument '--stdio'.", 0, true) then + return vim.notify( + "The roslyn language server needs to be updated. Refer to the README for installation steps", + vim.log.levels.INFO, + { title = "roslyn.nvim" } + ) + end + roslyn_version_verified = true + end + -- Lock the target and always start with the currently selected solution if roslyn_config.lock_target and vim.g.roslyn_nvim_selected_solution then local sln_dir = vim.fs.dirname(vim.g.roslyn_nvim_selected_solution) diff --git a/lua/roslyn/lsp.lua b/lua/roslyn/lsp.lua index c527945..4ab23ee 100644 --- a/lua/roslyn/lsp.lua +++ b/lua/roslyn/lsp.lua @@ -1,5 +1,3 @@ -local server = require("roslyn.server") - local M = {} ---@param bufnr integer @@ -7,9 +5,9 @@ local M = {} ---@param on_init fun(client: vim.lsp.Client) function M.start(bufnr, root_dir, on_init) local roslyn_config = require("roslyn.config").get() - local cmd = vim.list_extend(vim.deepcopy(roslyn_config.exe), vim.deepcopy(roslyn_config.args)) local config = vim.deepcopy(roslyn_config.config) + config.cmd = vim.list_extend(vim.deepcopy(roslyn_config.exe), vim.deepcopy(roslyn_config.args)) config.name = "roslyn" config.root_dir = root_dir config.handlers = vim.tbl_deep_extend("force", { @@ -26,13 +24,15 @@ function M.start(bufnr, root_dir, on_init) ["workspace/projectInitializationComplete"] = function(_, _, ctx) vim.notify("Roslyn project initialization complete", vim.log.levels.INFO, { title = "roslyn.nvim" }) + ---NOTE: This is used by rzls.nvim for init + vim.api.nvim_exec_autocmds("User", { pattern = "RoslynInitialized", modeline = false }) + _G.roslyn_initialized = true + local buffers = vim.lsp.get_buffers_by_client_id(ctx.client_id) for _, buf in ipairs(buffers) do vim.lsp.util._refresh("textDocument/diagnostic", { bufnr = buf }) end - ---NOTE: This is used by rzls.nvim for init - vim.api.nvim_exec_autocmds("User", { pattern = "RoslynInitialized", modeline = false }) end, ["workspace/_roslyn_projectHasUnresolvedDependencies"] = function() vim.notify("Detected missing dependencies. Run dotnet restore command.", vim.log.levels.ERROR, { @@ -107,7 +107,6 @@ function M.start(bufnr, root_dir, on_init) config.on_exit = function(code, signal, client_id) vim.g.roslyn_nvim_selected_solution = nil - server.stop_server(client_id) vim.schedule(function() vim.notify("Roslyn server stopped", vim.log.levels.INFO, { title = "roslyn.nvim" }) end) @@ -161,7 +160,7 @@ function M.start(bufnr, root_dir, on_init) end, }) - server.start_server(bufnr, cmd, config) + vim.lsp.start(config, { bufnr = bufnr }) end ---@param client vim.lsp.Client diff --git a/lua/roslyn/server.lua b/lua/roslyn/server.lua deleted file mode 100644 index 5f76915..0000000 --- a/lua/roslyn/server.lua +++ /dev/null @@ -1,108 +0,0 @@ ----@type table -local _pipe_names = {} - ----@type boolean -local start_pending = false - ----@type vim.SystemObj? -local _current_server_object = nil - ----@type vim.SystemObj[] -local _server_objects = {} - ---- @param client vim.lsp.Client ---- @param config vim.lsp.ClientConfig ---- @return boolean -local function reuse_client_default(client, config) - return vim.iter(client.workspace_folders or {}):any(function(it) - return config.root_dir == it.name - end) -end - -local M = {} - ----@param bufnr integer ----@param config vim.lsp.ClientConfig Configuration for the server. ----@param pipe_name string -local function with_pipe_name(bufnr, config, pipe_name) - config.cmd = vim.lsp.rpc.connect(pipe_name) - local client_id = vim.lsp.start(config, { - bufnr = bufnr, - }) - if client_id then - _server_objects[client_id] = _current_server_object - end - vim.defer_fn(function() - --NOTE: Give the server some time to init - start_pending = false - end, 1000) -end - ----@param bufnr integer ----@param cmd string[] ----@param config vim.lsp.ClientConfig Configuration for the server. -function M.start_server(bufnr, cmd, config) - if start_pending then - --NOTE: Wait for the previous server to start (rzls spawns lots of roslyns) - vim.defer_fn(function() - M.start_server(bufnr, cmd, config) - end, 250) - return - end - start_pending = true - local all_clients = vim.lsp.get_clients({ name = "roslyn" }) - for _, client in pairs(all_clients) do - if reuse_client_default(client, config) and _pipe_names[config.root_dir] then - return with_pipe_name(bufnr, config, _pipe_names[config.root_dir]) - end - end - - _current_server_object = vim.system(cmd, { - detach = not vim.uv.os_uname().version:find("Windows"), - stdout = function(_, data) - local success, json_obj = pcall(vim.json.decode, data) - if not success then - return - end - - local pipe_name = json_obj["pipeName"] - if not pipe_name then - return - end - - -- Cache the pipe name so we only start roslyn once. - _pipe_names[config.root_dir] = pipe_name - - vim.schedule(function() - with_pipe_name(bufnr, config, pipe_name) - end) - end, - stderr = function(_, chunk) - if chunk then - vim.lsp.log.error("rpc", "dotnet", "stderr", chunk) - end - end, - }, function() - _pipe_names[config.root_dir] = nil - end) -end - -function M.stop_server(client_id) - local client = vim.lsp.get_client_by_id(client_id) - - local server_object = _server_objects[client_id] - if server_object then - server_object:kill(9) - end - - if client then - _pipe_names[client.root_dir] = nil - end -end - -function M.get_pipes() - ---NOTE: This is used by rzls.nvim for init - return _pipe_names -end - -return M