Skip to content

Commit a5c976b

Browse files
authored
VS: use JoinableTaskFactory for Quickinfo link navigation (#15087)
* use joinable task factory for QuickInfo navigation. This results in progress+cancel dialog if it takes over a second.
1 parent b085827 commit a5c976b

File tree

2 files changed

+42
-42
lines changed

2 files changed

+42
-42
lines changed

vsintegration/src/FSharp.Editor/Navigation/GoToDefinition.fs

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -568,46 +568,46 @@ type internal FSharpNavigation(metadataAsSource: FSharpMetadataAsSourceService,
568568
&& solution.TryGetDocumentIdFromFSharpRange(range, initialDoc.Project.Id)
569569
|> Option.isSome
570570

571-
member _.RelativePath(range: range) =
572-
let relativePathEscaped =
573-
match solution.FilePath with
574-
| null -> range.FileName
575-
| sfp ->
576-
let targetUri = Uri(range.FileName)
577-
Uri(sfp).MakeRelativeUri(targetUri).ToString()
578-
579-
relativePathEscaped |> Uri.UnescapeDataString
580-
581-
member _.NavigateTo(range: range, cancellationToken: CancellationToken) =
582-
asyncMaybe {
583-
let targetPath = range.FileName
584-
let! targetDoc = solution.TryGetDocumentFromFSharpRange(range, initialDoc.Project.Id)
585-
let! targetSource = targetDoc.GetTextAsync(cancellationToken)
586-
let! targetTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(targetSource, range)
587-
let gtd = GoToDefinition(metadataAsSource)
588-
589-
// To ensure proper navigation decsions, we need to check the type of document the navigation call
590-
// is originating from and the target we're provided by default:
591-
// - signature files (.fsi) should navigate to other signature files
592-
// - implementation files (.fs) should navigate to other implementation files
593-
let (|Signature|Implementation|) filepath =
594-
if isSignatureFile filepath then
595-
Signature
596-
else
597-
Implementation
598-
599-
match initialDoc.FilePath, targetPath with
600-
| Signature, Signature
601-
| Implementation, Implementation -> return gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, cancellationToken)
602-
603-
// Adjust the target from signature to implementation.
604-
| Implementation, Signature -> return! gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, range, cancellationToken)
605-
606-
// Adjust the target from implmentation to signature.
607-
| Signature, Implementation -> return! gtd.NavigateToSymbolDeclarationAsync(targetDoc, targetSource, range, cancellationToken)
608-
}
609-
|> Async.Ignore
610-
|> Async.StartImmediate
571+
member _.NavigateTo(range: range) =
572+
try
573+
ThreadHelper.JoinableTaskFactory.Run(
574+
SR.NavigatingTo(),
575+
(fun _progress cancellationToken ->
576+
Async.StartImmediateAsTask(
577+
asyncMaybe {
578+
let! targetDoc = solution.TryGetDocumentFromFSharpRange(range, initialDoc.Project.Id)
579+
let! targetSource = targetDoc.GetTextAsync(cancellationToken)
580+
let! targetTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan(targetSource, range)
581+
let gtd = GoToDefinition(metadataAsSource)
582+
583+
// Whenever possible:
584+
// - signature files (.fsi) should navigate to other signature files
585+
// - implementation files (.fs) should navigate to other implementation files
586+
if isSignatureFile initialDoc.FilePath then
587+
// Target range will point to .fsi file if only there is one so we can just use Roslyn navigation service.
588+
return gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, cancellationToken)
589+
else
590+
// Navigation request was made in a .fs file, so we try to find the implmentation of the symbol at target range.
591+
// This is the part that may take some time, because of type checks involved.
592+
let! result =
593+
gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, range, cancellationToken)
594+
|> liftAsync
595+
596+
if result.IsNone then
597+
// In case the above fails, we just navigate to target range.
598+
return gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, cancellationToken)
599+
}
600+
|> Async.Ignore,
601+
cancellationToken
602+
)),
603+
// Default wait time before VS shows the dialog allowing to cancel the long running task is 2 seconds.
604+
// This seems a bit too long to leave the user without any feedback, so we shorten it to 1 second.
605+
// Note: it seems anything less than 1 second will get rounded down to zero, resulting in flashing dialog
606+
// on each navigation, so 1 second is as low as we cen get from JoinableTaskFactory.
607+
TimeSpan.FromSeconds 1
608+
)
609+
with :? OperationCanceledException ->
610+
()
611611

612612
member _.FindDefinitions(position, cancellationToken) =
613613
let gtd = GoToDefinition(metadataAsSource)
@@ -701,7 +701,7 @@ type FSharpNavigableLocation(metadataAsSource: FSharpMetadataAsSourceService, sy
701701
| Signature -> return! gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, symbolRange, cancellationToken)
702702
| Implementation -> return! gtd.NavigateToSymbolDeclarationAsync(targetDoc, targetSource, symbolRange, cancellationToken)
703703
}
704-
|> Async.map (fun a -> a.IsSome)
704+
|> Async.map Option.isSome
705705
|> RoslynHelpers.StartAsyncAsTask cancellationToken
706706

707707
[<Export(typeof<IFSharpCrossLanguageSymbolNavigationService>)>]

vsintegration/src/FSharp.Editor/QuickInfo/Views.fs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ module internal QuickInfoViewProvider =
8383
| LineBreak :: rest -> loop rest [] (encloseRuns runs :: stack)
8484
| :? NavigableTaggedText as item :: rest when navigation.IsTargetValid item.Range ->
8585
let classificationTag = layoutTagToClassificationTag item.Tag
86-
let action = fun () -> navigation.NavigateTo(item.Range, CancellationToken.None)
86+
let action = fun () -> navigation.NavigateTo(item.Range)
8787

8888
let run =
8989
ClassifiedTextRun(classificationTag, item.Text, action, getTooltip item.Range.FileName)

0 commit comments

Comments
 (0)