From ba27093d362e37fa44a39b84f8e69a08e86d4f5f Mon Sep 17 00:00:00 2001 From: dacyberduck Date: Sun, 1 Nov 2026 01:00:55 +0530 Subject: [PATCH] materialize virtual URIs through the server csharp-ls decompiles symbols and generates "csharp:/path/to/decompiled/code" which is a virtual document URI; this materialize the virtual URI through the server before opening a real buffer. see csharp-ls "--features metadata-uris" https://github.com/razzmatazz/csharp-language-server#extended-client-capabilities --- autoload/lsp/symbol.vim | 16 ++++++------ autoload/lsp/util.vim | 55 ++++++++++++++++++++++++++++++++++++++--- 2 files changed, 59 insertions(+), 12 deletions(-) diff --git a/autoload/lsp/symbol.vim b/autoload/lsp/symbol.vim index bbb4859a..1b0b21b8 100644 --- a/autoload/lsp/symbol.vim +++ b/autoload/lsp/symbol.vim @@ -225,7 +225,7 @@ export def WorkspaceSymbolPopup(lspserver: dict, query: string, endif # interface SymbolInformation - fileName = util.LspUriToFile(symbol.location.uri) + fileName = util.LspUriToRealFile(symbol.location.uri, lspserver) symName = symbol.name if symbol->has_key('containerName') && symbol.containerName != '' @@ -291,7 +291,7 @@ def UpdatePeekFilePopup(lspserver: dict, locations: list>) var n = line('.', lspserver.peekSymbolPopup) - 1 var [uri, range] = util.LspLocationParse(locations[n]) - var fname: string = util.LspUriToFile(uri) + var fname: string = util.LspUriToRealFile(uri, lspserver) var bnr: number = fname->bufnr() if bnr == -1 @@ -374,7 +374,7 @@ def PeekLocations(lspserver: dict, locations: list>, var menuItems: list = [] for loc in locations var [uri, range] = util.LspLocationParse(loc) - var fname: string = util.LspUriToFile(uri) + var fname: string = util.LspUriToRealFile(uri, lspserver) var bnr: number = fname->bufnr() if bnr == -1 bnr = fname->bufadd() @@ -426,7 +426,7 @@ export def ShowLocations(lspserver: dict, locations: list>, var qflist: list> = [] for loc in locations var [uri, range] = util.LspLocationParse(loc) - var fname: string = util.LspUriToFile(uri) + var fname: string = util.LspUriToRealFile(uri, lspserver) var bnr: number = fname->bufnr() if bnr == -1 bnr = fname->bufadd() @@ -489,7 +489,7 @@ enddef # highlight the range in "location". def PeekSymbolLocation(lspserver: dict, location: dict) var [uri, range] = util.LspLocationParse(location) - var fname = util.LspUriToFile(uri) + var fname = util.LspUriToRealFile(uri, lspserver) var bnum = fname->bufadd() if bnum == 0 # Failed to create or find a buffer @@ -547,7 +547,7 @@ export def GotoSymbol(lspserver: dict, location: dict, else # Save the current cursor location in the tag stack. util.PushCursorToTagStack() - util.JumpToLspLocation(location, cmdmods) + util.JumpToLspLocation(location, cmdmods, lspserver) endif enddef @@ -563,7 +563,7 @@ export def TagFunc(lspserver: dict, tagitem.name = pat var [uri, range] = util.LspLocationParse(tagloc) - tagitem.filename = util.LspUriToFile(uri) + tagitem.filename = util.LspUriToRealFile(uri, lspserver) var bnr = util.LspUriToBufnr(uri) var rstart = range.start var startByteIdx = util.GetLineByteFromPos(bnr, rstart) @@ -592,7 +592,7 @@ def ProcessSymbolInfoTable(lspserver: dict, var symInfo: dict for syminfo in symbolInfoTable - fname = util.LspUriToFile(syminfo.location.uri) + fname = util.LspUriToRealFile(syminfo.location.uri, lspserver) symbolType = SymbolKindToName(syminfo.kind) name = syminfo.name if syminfo->has_key('containerName') diff --git a/autoload/lsp/util.vim b/autoload/lsp/util.vim index d8ff7a16..f8a88832 100644 --- a/autoload/lsp/util.vim +++ b/autoload/lsp/util.vim @@ -156,6 +156,12 @@ export def LspUriToFile(uri: string): string # Replace all the %xx numbers (e.g. %20 for space) in the URI to character var uri_decoded: string = UriDecode(uri) + # csharp:/... is a virtual document URI; leave it alone here so callers can + # materialize it through the server before opening a real buffer. + if uri_decoded =~# '^csharp:' + return uri_decoded + endif + # File URIs on MS-Windows start with file:///[a-zA-Z]:' if uri_decoded =~? '^file:///\a:' # MS-Windows URI @@ -175,11 +181,51 @@ export def LspUriToFile(uri: string): string return uri_decoded enddef +var csharpMetadataCache: dict = {} + +# csharp-ls emits virtual document URIs like: +# csharp:/.../decompiled/RimWorld.Plant.cs +# Those must be materialized with the csharp/metadata request before Vim can +# open them as a regular file. +export def LspUriToRealFile(uri: string, lspserver: dict = {}): string + var file = LspUriToFile(uri) + if uri !~# '^csharp:' + return file + endif + + if csharpMetadataCache->has_key(uri) + return csharpMetadataCache[uri] + endif + if lspserver->empty() + return file + endif + + var response = lspserver.rpc('csharp/metadata', + {textDocument: {uri: uri}}, + {handleError: false}) + if response->empty() || !response->has_key('result') + return file + endif + + var metadata = response.result + if metadata->type() != v:t_dict || !metadata->has_key('source') + return file + endif + + var path = tempname() .. '.cs' + if writefile(metadata.source->split("\n", true), path) != 0 + return file + endif + + csharpMetadataCache[uri] = path + return path +enddef + # Convert a LSP file URI (file://) to a Vim buffer number. # If the file is not in a Vim buffer, then adds the buffer. # Returns 0 on error. -export def LspUriToBufnr(uri: string): number - return LspUriToFile(uri)->bufadd() +export def LspUriToBufnr(uri: string, lspserver: dict = {}): number + return LspUriToRealFile(uri, lspserver)->bufadd() enddef # Returns if the URI refers to a remote file (e.g. ssh://) @@ -337,9 +383,10 @@ enddef # Jump to the LSP "location". The "location" contains the file name, line # number and character number. The user specified window command modifiers # (e.g. topleft) are in "cmdmods". -export def JumpToLspLocation(location: dict, cmdmods: string) +export def JumpToLspLocation(location: dict, cmdmods: string, + lspserver: dict = {}) var [uri, range] = LspLocationParse(location) - var fname = LspUriToFile(uri) + var fname = LspUriToRealFile(uri, lspserver) # jump to the file and line containing the symbol var bnr: number = fname->bufnr()