diff --git a/FSharp.Editor.fsproj b/FSharp.Editor.fsproj
index 3cb82ad13b9..ca16dd8b9a6 100644
--- a/FSharp.Editor.fsproj
+++ b/FSharp.Editor.fsproj
@@ -73,6 +73,8 @@
+
+
diff --git a/Navigation/GoToDefinition.fs b/Navigation/GoToDefinition.fs
new file mode 100644
index 00000000000..703d11407cd
--- /dev/null
+++ b/Navigation/GoToDefinition.fs
@@ -0,0 +1,383 @@
+// 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.Runtime.InteropServices
+
+open Microsoft.CodeAnalysis
+open Microsoft.CodeAnalysis.FindSymbols
+open Microsoft.CodeAnalysis.Text
+open Microsoft.CodeAnalysis.Navigation
+
+open Microsoft.VisualStudio.Shell.Interop
+
+open Microsoft.FSharp.Compiler.Range
+open Microsoft.FSharp.Compiler.SourceCodeServices
+
+module private Symbol =
+ let fullName (root: ISymbol) : string =
+ let rec inner parts (sym: ISymbol) =
+ match sym with
+ | null ->
+ parts
+ // TODO: do we have any other terminating cases?
+ | sym when sym.Kind = SymbolKind.NetModule || sym.Kind = SymbolKind.Assembly ->
+ parts
+ | sym when sym.MetadataName <> "" ->
+ inner (sym.MetadataName :: parts) sym.ContainingSymbol
+ | sym ->
+ inner parts sym.ContainingSymbol
+
+ inner [] root |> String.concat "."
+
+module private ExternalType =
+ let rec tryOfRoslynType (typesym: ITypeSymbol): ExternalType option =
+ match typesym with
+ | :? IPointerTypeSymbol as ptrparam ->
+ tryOfRoslynType ptrparam.PointedAtType |> Option.map ExternalType.Pointer
+ | :? IArrayTypeSymbol as arrparam ->
+ tryOfRoslynType arrparam.ElementType |> Option.map ExternalType.Array
+ | :? ITypeParameterSymbol as typaram ->
+ Some (ExternalType.TypeVar typaram.Name)
+ | :? INamedTypeSymbol as namedTypeSym ->
+ namedTypeSym.TypeArguments
+ |> Seq.map tryOfRoslynType
+ |> List.ofSeq
+ |> Option.ofOptionList
+ |> Option.map (fun genericArgs ->
+ ExternalType.Type (Symbol.fullName typesym, genericArgs))
+ | _ ->
+ Debug.Assert(false, sprintf "GoToDefinitionService: Unexpected Roslyn type symbol subclass: %O" (typesym.GetType()))
+ None
+
+module private ParamTypeSymbol =
+
+ let tryOfRoslynParameter (param: IParameterSymbol): ParamTypeSymbol option =
+ ExternalType.tryOfRoslynType param.Type
+ |> Option.map (
+ if param.RefKind = RefKind.None then ParamTypeSymbol.Param
+ else ParamTypeSymbol.Byref)
+
+ let tryOfRoslynParameters (paramSyms: ImmutableArray): ParamTypeSymbol list option =
+ paramSyms
+ |> Seq.map tryOfRoslynParameter
+ |> Seq.toList
+ |> Option.ofOptionList
+
+module private ExternalSymbol =
+ let rec ofRoslynSymbol (symbol: ISymbol) : (ISymbol * ExternalSymbol) list =
+ let container = Symbol.fullName symbol.ContainingSymbol
+
+ match symbol with
+ | :? INamedTypeSymbol as typesym ->
+ let fullTypeName = Symbol.fullName typesym
+
+ let constructors =
+ typesym.InstanceConstructors
+ |> Seq.choose<_,ISymbol * ExternalSymbol> (fun methsym ->
+ ParamTypeSymbol.tryOfRoslynParameters methsym.Parameters
+ |> Option.map (fun args -> upcast methsym, ExternalSymbol.Constructor(fullTypeName, args))
+ )
+ |> List.ofSeq
+
+ (symbol, ExternalSymbol.Type fullTypeName) :: constructors
+
+ | :? IMethodSymbol as methsym ->
+ ParamTypeSymbol.tryOfRoslynParameters methsym.Parameters
+ |> Option.map (fun args ->
+ symbol, ExternalSymbol.Method(container, methsym.MetadataName, args, methsym.TypeParameters.Length))
+ |> Option.toList
+
+ | :? IPropertySymbol as propsym ->
+ [upcast propsym, ExternalSymbol.Property(container, propsym.MetadataName)]
+
+ | :? IFieldSymbol as fieldsym ->
+ [upcast fieldsym, ExternalSymbol.Field(container, fieldsym.MetadataName)]
+
+ | :? IEventSymbol as eventsym ->
+ [upcast eventsym, ExternalSymbol.Event(container, eventsym.MetadataName)]
+
+ | _ -> []
+
+type internal FSharpNavigableItem(document: Document, textSpan: TextSpan) =
+ interface INavigableItem with
+ member __.Glyph = Glyph.BasicFile
+ member __.DisplayFileLocation = true
+ member __.IsImplicitlyDeclared = false
+ member __.Document = document
+ member __.SourceSpan = textSpan
+ member __.DisplayTaggedParts = ImmutableArray.Empty
+ member __.ChildItems = ImmutableArray.Empty
+
+type internal StatusBar(statusBar: IVsStatusbar) =
+ let mutable searchIcon = int16 Microsoft.VisualStudio.Shell.Interop.Constants.SBAI_Find :> obj
+
+ let clear() =
+ // unfreeze the statusbar
+ statusBar.FreezeOutput 0 |> ignore
+ statusBar.Clear() |> ignore
+
+ member __.Message(msg: string) =
+ let _, frozen = statusBar.IsFrozen()
+ // unfreeze the status bar
+ if frozen <> 0 then statusBar.FreezeOutput 0 |> ignore
+ statusBar.SetText msg |> ignore
+ // freeze the status bar
+ statusBar.FreezeOutput 1 |> ignore
+
+ member this.TempMessage(msg: string) =
+ this.Message msg
+ async {
+ do! Async.Sleep 4000
+ match statusBar.GetText() with
+ | 0, currentText when currentText <> msg -> ()
+ | _ -> clear()
+ }|> Async.Start
+
+ member __.Clear() = clear()
+
+ /// Animated magnifying glass that displays on the status bar while a symbol search is in progress.
+ member __.Animate() : IDisposable =
+ statusBar.Animation (1, &searchIcon) |> ignore
+ { new IDisposable with
+ member __.Dispose() = statusBar.Animation(0, &searchIcon) |> ignore }
+
+type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpProjectOptionsManager) =
+ let userOpName = "GoToDefinition"
+
+ /// Use an origin document to provide the solution & workspace used to
+ /// find the corresponding textSpan and INavigableItem for the range
+ let rangeToNavigableItem (range: range, document: Document) =
+ async {
+ let fileName = try System.IO.Path.GetFullPath range.FileName with _ -> range.FileName
+ let refDocumentIds = document.Project.Solution.GetDocumentIdsWithFilePath fileName
+ if not refDocumentIds.IsEmpty then
+ let refDocumentId = refDocumentIds.First()
+ let refDocument = document.Project.Solution.GetDocument refDocumentId
+ let! cancellationToken = Async.CancellationToken
+ let! refSourceText = refDocument.GetTextAsync(cancellationToken) |> Async.AwaitTask
+ match RoslynHelpers.TryFSharpRangeToTextSpan (refSourceText, range) with
+ | None -> return None
+ | Some refTextSpan -> return Some (FSharpNavigableItem (refDocument, refTextSpan))
+ else return None
+ }
+
+ /// Helper function that is used to determine the navigation strategy to apply, can be tuned towards signatures or implementation files.
+ member private __.FindSymbolHelper (originDocument: Document, originRange: range, sourceText: SourceText, preferSignature: bool) =
+ asyncMaybe {
+ let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject originDocument
+ let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions
+ let! originTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sourceText, originRange)
+ let position = originTextSpan.Start
+ let! lexerSymbol = Tokenizer.getSymbolAtPosition (originDocument.Id, sourceText, position, originDocument.FilePath, defines, SymbolLookupKind.Greedy, false)
+
+ let textLinePos = sourceText.Lines.GetLinePosition position
+ let fcsTextLineNumber = Line.fromZ textLinePos.Line
+ let lineText = (sourceText.Lines.GetLineFromPosition position).ToString()
+
+ let! _, _, checkFileResults = checker.ParseAndCheckDocument (originDocument, projectOptions, allowStaleResults=true,sourceText=sourceText, userOpName=userOpName)
+ let idRange = lexerSymbol.Ident.idRange
+ let! fsSymbolUse = checkFileResults.GetSymbolUseAtLocation (fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland, userOpName=userOpName)
+ let symbol = fsSymbolUse.Symbol
+ // if the tooltip was spawned in an implementation file and we have a range targeting
+ // a signature file, try to find the corresponding implementation file and target the
+ // desired symbol
+ if isSignatureFile fsSymbolUse.FileName && preferSignature = false then
+ let fsfilePath = Path.ChangeExtension (originRange.FileName,"fs")
+ if not (File.Exists fsfilePath) then return! None else
+ let! implDoc = originDocument.Project.Solution.TryGetDocumentFromPath fsfilePath
+ let! implSourceText = implDoc.GetTextAsync ()
+ let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject implDoc
+ let! _, _, checkFileResults = checker.ParseAndCheckDocument (implDoc, projectOptions, allowStaleResults=true, sourceText=implSourceText, userOpName=userOpName)
+ let! symbolUses = checkFileResults.GetUsesOfSymbolInFile symbol |> liftAsync
+ let! implSymbol = symbolUses |> Array.tryHead
+ let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, implSymbol.RangeAlternate)
+ return FSharpNavigableItem (implDoc, implTextSpan)
+ else
+ let! targetDocument = originDocument.Project.Solution.TryGetDocumentFromFSharpRange fsSymbolUse.RangeAlternate
+ return! rangeToNavigableItem (fsSymbolUse.RangeAlternate, targetDocument)
+ }
+
+ /// Use the targetSymbol to find the first instance of its presence in the provided source file.
+ member private __.FindSymbolDeclarationInFile(targetSymbolUse: FSharpSymbolUse, filePath: string, source: string, options: FSharpProjectOptions, fileVersion:int) =
+ asyncMaybe {
+ let! _, checkFileAnswer = checker.ParseAndCheckFileInProject (filePath, fileVersion, source, options, userOpName=userOpName) |> liftAsync
+ match checkFileAnswer with
+ | FSharpCheckFileAnswer.Aborted -> return! None
+ | FSharpCheckFileAnswer.Succeeded checkFileResults ->
+ let! symbolUses = checkFileResults.GetUsesOfSymbolInFile targetSymbolUse.Symbol |> liftAsync
+ let! implSymbol = symbolUses |> Array.tryHead
+ return implSymbol.RangeAlternate
+ }
+
+ member private this.FindDefinitionAtPosition(originDocument: Document, position: int) =
+ asyncMaybe {
+ let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject originDocument
+ let! sourceText = originDocument.GetTextAsync () |> liftTaskAsync
+ let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions
+ let textLine = sourceText.Lines.GetLineFromPosition position
+ let textLinePos = sourceText.Lines.GetLinePosition position
+ let fcsTextLineNumber = Line.fromZ textLinePos.Line
+ let lineText = (sourceText.Lines.GetLineFromPosition position).ToString()
+
+ let preferSignature = isSignatureFile originDocument.FilePath
+
+ let! _, _, checkFileResults = checker.ParseAndCheckDocument (originDocument, projectOptions, allowStaleResults=true, sourceText=sourceText, userOpName=userOpName)
+
+ let! lexerSymbol = Tokenizer.getSymbolAtPosition (originDocument.Id, sourceText, position,originDocument.FilePath, defines, SymbolLookupKind.Greedy, false)
+ let idRange = lexerSymbol.Ident.idRange
+
+ let! declarations = checkFileResults.GetDeclarationLocation (fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLine.ToString(), lexerSymbol.FullIsland, preferSignature, userOpName=userOpName) |> liftAsync
+ let! targetSymbolUse = checkFileResults.GetSymbolUseAtLocation (fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland, userOpName=userOpName)
+
+ match declarations with
+ | FSharpFindDeclResult.ExternalDecl (assy, targetExternalSym) ->
+ let! project = originDocument.Project.Solution.Projects |> Seq.tryFind (fun p -> p.AssemblyName.Equals(assy, 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 (FSharpNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan), idRange)
+
+ | FSharpFindDeclResult.DeclFound targetRange ->
+ // if goto definition is called at we are alread at the declaration location of a symbol in
+ // either a signature or an implementation file then we jump to it's respective postion in thethe
+ if lexerSymbol.Range = targetRange then
+ // jump from signature to the corresponding implementation
+ if isSignatureFile originDocument.FilePath then
+ let implFilePath = Path.ChangeExtension (originDocument.FilePath,"fs")
+ if not (File.Exists implFilePath) then return! None else
+ let! implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath
+ let! implSourceText = implDocument.GetTextAsync () |> liftTaskAsync
+ let! implVersion = implDocument.GetTextVersionAsync () |> liftTaskAsync
+
+ let! targetRange = this.FindSymbolDeclarationInFile(targetSymbolUse, implFilePath, implSourceText.ToString(), projectOptions, implVersion.GetHashCode())
+
+ let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange)
+ let navItem = FSharpNavigableItem (implDocument, implTextSpan)
+ return (navItem, idRange)
+ else // jump from implementation to the corresponding signature
+ let! declarations = checkFileResults.GetDeclarationLocation (fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLine.ToString(), lexerSymbol.FullIsland, true, userOpName=userOpName) |> liftAsync
+ match declarations with
+ | FSharpFindDeclResult.DeclFound targetRange ->
+ let! sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName
+ let! sigSourceText = sigDocument.GetTextAsync () |> liftTaskAsync
+ let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sigSourceText, targetRange)
+ let navItem = FSharpNavigableItem (sigDocument, sigTextSpan)
+ return (navItem, idRange)
+ | _ ->
+ return! None
+ // when the target range is different follow the navigation convention of
+ // - gotoDefn origin = signature , gotoDefn destination = signature
+ // - gotoDefn origin = implementation, gotoDefn destination = implementation
+ else
+ let! sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName
+ let! sigSourceText = sigDocument.GetTextAsync () |> liftTaskAsync
+ let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sigSourceText, targetRange)
+ // 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 = FSharpNavigableItem (sigDocument, sigTextSpan)
+ return (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 =
+ // Bugfix: apparently sigDocument not always is a signature file
+ if isSignatureFile sigDocument.FilePath then Path.ChangeExtension (sigDocument.FilePath, "fs")
+ else sigDocument.FilePath
+
+ let! implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath
+ let! implVersion = implDocument.GetTextVersionAsync () |> liftTaskAsync
+ let! implSourceText = implDocument.GetTextAsync () |> liftTaskAsync
+ let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject implDocument
+
+ let! targetRange = this.FindSymbolDeclarationInFile(targetSymbolUse, implFilePath, implSourceText.ToString(), projectOptions, implVersion.GetHashCode())
+
+ let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange)
+ let navItem = FSharpNavigableItem (implDocument, implTextSpan)
+ return (navItem, idRange)
+ | _ ->
+ return! None
+ }
+
+ /// find the declaration location (signature file/.fsi) of the target symbol if possible, fall back to definition
+ member this.FindDeclarationOfSymbolAtRange(targetDocument: Document, symbolRange: range, targetSource: SourceText) =
+ this.FindSymbolHelper(targetDocument, symbolRange, targetSource, preferSignature=true)
+
+ /// find the definition location (implementation file/.fs) of the target symbol
+ member this.FindDefinitionOfSymbolAtRange(targetDocument: Document, symbolRange: range, targetSourceText: SourceText) =
+ this.FindSymbolHelper(targetDocument, symbolRange, targetSourceText, preferSignature=false)
+
+ member this.FindDefinitionsForPeekTask(originDocument: Document, position: int, cancellationToken: CancellationToken) =
+ this.FindDefinitionAtPosition(originDocument, position)
+ |> Async.map (
+ Option.map (fun (navItem, _) -> (navItem :> INavigableItem))
+ >> Option.toArray
+ >> Array.toSeq)
+ |> RoslynHelpers.StartAsyncAsTask cancellationToken
+
+ /// Construct a task that will return a navigation target for the implementation definition of the symbol
+ /// at the provided position in the document.
+ member this.FindDefinitionTask(originDocument: Document, position: int, cancellationToken: CancellationToken) =
+ this.FindDefinitionAtPosition(originDocument, position)
+ |> Async.map (Option.map (fun (navItem, range) -> (navItem :> INavigableItem, range)))
+ |> RoslynHelpers.StartAsyncAsTask cancellationToken
+
+ /// 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) =
+ let navigableItem = FSharpNavigableItem(document, textSpan) :> INavigableItem
+ let workspace = document.Project.Solution.Workspace
+ let navigationService = workspace.Services.GetService()
+ let options = workspace.Options.WithChangedOption(NavigationOptions.PreferProvisionalTab, true)
+ let navigationSucceeded = navigationService.TryNavigateToSpan(workspace, navigableItem.Document.Id, navigableItem.SourceSpan, options)
+
+ if not navigationSucceeded then
+ statusBar.TempMessage (SR.CannotNavigateUnknown())
+
+ member __.NavigateToItem(navigableItem: #INavigableItem, statusBar: StatusBar) =
+ use __ = statusBar.Animate()
+
+ statusBar.Message (SR.NavigatingTo())
+
+ let workspace = navigableItem.Document.Project.Solution.Workspace
+ let navigationService = workspace.Services.GetService()
+
+ // Prefer open documents in the preview tab.
+ let options = workspace.Options.WithChangedOption(NavigationOptions.PreferProvisionalTab, true)
+ let result = navigationService.TryNavigateToSpan(workspace, navigableItem.Document.Id, navigableItem.SourceSpan, options)
+
+ if result then
+ statusBar.Clear()
+ else
+ statusBar.TempMessage (SR.CannotNavigateUnknown())
+
+ /// Find the declaration location (signature file/.fsi) of the target symbol if possible, fall back to definition
+ member this.NavigateToSymbolDeclarationAsync(targetDocument: Document, targetSourceText: SourceText, symbolRange: range, statusBar: StatusBar) =
+ asyncMaybe {
+ let! item = this.FindDeclarationOfSymbolAtRange(targetDocument, symbolRange, targetSourceText)
+ return this.NavigateToItem(item, statusBar)
+ }
+
+ /// Find the definition location (implementation file/.fs) of the target symbol
+ member this.NavigateToSymbolDefinitionAsync(targetDocument: Document, targetSourceText: SourceText, symbolRange: range, statusBar: StatusBar) =
+ asyncMaybe {
+ let! item = this.FindDefinitionOfSymbolAtRange(targetDocument, symbolRange, targetSourceText)
+ return this.NavigateToItem(item, statusBar)
+ }
diff --git a/Navigation/GoToDefinitionService.fs b/Navigation/GoToDefinitionService.fs
index 91bc81690d3..dd201ab7750 100644
--- a/Navigation/GoToDefinitionService.fs
+++ b/Navigation/GoToDefinitionService.fs
@@ -3,235 +3,16 @@
namespace Microsoft.VisualStudio.FSharp.Editor
open System.Composition
-open System.IO
-open System.Collections.Immutable
-open System.Linq
open System.Threading
open System.Threading.Tasks
open Microsoft.CodeAnalysis
open Microsoft.CodeAnalysis.Editor
-open Microsoft.CodeAnalysis.Navigation
open Microsoft.CodeAnalysis.Host.Mef
-open Microsoft.CodeAnalysis.Text
-open Microsoft.CodeAnalysis.FindSymbols
-open Microsoft.FSharp.Compiler.Range
-open Microsoft.FSharp.Compiler.SourceCodeServices
open Microsoft.VisualStudio.Shell
open Microsoft.VisualStudio.Shell.Interop
open System
-open System.Diagnostics
-
-type internal FSharpNavigableItem(document: Document, textSpan: TextSpan) =
- interface INavigableItem with
- member this.Glyph = Glyph.BasicFile
- member this.DisplayFileLocation = true
- member this.IsImplicitlyDeclared = false
- member this.Document = document
- member this.SourceSpan = textSpan
- member this.DisplayTaggedParts = ImmutableArray.Empty
- member this.ChildItems = ImmutableArray.Empty
-
-type internal GoToDefinition(checker: FSharpChecker, projectInfoManager: FSharpProjectOptionsManager) =
-
- static let userOpName = "GoToDefinition"
-
- /// Use an origin document to provide the solution & workspace used to
- /// find the corresponding textSpan and INavigableItem for the range
- let rangeToNavigableItem (range: range, document: Document) =
- async {
- let fileName = try System.IO.Path.GetFullPath range.FileName with _ -> range.FileName
- let refDocumentIds = document.Project.Solution.GetDocumentIdsWithFilePath fileName
- if not refDocumentIds.IsEmpty then
- let refDocumentId = refDocumentIds.First()
- let refDocument = document.Project.Solution.GetDocument refDocumentId
- let! cancellationToken = Async.CancellationToken
- let! refSourceText = refDocument.GetTextAsync(cancellationToken) |> Async.AwaitTask
- match RoslynHelpers.TryFSharpRangeToTextSpan (refSourceText, range) with
- | None -> return None
- | Some refTextSpan -> return Some (FSharpNavigableItem (refDocument, refTextSpan))
- else return None
- }
-
- /// Helper function that is used to determine the navigation strategy to apply, can be tuned towards signatures or implementation files.
- let findSymbolHelper (originDocument: Document, originRange: range, sourceText: SourceText, preferSignature: bool) : Async =
- asyncMaybe {
- let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject originDocument
- let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions
- let! originTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sourceText, originRange)
- let position = originTextSpan.Start
- let! lexerSymbol = Tokenizer.getSymbolAtPosition (originDocument.Id, sourceText, position, originDocument.FilePath, defines, SymbolLookupKind.Greedy, false)
-
- let textLinePos = sourceText.Lines.GetLinePosition position
- let fcsTextLineNumber = Line.fromZ textLinePos.Line
- let lineText = (sourceText.Lines.GetLineFromPosition position).ToString()
-
- let! _, _, checkFileResults = checker.ParseAndCheckDocument (originDocument, projectOptions, allowStaleResults=true,sourceText=sourceText, userOpName = userOpName)
- let idRange = lexerSymbol.Ident.idRange
- let! fsSymbolUse = checkFileResults.GetSymbolUseAtLocation (fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland, userOpName=userOpName)
- let symbol = fsSymbolUse.Symbol
- // if the tooltip was spawned in an implementation file and we have a range targeting
- // a signature file, try to find the corresponding implementation file and target the
- // desired symbol
- if isSignatureFile fsSymbolUse.FileName && preferSignature = false then
- let fsfilePath = Path.ChangeExtension (originRange.FileName,"fs")
- if not (File.Exists fsfilePath) then return! None else
- let! implDoc = originDocument.Project.Solution.TryGetDocumentFromPath fsfilePath
- let! implSourceText = implDoc.GetTextAsync ()
- let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject implDoc
- let! _, _, checkFileResults = checker.ParseAndCheckDocument (implDoc, projectOptions, allowStaleResults=true, sourceText=implSourceText, userOpName = userOpName)
- let! symbolUses = checkFileResults.GetUsesOfSymbolInFile symbol |> liftAsync
- let! implSymbol = symbolUses |> Array.tryHead
- let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, implSymbol.RangeAlternate)
- return FSharpNavigableItem (implDoc, implTextSpan)
- else
- let! targetDocument = originDocument.Project.Solution.TryGetDocumentFromFSharpRange fsSymbolUse.RangeAlternate
- return! rangeToNavigableItem (fsSymbolUse.RangeAlternate, targetDocument)
- }
-
- /// find the declaration location (signature file/.fsi) of the target symbol if possible, fall back to definition
- member __.FindDeclarationOfSymbolAtRange(targetDocument: Document, symbolRange: range, targetSource: SourceText) =
- findSymbolHelper (targetDocument, symbolRange, targetSource, true)
-
- /// find the definition location (implementation file/.fs) of the target symbol
- member __.FindDefinitionOfSymbolAtRange(targetDocument: Document, symbolRange: range, targetSourceText: SourceText) =
- findSymbolHelper (targetDocument, symbolRange, targetSourceText, false)
-
- /// use the targetSymbol to find the first instance of its presence in the provided source file
- member __.FindSymbolDeclarationInFile(targetSymbolUse: FSharpSymbolUse, filePath: string, source: string, options: FSharpProjectOptions, fileVersion:int) =
- asyncMaybe {
- let! _, checkFileAnswer = checker.ParseAndCheckFileInProject (filePath, fileVersion, source, options, userOpName = userOpName) |> liftAsync
- match checkFileAnswer with
- | FSharpCheckFileAnswer.Aborted -> return! None
- | FSharpCheckFileAnswer.Succeeded checkFileResults ->
- let! symbolUses = checkFileResults.GetUsesOfSymbolInFile targetSymbolUse.Symbol |> liftAsync
- let! implSymbol = symbolUses |> Array.tryHead
- return implSymbol.RangeAlternate
- }
-
-type private StatusBar(statusBar: IVsStatusbar) =
-
- let mutable searchIcon = int16 Microsoft.VisualStudio.Shell.Interop.Constants.SBAI_Find :> obj
-
- let clear() =
- // unfreeze the statusbar
- statusBar.FreezeOutput 0 |> ignore
- statusBar.Clear() |> ignore
-
- member __.Message(msg: string) =
- let _, frozen = statusBar.IsFrozen()
- // unfreeze the status bar
- if frozen <> 0 then statusBar.FreezeOutput 0 |> ignore
- statusBar.SetText msg |> ignore
- // freeze the status bar
- statusBar.FreezeOutput 1 |> ignore
-
- member this.TempMessage(msg: string) =
- this.Message msg
- async {
- do! Async.Sleep 4000
- match statusBar.GetText() with
- | 0, currentText when currentText <> msg -> ()
- | _ -> clear()
- }|> Async.Start
-
- member __.Clear() = clear()
-
- /// Animated magnifying glass that displays on the status bar while a symbol search is in progress.
- member __.Animate() : IDisposable =
- statusBar.Animation (1, &searchIcon) |> ignore
- { new IDisposable with
- member __.Dispose() = statusBar.Animation(0, &searchIcon) |> ignore }
-
-module internal Symbol =
-
- let fullName (root: ISymbol) : string =
-
- let rec inner parts (sym: ISymbol) =
- match sym with
- | null ->
- parts
- // TODO: do we have any other terminating cases?
- | sym when sym.Kind = SymbolKind.NetModule || sym.Kind = SymbolKind.Assembly ->
- parts
- | sym when sym.MetadataName <> "" ->
- inner (sym.MetadataName :: parts) sym.ContainingSymbol
- | sym ->
- inner parts sym.ContainingSymbol
-
- inner [] root |> String.concat "."
-
-module internal ExternalType =
-
- let rec tryOfRoslynType (typesym: ITypeSymbol): ExternalType option =
- match typesym with
- | :? IPointerTypeSymbol as ptrparam ->
- tryOfRoslynType ptrparam.PointedAtType |> Option.map ExternalType.Pointer
- | :? IArrayTypeSymbol as arrparam ->
- tryOfRoslynType arrparam.ElementType |> Option.map ExternalType.Array
- | :? ITypeParameterSymbol as typaram ->
- Some (ExternalType.TypeVar typaram.Name)
- | :? INamedTypeSymbol as namedTypeSym ->
- namedTypeSym.TypeArguments
- |> Seq.map tryOfRoslynType
- |> List.ofSeq
- |> Option.ofOptionList
- |> Option.map (fun genericArgs ->
- ExternalType.Type (Symbol.fullName typesym, genericArgs)
- )
- | _ ->
- Debug.Assert(false, sprintf "GoToDefinitionService: Unexpected Roslyn type symbol subclass: %O" (typesym.GetType()))
- None
-
-module internal ParamTypeSymbol =
-
- let tryOfRoslynParameter (param: IParameterSymbol): ParamTypeSymbol option =
- ExternalType.tryOfRoslynType param.Type
- |> Option.map (
- if param.RefKind = RefKind.None then ParamTypeSymbol.Param
- else ParamTypeSymbol.Byref
- )
-
- let tryOfRoslynParameters (paramSyms: ImmutableArray): ParamTypeSymbol list option =
- paramSyms |> Seq.map tryOfRoslynParameter |> Seq.toList |> Option.ofOptionList
-
-module internal ExternalSymbol =
-
- let rec ofRoslynSymbol (symbol: ISymbol) : (ISymbol * ExternalSymbol) list =
- let container = Symbol.fullName symbol.ContainingSymbol
-
- match symbol with
- | :? INamedTypeSymbol as typesym ->
- let fullTypeName = Symbol.fullName typesym
-
- let constructors =
- typesym.InstanceConstructors
- |> Seq.choose<_,ISymbol * ExternalSymbol> (fun methsym ->
- ParamTypeSymbol.tryOfRoslynParameters methsym.Parameters
- |> Option.map (fun args -> upcast methsym, ExternalSymbol.Constructor(fullTypeName, args))
- )
- |> List.ofSeq
-
- (symbol, ExternalSymbol.Type fullTypeName) :: constructors
-
- | :? IMethodSymbol as methsym ->
- ParamTypeSymbol.tryOfRoslynParameters methsym.Parameters
- |> Option.map (fun args ->
- symbol, ExternalSymbol.Method(container, methsym.MetadataName, args, methsym.TypeParameters.Length))
- |> Option.toList
-
- | :? IPropertySymbol as propsym ->
- [upcast propsym, ExternalSymbol.Property(container, propsym.MetadataName)]
-
- | :? IFieldSymbol as fieldsym ->
- [upcast fieldsym, ExternalSymbol.Field(container, fieldsym.MetadataName)]
-
- | :? IEventSymbol as eventsym ->
- [upcast eventsym, ExternalSymbol.Event(container, eventsym.MetadataName)]
-
- | _ -> []
-
[, FSharpConstants.FSharpLanguageName)>]
[)>]
@@ -242,202 +23,46 @@ type internal FSharpGoToDefinitionService
projectInfoManager: FSharpProjectOptionsManager
) =
- static let userOpName = "GoToDefinition"
- let gotoDefinition = GoToDefinition(checkerProvider.Checker, projectInfoManager)
- let serviceProvider = ServiceProvider.GlobalProvider
- let statusBar = StatusBar(serviceProvider.GetService())
-
- let tryNavigateToItem (navigableItem: #INavigableItem option) =
- use __ = statusBar.Animate()
-
- match navigableItem with
- | Some navigableItem ->
- statusBar.Message (SR.NavigatingTo())
-
- let workspace = navigableItem.Document.Project.Solution.Workspace
- let navigationService = workspace.Services.GetService()
- // prefer open documents in the preview tab
- let options = workspace.Options.WithChangedOption (NavigationOptions.PreferProvisionalTab, true)
- let result = navigationService.TryNavigateToSpan (workspace, navigableItem.Document.Id, navigableItem.SourceSpan, options)
-
- if result then
- statusBar.Clear()
- else
- statusBar.TempMessage (SR.CannotNavigateUnknown())
-
- result
- | None ->
- statusBar.TempMessage (SR.CannotDetermineSymbol())
- true
-
- /// 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 this.TryNavigateToTextSpan (document: Document, textSpan: TextSpan) =
- let navigableItem = FSharpNavigableItem (document, textSpan) :> INavigableItem
- let workspace = document.Project.Solution.Workspace
- let navigationService = workspace.Services.GetService()
- let options = workspace.Options.WithChangedOption (NavigationOptions.PreferProvisionalTab, true)
- if navigationService.TryNavigateToSpan (workspace, navigableItem.Document.Id, navigableItem.SourceSpan, options) then
- true
- else
- statusBar.TempMessage (SR.CannotNavigateUnknown())
- false
-
- /// find the declaration location (signature file/.fsi) of the target symbol if possible, fall back to definition
- member __.NavigateToSymbolDeclarationAsync (targetDocument: Document, targetSourceText: SourceText, symbolRange: range) =
- gotoDefinition.FindDeclarationOfSymbolAtRange(targetDocument, symbolRange, targetSourceText) |> Async.map tryNavigateToItem
-
- /// find the definition location (implementation file/.fs) of the target symbol
- member this.NavigateToSymbolDefinitionAsync (targetDocument: Document, targetSourceText: SourceText, symbolRange: range) =
- gotoDefinition.FindDefinitionOfSymbolAtRange(targetDocument, symbolRange, targetSourceText) |> Async.map tryNavigateToItem
-
- /// Construct a task that will return a navigation target for the implementation definition of the symbol
- /// at the provided position in the document.
- member __.FindDefinitionsTask(originDocument: Document, position: int, cancellationToken: CancellationToken) =
- asyncMaybe {
- let! parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject originDocument
- let! sourceText = originDocument.GetTextAsync () |> liftTaskAsync
- let defines = CompilerEnvironment.GetCompilationDefinesForEditing parsingOptions
- let textLine = sourceText.Lines.GetLineFromPosition position
- let textLinePos = sourceText.Lines.GetLinePosition position
- let fcsTextLineNumber = Line.fromZ textLinePos.Line
- let lineText = (sourceText.Lines.GetLineFromPosition position).ToString()
-
- let preferSignature = isSignatureFile originDocument.FilePath
-
- let! _, _, checkFileResults = checkerProvider.Checker.ParseAndCheckDocument (originDocument, projectOptions, allowStaleResults=true, sourceText=sourceText, userOpName=userOpName)
-
- let! lexerSymbol = Tokenizer.getSymbolAtPosition (originDocument.Id, sourceText, position,originDocument.FilePath, defines, SymbolLookupKind.Greedy, false)
- let idRange = lexerSymbol.Ident.idRange
-
- let! declarations = checkFileResults.GetDeclarationLocation (fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLine.ToString(), lexerSymbol.FullIsland, preferSignature, userOpName=userOpName) |> liftAsync
- let! targetSymbolUse = checkFileResults.GetSymbolUseAtLocation (fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland, userOpName=userOpName)
-
- match declarations with
- | FSharpFindDeclResult.ExternalDecl (assy, targetExternalSym) ->
- let! project = originDocument.Project.Solution.Projects |> Seq.tryFind (fun p -> p.AssemblyName.Equals(assy, 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 FSharpNavigableItem(project.GetDocument(location.SourceTree), location.SourceSpan)
-
- | FSharpFindDeclResult.DeclFound targetRange ->
- // if goto definition is called at we are alread at the declaration location of a symbol in
- // either a signature or an implementation file then we jump to it's respective postion in thethe
- if lexerSymbol.Range = targetRange then
- // jump from signature to the corresponding implementation
- if isSignatureFile originDocument.FilePath then
- let implFilePath = Path.ChangeExtension (originDocument.FilePath,"fs")
- if not (File.Exists implFilePath) then return! None else
- let! implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath
- let! implSourceText = implDocument.GetTextAsync () |> liftTaskAsync
- let! implVersion = implDocument.GetTextVersionAsync () |> liftTaskAsync
-
- let! targetRange =
- gotoDefinition.FindSymbolDeclarationInFile(targetSymbolUse, implFilePath, implSourceText.ToString(), projectOptions, implVersion.GetHashCode())
-
- let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange)
- let navItem = FSharpNavigableItem (implDocument, implTextSpan)
- return navItem
- else // jump from implementation to the corresponding signature
- let! declarations = checkFileResults.GetDeclarationLocation (fcsTextLineNumber, lexerSymbol.Ident.idRange.EndColumn, textLine.ToString(), lexerSymbol.FullIsland, true, userOpName=userOpName) |> liftAsync
- match declarations with
- | FSharpFindDeclResult.DeclFound targetRange ->
- let! sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName
- let! sigSourceText = sigDocument.GetTextAsync () |> liftTaskAsync
- let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sigSourceText, targetRange)
- let navItem = FSharpNavigableItem (sigDocument, sigTextSpan)
- return navItem
- | _ -> return! None
- // when the target range is different follow the navigation convention of
- // - gotoDefn origin = signature , gotoDefn destination = signature
- // - gotoDefn origin = implementation, gotoDefn destination = implementation
- else
- let! sigDocument = originDocument.Project.Solution.TryGetDocumentFromPath targetRange.FileName
- let! sigSourceText = sigDocument.GetTextAsync () |> liftTaskAsync
- let! sigTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sigSourceText, targetRange)
- // 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 = FSharpNavigableItem (sigDocument, sigTextSpan)
- return navItem
- 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 =
- // Bugfix: apparently sigDocument not always is a signature file
- if isSignatureFile sigDocument.FilePath then Path.ChangeExtension (sigDocument.FilePath, "fs")
- else sigDocument.FilePath
-
- let! implDocument = originDocument.Project.Solution.TryGetDocumentFromPath implFilePath
- let! implVersion = implDocument.GetTextVersionAsync () |> liftTaskAsync
- let! implSourceText = implDocument.GetTextAsync () |> liftTaskAsync
- let! _parsingOptions, projectOptions = projectInfoManager.TryGetOptionsForEditingDocumentOrProject implDocument
-
- let! targetRange =
- gotoDefinition.FindSymbolDeclarationInFile(targetSymbolUse, implFilePath, implSourceText.ToString(), projectOptions, implVersion.GetHashCode())
-
- let! implTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (implSourceText, targetRange)
- let navItem = FSharpNavigableItem (implDocument, implTextSpan)
- return navItem
- | _ -> return! None
- }
- |> Async.map (Option.map (fun x -> x :> INavigableItem) >> Option.toArray >> Array.toSeq)
- |> RoslynHelpers.StartAsyncAsTask cancellationToken
+ let gtd = GoToDefinition(checkerProvider.Checker, projectInfoManager)
+ let statusBar = StatusBar(ServiceProvider.GlobalProvider.GetService())
interface IGoToDefinitionService with
- // used for 'definition peek'
- member this.FindDefinitionsAsync (document: Document, position: int, cancellationToken: CancellationToken) =
- this.FindDefinitionsTask (document, position, cancellationToken)
+ /// Invoked with Peek Definition.
+ member __.FindDefinitionsAsync (document: Document, position: int, cancellationToken: CancellationToken) =
+ gtd.FindDefinitionsForPeekTask(document, position, cancellationToken)
- // used for 'goto definition' proper
+ /// Invoked with Go to Definition.
/// Try to navigate to the definiton of the symbol at the symbolRange in the originDocument
- member this.TryGoToDefinition(document: Document, position: int, cancellationToken: CancellationToken) =
- let definitionTask = this.FindDefinitionsTask (document, position, cancellationToken)
-
- statusBar.Message (SR.LocatingSymbol())
+ member __.TryGoToDefinition(document: Document, position: int, cancellationToken: CancellationToken) =
+ statusBar.Message(SR.LocatingSymbol())
use __ = statusBar.Animate()
+ let gtdTask = gtd.FindDefinitionTask(document, position, cancellationToken)
+
// Wrap this in a try/with as if the user clicks "Cancel" on the thread dialog, we'll be cancelled
// Task.Wait throws an exception if the task is cancelled, so be sure to catch it.
- let completionError =
+ let gtdCompletionOrError =
try
- // REVIEW: document this use of a blocking wait on the cancellation token, explaining why it is ok
- definitionTask.Wait()
- None
- with exc -> Some <| Exception.flattenMessage exc
+ // This call to Wait() is fine because we want to be able to provide the error message in the status bar.
+ gtdTask.Wait()
+ Ok gtdTask
+ with exc ->
+ Error(Exception.flattenMessage exc)
- match completionError with
- | Some message ->
- statusBar.TempMessage <| String.Format(SR.NavigateToFailed(), message)
+ match gtdCompletionOrError with
+ | Ok task ->
+ if task.Status = TaskStatus.RanToCompletion && task.Result.IsSome then
+ let item, _ = task.Result.Value
+ gtd.NavigateToItem(item, statusBar)
+
+ // 'true' means do it, like Sheev Palpatine would want us to.
+ true
+ else
+ statusBar.TempMessage (SR.CannotDetermineSymbol())
+ false
+ | Error message ->
+ statusBar.TempMessage(String.Format(SR.NavigateToFailed(), message))
// Don't show the dialog box as it's most likely that the user cancelled.
// Don't make them click twice.
- true
- | None ->
- if definitionTask.Status = TaskStatus.RanToCompletion && definitionTask.Result <> null && definitionTask.Result.Any() then
- let navigableItem = definitionTask.Result.First() // F# API provides only one INavigableItem
- tryNavigateToItem (Some navigableItem)
-
- // FSROSLYNTODO: potentially display multiple results here
- // If GotoDef returns one result then it should try to jump to a discovered location. If it returns multiple results then it should use
- // presenters to render items so user can choose whatever he needs. Given that per comment F# API always returns only one item then we
- // should always navigate to definition and get rid of presenters.
- //
- //let refDisplayString = refSourceText.GetSubText(refTextSpan).ToString()
- //for presenter in presenters do
- // presenter.DisplayResult(navigableItem.DisplayString, definitionTask.Result)
- //true
- else
- statusBar.TempMessage (SR.CannotDetermineSymbol())
- false
\ No newline at end of file
+ true
\ No newline at end of file
diff --git a/Navigation/NavigableSymbolsService.fs b/Navigation/NavigableSymbolsService.fs
new file mode 100644
index 00000000000..4ec1ef238fb
--- /dev/null
+++ b/Navigation/NavigableSymbolsService.fs
@@ -0,0 +1,103 @@
+// 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.Threading.Tasks
+open System.ComponentModel.Composition
+
+open Microsoft.CodeAnalysis.Text
+open Microsoft.CodeAnalysis.Navigation
+
+open Microsoft.VisualStudio.Language.Intellisense
+open Microsoft.VisualStudio.Text
+open Microsoft.VisualStudio.Text.Editor
+open Microsoft.VisualStudio.Shell.Interop
+open Microsoft.VisualStudio.Utilities
+open Microsoft.VisualStudio.Shell
+
+[]
+type internal FSharpNavigableSymbol(item: INavigableItem, span: SnapshotSpan, gtd: GoToDefinition, statusBar: StatusBar) =
+ interface INavigableSymbol with
+ member __.Navigate(_: INavigableRelationship) =
+ gtd.NavigateToItem(item, statusBar)
+
+ member __.Relationships = seq { yield PredefinedNavigableRelationships.Definition }
+
+ member __.SymbolSpan = span
+
+type internal FSharpNavigableSymbolSource(checkerProvider: FSharpCheckerProvider, projectInfoManager: FSharpProjectOptionsManager, serviceProvider: IServiceProvider) =
+
+ let mutable disposed = false
+ let gtd = GoToDefinition(checkerProvider.Checker, projectInfoManager)
+ let statusBar = StatusBar(serviceProvider.GetService())
+
+ interface INavigableSymbolSource with
+ member __.GetNavigableSymbolAsync(triggerSpan: SnapshotSpan, cancellationToken: CancellationToken) =
+ // Yes, this is a code smell. But this is how the editor API accepts what we would treat as None.
+ if disposed then null
+ else
+ asyncMaybe {
+ let snapshot = triggerSpan.Snapshot
+ let position = triggerSpan.Start.Position
+ let document = snapshot.GetOpenDocumentInCurrentContextWithChanges()
+ let! sourceText = document.GetTextAsync () |> liftTaskAsync
+
+ statusBar.Message(SR.LocatingSymbol())
+ use _ = statusBar.Animate()
+
+ let gtdTask = gtd.FindDefinitionTask(document, position, cancellationToken)
+
+ // Wrap this in a try/with as if the user clicks "Cancel" on the thread dialog, we'll be cancelled
+ // Task.Wait throws an exception if the task is cancelled, so be sure to catch it.
+ let gtdCompletedOrError =
+ try
+ // This call to Wait() is fine because we want to be able to provide the error message in the status bar.
+ gtdTask.Wait()
+ Ok gtdTask
+ with exc ->
+ Error(Exception.flattenMessage exc)
+
+ match gtdCompletedOrError with
+ | Ok task ->
+ if task.Status = TaskStatus.RanToCompletion && task.Result.IsSome then
+ let (navigableItem, range) = task.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
+ else
+ statusBar.TempMessage (SR.CannotDetermineSymbol())
+
+ // The NavigableSymbols API accepts 'null' when there's nothing to navigate to.
+ return null
+ | Error message ->
+ statusBar.TempMessage (String.Format(SR.NavigateToFailed(), message))
+
+ // The NavigableSymbols API accepts 'null' when there's nothing to navigate to.
+ return null
+ }
+ |> Async.map Option.toObj
+ |> RoslynHelpers.StartAsyncAsTask cancellationToken
+
+ member __.Dispose() =
+ disposed <- true
+
+[)>]
+[]
+[]
+[]
+type internal FSharpNavigableSymbolService
+ []
+ (
+ [)>] serviceProvider: IServiceProvider,
+ checkerProvider: FSharpCheckerProvider,
+ projectInfoManager: FSharpProjectOptionsManager
+ ) =
+
+ interface INavigableSymbolSourceProvider with
+ member __.TryCreateNavigableSymbolSource(_: ITextView, _: ITextBuffer) =
+ new FSharpNavigableSymbolSource(checkerProvider, projectInfoManager, serviceProvider) :> INavigableSymbolSource
\ No newline at end of file
diff --git a/QuickInfo/Navigation.fs b/QuickInfo/Navigation.fs
index 6b8773e1542..d1904b96cd6 100644
--- a/QuickInfo/Navigation.fs
+++ b/QuickInfo/Navigation.fs
@@ -3,18 +3,26 @@
namespace Microsoft.VisualStudio.FSharp.Editor
open System
+
open Microsoft.CodeAnalysis
+
+open Microsoft.FSharp.Compiler.SourceCodeServices
+
open Microsoft.FSharp.Compiler.Range
+open Microsoft.VisualStudio.Shell.Interop
type internal QuickInfoNavigation
(
- gotoDefinitionService: FSharpGoToDefinitionService,
+ serviceProvider: IServiceProvider,
+ checker: FSharpChecker,
+ projectInfoManager: FSharpProjectOptionsManager,
initialDoc: Document,
thisSymbolUseRange: range
) =
let workspace = initialDoc.Project.Solution.Workspace
let solution = workspace.CurrentSolution
+ let statusBar = StatusBar(serviceProvider.GetService())
member __.IsTargetValid (range: range) =
range <> rangeStartup &&
@@ -36,8 +44,10 @@ type internal QuickInfoNavigation
let! targetDoc = solution.TryGetDocumentFromFSharpRange (range, initialDoc.Project.Id)
let! targetSource = targetDoc.GetTextAsync()
let! targetTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (targetSource, range)
- // to ensure proper navigation decsions we need to check the type of document the navigation call
- // is originating from and the target we're provided by default
+ let gtd = GoToDefinition(checker, projectInfoManager)
+
+ // To ensure proper navigation decsions, we need to check the type of document the navigation call
+ // is originating from and the target we're provided by default:
// - signature files (.fsi) should navigate to other signature files
// - implementation files (.fs) should navigate to other implementation files
let (|Signature|Implementation|) filepath =
@@ -46,11 +56,13 @@ type internal QuickInfoNavigation
match initialDoc.FilePath, targetPath with
| Signature, Signature
| Implementation, Implementation ->
- return gotoDefinitionService.TryNavigateToTextSpan (targetDoc, targetTextSpan)
- // adjust the target from signature to implementation
+ return gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, statusBar)
+
+ // Adjust the target from signature to implementation.
| Implementation, Signature ->
- return! gotoDefinitionService.NavigateToSymbolDefinitionAsync (targetDoc, targetSource, range) |> liftAsync
- // adjust the target from implmentation to signature
+ return! gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, range, statusBar)
+
+ // Adjust the target from implmentation to signature.
| Signature, Implementation ->
- return! gotoDefinitionService.NavigateToSymbolDeclarationAsync (targetDoc, targetSource, range) |> liftAsync
+ return! gtd.NavigateToSymbolDeclarationAsync(targetDoc, targetSource, range, statusBar)
} |> Async.Ignore |> Async.StartImmediate
diff --git a/QuickInfo/QuickInfoProvider.fs b/QuickInfo/QuickInfoProvider.fs
index 6deb00f4d1c..a41fa05a5d3 100644
--- a/QuickInfo/QuickInfoProvider.fs
+++ b/QuickInfo/QuickInfoProvider.fs
@@ -159,10 +159,10 @@ module private FSharpQuickInfo =
type internal FSharpAsyncQuickInfoSource
(
+ serviceProvider: IServiceProvider,
xmlMemberIndexService: IVsXMLMemberIndexService,
checkerProvider:FSharpCheckerProvider,
projectInfoManager:FSharpProjectOptionsManager,
- gotoDefinitionService:FSharpGoToDefinitionService,
textBuffer:ITextBuffer
) =
@@ -211,7 +211,7 @@ type internal FSharpAsyncQuickInfoSource
let mainDescription, documentation, typeParameterMap, usage, exceptions = ResizeArray(), ResizeArray(), ResizeArray(), ResizeArray(), ResizeArray()
XmlDocumentation.BuildDataTipText(documentationBuilder, mainDescription.Add, documentation.Add, typeParameterMap.Add, usage.Add, exceptions.Add, quickInfo.StructuredText)
let imageId = Tokenizer.GetImageIdForSymbol(quickInfo.Symbol, quickInfo.SymbolKind)
- let navigation = QuickInfoNavigation(gotoDefinitionService, document, symbolUse.RangeAlternate)
+ let navigation = QuickInfoNavigation(serviceProvider, checkerProvider.Checker, projectInfoManager, document, symbolUse.RangeAlternate)
let docs = joinWithLineBreaks [documentation; typeParameterMap; usage; exceptions]
let content = QuickInfoViewProvider.provideContent(imageId, mainDescription, docs, navigation)
let span = getTrackingSpan quickInfo.Span
@@ -242,7 +242,7 @@ type internal FSharpAsyncQuickInfoSource
] |> ResizeArray
let docs = joinWithLineBreaks [documentation; typeParameterMap; usage; exceptions]
let imageId = Tokenizer.GetImageIdForSymbol(targetQuickInfo.Symbol, targetQuickInfo.SymbolKind)
- let navigation = QuickInfoNavigation(gotoDefinitionService, document, symbolUse.RangeAlternate)
+ let navigation = QuickInfoNavigation(serviceProvider, checkerProvider.Checker, projectInfoManager, document, symbolUse.RangeAlternate)
let content = QuickInfoViewProvider.provideContent(imageId, mainDescription, docs, navigation)
let span = getTrackingSpan targetQuickInfo.Span
return QuickInfoItem(span, content)
@@ -256,11 +256,10 @@ type internal FSharpAsyncQuickInfoSource
type internal FSharpAsyncQuickInfoSourceProvider
[]
(
- [)>] serviceProvider:IServiceProvider,
+ [)>] serviceProvider: IServiceProvider,
checkerProvider:FSharpCheckerProvider,
- projectInfoManager:FSharpProjectOptionsManager,
- gotoDefinitionService:FSharpGoToDefinitionService
+ projectInfoManager:FSharpProjectOptionsManager
) =
interface IAsyncQuickInfoSourceProvider with
override __.TryCreateQuickInfoSource(textBuffer:ITextBuffer) : IAsyncQuickInfoSource =
- new FSharpAsyncQuickInfoSource(serviceProvider.XMLMemberIndexService, checkerProvider, projectInfoManager, gotoDefinitionService, textBuffer) :> IAsyncQuickInfoSource
+ new FSharpAsyncQuickInfoSource(serviceProvider, serviceProvider.XMLMemberIndexService, checkerProvider, projectInfoManager, textBuffer) :> IAsyncQuickInfoSource