diff --git a/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs b/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs index 11f07e2da927a..f90e3e40c6741 100644 --- a/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs +++ b/src/EditorFeatures/Core/CodeDefinitionWindow/DefinitionContextTracker.cs @@ -171,7 +171,8 @@ internal async Task> GetContextFrom if (symbol != null) { var symbolNavigationService = workspace.Services.GetRequiredService(); - var definitionItem = symbol.ToNonClassifiedDefinitionItem(document.Project.Solution, includeHiddenLocations: false); + var definitionItem = await symbol.ToNonClassifiedDefinitionItemAsync( + document.Project.Solution, includeHiddenLocations: false, cancellationToken).ConfigureAwait(false); var result = await symbolNavigationService.GetExternalNavigationSymbolLocationAsync(definitionItem, cancellationToken).ConfigureAwait(false); if (result != null) diff --git a/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs b/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs index ac97ff425d6d4..28ff6636a0106 100644 --- a/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs +++ b/src/EditorFeatures/Core/Host/IStreamingFindReferencesPresenter.cs @@ -120,6 +120,11 @@ public static async Task TryPresentLocationOrNavigateIfOneAsync( if (presenter == null) return null; + // Find the first location from a non-generated document to navigate to automatically. + // This way the user gets taken to their user code while still being able to see all the results + // (including generated code) in the tool window. + var preferredLocation = GetPreferredNonGeneratedLocation(builder); + var navigableItems = builder.SelectAsArray(t => t.item); return new NavigableLocation(async (options, cancellationToken) => { @@ -145,4 +150,24 @@ public static async Task TryPresentLocationOrNavigateIfOneAsync( return true; }); } + + /// + /// Finds the first navigable location from a non-generated document in the list of definition items. + /// This allows Go To Definition to automatically navigate to user code when there are multiple locations + /// including generated code. + /// + private static INavigableLocation? GetPreferredNonGeneratedLocation( + ArrayBuilder<(DefinitionItem item, INavigableLocation location)> builder) + { + foreach (var (item, location) in builder) + { + foreach (var sourceSpan in item.SourceSpans) + { + if (!sourceSpan.IsGeneratedCode) + return location; + } + } + + return null; + } } diff --git a/src/EditorFeatures/Core/Navigation/AbstractDefinitionLocationService.cs b/src/EditorFeatures/Core/Navigation/AbstractDefinitionLocationService.cs index 95a6002282571..90ec4fd34af97 100644 --- a/src/EditorFeatures/Core/Navigation/AbstractDefinitionLocationService.cs +++ b/src/EditorFeatures/Core/Navigation/AbstractDefinitionLocationService.cs @@ -215,7 +215,8 @@ await TryGetInterceptedLocationAsync().ConfigureAwait(false) ?? var title = string.Format(EditorFeaturesResources._0_intercepted_locations, FindUsagesHelpers.GetDisplayName(method)); - var definitionItem = method.ToNonClassifiedDefinitionItem(solution, includeHiddenLocations: true); + var definitionItem = await method.ToNonClassifiedDefinitionItemAsync( + solution, includeHiddenLocations: true, cancellationToken).ConfigureAwait(false); var referenceItems = new List(capacity: documentSpans.Count); var classificationOptions = ClassificationOptions.Default with { ClassifyObsoleteSymbols = false }; diff --git a/src/EditorFeatures/Core/Peek/PeekableItemFactory.cs b/src/EditorFeatures/Core/Peek/PeekableItemFactory.cs index f8f2360606714..f5bd137394204 100644 --- a/src/EditorFeatures/Core/Peek/PeekableItemFactory.cs +++ b/src/EditorFeatures/Core/Peek/PeekableItemFactory.cs @@ -67,7 +67,8 @@ public async Task> GetPeekableItemsAsync( var originatingProject = solution.GetProject(symbol.ContainingAssembly, cancellationToken); project = originatingProject ?? project; - var definitionItem = symbol.ToNonClassifiedDefinitionItem(solution, includeHiddenLocations: true); + var definitionItem = await symbol.ToNonClassifiedDefinitionItemAsync( + solution, includeHiddenLocations: true, cancellationToken).ConfigureAwait(false); var symbolNavigationService = solution.Services.GetService(); var result = await symbolNavigationService.GetExternalNavigationSymbolLocationAsync(definitionItem, cancellationToken).ConfigureAwait(false); diff --git a/src/EditorFeatures/Test/StackTraceExplorer/StackTraceExplorerTests.cs b/src/EditorFeatures/Test/StackTraceExplorer/StackTraceExplorerTests.cs index 93041af759ae3..025e431a6a5c7 100644 --- a/src/EditorFeatures/Test/StackTraceExplorer/StackTraceExplorerTests.cs +++ b/src/EditorFeatures/Test/StackTraceExplorer/StackTraceExplorerTests.cs @@ -52,7 +52,8 @@ private static async Task TestSymbolFoundAsync(string inputLine, string code) AssertEx.NotNull(expectedSymbol); // Compare the definition found to the definition for the test symbol - var expectedDefinition = expectedSymbol.ToNonClassifiedDefinitionItem(workspace.CurrentSolution, includeHiddenLocations: true); + var expectedDefinition = await expectedSymbol.ToNonClassifiedDefinitionItemAsync( + workspace.CurrentSolution, includeHiddenLocations: true, CancellationToken.None); Assert.Equal(expectedDefinition.IsExternal, definition.IsExternal); AssertEx.SetEqual(expectedDefinition.NameDisplayParts, definition.NameDisplayParts); diff --git a/src/Features/Core/Portable/DocumentSpan.cs b/src/Features/Core/Portable/DocumentSpan.cs index 0adaec76cf739..d91a5102bf79e 100644 --- a/src/Features/Core/Portable/DocumentSpan.cs +++ b/src/Features/Core/Portable/DocumentSpan.cs @@ -9,4 +9,5 @@ namespace Microsoft.CodeAnalysis; /// /// Represents a location in a . /// -internal readonly record struct DocumentSpan(Document Document, TextSpan SourceSpan); +internal readonly record struct DocumentSpan( + Document Document, TextSpan SourceSpan, bool IsGeneratedCode = false); diff --git a/src/Features/Core/Portable/FindUsages/DefinitionItemFactory.cs b/src/Features/Core/Portable/FindUsages/DefinitionItemFactory.cs index 07c34ced94b4f..fdf4e96c20070 100644 --- a/src/Features/Core/Portable/FindUsages/DefinitionItemFactory.cs +++ b/src/Features/Core/Portable/FindUsages/DefinitionItemFactory.cs @@ -26,28 +26,32 @@ internal static class DefinitionItemFactory memberOptions: SymbolDisplayMemberOptions.IncludeContainingType, globalNamespaceStyle: SymbolDisplayGlobalNamespaceStyle.OmittedAsContaining); - public static DefinitionItem ToNonClassifiedDefinitionItem( + public static Task ToNonClassifiedDefinitionItemAsync( this ISymbol definition, Solution solution, - bool includeHiddenLocations) - => ToNonClassifiedDefinitionItem(definition, solution, FindReferencesSearchOptions.Default, includeHiddenLocations); + bool includeHiddenLocations, + CancellationToken cancellationToken) + => ToNonClassifiedDefinitionItemAsync(definition, solution, FindReferencesSearchOptions.Default, includeHiddenLocations, cancellationToken); - public static DefinitionItem ToNonClassifiedDefinitionItem( + public static Task ToNonClassifiedDefinitionItemAsync( this ISymbol definition, Solution solution, FindReferencesSearchOptions options, - bool includeHiddenLocations) - => ToNonClassifiedDefinitionItem(definition, definition.Locations, solution, options, isPrimary: false, includeHiddenLocations); + bool includeHiddenLocations, + CancellationToken cancellationToken) + => ToNonClassifiedDefinitionItemAsync(definition, definition.Locations, solution, options, isPrimary: false, includeHiddenLocations, cancellationToken); - private static DefinitionItem ToNonClassifiedDefinitionItem( + private static async Task ToNonClassifiedDefinitionItemAsync( ISymbol definition, ImmutableArray locations, Solution solution, FindReferencesSearchOptions options, bool isPrimary, - bool includeHiddenLocations) + bool includeHiddenLocations, + CancellationToken cancellationToken) { - var sourceLocations = GetSourceLocations(definition, locations, solution, includeHiddenLocations); + var sourceLocations = await GetSourceLocationsAsync( + solution, definition, locations, includeHiddenLocations, cancellationToken).ConfigureAwait(false); return ToDefinitionItem( definition, @@ -67,7 +71,7 @@ public static async ValueTask ToClassifiedDefinitionItemAsync( bool includeHiddenLocations, CancellationToken cancellationToken) { - var sourceLocations = GetSourceLocations(definition, definition.Locations, solution, includeHiddenLocations); + var sourceLocations = await GetSourceLocationsAsync(solution, definition, definition.Locations, includeHiddenLocations, cancellationToken).ConfigureAwait(false); var classifiedSpans = await ClassifyDocumentSpansAsync(classificationOptions, sourceLocations, cancellationToken).ConfigureAwait(false); return ToDefinitionItem(definition, sourceLocations, classifiedSpans, solution, options, isPrimary); } @@ -85,7 +89,7 @@ public static async ValueTask ToClassifiedDefinitionItemAsync( var definition = group.Symbols.First(); var locations = group.Symbols.SelectManyAsArray(s => s.Locations); - var sourceLocations = GetSourceLocations(definition, locations, solution, includeHiddenLocations); + var sourceLocations = await GetSourceLocationsAsync(solution, definition, locations, includeHiddenLocations, cancellationToken).ConfigureAwait(false); var classifiedSpans = await ClassifyDocumentSpansAsync(classificationOptions, sourceLocations, cancellationToken).ConfigureAwait(false); return ToDefinitionItem(definition, sourceLocations, classifiedSpans, solution, options, isPrimary); } @@ -221,7 +225,12 @@ internal static ImmutableArray GetMetadataLocations(ISymbol de return []; } - private static ImmutableArray GetSourceLocations(ISymbol definition, ImmutableArray locations, Solution solution, bool includeHiddenLocations) + private static async Task> GetSourceLocationsAsync( + Solution solution, + ISymbol definition, + ImmutableArray locations, + bool includeHiddenLocations, + CancellationToken cancellationToken) { // Assembly, module, global namespace and preprocessing symbol locations include all source documents; displaying all of them is not useful. // We could consider creating a definition item that points to the project source instead. @@ -238,7 +247,8 @@ private static ImmutableArray GetSourceLocations(ISymbol definitio (includeHiddenLocations || location.IsVisibleSourceLocation()) && solution.GetDocument(location.SourceTree) is { } document) { - source.Add(new DocumentSpan(document, location.SourceSpan)); + var isGeneratedCode = await document.IsGeneratedCodeAsync(cancellationToken).ConfigureAwait(false); + source.Add(new DocumentSpan(document, location.SourceSpan, isGeneratedCode)); } } diff --git a/src/Features/Core/Portable/FindUsages/IRemoteFindUsagesService.cs b/src/Features/Core/Portable/FindUsages/IRemoteFindUsagesService.cs index 65610abfb1b33..30f9d6dd4e01d 100644 --- a/src/Features/Core/Portable/FindUsages/IRemoteFindUsagesService.cs +++ b/src/Features/Core/Portable/FindUsages/IRemoteFindUsagesService.cs @@ -159,7 +159,8 @@ private DefinitionItem GetDefinition(int definitionId) } [DataContract] -internal readonly struct SerializableDocumentSpan(DocumentId documentId, TextSpan sourceSpan) +internal readonly struct SerializableDocumentSpan( + DocumentId documentId, TextSpan sourceSpan, bool isGeneratedCode) { [DataMember(Order = 0)] public readonly DocumentId DocumentId = documentId; @@ -167,15 +168,18 @@ internal readonly struct SerializableDocumentSpan(DocumentId documentId, TextSpa [DataMember(Order = 1)] public readonly TextSpan SourceSpan = sourceSpan; + [DataMember(Order = 2)] + public readonly bool IsGeneratedCode = isGeneratedCode; + public static SerializableDocumentSpan Dehydrate(DocumentSpan documentSpan) - => new(documentSpan.Document.Id, documentSpan.SourceSpan); + => new(documentSpan.Document.Id, documentSpan.SourceSpan, documentSpan.IsGeneratedCode); public async ValueTask RehydrateAsync(Solution solution, CancellationToken cancellationToken) { var document = solution.GetDocument(DocumentId) ?? - await solution.GetSourceGeneratedDocumentAsync(DocumentId, cancellationToken).ConfigureAwait(false); + await solution.GetSourceGeneratedDocumentAsync(DocumentId, cancellationToken).ConfigureAwait(false); Contract.ThrowIfNull(document); - return new DocumentSpan(document, SourceSpan); + return new DocumentSpan(document, SourceSpan, IsGeneratedCode); } } diff --git a/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs b/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs index 734402b53aad1..2c9c30f103d15 100644 --- a/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs +++ b/src/Features/Core/Portable/GoToBase/AbstractGoToBaseService.cs @@ -73,8 +73,8 @@ await context.SetSearchTitleAsync( } else if (baseSymbol.Locations.Any(static l => l.IsInMetadata)) { - var definitionItem = baseSymbol.ToNonClassifiedDefinitionItem( - solution, FindReferencesSearchOptions.Default, includeHiddenLocations: true); + var definitionItem = await baseSymbol.ToNonClassifiedDefinitionItemAsync( + solution, FindReferencesSearchOptions.Default, includeHiddenLocations: true, cancellationToken).ConfigureAwait(false); await context.OnDefinitionFoundAsync(definitionItem, cancellationToken).ConfigureAwait(false); found = true; } diff --git a/src/Features/Core/Portable/GoToDefinition/GoToDefinitionFeatureHelpers.cs b/src/Features/Core/Portable/GoToDefinition/GoToDefinitionFeatureHelpers.cs index e6b2c5afa6f82..70028277dc232 100644 --- a/src/Features/Core/Portable/GoToDefinition/GoToDefinitionFeatureHelpers.cs +++ b/src/Features/Core/Portable/GoToDefinition/GoToDefinitionFeatureHelpers.cs @@ -80,7 +80,8 @@ public static async Task> GetDefinitionsAsync( // So, if we only have a single location to go to, this does no unnecessary work. And, // if we do have multiple locations to show, it will just be done in the BG, unblocking // this command thread so it can return the user faster. - var definitionItem = symbol.ToNonClassifiedDefinitionItem(solution, includeHiddenLocations: true); + var definitionItem = await symbol.ToNonClassifiedDefinitionItemAsync( + solution, includeHiddenLocations: true, cancellationToken).ConfigureAwait(false); if (thirdPartyNavigationAllowed) { diff --git a/src/Features/Core/Portable/InheritanceMargin/AbstractInheritanceMarginService_Helpers.cs b/src/Features/Core/Portable/InheritanceMargin/AbstractInheritanceMarginService_Helpers.cs index a943c9fcfd612..9820bb2e4cd49 100644 --- a/src/Features/Core/Portable/InheritanceMargin/AbstractInheritanceMarginService_Helpers.cs +++ b/src/Features/Core/Portable/InheritanceMargin/AbstractInheritanceMarginService_Helpers.cs @@ -563,7 +563,7 @@ private static async ValueTask AddInheritanceMemberItemsForMembersAsync( targetSymbol = symbolInSource ?? targetSymbol; // Right now the targets are not shown in a classified way. - var definition = ToSlimDefinitionItem(targetSymbol, solution); + var definition = await ToSlimDefinitionItemAsync(solution, targetSymbol, cancellationToken).ConfigureAwait(false); if (definition == null) return null; @@ -711,15 +711,17 @@ private static async Task> GetDerivedTypesAndIm /// Otherwise, create the full non-classified DefinitionItem. Because in such case we want to display all the locations to the user /// by reusing the FAR window. /// - private static DefinitionItem? ToSlimDefinitionItem(ISymbol symbol, Solution solution) + private static async Task ToSlimDefinitionItemAsync( + Solution solution, ISymbol symbol, CancellationToken cancellation) { var locations = symbol.Locations; if (locations.Length > 1) { - return symbol.ToNonClassifiedDefinitionItem( + return await symbol.ToNonClassifiedDefinitionItemAsync( solution, FindReferencesSearchOptions.Default with { UnidirectionalHierarchyCascade = true }, - includeHiddenLocations: false); + includeHiddenLocations: false, + cancellation).ConfigureAwait(false); } if (locations is [var location]) diff --git a/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerUtilities.cs b/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerUtilities.cs index 3df2afe735704..9e168f79f1757 100644 --- a/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerUtilities.cs +++ b/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerUtilities.cs @@ -63,9 +63,7 @@ internal static class StackTraceExplorerUtilities { var method = await TryGetBestMatchAsync(project, fullyQualifiedTypeName, methodNode, methodArguments, methodTypeArguments, cancellationToken).ConfigureAwait(false); if (method is not null) - { - return GetDefinition(method); - } + return await GetDefinitionAsync(method).ConfigureAwait(false); } else { @@ -81,9 +79,7 @@ internal static class StackTraceExplorerUtilities { var method = await TryGetBestMatchAsync(project, fullyQualifiedTypeName, methodNode, methodArguments, methodTypeArguments, cancellationToken).ConfigureAwait(false); if (method is not null) - { - return GetDefinition(method); - } + return await GetDefinitionAsync(method).ConfigureAwait(false); } return null; @@ -92,7 +88,7 @@ internal static class StackTraceExplorerUtilities // Local Functions // - DefinitionItem GetDefinition(IMethodSymbol method) + Task GetDefinitionAsync(IMethodSymbol method) { ISymbol symbol = method; if (symbolPart == StackFrameSymbolPart.ContainingType) @@ -100,10 +96,11 @@ DefinitionItem GetDefinition(IMethodSymbol method) symbol = method.ContainingType; } - return symbol.ToNonClassifiedDefinitionItem( + return symbol.ToNonClassifiedDefinitionItemAsync( solution, FindReferencesSearchOptions.Default with { UnidirectionalHierarchyCascade = true }, - includeHiddenLocations: true); + includeHiddenLocations: true, + cancellationToken); } } diff --git a/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs b/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs index 69ee3b7d73fc7..9694099b75a61 100644 --- a/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs +++ b/src/LanguageServer/Protocol/Handler/References/FindUsagesLSPContext.cs @@ -225,7 +225,7 @@ public override async ValueTask OnReferencesFoundAsync(IAsyncEnumerable TrySymbolNavigationNotifyAsync(ISymbol symbol, Project p { await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken); - var definitionItem = symbol.ToNonClassifiedDefinitionItem(project.Solution, includeHiddenLocations: true); + var definitionItem = await symbol.ToNonClassifiedDefinitionItemAsync( + project.Solution, includeHiddenLocations: true, cancellationToken).ConfigureAwait(false); definitionItem.Properties.TryGetValue(DefinitionItem.RQNameKey1, out var rqName); var result = await TryGetNavigationAPIRequiredArgumentsAsync(definitionItem, rqName, cancellationToken).ConfigureAwait(true);