Skip to content

Fix Stack Trace Explorer for additional documents #77517

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

Merged
merged 4 commits into from
Mar 13, 2025
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public static async Task<bool> 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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ internal interface IUnitTestingStackTraceServiceAccessor : IWorkspaceService
{
Task<ImmutableArray<UnitTestingParsedFrameWrapper>> TryParseAsync(string input, Workspace workspace, CancellationToken cancellationToken);
Task<UnitTestingDefinitionItemWrapper?> TryFindMethodDefinitionAsync(Workspace workspace, UnitTestingParsedFrameWrapper parsedFrame, CancellationToken cancellationToken);
(Document? document, int lineNumber) GetDocumentAndLine(Workspace workspace, UnitTestingParsedFrameWrapper parsedFrame);
(TextDocument? document, int lineNumber) GetDocumentAndLine(Workspace workspace, UnitTestingParsedFrameWrapper parsedFrame);
Task<bool> TryNavigateToAsync(Workspace workspace, UnitTestingDefinitionItemWrapper definitionItem, bool showInPreviewTab, bool activateTab, CancellationToken cancellationToken);
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ internal sealed class UnitTestingStackTraceServiceAccessor(
{
private readonly IStackTraceExplorerService _stackTraceExplorerService = stackTraceExplorerService;

public (Document? document, int lineNumber) GetDocumentAndLine(Workspace workspace, UnitTestingParsedFrameWrapper parsedFrame)
public (TextDocument? document, int lineNumber) GetDocumentAndLine(Workspace workspace, UnitTestingParsedFrameWrapper parsedFrame)
=> _stackTraceExplorerService.GetDocumentAndLine(workspace.CurrentSolution, parsedFrame.UnderlyingObject);

public async Task<UnitTestingDefinitionItemWrapper?> TryFindMethodDefinitionAsync(Workspace workspace, UnitTestingParsedFrameWrapper parsedFrame, CancellationToken cancellationToken)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal interface IStackTraceExplorerService : IWorkspaceService
/// in a solution. Looks for an exact filepath match first, then defaults to
/// a best guess.
/// </summary>
(Document? document, int line) GetDocumentAndLine(Solution solution, ParsedFrame frame);
(TextDocument? document, int line) GetDocumentAndLine(Solution solution, ParsedFrame frame);
Task<DefinitionItem?> TryFindDefinitionAsync(Solution solution, ParsedFrame frame, StackFrameSymbolPart symbolPart, CancellationToken cancellationToken);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
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;
Expand All @@ -17,15 +18,11 @@
namespace Microsoft.CodeAnalysis.StackTraceExplorer;

[ExportWorkspaceService(typeof(IStackTraceExplorerService)), Shared]
internal sealed class StackTraceExplorerService : IStackTraceExplorerService
[method: ImportingConstructor]
[method: System.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)
{
Expand Down Expand Up @@ -73,7 +70,7 @@ public StackTraceExplorerService()
return await StackTraceExplorerUtilities.GetDefinitionAsync(solution, parsedFrame.Root, symbolPart, cancellationToken).ConfigureAwait(false);
}

private static ImmutableArray<Document> GetFileMatches(Solution solution, StackFrameCompilationUnit root, out int lineNumber)
private static ImmutableArray<TextDocument> GetFileMatches(Solution solution, StackFrameCompilationUnit root, out int lineNumber)
{
lineNumber = 0;
if (root.FileInformationExpression is null)
Expand All @@ -87,11 +84,16 @@ private static ImmutableArray<Document> GetFileMatches(Solution solution, StackF
lineNumber = int.Parse(lineString);

var documentName = Path.GetFileName(fileName);
var potentialMatches = new HashSet<Document>();
var potentialMatches = new HashSet<TextDocument>();

foreach (var project in solution.Projects)
{
foreach (var document in project.Documents)
// 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);

foreach (var document in allDocuments)
{
if (document.FilePath == fileName)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If matching by an exact file path is always the winner, shouldn't we be calling GetDocumentIdsWithFilePath and only if it doesn't find anything, then falling back to the weaker search?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Considering it is returning the first document, I suggested GetFirstDocumentIdWithFilePath

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetFirstDocumentIdWithFilePath this appears to be only on ProjectState. I don't see an equivalent for Solution

{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,31 +28,22 @@ namespace Microsoft.VisualStudio.LanguageServices.StackTraceExplorer;
using StackFrameToken = EmbeddedSyntaxToken<StackFrameKind>;
using StackFrameTrivia = EmbeddedSyntaxTrivia<StackFrameKind>;

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<IStackTraceExplorerService>();
private readonly Dictionary<StackFrameSymbolPart, DefinitionItem?> _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<IStackTraceExplorerService>();
}

public override bool ShowMouseOver => true;

public void NavigateToClass()
Expand Down Expand Up @@ -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
Expand All @@ -131,8 +119,13 @@ 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))
Expand Down Expand Up @@ -208,7 +201,7 @@ protected override IEnumerable<Inline> 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)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -189,8 +189,8 @@ 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 textDocument = await solution.GetRequiredTextDocumentAsync(documentId, cancellationToken).ConfigureAwait(false);
if (textDocument is SourceGeneratedDocument)
{
var project = solution.GetProject(documentId.ProjectId);
if (project is null)
Expand All @@ -210,14 +210,15 @@ static VsTextSpan GetVsTextSpanFromPosition(SourceText text, int position, int v
}

// 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<ISpanMappingService>();
if (spanMappingService != null)
if (textDocument is Document document &&
textDocument.DocumentServiceProvider.GetService<ISpanMappingService>() 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.
Expand Down
Loading