Closed iainnicol closed 1 year ago
There was a discussion about it. It turned out Roslyn must use SVsSymbolicNavigationManager
so we could hook into it, read the full discussion here https://github.com/Microsoft/visualfsharp/pull/3357#issuecomment-317261778
Checking in about this at github/roslyn it seems they are unwilling to do this.
https://github.com/dotnet/roslyn/issues/8105
How could we move this forward? What alternatives are there?
@jannesiera My read of that issue is that there isn't a strong appetite to use those older APIs, but I wouldn't rule out a contribution to Roslyn that uses them.
Over at dotnet/roslyn#8105 there was mention of introducing a MEF interface so we could intregrate with Roslyn to get this to work. It would look something like this:
public interface ISymbolicNavigationService { Task<bool> TryNavigateToSymbol(ISymbol symbol, CancellationToken cancellationToken); Task<bool> TryNavigateToSymbol(string rqName, CancellationToken cancellationToken); }
From Roslyn's perspective would work something like this (quoting @CyrusNajmabadi):
This API would just be about the language saying "i can take it from here". i.e. in an async-cancellable manner, it would introspect itself and say "yup! that looks like a symbol from my language (or not)". And it woudl return true/false accordingly. If 'false', nothing left for it to do. If 'true', it would take over and actually do the navigation however it wanted.
Over the weekend I was able to set up a local testing environment and, after providing an implementation of ISymbolicNavigationService on our side, I could trigger TryNavigateToSymbol after opening a C# projecting pressing F12 on a function provided by a referenced F# project.
I'm not sure what would be the best way to proceed from here on, being unfamiliar with the internals of visualfsharp. At first sight it seems trying to guess which FSharpSymbol the provided ISymbol might represent and then trying to find that FSharpSymbol, and navigate to it if found.
Some guidance here would be very welcome.
Tagging some people that worked on the initial issue: @vasily-kirichenko @dsyme @saul
@jannesiera What about me, I've not used Visual Studio for a couple of years and I hope won't ever use it (I switched to Rider where this feature has worked from the beginning).
@jannesiera Nice work! The work on the F# side (at a high level) is the following:
ISymbol
to find the correct F# symbol definition, in a way that doesn't require calling GetAllUsesOfAllSymbols
. There may be usable be info about the project and namespace that a symbol is declared in that could be used here.range
corresponding to the symbol declaration . . . but the difference is the initialDoc
is not an F# document, so it likely couldn't be used directly, but pieces of it could be extracted/refactored to allow use from your entry point.The first bit is likely going to involve some experimentation, since ISymbol
is not a concept that the F# tooling understands. There may be some work to convert data from there to data the F# compiler service can understand, and the usual juggling of existing APIs - look in QuickInfo or NavigableSymbols for examples. If you need to construct some artificial Roslyn objects to use them, that shouldn't be too difficult.
If it's not possible to use existing APIs, you may need to add a new one to the compiler service. Something like, "given a fully qualified symbol name, give me that symbol". That may get more difficult to do efficiently, since the naiive implementation would be proportional to Find All References in terms of the work it does.
Do you guys support rqnames?
@cartermp
I got a basic PoC working (at least for very simple declarations, like module and function references), but:
Use information from an
ISymbol
to find the correct F# symbol definition, in a way that doesn't require callingGetAllUsesOfAllSymbols
. There may be usable be info about the project and namespace that a symbol is declared in that could be used here.
Currently I'm doing this procedure:
ISymbol
(roslynSymbol) from Roslyn.GetAllUsesOfAllSymbols
on the parsed project.NavigateToItem
.This is (1) obviously not very fast and (2) I'm unsure about my strategy of matching the roslynSymbol to an appropriate fsharpSymbol.
Regarding (1) I do have the namespace available. How could I use this to check less files? Also, is there a way I could avoid parsing the project with FSharpChecker's ParseAndCheckProject
? Is there an in-memory database of all known symbols indexed by namespace or something alike (or is it naive to think like this)?
@jannesiera I'd say at this point, this sounds good enough to submit a PR for that can be reviewed.
Generally speaking, symbols should be cached so it may not be too slow to scan GetAllUsesOfAllSymbols
for a single project, but we'd have to use it on real codebases to really know for sure. If the time spent doing this work is proportional to the time for any other operation that requires a call to ParseAndCheckProject
then it should be okay. Optimizing this sounds like it would imply a much larger change to the compiler service. There are a great deal of things we'd like to do (e.g., move to a snapshot model akin to Roslyn, implement some speculative model for understanding if cross-project references have semantic changes in them, etc.).
As far as mapping an ISymbol into an F# symbol goes, I think once you have the fully qualified name it should be enough to be correct. If you submit a PR we can establish a thorough test plan to validate that it works.
My main concern in light of this is making sure that we do everything right at the Roslyn <--> F# boundary. To answer @CyrusNajmabadi's question, I don't think we support rqnames, no. It certainly doesn't show up in our codebase since we plug into Roslyn or newer editor APIs directly.
Duplicate
In #3357 there was awesome work so that Visual Studio's Go To Definition (F12) can work from F# to C#.
It would be treat if this worked the other way, from C# to F#.
Where do we think we need to hook into Roslyn to enable this?