Skip to content

Commit

Permalink
feat: csharpls_extended properly supports navigating inside $metadata$
Browse files Browse the repository at this point in the history
Problem - real project files will now report duplicate type definition, restart fixes this
  • Loading branch information
konradmalik committed Nov 11, 2023
1 parent 434357f commit 20acaca
Show file tree
Hide file tree
Showing 3 changed files with 54 additions and 85 deletions.
126 changes: 48 additions & 78 deletions config/native/lua/csharpls-extended-lsp/init.lua
Original file line number Diff line number Diff line change
@@ -1,103 +1,72 @@
local utils = require("csharpls-extended-lsp.utils")
local client_name = "csharp_ls"

local M = {}

M.handler = function(err, result, ctx, config)
local offset_encoding = vim.lsp.get_client_by_id(ctx.client_id).offset_encoding
local locations = M.textdocument_definition_to_locations(result)
local handled = M.handle_locations(locations, offset_encoding)
if not handled then
return vim.lsp.handlers["textDocument/definition"](err, result, ctx, config)
end
end

M.defolderize = function(str)
-- private static string Folderize(string path) => string.Join("/", path.Split('.'));
return string.gsub(str, "[/\\]", ".")
local client_name = "csharp_ls"
local get_csharpls_client = function()
local clients = vim.lsp.get_clients({ bufnr = 0, name = client_name })
return clients[1]
end

M.matcher = "metadata[/\\]projects[/\\](.*)[/\\]assemblies[/\\](.*)[/\\]symbols[/\\](.*).cs"

M.parse_meta_uri = function(uri)
--print(uri)
--local found, _, project, assembly, symbol = string.find(uri, M.matcher)
local found, _, project, assembly, symbol = string.find(uri, M.matcher)
--print(found)
if found ~= nil then
return found, M.defolderize(project), M.defolderize(assembly), M.defolderize(symbol)
local matcher = "metadata[/\\]projects[/\\](.*)[/\\]assemblies[/\\](.*)[/\\]symbols[/\\](.*).cs"
local is_meta_uri = function(uri)
local found = string.find(uri, matcher)
if found then
return found
end
return nil
end

M.get_csharpls_client = function()
local clients = vim.lsp.get_clients({ bufnr = 0 })
for _, client in pairs(clients) do
if client.name == client_name then
return client
end
local textdocument_definition_to_locations = function(result)
if not vim.tbl_islist(result) then
return { result }
end

return nil
return result
end

M.buf_from_metadata = function(result, client_id)
local buf_from_metadata = function(result, working_dir)
local normalized = string.gsub(result.source, "\r\n", "\n")
local source_lines = utils.split(normalized, "\n")

-- normalize backwards slash to forwards slash
local normalized_source_name = string.gsub(result.assemblyName, "\\", "/")
local file_name = "/" .. normalized_source_name
local file_name =
table.concat({ working_dir, result.projectName, "$metadata$", result.assemblyName, result.symbolName }, "/")

-- this will be /$metadata$/...
local bufnr = utils.get_or_create_buf(file_name)
-- TODO: check if bufnr == 0 -> error
vim.api.nvim_buf_set_option(bufnr, "modifiable", true)
vim.api.nvim_buf_set_option(bufnr, "readonly", false)
vim.api.nvim_buf_set_lines(bufnr, 0, -1, true, source_lines)
vim.api.nvim_buf_set_option(bufnr, "modifiable", false)
vim.api.nvim_buf_set_option(bufnr, "readonly", true)
vim.api.nvim_buf_set_option(bufnr, "filetype", "cs")
vim.api.nvim_buf_set_option(bufnr, "modified", false)

-- attach lsp client ??
vim.lsp.buf_attach_client(bufnr, client_id)
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("filetype", "cs", { buf = bufnr })

return bufnr, file_name
end

-- Gets metadata for all locations with $metadata$
-- Returns: boolean whether any requests were made
M.get_metadata = function(locations)
local client = M.get_csharpls_client()
---Gets metadata for all locations with $metadata$
---Creates buffers with their contents.
---For now we don't use the result of this function, but maybe we'll do
---@return table results table
local get_metadata = function(locations)
local client = get_csharpls_client()
if not client then
vim.notify(string.format("cannot find %s client", client_name), vim.log.levels.ERROR)
return false
return {}
end

local fetched = {}
for _, loc in pairs(locations) do
-- url, get the message from csharp_ls
local uri = utils.urldecode(loc.uri)
--print(uri)
--if has get messages
local is_meta, _, _, _ = M.parse_meta_uri(uri)
--print(is_meta,project,assembly,symbol)
local is_meta = is_meta_uri(uri)
if is_meta then
--print(uri)
local params = {
timeout = 5000,
textDocument = {
uri = uri,
},
}
--print("ssss")
-- request_sync?
-- if async, need to trigger when all are finished
local result, err = client.request_sync("csharp/metadata", params, 10000)
--print(result.result.source)
if not err then
local bufnr, name = M.buf_from_metadata(result.result, client.id)
local bufnr, name = buf_from_metadata(result.result, client.config.root_dir)
vim.lsp.buf_attach_client(bufnr, client.id)
-- change location name to the one returned from metadata
-- alternative is to open buffer under location uri
-- not sure which one is better
Expand All @@ -113,28 +82,29 @@ M.get_metadata = function(locations)
return fetched
end

M.textdocument_definition_to_locations = function(result)
if not vim.tbl_islist(result) then
return { result }
local handle_locations = function(locations, offset_encoding)
local fetched = get_metadata(locations)
if vim.tbl_isempty(fetched) then
return false
end

return result
if #locations > 1 then
utils.set_qflist_locations(locations, offset_encoding)
vim.api.nvim_command("copen")
else
vim.lsp.util.jump_to_location(locations[1], offset_encoding)
end
return true
end

M.handle_locations = function(locations, offset_encoding)
local fetched = M.get_metadata(locations)

if not vim.tbl_isempty(fetched) then
if #locations > 1 then
utils.set_qflist_locations(locations, offset_encoding)
vim.api.nvim_command("copen")
return true
else
vim.lsp.util.jump_to_location(locations[1], offset_encoding)
return true
end
else
return false
local M = {}

M.handler = function(err, result, ctx, config)
local offset_encoding = vim.lsp.get_client_by_id(ctx.client_id).offset_encoding
local locations = textdocument_definition_to_locations(result)
local handled = handle_locations(locations, offset_encoding)
if not handled then
return vim.lsp.handlers["textDocument/definition"](err, result, ctx, config)
end
end

Expand Down
8 changes: 5 additions & 3 deletions config/native/lua/csharpls-extended-lsp/utils.lua
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ U.split = function(str, delimiter)
end

U.get_or_create_buf = function(name)
-- if we are looking for $metadata$ buffer, search for entire string anywhere
-- in buffer name. On Windows nvim_buf_set_name might change the buffer name and include some stuff before.
local looking_for_metadata = string.find(name, "^/%$metadata%$/.*$")

local buffers = vim.api.nvim_list_bufs()
for _, buf in pairs(buffers) do
local bufname = vim.api.nvim_buf_get_name(buf)

-- if we are looking for $metadata$ buffer, search for entire string anywhere
-- in buffer name. On Windows nvim_buf_set_name might change the buffer name and include some stuff before.
if string.find(name, "^/%$metadata%$/.*$") then
if looking_for_metadata then
local normalized_bufname = string.gsub(bufname, "\\", "/")
if string.find(normalized_bufname, name) then
return buf
Expand Down
5 changes: 1 addition & 4 deletions config/native/lua/konrad/lsp/configs/csharp_ls.lua
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,10 @@ local M = {}
M.config = {
-- this concrete name is needed by csharpls_extended
name = "csharp_ls",
cmd = { binaries.csharp_ls() },
cmd = { binaries.csharp_ls(), "-l", "warning" },
handlers = {
["textDocument/definition"] = require("csharpls-extended-lsp").handler,
},
init_options = {
AutomaticWorkspaceInit = true,
},
root_dir = function()
return configs.root_dir(".sln") or configs.root_dir(".csproj") or configs.root_dir(".git")
end,
Expand Down

0 comments on commit 20acaca

Please sign in to comment.