-
Notifications
You must be signed in to change notification settings - Fork 788
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
VS Navigation consolidation #12105
Merged
Merged
VS Navigation consolidation #12105
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
d693025
Initial work for consolidation
TIHan b26ca5b
revert back to how TryGoToDefinition was working
TIHan 800e3b7
minor comment removal
TIHan dff7f4b
Merge remote-tracking branch 'remote/main' into nav-consolidation
TIHan da37721
Merge remote-tracking branch 'remote/main' into nav-consolidation
TIHan cca004d
Merge remote-tracking branch 'remote/main' into nav-consolidation
TIHan 31604dd
Fixing tests
TIHan File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ namespace Microsoft.VisualStudio.FSharp.Editor | |
|
||
open System | ||
open System.Threading | ||
open System.Threading.Tasks | ||
open System.Collections.Immutable | ||
open System.Diagnostics | ||
open System.IO | ||
|
@@ -21,7 +22,9 @@ open FSharp.Compiler | |
open FSharp.Compiler.CodeAnalysis | ||
open FSharp.Compiler.EditorServices | ||
open FSharp.Compiler.Text | ||
open FSharp.Compiler.Text.Range | ||
open FSharp.Compiler.Symbols | ||
open FSharp.Compiler.Tokenization | ||
|
||
|
||
module private Symbol = | ||
|
@@ -488,4 +491,237 @@ type internal GoToDefinition(metadataAsSource: FSharpMetadataAsSourceService) = | |
if result then | ||
statusBar.Clear() | ||
else | ||
statusBar.TempMessage (SR.CannotNavigateUnknown()) | ||
statusBar.TempMessage (SR.CannotNavigateUnknown()) | ||
|
||
type internal QuickInfo = | ||
{ StructuredText: ToolTipText | ||
Span: TextSpan | ||
Symbol: FSharpSymbol option | ||
SymbolKind: LexerSymbolKind } | ||
|
||
module internal FSharpQuickInfo = | ||
|
||
let userOpName = "QuickInfo" | ||
|
||
// when a construct has been declared in a signature file the documentation comments that are | ||
// written in that file are the ones that go into the generated xml when the project is compiled | ||
// therefore we should include these doccoms in our design time quick info | ||
let getQuickInfoFromRange | ||
( | ||
document: Document, | ||
declRange: range, | ||
cancellationToken: CancellationToken | ||
) | ||
: Async<QuickInfo option> = | ||
|
||
asyncMaybe { | ||
let userOpName = "getQuickInfoFromRange" | ||
let solution = document.Project.Solution | ||
// ascertain the location of the target declaration in the signature file | ||
let! extDocId = solution.GetDocumentIdsWithFilePath declRange.FileName |> Seq.tryHead | ||
let extDocument = solution.GetProject(extDocId.ProjectId).GetDocument extDocId | ||
let! extSourceText = extDocument.GetTextAsync cancellationToken | ||
let! extSpan = RoslynHelpers.TryFSharpRangeToTextSpan (extSourceText, declRange) | ||
let extLineText = (extSourceText.Lines.GetLineFromPosition extSpan.Start).ToString() | ||
|
||
// project options need to be retrieved because the signature file could be in another project | ||
let! extLexerSymbol = extDocument.TryFindFSharpLexerSymbolAsync(extSpan.Start, SymbolLookupKind.Greedy, true, true, userOpName) | ||
let! _, extCheckFileResults = extDocument.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync | ||
|
||
let extQuickInfoText = | ||
extCheckFileResults.GetToolTip | ||
(declRange.StartLine, extLexerSymbol.Ident.idRange.EndColumn, extLineText, extLexerSymbol.FullIsland, FSharpTokenTag.IDENT) | ||
|
||
match extQuickInfoText with | ||
| ToolTipText [] | ||
| ToolTipText [ToolTipElement.None] -> return! None | ||
| extQuickInfoText -> | ||
let! extSymbolUse = | ||
extCheckFileResults.GetSymbolUseAtLocation(declRange.StartLine, extLexerSymbol.Ident.idRange.EndColumn, extLineText, extLexerSymbol.FullIsland) | ||
let! span = RoslynHelpers.TryFSharpRangeToTextSpan (extSourceText, extLexerSymbol.Range) | ||
|
||
return { StructuredText = extQuickInfoText | ||
Span = span | ||
Symbol = Some extSymbolUse.Symbol | ||
SymbolKind = extLexerSymbol.Kind } | ||
} | ||
|
||
/// Get QuickInfo combined from doccom of Signature and definition | ||
let getQuickInfo | ||
( | ||
document: Document, | ||
position: int, | ||
cancellationToken: CancellationToken | ||
) | ||
: Async<(range * QuickInfo option * QuickInfo option) option> = | ||
|
||
asyncMaybe { | ||
let userOpName = "getQuickInfo" | ||
let! lexerSymbol = document.TryFindFSharpLexerSymbolAsync(position, SymbolLookupKind.Greedy, true, true, userOpName) | ||
let! _, checkFileResults = document.GetFSharpParseAndCheckResultsAsync(userOpName) |> liftAsync | ||
let! sourceText = document.GetTextAsync cancellationToken | ||
let idRange = lexerSymbol.Ident.idRange | ||
let textLinePos = sourceText.Lines.GetLinePosition position | ||
let fcsTextLineNumber = Line.fromZ textLinePos.Line | ||
let lineText = (sourceText.Lines.GetLineFromPosition position).ToString() | ||
|
||
/// Gets the QuickInfo information for the orignal target | ||
let getTargetSymbolQuickInfo (symbol, tag) = | ||
asyncMaybe { | ||
let targetQuickInfo = | ||
checkFileResults.GetToolTip | ||
(fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland,tag) | ||
|
||
match targetQuickInfo with | ||
| ToolTipText [] | ||
| ToolTipText [ToolTipElement.None] -> return! None | ||
| _ -> | ||
let! targetTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (sourceText, lexerSymbol.Range) | ||
return { StructuredText = targetQuickInfo | ||
Span = targetTextSpan | ||
Symbol = symbol | ||
SymbolKind = lexerSymbol.Kind } | ||
} | ||
|
||
match lexerSymbol.Kind with | ||
| LexerSymbolKind.String -> | ||
let! targetQuickInfo = getTargetSymbolQuickInfo (None, FSharpTokenTag.STRING) | ||
return lexerSymbol.Range, None, Some targetQuickInfo | ||
|
||
| _ -> | ||
let! symbolUse = checkFileResults.GetSymbolUseAtLocation (fcsTextLineNumber, idRange.EndColumn, lineText, lexerSymbol.FullIsland) | ||
|
||
// if the target is in a signature file, adjusting the quick info is unnecessary | ||
if isSignatureFile document.FilePath then | ||
let! targetQuickInfo = getTargetSymbolQuickInfo (Some symbolUse.Symbol, FSharpTokenTag.IDENT) | ||
return symbolUse.Range, None, Some targetQuickInfo | ||
else | ||
// find the declaration location of the target symbol, with a preference for signature files | ||
let findSigDeclarationResult = checkFileResults.GetDeclarationLocation (idRange.StartLine, idRange.EndColumn, lineText, lexerSymbol.FullIsland, preferFlag=true) | ||
|
||
// it is necessary to retrieve the backup quick info because this acquires | ||
// the textSpan designating where we want the quick info to appear. | ||
let! targetQuickInfo = getTargetSymbolQuickInfo (Some symbolUse.Symbol, FSharpTokenTag.IDENT) | ||
|
||
let! result = | ||
match findSigDeclarationResult with | ||
| FindDeclResult.DeclFound declRange when isSignatureFile declRange.FileName -> | ||
asyncMaybe { | ||
let! sigQuickInfo = getQuickInfoFromRange(document, declRange, cancellationToken) | ||
|
||
// if the target was declared in a signature file, and the current file | ||
// is not the corresponding module implementation file for that signature, | ||
// the doccoms from the signature will overwrite any doccoms that might be | ||
// present on the definition/implementation | ||
let findImplDefinitionResult = checkFileResults.GetDeclarationLocation (idRange.StartLine, idRange.EndColumn, lineText, lexerSymbol.FullIsland, preferFlag=false) | ||
|
||
match findImplDefinitionResult with | ||
| FindDeclResult.DeclNotFound _ | ||
| FindDeclResult.ExternalDecl _ -> | ||
return symbolUse.Range, Some sigQuickInfo, None | ||
| FindDeclResult.DeclFound declRange -> | ||
let! implQuickInfo = getQuickInfoFromRange(document, declRange, cancellationToken) | ||
return symbolUse.Range, Some sigQuickInfo, Some { implQuickInfo with Span = targetQuickInfo.Span } | ||
} | ||
| _ -> async.Return None | ||
|> liftAsync | ||
|
||
return result |> Option.defaultValue (symbolUse.Range, None, Some targetQuickInfo) | ||
} | ||
|
||
type internal FSharpNavigation | ||
( | ||
statusBar: StatusBar, | ||
metadataAsSource: FSharpMetadataAsSourceService, | ||
initialDoc: Document, | ||
thisSymbolUseRange: range | ||
) = | ||
|
||
let workspace = initialDoc.Project.Solution.Workspace | ||
let solution = workspace.CurrentSolution | ||
|
||
member _.IsTargetValid (range: range) = | ||
range <> rangeStartup && | ||
range <> thisSymbolUseRange && | ||
solution.TryGetDocumentIdFromFSharpRange (range, initialDoc.Project.Id) |> Option.isSome | ||
|
||
member _.RelativePath (range: range) = | ||
let relativePathEscaped = | ||
match solution.FilePath with | ||
| null -> range.FileName | ||
| sfp -> | ||
let targetUri = Uri(range.FileName) | ||
Uri(sfp).MakeRelativeUri(targetUri).ToString() | ||
relativePathEscaped |> Uri.UnescapeDataString | ||
|
||
member _.NavigateTo (range: range) = | ||
asyncMaybe { | ||
let targetPath = range.FileName | ||
let! targetDoc = solution.TryGetDocumentFromFSharpRange (range, initialDoc.Project.Id) | ||
let! targetSource = targetDoc.GetTextAsync() | ||
let! targetTextSpan = RoslynHelpers.TryFSharpRangeToTextSpan (targetSource, range) | ||
let gtd = GoToDefinition(metadataAsSource) | ||
|
||
// 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 = | ||
if isSignatureFile filepath then Signature else Implementation | ||
|
||
match initialDoc.FilePath, targetPath with | ||
| Signature, Signature | ||
| Implementation, Implementation -> | ||
return gtd.TryNavigateToTextSpan(targetDoc, targetTextSpan, statusBar) | ||
|
||
// Adjust the target from signature to implementation. | ||
| Implementation, Signature -> | ||
return! gtd.NavigateToSymbolDefinitionAsync(targetDoc, targetSource, range, statusBar) | ||
|
||
// Adjust the target from implmentation to signature. | ||
| Signature, Implementation -> | ||
return! gtd.NavigateToSymbolDeclarationAsync(targetDoc, targetSource, range, statusBar) | ||
} | ||
|> Async.Ignore |> Async.StartImmediate | ||
|
||
member _.FindDefinitions(position, cancellationToken) = | ||
let gtd = GoToDefinition(metadataAsSource) | ||
let task = gtd.FindDefinitionsForPeekTask(initialDoc, 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 | ||
|
||
member this.TryGoToDefinition(position, cancellationToken) = | ||
let gtd = GoToDefinition(metadataAsSource) | ||
let gtdTask = gtd.FindDefinitionTask(initialDoc, 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. | ||
try | ||
// This call to Wait() is fine because we want to be able to provide the error message in the status bar. | ||
gtdTask.Wait(cancellationToken) | ||
if gtdTask.Status = TaskStatus.RanToCompletion && gtdTask.Result.IsSome then | ||
match gtdTask.Result.Value with | ||
| FSharpGoToDefinitionResult.NavigableItem(navItem), _ -> | ||
gtd.NavigateToItem(navItem, statusBar) | ||
// 'true' means do it, like Sheev Palpatine would want us to. | ||
true | ||
| FSharpGoToDefinitionResult.ExternalAssembly(targetSymbolUse, metadataReferences), _ -> | ||
gtd.NavigateToExternalDeclaration(targetSymbolUse, metadataReferences, cancellationToken, statusBar) | ||
// 'true' means do it, like Sheev Palpatine would want us to. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do it |
||
true | ||
else | ||
statusBar.TempMessage (SR.CannotDetermineSymbol()) | ||
false | ||
with exc -> | ||
statusBar.TempMessage(String.Format(SR.NavigateToFailed(), Exception.flattenMessage exc)) | ||
|
||
// Don't show the dialog box as it's most likely that the user cancelled. | ||
// Don't make them click twice. | ||
true |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do it