diff options
Diffstat (limited to '.config/nvim/lsp/roslyn_ls.lua')
-rw-r--r-- | .config/nvim/lsp/roslyn_ls.lua | 237 |
1 files changed, 237 insertions, 0 deletions
diff --git a/.config/nvim/lsp/roslyn_ls.lua b/.config/nvim/lsp/roslyn_ls.lua new file mode 100644 index 0000000..e332443 --- /dev/null +++ b/.config/nvim/lsp/roslyn_ls.lua @@ -0,0 +1,237 @@ +---@brief +--- +--- https://github.com/dotnet/roslyn +-- +-- To install the server, compile from source or download as nuget package. +-- Go to `https://dev.azure.com/azure-public/vside/_artifacts/feed/vs-impl/NuGet/Microsoft.CodeAnalysis.LanguageServer.<platform>/overview` +-- replace `<platform>` with one of the following `linux-x64`, `osx-x64`, `win-x64`, `neutral` (for more info on the download location see https://github.com/dotnet/roslyn/issues/71474#issuecomment-2177303207). +-- Download and extract it (nuget's are zip files). +-- - if you chose `neutral` nuget version, then you have to change the `cmd` like so: +-- cmd = { +-- 'dotnet', +-- '<my_folder>/Microsoft.CodeAnalysis.LanguageServer.dll', +-- '--logLevel', -- this property is required by the server +-- 'Information', +-- '--extensionLogDirectory', -- this property is required by the server +-- fs.joinpath(uv.os_tmpdir(), 'roslyn_ls/logs'), +-- '--stdio', +-- }, +-- where `<my_folder>` has to be the folder you extracted the nuget package to. +-- - for all other platforms put the extracted folder to neovim's PATH (`vim.env.PATH`) + +local uv = vim.uv +local fs = vim.fs + +local group = vim.api.nvim_create_augroup('lspconfig.roslyn_ls', { clear = true }) + +---@param client vim.lsp.Client +---@param target string +local function on_init_sln(client, target) + vim.notify('Initializing: ' .. target, vim.log.levels.TRACE, { title = 'roslyn_ls' }) + ---@diagnostic disable-next-line: param-type-mismatch + client:notify('solution/open', { + solution = vim.uri_from_fname(target), + }) +end + +---@param client vim.lsp.Client +---@param project_files string[] +local function on_init_project(client, project_files) + vim.notify('Initializing: projects', vim.log.levels.TRACE, { title = 'roslyn_ls' }) + ---@diagnostic disable-next-line: param-type-mismatch + client:notify('project/open', { + projects = vim.tbl_map(function(file) + return vim.uri_from_fname(file) + end, project_files), + }) +end + +---@param client vim.lsp.Client +local function refresh_diagnostics(client) + local buffers = vim.lsp.get_buffers_by_client_id(client.id) + for _, buf in ipairs(buffers) do + if vim.api.nvim_buf_is_loaded(buf) then + client:request( + vim.lsp.protocol.Methods.textDocument_diagnostic, + { textDocument = vim.lsp.util.make_text_document_params(buf) }, + nil, + buf + ) + end + end +end + +local function roslyn_handlers() + return { + ['workspace/projectInitializationComplete'] = function(_, _, ctx) + vim.notify('Roslyn project initialization complete', vim.log.levels.INFO, { title = 'roslyn_ls' }) + local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) + refresh_diagnostics(client) + return vim.NIL + end, + ['workspace/_roslyn_projectNeedsRestore'] = function(_, result, ctx) + local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) + + ---@diagnostic disable-next-line: param-type-mismatch + client:request('workspace/_roslyn_restore', result, function(err, response) + if err then + vim.notify(err.message, vim.log.levels.ERROR, { title = 'roslyn_ls' }) + end + if response then + for _, v in ipairs(response) do + vim.notify(v.message, vim.log.levels.INFO, { title = 'roslyn_ls' }) + end + end + end) + + return vim.NIL + end, + ['razor/provideDynamicFileInfo'] = function(_, _, _) + vim.notify( + 'Razor is not supported.\nPlease use https://github.com/tris203/rzls.nvim', + vim.log.levels.WARN, + { title = 'roslyn_ls' } + ) + return vim.NIL + end, + } +end + +---@type vim.lsp.Config +return { + name = 'roslyn_ls', + offset_encoding = 'utf-8', + cmd = { + 'Microsoft.CodeAnalysis.LanguageServer', + '--logLevel', + 'Information', + '--extensionLogDirectory', + fs.joinpath(uv.os_tmpdir(), 'roslyn_ls/logs'), + '--stdio', + }, + filetypes = { 'cs' }, + handlers = roslyn_handlers(), + + commands = { + ['roslyn.client.completionComplexEdit'] = function(command, ctx) + local client = assert(vim.lsp.get_client_by_id(ctx.client_id)) + local args = command.arguments or {} + local uri, edit = args[1], args[2] + + if uri and edit and edit.newText and edit.range then + local workspace_edit = { + changes = { + [uri.uri] = { + { + range = edit.range, + newText = edit.newText, + }, + }, + }, + } + vim.lsp.util.apply_workspace_edit(workspace_edit, client.offset_encoding) + else + vim.notify('roslyn_ls: completionComplexEdit args not understood: ' .. vim.inspect(args), vim.log.levels.WARN) + end + end, + }, + + root_dir = function(bufnr, cb) + local bufname = vim.api.nvim_buf_get_name(bufnr) + -- don't try to find sln or csproj for files from libraries + -- outside of the project + if not bufname:match('^' .. fs.joinpath('/tmp/MetadataAsSource/')) then + -- try find solutions root first + local root_dir = fs.root(bufnr, function(fname, _) + return fname:match('%.sln[x]?$') ~= nil + end) + + if not root_dir then + -- try find projects root + root_dir = fs.root(bufnr, function(fname, _) + return fname:match('%.csproj$') ~= nil + end) + end + + if root_dir then + cb(root_dir) + end + end + end, + on_init = { + function(client) + local root_dir = client.config.root_dir + + -- try load first solution we find + for entry, type in fs.dir(root_dir) do + if type == 'file' and (vim.endswith(entry, '.sln') or vim.endswith(entry, '.slnx')) then + on_init_sln(client, fs.joinpath(root_dir, entry)) + return + end + end + + -- if no solution is found load project + for entry, type in fs.dir(root_dir) do + if type == 'file' and vim.endswith(entry, '.csproj') then + on_init_project(client, { fs.joinpath(root_dir, entry) }) + end + end + end, + }, + + on_attach = function(client, bufnr) + -- avoid duplicate autocmds for same buffer + if vim.api.nvim_get_autocmds({ buffer = bufnr, group = group })[1] then + return + end + + vim.api.nvim_create_autocmd({ 'BufWritePost', 'InsertLeave' }, { + group = group, + buffer = bufnr, + callback = function() + refresh_diagnostics(client) + end, + desc = 'roslyn_ls: refresh diagnostics', + }) + end, + + capabilities = { + -- HACK: Doesn't show any diagnostics if we do not set this to true + textDocument = { + diagnostic = { + dynamicRegistration = true, + }, + }, + }, + settings = { + ['csharp|background_analysis'] = { + dotnet_analyzer_diagnostics_scope = 'fullSolution', + dotnet_compiler_diagnostics_scope = 'fullSolution', + }, + ['csharp|inlay_hints'] = { + csharp_enable_inlay_hints_for_implicit_object_creation = true, + csharp_enable_inlay_hints_for_implicit_variable_types = true, + csharp_enable_inlay_hints_for_lambda_parameter_types = true, + csharp_enable_inlay_hints_for_types = true, + dotnet_enable_inlay_hints_for_indexer_parameters = true, + dotnet_enable_inlay_hints_for_literal_parameters = true, + dotnet_enable_inlay_hints_for_object_creation_parameters = true, + dotnet_enable_inlay_hints_for_other_parameters = true, + dotnet_enable_inlay_hints_for_parameters = true, + dotnet_suppress_inlay_hints_for_parameters_that_differ_only_by_suffix = true, + dotnet_suppress_inlay_hints_for_parameters_that_match_argument_name = true, + dotnet_suppress_inlay_hints_for_parameters_that_match_method_intent = true, + }, + ['csharp|symbol_search'] = { + dotnet_search_reference_assemblies = true, + }, + ['csharp|completion'] = { + dotnet_show_name_completion_suggestions = true, + dotnet_show_completion_items_from_unimported_namespaces = true, + dotnet_provide_regex_completions = true, + }, + ['csharp|code_lens'] = { + dotnet_enable_references_code_lens = true, + }, + }, +} |