From 9ed229b3ae4d44f1cd0c13e5558ecd6f2c1f981c Mon Sep 17 00:00:00 2001 From: Will Smith Date: Fri, 22 Jan 2021 02:14:34 -0800 Subject: [PATCH 1/4] Initial commit --- .../Navigation/GoToDefinition.fs | 105 +++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 00b8152934f..90624d70704 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -8,19 +8,122 @@ open System.Collections.Immutable open System.Diagnostics open System.IO open System.Linq +open System.Text open System.Runtime.InteropServices +open System.Reflection.PortableExecutable open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.FindSymbols open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Navigation open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation +open Microsoft.VisualStudio.ComponentModelHost +open Microsoft.VisualStudio +open Microsoft.VisualStudio.Threading +open Microsoft.VisualStudio.Shell open Microsoft.VisualStudio.Shell.Interop +open Microsoft.VisualStudio.TextManager.Interop open FSharp.Compiler.SourceCodeServices open FSharp.Compiler.Text +module private CSharpDecompiler = + + open Microsoft.CodeAnalysis.CSharp + open ICSharpCode.Decompiler + open ICSharpCode.Decompiler.CSharp + open ICSharpCode.Decompiler.Metadata + open ICSharpCode.Decompiler.CSharp.Transforms + open ICSharpCode.Decompiler.TypeSystem + + let GenerateTemporaryCSharpDocument (asmIdentity: AssemblyIdentity, name: string, metadataReferences) = + let rootPath = Path.Combine(Path.GetTempPath(), "MetadataAsSource") + let extension = ".cs" + let directoryName = Guid.NewGuid().ToString("N") + let temporaryFilePath = Path.Combine(rootPath, directoryName, name + extension) + + let projectId = ProjectId.CreateNewId() + + let parseOptions = CSharpParseOptions.Default.WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.Preview) + // Just say it's always a DLL since we probably won't have a Main method + let compilationOptions = Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + + let extension = ".cs" + + // We need to include the version information of the assembly so InternalsVisibleTo and stuff works + let assemblyInfoDocumentId = DocumentId.CreateNewId(projectId) + let assemblyInfoFileName = "AssemblyInfo" + extension + let assemblyInfoString = String.Format(@"[assembly: System.Reflection.AssemblyVersion(""{0}"")]", asmIdentity.Version) + + let assemblyInfoSourceTextContainer = SourceText.From(assemblyInfoString, Encoding.UTF8).Container + + let assemblyInfoDocument = + DocumentInfo.Create( + assemblyInfoDocumentId, + assemblyInfoFileName, + loader = TextLoader.From(assemblyInfoSourceTextContainer, VersionStamp.Default)) + + let generatedDocumentId = DocumentId.CreateNewId(projectId) + let generatedDocument = + DocumentInfo.Create( + generatedDocumentId, + Path.GetFileName(temporaryFilePath), + filePath = temporaryFilePath, + loader = FileTextLoader(temporaryFilePath, Encoding.UTF8)) + + let projectInfo = + ProjectInfo.Create( + projectId, + VersionStamp.Default, + name = asmIdentity.Name, + assemblyName = asmIdentity.Name, + language = LanguageNames.CSharp, + compilationOptions = compilationOptions, + parseOptions = parseOptions, + documents = [|assemblyInfoDocument;generatedDocument|], + metadataReferences = metadataReferences) + + (projectInfo, generatedDocumentId) + + let Decompile (document: Document, symbolFullName: string, assemblyLocation: string) = + let logger = new StringBuilder(); + + // Initialize a decompiler with default settings. + let decompiler = CSharpDecompiler(assemblyLocation, DecompilerSettings()) + // Escape invalid identifiers to prevent Roslyn from failing to parse the generated code. + // (This happens for example, when there is compiler-generated code that is not yet recognized/transformed by the decompiler.) + decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers()) + + let fullTypeName = FullTypeName(symbolFullName) + + // Try to decompile; if an exception is thrown the caller will handle it + let mutable text = decompiler.DecompileTypeAsString(fullTypeName); + + text <- text + "#if false // " + Environment.NewLine; + text <- text + logger.ToString(); + text <- text + "#endif" + Environment.NewLine; + + document.WithText(SourceText.From(text)); + + let ShowDocument (document: Document, serviceProvider: IServiceProvider) = + let vsRunningDocumentTable4 = serviceProvider.GetService() + let fileAlreadyOpen = vsRunningDocumentTable4.IsMonikerValid(document.FilePath) + + let openDocumentService = serviceProvider.GetService() + let mutable localServiceProvider = Unchecked.defaultof<_> + let mutable hierarchy = Unchecked.defaultof<_> + let mutable itemId = Unchecked.defaultof<_> + let mutable windowFrame = Unchecked.defaultof<_> + openDocumentService.OpenDocumentViaProject(document.FilePath, ref VSConstants.LOGVIEWID.TextView_guid, &localServiceProvider, &hierarchy, &itemId, &windowFrame) |> ignore + + if not fileAlreadyOpen then + ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_IsProvisional, true)) |> ignore + ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_OverrideCaption, document.Name)) |> ignore + ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_OverrideToolTip, document.Name)) |> ignore + + windowFrame.Show() |> ignore + module private Symbol = let fullName (root: ISymbol) : string = let rec inner parts (sym: ISymbol) = @@ -343,7 +446,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP /// Navigate to the positon of the textSpan in the provided document /// used by quickinfo link navigation when the tooltip contains the correct destination range. - member _.TryNavigateToTextSpan(document: Document, textSpan: TextSpan, statusBar: StatusBar) = + member _.TryNavigateToTextSpan(document: Document, textSpan: Microsoft.CodeAnalysis.Text.TextSpan, statusBar: StatusBar) = let navigableItem = FSharpGoToDefinitionNavigableItem(document, textSpan) let workspace = document.Project.Solution.Workspace let navigationService = workspace.Services.GetService() From c977d26f5361476bd8b84120413744c83cd6d138 Mon Sep 17 00:00:00 2001 From: Will Smith Date: Mon, 25 Jan 2021 20:28:48 -0800 Subject: [PATCH 2/4] Openining document, but incorrect project association --- .../Navigation/GoToDefinition.fs | 98 ++++++++++++------- .../Navigation/GoToDefinitionService.fs | 54 ++++++++-- .../Navigation/NavigableSymbolsService.fs | 8 +- 3 files changed, 116 insertions(+), 44 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 90624d70704..4f76d298a6d 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -20,6 +20,7 @@ open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation open Microsoft.VisualStudio.ComponentModelHost open Microsoft.VisualStudio +open Microsoft.VisualStudio.Editor open Microsoft.VisualStudio.Threading open Microsoft.VisualStudio.Shell open Microsoft.VisualStudio.Shell.Interop @@ -28,7 +29,7 @@ open Microsoft.VisualStudio.TextManager.Interop open FSharp.Compiler.SourceCodeServices open FSharp.Compiler.Text -module private CSharpDecompiler = +module internal CSharpDecompiler = open Microsoft.CodeAnalysis.CSharp open ICSharpCode.Decompiler @@ -86,7 +87,7 @@ module private CSharpDecompiler = (projectInfo, generatedDocumentId) - let Decompile (document: Document, symbolFullName: string, assemblyLocation: string) = + let Decompile (symbolFullName: string, assemblyLocation: string) = let logger = new StringBuilder(); // Initialize a decompiler with default settings. @@ -98,32 +99,50 @@ module private CSharpDecompiler = let fullTypeName = FullTypeName(symbolFullName) // Try to decompile; if an exception is thrown the caller will handle it - let mutable text = decompiler.DecompileTypeAsString(fullTypeName); + let mutable text = decompiler.DecompileTypeAsString(fullTypeName) - text <- text + "#if false // " + Environment.NewLine; - text <- text + logger.ToString(); - text <- text + "#endif" + Environment.NewLine; + text <- text + "#if false // " + Environment.NewLine + text <- text + logger.ToString() + text <- text + "#endif" + Environment.NewLine - document.WithText(SourceText.From(text)); + SourceText.From(text) - let ShowDocument (document: Document, serviceProvider: IServiceProvider) = + let ShowDocument (filePath, name, serviceProvider: IServiceProvider) = let vsRunningDocumentTable4 = serviceProvider.GetService() - let fileAlreadyOpen = vsRunningDocumentTable4.IsMonikerValid(document.FilePath) + let fileAlreadyOpen = vsRunningDocumentTable4.IsMonikerValid(filePath) let openDocumentService = serviceProvider.GetService() let mutable localServiceProvider = Unchecked.defaultof<_> let mutable hierarchy = Unchecked.defaultof<_> let mutable itemId = Unchecked.defaultof<_> let mutable windowFrame = Unchecked.defaultof<_> - openDocumentService.OpenDocumentViaProject(document.FilePath, ref VSConstants.LOGVIEWID.TextView_guid, &localServiceProvider, &hierarchy, &itemId, &windowFrame) |> ignore + openDocumentService.OpenDocumentViaProject(filePath, ref VSConstants.LOGVIEWID.TextView_guid, &localServiceProvider, &hierarchy, &itemId, &windowFrame) |> ignore + + let componentModel = serviceProvider.GetService() + let editorAdaptersFactory = componentModel.GetService(); + let documentCookie = vsRunningDocumentTable4.GetDocumentCookie(filePath) + let vsTextBuffer = vsRunningDocumentTable4.GetDocumentData(documentCookie) :?> IVsTextBuffer + let textBuffer = editorAdaptersFactory.GetDataBuffer(vsTextBuffer) if not fileAlreadyOpen then ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_IsProvisional, true)) |> ignore - ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_OverrideCaption, document.Name)) |> ignore - ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_OverrideToolTip, document.Name)) |> ignore + ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_OverrideCaption, name)) |> ignore + ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_OverrideToolTip, name)) |> ignore windowFrame.Show() |> ignore + let textContainer = textBuffer.AsTextContainer() + let mutable workspace = Unchecked.defaultof<_> + if Workspace.TryGetWorkspace(textContainer, &workspace) then + let solution = workspace.CurrentSolution + let documentId = workspace.GetDocumentIdInCurrentContext(textContainer) + match box documentId with + | null -> None + | _ -> solution.GetDocument(documentId) |> Some + else + None + + module private Symbol = let fullName (root: ISymbol) : string = let rec inner parts (sym: ISymbol) = @@ -248,6 +267,11 @@ type internal StatusBar(statusBar: IVsStatusbar) = type internal FSharpGoToDefinitionNavigableItem(document, sourceSpan) = inherit FSharpNavigableItem(Glyph.BasicFile, ImmutableArray.Empty, document, sourceSpan) +[] +type internal FSharpGoToDefinitionResult = + | NavigableItem of FSharpNavigableItem + | ExternalAssembly of ProjectInfo * DocumentId * FSharpSymbolUse + type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpProjectOptionsManager) = let userOpName = "GoToDefinition" @@ -344,23 +368,28 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP match declarations with | FSharpFindDeclResult.ExternalDecl (assembly, targetExternalSym) -> - let! project = originDocument.Project.Solution.Projects |> Seq.tryFind (fun p -> p.AssemblyName.Equals(assembly, StringComparison.OrdinalIgnoreCase)) - let! symbols = SymbolFinder.FindSourceDeclarationsAsync(project, fun _ -> true) - - let roslynSymbols = - symbols - |> Seq.collect ExternalSymbol.ofRoslynSymbol - |> Array.ofSeq - - let! symbol = - roslynSymbols - |> Seq.tryPick (fun (sym, externalSym) -> - if externalSym = targetExternalSym then Some sym - else None - ) - - let! location = symbol.Locations |> Seq.tryHead - return (FSharpGoToDefinitionNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan), idRange) + let projectOpt = originDocument.Project.Solution.Projects |> Seq.tryFind (fun p -> p.AssemblyName.Equals(assembly, StringComparison.OrdinalIgnoreCase)) + if projectOpt.IsSome then + let project = projectOpt.Value + let! symbols = SymbolFinder.FindSourceDeclarationsAsync(project, fun _ -> true) + + let roslynSymbols = + symbols + |> Seq.collect ExternalSymbol.ofRoslynSymbol + |> Array.ofSeq + + let! symbol = + roslynSymbols + |> Seq.tryPick (fun (sym, externalSym) -> + if externalSym = targetExternalSym then Some sym + else None + ) + + let! location = symbol.Locations |> Seq.tryHead + return (FSharpGoToDefinitionResult.NavigableItem(FSharpGoToDefinitionNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan)), idRange) + else + let tmpProjInfo, tmpDocId = CSharpDecompiler.GenerateTemporaryCSharpDocument(AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName), targetSymbolUse.Symbol.DisplayName, originDocument.Project.MetadataReferences) + return (FSharpGoToDefinitionResult.ExternalAssembly(tmpProjInfo, tmpDocId, targetSymbolUse), idRange) | FSharpFindDeclResult.DeclFound targetRange -> // if goto definition is called at we are alread at the declaration location of a symbol in @@ -378,7 +407,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange) let navItem = FSharpGoToDefinitionNavigableItem (implDocument, implTextSpan) - return (navItem, idRange) + return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) else // jump from implementation to the corresponding signature let declarations = checkFileResults.GetDeclarationLocation (fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLineString, lexerSymbol.FullIsland, true) match declarations with @@ -387,7 +416,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP let! sigSourceText = sigDocument.GetTextAsync () |> liftTaskAsync let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sigSourceText, targetRange) let navItem = FSharpGoToDefinitionNavigableItem (sigDocument, sigTextSpan) - return (navItem, idRange) + return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) | _ -> return! None // when the target range is different follow the navigation convention of @@ -400,7 +429,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP // if the gotodef call originated from a signature and the returned target is a signature, navigate there if isSignatureFile targetRange.FileName && preferSignature then let navItem = FSharpGoToDefinitionNavigableItem (sigDocument, sigTextSpan) - return (navItem, idRange) + return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) else // we need to get an FSharpSymbol from the targetRange found in the signature // that symbol will be used to find the destination in the corresponding implementation file let implFilePath = @@ -417,7 +446,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange) let navItem = FSharpGoToDefinitionNavigableItem (implDocument, implTextSpan) - return (navItem, idRange) + return (FSharpGoToDefinitionResult.NavigableItem(navItem), idRange) | _ -> return! None } @@ -433,8 +462,7 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP member this.FindDefinitionsForPeekTask(originDocument: Document, position: int, cancellationToken: CancellationToken) = this.FindDefinitionAtPosition(originDocument, position) |> Async.map ( - Option.map (fun (navItem, _) -> navItem :> FSharpNavigableItem) - >> Option.toArray + Option.toArray >> Array.toSeq) |> RoslynHelpers.StartAsyncAsTask cancellationToken diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index e8bb0d1fdc9..d5af30c93f8 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -3,6 +3,7 @@ namespace Microsoft.VisualStudio.FSharp.Editor open System.Composition +open System.IO open System.Threading open System.Threading.Tasks @@ -10,6 +11,7 @@ open Microsoft.CodeAnalysis open Microsoft.CodeAnalysis.Editor open Microsoft.CodeAnalysis.Host.Mef open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor +open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation open Microsoft.VisualStudio.Shell open Microsoft.VisualStudio.Shell.Interop @@ -30,7 +32,16 @@ type internal FSharpGoToDefinitionService interface IFSharpGoToDefinitionService with /// Invoked with Peek Definition. member _.FindDefinitionsAsync (document: Document, position: int, cancellationToken: CancellationToken) = - gtd.FindDefinitionsForPeekTask(document, position, cancellationToken) + let task = gtd.FindDefinitionsForPeekTask(document, position, cancellationToken) + task.Wait(cancellationToken) + let results = task.Result + results + |> Seq.choose(fun (result, _) -> + match result with + | FSharpGoToDefinitionResult.NavigableItem(navItem) -> Some navItem + | _ -> None + ) + |> Task.FromResult /// Invoked with Go to Definition. /// Try to navigate to the definiton of the symbol at the symbolRange in the originDocument @@ -44,13 +55,42 @@ type internal FSharpGoToDefinitionService // Task.Wait throws an exception if the task is cancelled, so be sure to catch it. try // This call to Wait() is fine because we want to be able to provide the error message in the status bar. - gtdTask.Wait() + gtdTask.Wait(cancellationToken) if gtdTask.Status = TaskStatus.RanToCompletion && gtdTask.Result.IsSome then - let item, _ = gtdTask.Result.Value - gtd.NavigateToItem(item, statusBar) - - // 'true' means do it, like Sheev Palpatine would want us to. - true + let result, _ = gtdTask.Result.Value + match result with + | FSharpGoToDefinitionResult.NavigableItem(navItem) -> + gtd.NavigateToItem(navItem, statusBar) + // 'true' means do it, like Sheev Palpatine would want us to. + true + | FSharpGoToDefinitionResult.ExternalAssembly(tmpProjInfo, tmpDocId, targetSymbolUse) -> + match targetSymbolUse.Symbol.Assembly.FileName with + | Some targetSymbolAssemblyFileName -> + let text = CSharpDecompiler.Decompile(targetSymbolUse.Symbol.FullName, targetSymbolAssemblyFileName) + let workspace = new AdhocWorkspace(document.Project.Solution.Workspace.Services.HostServices, WorkspaceKind.MetadataAsSource) + let proj = workspace.AddProject(tmpProjInfo) + let tmpDoc = proj.GetDocument(tmpDocId) + let _ = + let directoryName = Path.GetDirectoryName(tmpDoc.FilePath) + if Directory.Exists(directoryName) |> not then + Directory.CreateDirectory(directoryName) |> ignore + use fileStream = new FileStream(tmpDoc.FilePath, IO.FileMode.Create) + use writer = new StreamWriter(fileStream) + text.Write(writer) + + workspace.OpenDocument(tmpDoc.Id, true) + let tmpShownDocOpt = CSharpDecompiler.ShowDocument(tmpDoc.FilePath, tmpDoc.Name, ServiceProvider.GlobalProvider) + match tmpShownDocOpt with + | Some tmpShownDoc -> + let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, Text.TextSpan()) + gtd.NavigateToItem(navItem, statusBar) + true + | _ -> + statusBar.TempMessage (SR.CannotDetermineSymbol()) + false + | _ -> + statusBar.TempMessage (SR.CannotDetermineSymbol()) + false else statusBar.TempMessage (SR.CannotDetermineSymbol()) false diff --git a/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs b/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs index 2c7b1b7a894..941a9c30e6e 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/NavigableSymbolsService.fs @@ -58,13 +58,17 @@ type internal FSharpNavigableSymbolSource(checkerProvider: FSharpCheckerProvider statusBar.Clear() if gtdTask.Status = TaskStatus.RanToCompletion && gtdTask.Result.IsSome then - let navigableItem, range = gtdTask.Result.Value + let result, range = gtdTask.Result.Value let declarationTextSpan = RoslynHelpers.FSharpRangeToTextSpan(sourceText, range) let declarationSpan = Span(declarationTextSpan.Start, declarationTextSpan.Length) let symbolSpan = SnapshotSpan(snapshot, declarationSpan) - return FSharpNavigableSymbol(navigableItem, symbolSpan, gtd, statusBar) :> INavigableSymbol + match result with + | FSharpGoToDefinitionResult.NavigableItem(navItem) -> + return FSharpNavigableSymbol(navItem, symbolSpan, gtd, statusBar) :> INavigableSymbol + | _ -> + return null else statusBar.TempMessage(SR.CannotDetermineSymbol()) From 8a8e3ec937bfd7b8c281aaf442f787593b5140f9 Mon Sep 17 00:00:00 2001 From: Will Smith Date: Tue, 26 Jan 2021 18:59:25 -0800 Subject: [PATCH 3/4] Partial implementation for go-to-def of external symbols --- .../src/FSharp.Editor/FSharp.Editor.fsproj | 1 + .../LanguageService/FSharpCheckerProvider.fs | 4 + .../FSharpProjectOptionsManager.fs | 2 + .../LanguageService/MetadataAsSource.fs | 159 ++++++++++++++++++ .../LanguageService/SingleFileWorkspaceMap.fs | 25 +++ .../Navigation/GoToDefinition.fs | 119 +------------ .../Navigation/GoToDefinitionService.fs | 51 +++--- 7 files changed, 223 insertions(+), 138 deletions(-) create mode 100644 vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs diff --git a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj index 474b952c478..5b81796d8d5 100644 --- a/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj +++ b/vsintegration/src/FSharp.Editor/FSharp.Editor.fsproj @@ -46,6 +46,7 @@ + diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs index 56c074d78b2..f3212db489f 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpCheckerProvider.fs @@ -26,6 +26,8 @@ type internal FSharpCheckerProvider settings: EditorOptions ) = + let metadataAsSource = FSharpMetadataAsSourceService() + let tryGetMetadataSnapshot (path, timeStamp) = try let md = Microsoft.CodeAnalysis.ExternalAccess.FSharp.LanguageServices.FSharpVisualStudioWorkspaceExtensions.GetMetadata(workspace, path, timeStamp) @@ -85,3 +87,5 @@ type internal FSharpCheckerProvider member this.Checker = checker.Value + member _.MetadataAsSource = metadataAsSource + diff --git a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs index a35323f9de2..c077def059a 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/FSharpProjectOptionsManager.fs @@ -433,3 +433,5 @@ type internal FSharpProjectOptionsManager reactor.SetCpsCommandLineOptions(projectId, sourcePaths, options.ToArray()) member _.Checker = checkerProvider.Checker + + member _.MetadataAsSource = checkerProvider.MetadataAsSource diff --git a/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs new file mode 100644 index 00000000000..4bc4e1011cc --- /dev/null +++ b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs @@ -0,0 +1,159 @@ +// Copyright (c) Microsoft Corporation. All Rights Reserved. See License.txt in the project root for license information. + +namespace Microsoft.VisualStudio.FSharp.Editor + +open System +open System.Threading +open System.Collections.Immutable +open System.Diagnostics +open System.IO +open System.Linq +open System.Text +open System.Runtime.InteropServices +open System.Reflection.PortableExecutable + +open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.FindSymbols +open Microsoft.CodeAnalysis.Text +open Microsoft.CodeAnalysis.Navigation +open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation +open Microsoft.VisualStudio.ComponentModelHost + +open Microsoft.VisualStudio +open Microsoft.VisualStudio.Editor +open Microsoft.VisualStudio.Threading +open Microsoft.VisualStudio.Shell +open Microsoft.VisualStudio.Shell.Interop +open Microsoft.VisualStudio.TextManager.Interop + +open FSharp.Compiler.SourceCodeServices +open FSharp.Compiler.Text + +module internal MetadataAsSource = + + open Microsoft.CodeAnalysis.CSharp + open ICSharpCode.Decompiler + open ICSharpCode.Decompiler.CSharp + open ICSharpCode.Decompiler.Metadata + open ICSharpCode.Decompiler.CSharp.Transforms + open ICSharpCode.Decompiler.TypeSystem + + let GenerateTemporaryCSharpDocument (asmIdentity: AssemblyIdentity, name: string, metadataReferences) = + let rootPath = Path.Combine(Path.GetTempPath(), "MetadataAsSource") + let extension = ".cs" + let directoryName = Guid.NewGuid().ToString("N") + let temporaryFilePath = Path.Combine(rootPath, directoryName, name + extension) + + let projectId = ProjectId.CreateNewId() + + let parseOptions = CSharpParseOptions.Default.WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.Preview) + // Just say it's always a DLL since we probably won't have a Main method + let compilationOptions = Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) + + // We need to include the version information of the assembly so InternalsVisibleTo and stuff works + let assemblyInfoDocumentId = DocumentId.CreateNewId(projectId) + let assemblyInfoFileName = "AssemblyInfo" + extension + let assemblyInfoString = String.Format(@"[assembly: System.Reflection.AssemblyVersion(""{0}"")]", asmIdentity.Version) + + let assemblyInfoSourceTextContainer = SourceText.From(assemblyInfoString, Encoding.UTF8).Container + + let assemblyInfoDocument = + DocumentInfo.Create( + assemblyInfoDocumentId, + assemblyInfoFileName, + loader = TextLoader.From(assemblyInfoSourceTextContainer, VersionStamp.Default)) + + let generatedDocumentId = DocumentId.CreateNewId(projectId) + let documentInfo = + DocumentInfo.Create( + generatedDocumentId, + Path.GetFileName(temporaryFilePath), + filePath = temporaryFilePath, + loader = FileTextLoader(temporaryFilePath, Encoding.UTF8)) + + let projectInfo = + ProjectInfo.Create( + projectId, + VersionStamp.Default, + name = asmIdentity.Name, + assemblyName = asmIdentity.Name, + language = LanguageNames.CSharp, + compilationOptions = compilationOptions, + parseOptions = parseOptions, + documents = [|assemblyInfoDocument;documentInfo|], + metadataReferences = metadataReferences) + + (projectInfo, documentInfo) + + let DecompileCSharp (symbolFullTypeName: string, assemblyLocation: string) = + let logger = new StringBuilder(); + + // Initialize a decompiler with default settings. + let decompiler = CSharpDecompiler(assemblyLocation, DecompilerSettings()) + // Escape invalid identifiers to prevent Roslyn from failing to parse the generated code. + // (This happens for example, when there is compiler-generated code that is not yet recognized/transformed by the decompiler.) + decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers()) + + let fullTypeName = FullTypeName(symbolFullTypeName) + + // Try to decompile; if an exception is thrown the caller will handle it + let mutable text = decompiler.DecompileTypeAsString(fullTypeName) + + text <- text + "#if false // " + Environment.NewLine + text <- text + logger.ToString() + text <- text + "#endif" + Environment.NewLine + + SourceText.From(text) + + let ShowDocument (filePath, name, serviceProvider: IServiceProvider) = + let vsRunningDocumentTable4 = serviceProvider.GetService() + let fileAlreadyOpen = vsRunningDocumentTable4.IsMonikerValid(filePath) + + let openDocumentService = serviceProvider.GetService() + let mutable localServiceProvider = Unchecked.defaultof<_> + let mutable hierarchy = Unchecked.defaultof<_> + let mutable itemId = Unchecked.defaultof<_> + let mutable windowFrame = Unchecked.defaultof<_> + openDocumentService.OpenDocumentViaProject(filePath, ref VSConstants.LOGVIEWID.TextView_guid, &localServiceProvider, &hierarchy, &itemId, &windowFrame) |> ignore + + let componentModel = serviceProvider.GetService() + let editorAdaptersFactory = componentModel.GetService(); + let documentCookie = vsRunningDocumentTable4.GetDocumentCookie(filePath) + let vsTextBuffer = vsRunningDocumentTable4.GetDocumentData(documentCookie) :?> IVsTextBuffer + let textBuffer = editorAdaptersFactory.GetDataBuffer(vsTextBuffer) + + if not fileAlreadyOpen then + ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_IsProvisional, true)) |> ignore + ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_OverrideCaption, name)) |> ignore + ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_OverrideToolTip, name)) |> ignore + + windowFrame.Show() |> ignore + + let textContainer = textBuffer.AsTextContainer() + let mutable workspace = Unchecked.defaultof<_> + if Workspace.TryGetWorkspace(textContainer, &workspace) then + let solution = workspace.CurrentSolution + let documentId = workspace.GetDocumentIdInCurrentContext(textContainer) + match box documentId with + | null -> None + | _ -> solution.GetDocument(documentId) |> Some + else + None + +[] +type internal FSharpMetadataAsSourceService() = + + member val CSharpFiles = System.Collections.Concurrent.ConcurrentDictionary(StringComparer.OrdinalIgnoreCase) + + member this.ShowCSharpDocument(projInfo: ProjectInfo, docInfo: DocumentInfo, text: Text.SourceText) = + let _ = + let directoryName = Path.GetDirectoryName(docInfo.FilePath) + if Directory.Exists(directoryName) |> not then + Directory.CreateDirectory(directoryName) |> ignore + use fileStream = new FileStream(docInfo.FilePath, IO.FileMode.Create) + use writer = new StreamWriter(fileStream) + text.Write(writer) + + this.CSharpFiles.[docInfo.FilePath] <- (projInfo, docInfo) + + MetadataAsSource.ShowDocument(docInfo.FilePath, docInfo.Name, ServiceProvider.GlobalProvider) \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs b/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs index 5947c59b8ae..5b1ef7a2175 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs @@ -33,11 +33,34 @@ type internal SingleFileWorkspaceMap(workspace: VisualStudioWorkspace, projectContext.AddSourceFile(filePath, sourceCodeKind = createSourceCodeKind filePath) projectContext + let createCSharpMetadataProjectContext (projInfo: ProjectInfo) (docInfo: DocumentInfo) = + let projectContext = projectContextFactory.CreateProjectContext(LanguageNames.CSharp, projInfo.Id.ToString(), projInfo.FilePath, Guid.NewGuid(), null, null) + projectContext.DisplayName <- projInfo.Name + projectContext.AddSourceFile(docInfo.FilePath, sourceCodeKind = SourceCodeKind.Regular) + + projInfo.MetadataReferences + |> Seq.iter (function + | :? PortableExecutableReference as peRef -> + projectContext.AddMetadataReference(peRef.FilePath, MetadataReferenceProperties.Assembly) + | _ -> + () + ) + + projectContext + do miscFilesWorkspace.DocumentOpened.Add(fun args -> let document = args.Document + if document.Project.Language = FSharpConstants.FSharpLanguageName && workspace.CurrentSolution.GetDocumentIdsWithFilePath(document.FilePath).Length = 0 then files.[document.FilePath] <- createProjectContext document.FilePath + + if optionsManager.MetadataAsSource.CSharpFiles.ContainsKey(document.FilePath) && workspace.CurrentSolution.GetDocumentIdsWithFilePath(document.FilePath).Length = 0 then + match optionsManager.MetadataAsSource.CSharpFiles.TryGetValue(document.FilePath) with + | true, (projInfo, docInfo) -> + files.[document.FilePath] <- createCSharpMetadataProjectContext projInfo docInfo + | _ -> + () ) workspace.DocumentOpened.Add(fun args -> @@ -57,6 +80,8 @@ type internal SingleFileWorkspaceMap(workspace: VisualStudioWorkspace, optionsManager.ClearSingleFileOptionsCache(document.Id) projectContext.Dispose() | _ -> () + + optionsManager.MetadataAsSource.CSharpFiles.TryRemove(document.FilePath) |> ignore ) do diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index 4f76d298a6d..dbebfd02d7c 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -29,119 +29,6 @@ open Microsoft.VisualStudio.TextManager.Interop open FSharp.Compiler.SourceCodeServices open FSharp.Compiler.Text -module internal CSharpDecompiler = - - open Microsoft.CodeAnalysis.CSharp - open ICSharpCode.Decompiler - open ICSharpCode.Decompiler.CSharp - open ICSharpCode.Decompiler.Metadata - open ICSharpCode.Decompiler.CSharp.Transforms - open ICSharpCode.Decompiler.TypeSystem - - let GenerateTemporaryCSharpDocument (asmIdentity: AssemblyIdentity, name: string, metadataReferences) = - let rootPath = Path.Combine(Path.GetTempPath(), "MetadataAsSource") - let extension = ".cs" - let directoryName = Guid.NewGuid().ToString("N") - let temporaryFilePath = Path.Combine(rootPath, directoryName, name + extension) - - let projectId = ProjectId.CreateNewId() - - let parseOptions = CSharpParseOptions.Default.WithLanguageVersion(Microsoft.CodeAnalysis.CSharp.LanguageVersion.Preview) - // Just say it's always a DLL since we probably won't have a Main method - let compilationOptions = Microsoft.CodeAnalysis.CSharp.CSharpCompilationOptions(OutputKind.DynamicallyLinkedLibrary) - - let extension = ".cs" - - // We need to include the version information of the assembly so InternalsVisibleTo and stuff works - let assemblyInfoDocumentId = DocumentId.CreateNewId(projectId) - let assemblyInfoFileName = "AssemblyInfo" + extension - let assemblyInfoString = String.Format(@"[assembly: System.Reflection.AssemblyVersion(""{0}"")]", asmIdentity.Version) - - let assemblyInfoSourceTextContainer = SourceText.From(assemblyInfoString, Encoding.UTF8).Container - - let assemblyInfoDocument = - DocumentInfo.Create( - assemblyInfoDocumentId, - assemblyInfoFileName, - loader = TextLoader.From(assemblyInfoSourceTextContainer, VersionStamp.Default)) - - let generatedDocumentId = DocumentId.CreateNewId(projectId) - let generatedDocument = - DocumentInfo.Create( - generatedDocumentId, - Path.GetFileName(temporaryFilePath), - filePath = temporaryFilePath, - loader = FileTextLoader(temporaryFilePath, Encoding.UTF8)) - - let projectInfo = - ProjectInfo.Create( - projectId, - VersionStamp.Default, - name = asmIdentity.Name, - assemblyName = asmIdentity.Name, - language = LanguageNames.CSharp, - compilationOptions = compilationOptions, - parseOptions = parseOptions, - documents = [|assemblyInfoDocument;generatedDocument|], - metadataReferences = metadataReferences) - - (projectInfo, generatedDocumentId) - - let Decompile (symbolFullName: string, assemblyLocation: string) = - let logger = new StringBuilder(); - - // Initialize a decompiler with default settings. - let decompiler = CSharpDecompiler(assemblyLocation, DecompilerSettings()) - // Escape invalid identifiers to prevent Roslyn from failing to parse the generated code. - // (This happens for example, when there is compiler-generated code that is not yet recognized/transformed by the decompiler.) - decompiler.AstTransforms.Add(new EscapeInvalidIdentifiers()) - - let fullTypeName = FullTypeName(symbolFullName) - - // Try to decompile; if an exception is thrown the caller will handle it - let mutable text = decompiler.DecompileTypeAsString(fullTypeName) - - text <- text + "#if false // " + Environment.NewLine - text <- text + logger.ToString() - text <- text + "#endif" + Environment.NewLine - - SourceText.From(text) - - let ShowDocument (filePath, name, serviceProvider: IServiceProvider) = - let vsRunningDocumentTable4 = serviceProvider.GetService() - let fileAlreadyOpen = vsRunningDocumentTable4.IsMonikerValid(filePath) - - let openDocumentService = serviceProvider.GetService() - let mutable localServiceProvider = Unchecked.defaultof<_> - let mutable hierarchy = Unchecked.defaultof<_> - let mutable itemId = Unchecked.defaultof<_> - let mutable windowFrame = Unchecked.defaultof<_> - openDocumentService.OpenDocumentViaProject(filePath, ref VSConstants.LOGVIEWID.TextView_guid, &localServiceProvider, &hierarchy, &itemId, &windowFrame) |> ignore - - let componentModel = serviceProvider.GetService() - let editorAdaptersFactory = componentModel.GetService(); - let documentCookie = vsRunningDocumentTable4.GetDocumentCookie(filePath) - let vsTextBuffer = vsRunningDocumentTable4.GetDocumentData(documentCookie) :?> IVsTextBuffer - let textBuffer = editorAdaptersFactory.GetDataBuffer(vsTextBuffer) - - if not fileAlreadyOpen then - ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_IsProvisional, true)) |> ignore - ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_OverrideCaption, name)) |> ignore - ErrorHandler.ThrowOnFailure(windowFrame.SetProperty(int __VSFPROPID5.VSFPROPID_OverrideToolTip, name)) |> ignore - - windowFrame.Show() |> ignore - - let textContainer = textBuffer.AsTextContainer() - let mutable workspace = Unchecked.defaultof<_> - if Workspace.TryGetWorkspace(textContainer, &workspace) then - let solution = workspace.CurrentSolution - let documentId = workspace.GetDocumentIdInCurrentContext(textContainer) - match box documentId with - | null -> None - | _ -> solution.GetDocument(documentId) |> Some - else - None - module private Symbol = let fullName (root: ISymbol) : string = @@ -270,7 +157,7 @@ type internal FSharpGoToDefinitionNavigableItem(document, sourceSpan) = [] type internal FSharpGoToDefinitionResult = | NavigableItem of FSharpNavigableItem - | ExternalAssembly of ProjectInfo * DocumentId * FSharpSymbolUse + | ExternalAssembly of ProjectInfo * DocumentInfo * FSharpSymbolUse * FSharpExternalSymbol type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpProjectOptionsManager) = let userOpName = "GoToDefinition" @@ -388,8 +275,8 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP let! location = symbol.Locations |> Seq.tryHead return (FSharpGoToDefinitionResult.NavigableItem(FSharpGoToDefinitionNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan)), idRange) else - let tmpProjInfo, tmpDocId = CSharpDecompiler.GenerateTemporaryCSharpDocument(AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName), targetSymbolUse.Symbol.DisplayName, originDocument.Project.MetadataReferences) - return (FSharpGoToDefinitionResult.ExternalAssembly(tmpProjInfo, tmpDocId, targetSymbolUse), idRange) + let tmpProjInfo, tmpDocId = MetadataAsSource.GenerateTemporaryCSharpDocument(AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName), targetSymbolUse.Symbol.DisplayName, originDocument.Project.MetadataReferences) + return (FSharpGoToDefinitionResult.ExternalAssembly(tmpProjInfo, tmpDocId, targetSymbolUse, targetExternalSym), idRange) | FSharpFindDeclResult.DeclFound targetRange -> // if goto definition is called at we are alread at the declaration location of a symbol in diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index d5af30c93f8..b7a6333a5de 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -2,12 +2,14 @@ namespace Microsoft.VisualStudio.FSharp.Editor +open System open System.Composition open System.IO open System.Threading open System.Threading.Tasks open Microsoft.CodeAnalysis +open Microsoft.CodeAnalysis.Text open Microsoft.CodeAnalysis.Editor open Microsoft.CodeAnalysis.Host.Mef open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Editor @@ -15,7 +17,9 @@ open Microsoft.CodeAnalysis.ExternalAccess.FSharp.Navigation open Microsoft.VisualStudio.Shell open Microsoft.VisualStudio.Shell.Interop -open System +open Microsoft.VisualStudio.LanguageServices + +open FSharp.Compiler.SourceCodeServices [)>] [)>] @@ -27,7 +31,8 @@ type internal FSharpGoToDefinitionService ) = let gtd = GoToDefinition(checkerProvider.Checker, projectInfoManager) - let statusBar = StatusBar(ServiceProvider.GlobalProvider.GetService()) + let statusBar = StatusBar(ServiceProvider.GlobalProvider.GetService()) + let metadataAsSourceService = checkerProvider.MetadataAsSource interface IFSharpGoToDefinitionService with /// Invoked with Peek Definition. @@ -63,28 +68,30 @@ type internal FSharpGoToDefinitionService gtd.NavigateToItem(navItem, statusBar) // 'true' means do it, like Sheev Palpatine would want us to. true - | FSharpGoToDefinitionResult.ExternalAssembly(tmpProjInfo, tmpDocId, targetSymbolUse) -> + | FSharpGoToDefinitionResult.ExternalAssembly(tmpProjInfo, tmpDocInfo, targetSymbolUse, targetExternalSymbol) -> match targetSymbolUse.Symbol.Assembly.FileName with | Some targetSymbolAssemblyFileName -> - let text = CSharpDecompiler.Decompile(targetSymbolUse.Symbol.FullName, targetSymbolAssemblyFileName) - let workspace = new AdhocWorkspace(document.Project.Solution.Workspace.Services.HostServices, WorkspaceKind.MetadataAsSource) - let proj = workspace.AddProject(tmpProjInfo) - let tmpDoc = proj.GetDocument(tmpDocId) - let _ = - let directoryName = Path.GetDirectoryName(tmpDoc.FilePath) - if Directory.Exists(directoryName) |> not then - Directory.CreateDirectory(directoryName) |> ignore - use fileStream = new FileStream(tmpDoc.FilePath, IO.FileMode.Create) - use writer = new StreamWriter(fileStream) - text.Write(writer) - - workspace.OpenDocument(tmpDoc.Id, true) - let tmpShownDocOpt = CSharpDecompiler.ShowDocument(tmpDoc.FilePath, tmpDoc.Name, ServiceProvider.GlobalProvider) - match tmpShownDocOpt with - | Some tmpShownDoc -> - let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, Text.TextSpan()) - gtd.NavigateToItem(navItem, statusBar) - true + try + let symbolFullTypeName = + match targetExternalSymbol with + | FSharpExternalSymbol.Constructor(tyName, _) + | FSharpExternalSymbol.Event(tyName, _) + | FSharpExternalSymbol.Field(tyName, _) + | FSharpExternalSymbol.Method(tyName, _, _, _) + | FSharpExternalSymbol.Property(tyName, _) + | FSharpExternalSymbol.Type(tyName) -> tyName + + let text = MetadataAsSource.DecompileCSharp(symbolFullTypeName, targetSymbolAssemblyFileName) + let tmpShownDocOpt = metadataAsSourceService.ShowCSharpDocument(tmpProjInfo, tmpDocInfo, text) + match tmpShownDocOpt with + | Some tmpShownDoc -> + let navItem = FSharpGoToDefinitionNavigableItem(tmpShownDoc, TextSpan()) + gtd.NavigateToItem(navItem, statusBar) + true + | _ -> + statusBar.TempMessage (SR.CannotDetermineSymbol()) + false + with | _ -> statusBar.TempMessage (SR.CannotDetermineSymbol()) false From 2e4f5915b180aedd2110531075131dd62c30c894 Mon Sep 17 00:00:00 2001 From: Will Smith Date: Thu, 28 Jan 2021 19:41:46 -0800 Subject: [PATCH 4/4] Feedback --- .../LanguageService/MetadataAsSource.fs | 25 ++++++++----------- .../LanguageService/SingleFileWorkspaceMap.fs | 5 ++-- .../Navigation/GoToDefinition.fs | 8 +++--- .../Navigation/GoToDefinitionService.fs | 2 +- 4 files changed, 18 insertions(+), 22 deletions(-) diff --git a/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs index 4bc4e1011cc..b1e0df38153 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/MetadataAsSource.fs @@ -38,7 +38,7 @@ module internal MetadataAsSource = open ICSharpCode.Decompiler.CSharp.Transforms open ICSharpCode.Decompiler.TypeSystem - let GenerateTemporaryCSharpDocument (asmIdentity: AssemblyIdentity, name: string, metadataReferences) = + let generateTemporaryCSharpDocument (asmIdentity: AssemblyIdentity, name: string, metadataReferences) = let rootPath = Path.Combine(Path.GetTempPath(), "MetadataAsSource") let extension = ".cs" let directoryName = Guid.NewGuid().ToString("N") @@ -85,8 +85,8 @@ module internal MetadataAsSource = (projectInfo, documentInfo) - let DecompileCSharp (symbolFullTypeName: string, assemblyLocation: string) = - let logger = new StringBuilder(); + let decompileCSharp (symbolFullTypeName: string, assemblyLocation: string) = + let logger = new StringBuilder() // Initialize a decompiler with default settings. let decompiler = CSharpDecompiler(assemblyLocation, DecompilerSettings()) @@ -97,24 +97,21 @@ module internal MetadataAsSource = let fullTypeName = FullTypeName(symbolFullTypeName) // Try to decompile; if an exception is thrown the caller will handle it - let mutable text = decompiler.DecompileTypeAsString(fullTypeName) + let text = decompiler.DecompileTypeAsString(fullTypeName) - text <- text + "#if false // " + Environment.NewLine - text <- text + logger.ToString() - text <- text + "#endif" + Environment.NewLine + let text = text + "#if false // " + Environment.NewLine + let text = text + logger.ToString() + let text = text + "#endif" + Environment.NewLine SourceText.From(text) - let ShowDocument (filePath, name, serviceProvider: IServiceProvider) = + let showDocument (filePath, name, serviceProvider: IServiceProvider) = let vsRunningDocumentTable4 = serviceProvider.GetService() let fileAlreadyOpen = vsRunningDocumentTable4.IsMonikerValid(filePath) let openDocumentService = serviceProvider.GetService() - let mutable localServiceProvider = Unchecked.defaultof<_> - let mutable hierarchy = Unchecked.defaultof<_> - let mutable itemId = Unchecked.defaultof<_> - let mutable windowFrame = Unchecked.defaultof<_> - openDocumentService.OpenDocumentViaProject(filePath, ref VSConstants.LOGVIEWID.TextView_guid, &localServiceProvider, &hierarchy, &itemId, &windowFrame) |> ignore + + let (_, _, _, _, windowFrame) = openDocumentService.OpenDocumentViaProject(filePath, ref VSConstants.LOGVIEWID.TextView_guid) let componentModel = serviceProvider.GetService() let editorAdaptersFactory = componentModel.GetService(); @@ -156,4 +153,4 @@ type internal FSharpMetadataAsSourceService() = this.CSharpFiles.[docInfo.FilePath] <- (projInfo, docInfo) - MetadataAsSource.ShowDocument(docInfo.FilePath, docInfo.Name, ServiceProvider.GlobalProvider) \ No newline at end of file + MetadataAsSource.showDocument(docInfo.FilePath, docInfo.Name, ServiceProvider.GlobalProvider) \ No newline at end of file diff --git a/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs b/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs index 5b1ef7a2175..0e565801fd3 100644 --- a/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs +++ b/vsintegration/src/FSharp.Editor/LanguageService/SingleFileWorkspaceMap.fs @@ -38,13 +38,12 @@ type internal SingleFileWorkspaceMap(workspace: VisualStudioWorkspace, projectContext.DisplayName <- projInfo.Name projectContext.AddSourceFile(docInfo.FilePath, sourceCodeKind = SourceCodeKind.Regular) - projInfo.MetadataReferences - |> Seq.iter (function + for metaRef in projInfo.MetadataReferences do + match metaRef with | :? PortableExecutableReference as peRef -> projectContext.AddMetadataReference(peRef.FilePath, MetadataReferenceProperties.Assembly) | _ -> () - ) projectContext diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs index dbebfd02d7c..d5166440213 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs @@ -256,8 +256,8 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP match declarations with | FSharpFindDeclResult.ExternalDecl (assembly, targetExternalSym) -> let projectOpt = originDocument.Project.Solution.Projects |> Seq.tryFind (fun p -> p.AssemblyName.Equals(assembly, StringComparison.OrdinalIgnoreCase)) - if projectOpt.IsSome then - let project = projectOpt.Value + match projectOpt with + | Some project -> let! symbols = SymbolFinder.FindSourceDeclarationsAsync(project, fun _ -> true) let roslynSymbols = @@ -274,8 +274,8 @@ type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpP let! location = symbol.Locations |> Seq.tryHead return (FSharpGoToDefinitionResult.NavigableItem(FSharpGoToDefinitionNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan)), idRange) - else - let tmpProjInfo, tmpDocId = MetadataAsSource.GenerateTemporaryCSharpDocument(AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName), targetSymbolUse.Symbol.DisplayName, originDocument.Project.MetadataReferences) + | _ -> + let tmpProjInfo, tmpDocId = MetadataAsSource.generateTemporaryCSharpDocument(AssemblyIdentity(targetSymbolUse.Symbol.Assembly.QualifiedName), targetSymbolUse.Symbol.DisplayName, originDocument.Project.MetadataReferences) return (FSharpGoToDefinitionResult.ExternalAssembly(tmpProjInfo, tmpDocId, targetSymbolUse, targetExternalSym), idRange) | FSharpFindDeclResult.DeclFound targetRange -> diff --git a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs index b7a6333a5de..ba377a3929a 100644 --- a/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs +++ b/vsintegration/src/FSharp.Editor/Navigation/GoToDefinitionService.fs @@ -81,7 +81,7 @@ type internal FSharpGoToDefinitionService | FSharpExternalSymbol.Property(tyName, _) | FSharpExternalSymbol.Type(tyName) -> tyName - let text = MetadataAsSource.DecompileCSharp(symbolFullTypeName, targetSymbolAssemblyFileName) + let text = MetadataAsSource.decompileCSharp(symbolFullTypeName, targetSymbolAssemblyFileName) let tmpShownDocOpt = metadataAsSourceService.ShowCSharpDocument(tmpProjInfo, tmpDocInfo, text) match tmpShownDocOpt with | Some tmpShownDoc ->