diff --git a/src/EditorFeatures/Core/Navigation/IDocumentNavigationServiceExtensions.cs b/src/EditorFeatures/Core/Navigation/IDocumentNavigationServiceExtensions.cs index fbaa8a20e8f98..d2bc7ec0c15da 100644 --- a/src/EditorFeatures/Core/Navigation/IDocumentNavigationServiceExtensions.cs +++ b/src/EditorFeatures/Core/Navigation/IDocumentNavigationServiceExtensions.cs @@ -75,7 +75,7 @@ public static async Task TryNavigateToLineAndOffsetAsync( // Navigation should not change the context of linked files and Shared Projects. documentId = workspace.GetDocumentIdInCurrentContext(documentId); - var document = workspace.CurrentSolution.GetDocument(documentId); + var document = workspace.CurrentSolution.GetTextDocument(documentId); if (document is null) return false; diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/IUnitTestingStackTraceServiceAccessor.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/IUnitTestingStackTraceServiceAccessor.cs index 3fe7fe959d471..b043ef3e96381 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/IUnitTestingStackTraceServiceAccessor.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/API/IUnitTestingStackTraceServiceAccessor.cs @@ -14,5 +14,6 @@ internal interface IUnitTestingStackTraceServiceAccessor : IWorkspaceService Task> TryParseAsync(string input, Workspace workspace, CancellationToken cancellationToken); Task TryFindMethodDefinitionAsync(Workspace workspace, UnitTestingParsedFrameWrapper parsedFrame, CancellationToken cancellationToken); (Document? document, int lineNumber) GetDocumentAndLine(Workspace workspace, UnitTestingParsedFrameWrapper parsedFrame); + (TextDocument? textDocument, int lineNumber) GetTextDocumentAndLine(Workspace workspace, UnitTestingParsedFrameWrapper parsedFrame); Task TryNavigateToAsync(Workspace workspace, UnitTestingDefinitionItemWrapper definitionItem, bool showInPreviewTab, bool activateTab, CancellationToken cancellationToken); } diff --git a/src/Features/Core/Portable/ExternalAccess/UnitTesting/UnitTestingStackTraceServiceAccessor.cs b/src/Features/Core/Portable/ExternalAccess/UnitTesting/UnitTestingStackTraceServiceAccessor.cs index aeb2a32036ddb..394da78f82438 100644 --- a/src/Features/Core/Portable/ExternalAccess/UnitTesting/UnitTestingStackTraceServiceAccessor.cs +++ b/src/Features/Core/Portable/ExternalAccess/UnitTesting/UnitTestingStackTraceServiceAccessor.cs @@ -19,9 +19,20 @@ internal sealed class UnitTestingStackTraceServiceAccessor( { private readonly IStackTraceExplorerService _stackTraceExplorerService = stackTraceExplorerService; - public (Document? document, int lineNumber) GetDocumentAndLine(Workspace workspace, UnitTestingParsedFrameWrapper parsedFrame) + public (TextDocument? textDocument, int lineNumber) GetTextDocumentAndLine(Workspace workspace, UnitTestingParsedFrameWrapper parsedFrame) => _stackTraceExplorerService.GetDocumentAndLine(workspace.CurrentSolution, parsedFrame.UnderlyingObject); + public (Document? document, int lineNumber) GetDocumentAndLine(Workspace workspace, UnitTestingParsedFrameWrapper parsedFrame) + { + var (textDocument, lineNumber) = GetTextDocumentAndLine(workspace, parsedFrame); + if (textDocument is Document document) + { + return (document, lineNumber); + } + + return (null, default); + } + public async Task TryFindMethodDefinitionAsync(Workspace workspace, UnitTestingParsedFrameWrapper parsedFrame, CancellationToken cancellationToken) { var definition = await _stackTraceExplorerService.TryFindDefinitionAsync(workspace.CurrentSolution, parsedFrame.UnderlyingObject, StackFrameSymbolPart.Method, cancellationToken).ConfigureAwait(false); diff --git a/src/Features/Core/Portable/StackTraceExplorer/IStackTraceExplorerService.cs b/src/Features/Core/Portable/StackTraceExplorer/IStackTraceExplorerService.cs index 68f42960dfc3b..d8aabcce75751 100644 --- a/src/Features/Core/Portable/StackTraceExplorer/IStackTraceExplorerService.cs +++ b/src/Features/Core/Portable/StackTraceExplorer/IStackTraceExplorerService.cs @@ -16,7 +16,7 @@ internal interface IStackTraceExplorerService : IWorkspaceService /// in a solution. Looks for an exact filepath match first, then defaults to /// a best guess. /// - (Document? document, int line) GetDocumentAndLine(Solution solution, ParsedFrame frame); + (TextDocument? document, int line) GetDocumentAndLine(Solution solution, ParsedFrame frame); Task TryFindDefinitionAsync(Solution solution, ParsedFrame frame, StackFrameSymbolPart symbolPart, CancellationToken cancellationToken); } diff --git a/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs b/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs index e809b29c3f232..ba9ccd34b6f6b 100644 --- a/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs +++ b/src/Features/Core/Portable/StackTraceExplorer/StackTraceExplorerService.cs @@ -2,30 +2,29 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. +using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Composition; using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.CodeAnalysis.EmbeddedLanguages.StackFrame; using Microsoft.CodeAnalysis.FindUsages; using Microsoft.CodeAnalysis.Host.Mef; using Microsoft.CodeAnalysis.Remote; +using Microsoft.CodeAnalysis.Shared.Extensions; using Roslyn.Utilities; namespace Microsoft.CodeAnalysis.StackTraceExplorer; [ExportWorkspaceService(typeof(IStackTraceExplorerService)), Shared] -internal sealed class StackTraceExplorerService : IStackTraceExplorerService +[method: ImportingConstructor] +[method: Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] +internal sealed class StackTraceExplorerService() : IStackTraceExplorerService { - [ImportingConstructor] - [System.Obsolete(MefConstruction.ImportingConstructorMessage, error: true)] - public StackTraceExplorerService() - { - } - - public (Document? document, int line) GetDocumentAndLine(Solution solution, ParsedFrame frame) + public (TextDocument? document, int line) GetDocumentAndLine(Solution solution, ParsedFrame frame) { if (frame is ParsedStackFrame parsedFrame) { @@ -73,7 +72,7 @@ public StackTraceExplorerService() return await StackTraceExplorerUtilities.GetDefinitionAsync(solution, parsedFrame.Root, symbolPart, cancellationToken).ConfigureAwait(false); } - private static ImmutableArray GetFileMatches(Solution solution, StackFrameCompilationUnit root, out int lineNumber) + private static ImmutableArray GetFileMatches(Solution solution, StackFrameCompilationUnit root, out int lineNumber) { lineNumber = 0; if (root.FileInformationExpression is null) @@ -86,19 +85,27 @@ private static ImmutableArray GetFileMatches(Solution solution, StackF RoslynDebug.AssertNotNull(lineString); lineNumber = int.Parse(lineString); + var documentId = solution.GetDocumentIdsWithFilePath(fileName).FirstOrDefault(); + + if (documentId is not null) + { + var document = solution.GetRequiredDocument(documentId); + return [document]; + } + var documentName = Path.GetFileName(fileName); - var potentialMatches = new HashSet(); + var potentialMatches = new HashSet(); foreach (var project in solution.Projects) { - foreach (var document in project.Documents) - { - if (document.FilePath == fileName) - { - return [document]; - } + // As of writing there is no way to get all the documents for a specific project + // so we need to check both the main and additional documents. If more document types + // get added this likely will need to be updated. + var allDocuments = project.Documents.Concat(project.AdditionalDocuments); - else if (document.Name == documentName) + foreach (var document in allDocuments) + { + if (string.Equals(document.Name, documentName, StringComparison.OrdinalIgnoreCase)) { potentialMatches.Add(document); } diff --git a/src/VisualStudio/Core/Def/StackTraceExplorer/StackFrameViewModel.cs b/src/VisualStudio/Core/Def/StackTraceExplorer/StackFrameViewModel.cs index b97644aa038cc..bc18e7a50097c 100644 --- a/src/VisualStudio/Core/Def/StackTraceExplorer/StackFrameViewModel.cs +++ b/src/VisualStudio/Core/Def/StackTraceExplorer/StackFrameViewModel.cs @@ -28,31 +28,22 @@ namespace Microsoft.VisualStudio.LanguageServices.StackTraceExplorer; using StackFrameToken = EmbeddedSyntaxToken; using StackFrameTrivia = EmbeddedSyntaxTrivia; -internal class StackFrameViewModel : FrameViewModel +internal class StackFrameViewModel( + ParsedStackFrame frame, + IThreadingContext threadingContext, + Workspace workspace, + IClassificationFormatMap formatMap, + ClassificationTypeMap typeMap) : FrameViewModel(formatMap, typeMap) { - private readonly ParsedStackFrame _frame; - private readonly IThreadingContext _threadingContext; - private readonly Workspace _workspace; - private readonly IStackTraceExplorerService _stackExplorerService; + private readonly ParsedStackFrame _frame = frame; + private readonly IThreadingContext _threadingContext = threadingContext; + private readonly Workspace _workspace = workspace; + private readonly IStackTraceExplorerService _stackExplorerService = workspace.Services.GetRequiredService(); private readonly Dictionary _definitionCache = []; - private Document? _cachedDocument; + private TextDocument? _cachedDocument; private int _cachedLineNumber; - public StackFrameViewModel( - ParsedStackFrame frame, - IThreadingContext threadingContext, - Workspace workspace, - IClassificationFormatMap formatMap, - ClassificationTypeMap typeMap) - : base(formatMap, typeMap) - { - _frame = frame; - _threadingContext = threadingContext; - _workspace = workspace; - _stackExplorerService = workspace.Services.GetRequiredService(); - } - public override bool ShowMouseOver => true; public void NavigateToClass() @@ -112,14 +103,11 @@ public async Task NavigateToFileAsync(CancellationToken cancellationToken) { try { - var (document, lineNumber) = GetDocumentAndLine(); + var (textDocument, lineNumber) = GetDocumentAndLine(); - if (document is not null) + if (textDocument is not null) { - // While navigating do not activate the tab, which will change focus from the tool window - var options = new NavigationOptions(PreferProvisionalTab: true, ActivateTab: false); - - var sourceText = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false); + var sourceText = await textDocument.GetValueTextAsync(cancellationToken).ConfigureAwait(false); // If the line number is larger than the total lines in the file // then just go to the end of the file (lines count). This can happen @@ -131,8 +119,12 @@ public async Task NavigateToFileAsync(CancellationToken cancellationToken) if (navigationService is null) return; - var location = await navigationService.TryNavigateToLineAndOffsetAsync( - _threadingContext, _workspace, document.Id, lineNumber - 1, offset: 0, options, cancellationToken).ConfigureAwait(false); + // While navigating do not activate the tab, which will change focus from the tool window + var options = new NavigationOptions(PreferProvisionalTab: true, ActivateTab: false); + + await navigationService.TryNavigateToLineAndOffsetAsync( + _threadingContext, _workspace, textDocument.Id, lineNumber - 1, offset: 0, options, cancellationToken) + .ConfigureAwait(false); } } catch (Exception ex) when (FatalError.ReportAndCatchUnlessCanceled(ex, cancellationToken)) @@ -208,7 +200,7 @@ protected override IEnumerable CreateInlines() yield return MakeClassifiedRun(ClassificationTypeNames.Text, _frame.Root.EndOfLineToken.ToFullString()); } - private (Document? document, int lineNumber) GetDocumentAndLine() + private (TextDocument? document, int lineNumber) GetDocumentAndLine() { if (_cachedDocument is not null) { diff --git a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs index 97ec5a757a3ab..ced22ef8af18a 100644 --- a/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs +++ b/src/VisualStudio/Core/Def/Workspace/VisualStudioDocumentNavigationService.cs @@ -189,35 +189,30 @@ static VsTextSpan GetVsTextSpanFromPosition(SourceText text, int position, int v documentId = workspace.GetDocumentIdInCurrentContext(documentId); var solution = workspace.CurrentSolution; - var document = solution.GetDocument(documentId); - if (document == null) - { - var project = solution.GetProject(documentId.ProjectId); - if (project is null) - { - // This is a source generated document shown in Solution Explorer, but is no longer valid since - // the configuration and/or platform changed since the last generation completed. - return null; - } + var textDocument = await solution.GetTextDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); - var generatedDocument = await project.GetSourceGeneratedDocumentAsync(documentId, cancellationToken).ConfigureAwait(false); - if (generatedDocument == null) - return null; + if (textDocument is null) + { + return null; + } + if (textDocument is SourceGeneratedDocument generatedDocument) + { return _sourceGeneratedFileManager.Value.GetNavigationCallback( generatedDocument, await getTextSpanForMappingAsync(generatedDocument).ConfigureAwait(false)); } // Before attempting to open the document, check if the location maps to a different file that should be opened instead. - var spanMappingService = document.DocumentServiceProvider.GetService(); - if (spanMappingService != null) + if (textDocument is Document document && + textDocument.DocumentServiceProvider.GetService() is ISpanMappingService spanMappingService) { var mappedSpanResult = await GetMappedSpanAsync( spanMappingService, document, await getTextSpanForMappingAsync(document).ConfigureAwait(false), cancellationToken).ConfigureAwait(false); + if (mappedSpanResult is { IsDefault: false } mappedSpan) { // Check if the mapped file matches one already in the workspace.