提交 7fce39fd 编写于 作者: P Phillip Carter 提交者: Will Smith

Navigable symbols (ctrl+click to go to definition) (#4906)

* First cut

* fix

* Order in the court

* Rename

* get rid of unused gtdservice

* wut

* Add file

* Remove unused opens

* do it right (marolf)

* Cleanup

* unused value

* Make the navigable URL the decl symbol

* refactor

* IT WAS A DIFFERENT MEF

* clean up a bit
上级 23350bef
......@@ -73,6 +73,8 @@
<Compile Include="Completion\SignatureHelp.fs" />
<Compile Include="InlineRename\InlineRenameService.fs" />
<Compile Include="DocumentHighlights\DocumentHighlightsService.fs" />
<Compile Include="Navigation\GoToDefinition.fs" />
<Compile Include="Navigation\NavigableSymbolsService.fs" />
<Compile Include="Navigation\GoToDefinitionService.fs" />
<Compile Include="Navigation\NavigationBarItemService.fs" />
<Compile Include="Navigation\NavigateToSearchService.fs" />
......
// 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
[<AllowNullLiteral>]
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<SVsStatusbar,IVsStatusbar>())
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
[<Export(typeof<INavigableSymbolSourceProvider>)>]
[<Name("F# Navigable Symbol Service")>]
[<ContentType(Constants.FSharpContentType)>]
[<Order>]
type internal FSharpNavigableSymbolService
[<ImportingConstructor>]
(
[<Import(typeof<SVsServiceProvider>)>] 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
......@@ -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<SVsStatusbar,IVsStatusbar>())
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
......@@ -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
[<ImportingConstructor>]
(
[<Import(typeof<SVsServiceProvider>)>] serviceProvider:IServiceProvider,
[<Import(typeof<SVsServiceProvider>)>] 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
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册