diff --git a/src/VisualStudio/Core/Def/Extensions/VsTextSpanExtensions.cs b/src/VisualStudio/Core/Def/Extensions/VsTextSpanExtensions.cs
index 6d8f9d9361fc7..c4a89df92f61a 100644
--- a/src/VisualStudio/Core/Def/Extensions/VsTextSpanExtensions.cs
+++ b/src/VisualStudio/Core/Def/Extensions/VsTextSpanExtensions.cs
@@ -5,6 +5,7 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.VisualStudio.LanguageServices.Implementation.Venus;
using VsTextSpan = Microsoft.VisualStudio.TextManager.Interop.TextSpan;
@@ -24,6 +25,24 @@ internal static class VsTextSpanExtensions
return null;
await threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
+ return MapSpanFromSecondaryBufferToPrimaryBuffer(
+ spanInSecondaryBuffer, threadingContext, documentId, containedDocument);
+ }
+
+ ///
+ /// Only call this from the UI thread. See for the async version.
+ ///
+ public static VsTextSpan? MapSpanFromSecondaryBufferToPrimaryBuffer(
+ this VsTextSpan spanInSecondaryBuffer,
+ IThreadingContext threadingContext,
+ DocumentId documentId,
+ ContainedDocument? containedDocument = null)
+ {
+ threadingContext.ThrowIfNotOnUIThread();
+
+ containedDocument ??= ContainedDocument.TryGetContainedDocument(documentId);
+ if (containedDocument == null)
+ return null;
var bufferCoordinator = containedDocument.BufferCoordinator;
var primary = new VsTextSpan[1];
diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.VsLanguageDebugInfo.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.VsLanguageDebugInfo.cs
index 790628ecd98ad..1e3e8b50c8b64 100644
--- a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.VsLanguageDebugInfo.cs
+++ b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.VsLanguageDebugInfo.cs
@@ -177,8 +177,13 @@ public int ResolveName(string? pszName, uint dwFlags, out IVsEnumDebugName? ppNa
return VSConstants.S_FALSE;
}
+ // NOTE(cyrusn): We have to wait here because the debuggers' ResolveName
+ // call is synchronous. In the future it would be nice to make it async.
ppNames = this.ThreadingContext.JoinableTaskFactory.Run(async () =>
{
+ // We're in a blocking JTF run. So ConfigureAwait(true) all calls to ensure we're coming back
+ // and using the blocked thread whenever possible.
+
using (Logger.LogBlock(FunctionId.Debugging_VsLanguageDebugInfo_ResolveName, CancellationToken.None))
{
using var waitContext = _uiThreadOperationExecutor.BeginExecute(
@@ -192,14 +197,11 @@ public int ResolveName(string? pszName, uint dwFlags, out IVsEnumDebugName? ppNa
{
var solution = _languageService.Workspace.CurrentSolution;
- // NOTE(cyrusn): We have to wait here because the debuggers' ResolveName
- // call is synchronous. In the future it would be nice to make it async.
if (_breakpointService != null)
{
var breakpoints = await _breakpointService.ResolveBreakpointsAsync(
- solution, pszName, cancellationToken).ConfigureAwait(false);
- var debugNames = await breakpoints.SelectAsArrayAsync(
- bp => CreateDebugNameAsync(bp, cancellationToken)).ConfigureAwait(true);
+ solution, pszName, cancellationToken).ConfigureAwait(true);
+ var debugNames = breakpoints.SelectAsArray(bp => CreateDebugName(bp, cancellationToken));
return new VsEnumDebugName(debugNames);
}
@@ -210,23 +212,28 @@ public int ResolveName(string? pszName, uint dwFlags, out IVsEnumDebugName? ppNa
});
return ppNames != null ? VSConstants.S_OK : VSConstants.E_NOTIMPL;
- }
- private async ValueTask CreateDebugNameAsync(
- BreakpointResolutionResult breakpoint, CancellationToken cancellationToken)
- {
- var document = breakpoint.Document;
- var filePath = _languageService.Workspace.GetFilePath(document.Id);
- var text = await document.GetValueTextAsync(cancellationToken).ConfigureAwait(false);
- var span = text.GetVsTextSpanForSpan(breakpoint.TextSpan);
- // If we're inside an Venus code nugget, we need to map the span to the surface buffer.
- // Otherwise, we'll just use the original span.
- var mappedSpan = await span.MapSpanFromSecondaryBufferToPrimaryBufferAsync(
- this.ThreadingContext, document.Id, cancellationToken).ConfigureAwait(true);
- if (mappedSpan != null)
- span = mappedSpan.Value;
-
- return new VsDebugName(breakpoint.LocationNameOpt, filePath!, span);
+ IVsDebugName CreateDebugName(
+ BreakpointResolutionResult breakpoint, CancellationToken cancellationToken)
+ {
+ // We're in a blocking jtf run. So CA(true) all calls to ensure we're coming bac
+ // and using the blocked thread whenever possible.
+
+ var document = breakpoint.Document;
+ var filePath = _languageService.Workspace.GetFilePath(document.Id);
+
+ // We're (unfortunately) blocking the UI thread here. So avoid async io as we actually
+ // awant the IO to complete as quickly as possible, on this thread if necessary.
+ var text = document.GetTextSynchronously(cancellationToken);
+ var span = text.GetVsTextSpanForSpan(breakpoint.TextSpan);
+ // If we're inside an Venus code nugget, we need to map the span to the surface buffer.
+ // Otherwise, we'll just use the original span.
+ var mappedSpan = span.MapSpanFromSecondaryBufferToPrimaryBuffer(this.ThreadingContext, document.Id);
+ if (mappedSpan != null)
+ span = mappedSpan.Value;
+
+ return new VsDebugName(breakpoint.LocationNameOpt, filePath!, span);
+ }
}
public int ValidateBreakpointLocation(IVsTextBuffer pBuffer, int iLine, int iCol, VsTextSpan[] pCodeSpan)