diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageContextProvider.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageContextProvider.cs index 6528a1415ed3e..e2da72836fe0e 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageContextProvider.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.IVsLanguageContextProvider.cs @@ -2,15 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. // See the LICENSE file in the project root for more information. -#nullable disable - using System.Threading; using Microsoft.CodeAnalysis.Shared.Extensions; using Microsoft.CodeAnalysis.Text; using Microsoft.VisualStudio.LanguageServices.Implementation.F1Help; using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.TextManager.Interop; -using Roslyn.Utilities; namespace Microsoft.VisualStudio.LanguageServices.Implementation.LanguageService; @@ -18,45 +15,41 @@ internal abstract partial class AbstractLanguageService { - return VSConstants.E_UNEXPECTED; - } + var textBuffer = EditorAdaptersFactoryService.GetDataBuffer(pBuffer); + var context = (IVsUserContext)pUC; - var document = textBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); - if (document == null) - { - return VSConstants.E_FAIL; - } + if (textBuffer == null || context == null) + return VSConstants.E_UNEXPECTED; - var start = textBuffer.CurrentSnapshot.GetLineFromLineNumber(ptsSelection[0].iStartLine).Start + ptsSelection[0].iStartIndex; - var end = textBuffer.CurrentSnapshot.GetLineFromLineNumber(ptsSelection[0].iEndLine).Start + ptsSelection[0].iEndIndex; - var span = Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(start, end); + var document = textBuffer.CurrentSnapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document == null) + return VSConstants.E_FAIL; - var helpService = document.GetLanguageService(); - if (helpService == null) - { - return VSConstants.E_NOTIMPL; - } + var start = textBuffer.CurrentSnapshot.GetLineFromLineNumber(ptsSelection[0].iStartLine).Start + ptsSelection[0].iStartIndex; + var end = textBuffer.CurrentSnapshot.GetLineFromLineNumber(ptsSelection[0].iEndLine).Start + ptsSelection[0].iEndIndex; + var span = Microsoft.CodeAnalysis.Text.TextSpan.FromBounds(start, end); - // VS help is not cancellable. - var cancellationToken = CancellationToken.None; - var helpTerm = helpService.GetHelpTermAsync(document, span, cancellationToken).WaitAndGetResult(cancellationToken); + var helpService = document.GetLanguageService(); + if (helpService == null) + return VSConstants.E_NOTIMPL; - if (string.IsNullOrWhiteSpace(helpTerm)) - { - return VSConstants.S_FALSE; - } + // VS help is not cancellable. + var cancellationToken = CancellationToken.None; + var helpTerm = await helpService.GetHelpTermAsync( + document, span, cancellationToken).ConfigureAwait(true); + + if (string.IsNullOrWhiteSpace(helpTerm)) + return VSConstants.S_FALSE; - context.RemoveAttribute("keyword", null); - context.AddAttribute(VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_Filter, "devlang", helpService.Language); - context.AddAttribute(VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_Filter, "product", helpService.Product); - context.AddAttribute(VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_Filter, "product", "VS"); - context.AddAttribute(VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_LookupF1_CaseSensitive, "keyword", helpTerm); + context.RemoveAttribute("keyword", null); + context.AddAttribute(VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_Filter, "devlang", helpService.Language); + context.AddAttribute(VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_Filter, "product", helpService.Product); + context.AddAttribute(VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_Filter, "product", "VS"); + context.AddAttribute(VSUSERCONTEXTATTRIBUTEUSAGE.VSUC_Usage_LookupF1_CaseSensitive, "keyword", helpTerm); - return VSConstants.S_OK; + return VSConstants.S_OK; + }); } } diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.VsLanguageDebugInfo.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.VsLanguageDebugInfo.cs index e068d095d46e2..abdf5313d6093 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.VsLanguageDebugInfo.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.VsLanguageDebugInfo.cs @@ -13,6 +13,7 @@ using Microsoft.CodeAnalysis.Internal.Log; using Microsoft.CodeAnalysis.Text; using Microsoft.CodeAnalysis.Text.Shared.Extensions; +using Microsoft.Internal.VisualStudio.Shell.Interop; using Microsoft.VisualStudio.LanguageServices.Implementation.Extensions; using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities; using Microsoft.VisualStudio.Utilities; @@ -31,7 +32,6 @@ internal sealed class VsLanguageDebugInfo : IVsLanguageDebugInfo { private readonly Guid _languageId; private readonly TLanguageService _languageService; - private readonly IThreadingContext _threadingContext; private readonly ILanguageDebugInfoService? _languageDebugInfo; private readonly IBreakpointResolutionService? _breakpointService; private readonly IProximityExpressionsService? _proximityExpressionsService; @@ -41,7 +41,6 @@ public VsLanguageDebugInfo( Guid languageId, TLanguageService languageService, HostLanguageServices languageServiceProvider, - IThreadingContext threadingContext, IUIThreadOperationExecutor uiThreadOperationExecutor) { Contract.ThrowIfNull(languageService); @@ -49,13 +48,14 @@ public VsLanguageDebugInfo( _languageId = languageId; _languageService = languageService; - _threadingContext = threadingContext; _languageDebugInfo = languageServiceProvider.GetService(); _breakpointService = languageServiceProvider.GetService(); _proximityExpressionsService = languageServiceProvider.GetService(); _uiThreadOperationExecutor = uiThreadOperationExecutor; } + private IThreadingContext ThreadingContext => _languageService.ThreadingContext; + public int GetLanguageID(IVsTextBuffer pBuffer, int iLine, int iCol, out Guid pguidLanguageID) { pguidLanguageID = _languageId; @@ -71,109 +71,96 @@ public int GetLocationOfName(string pszName, out string? pbstrMkDoc, out VsTextS public int GetNameOfLocation(IVsTextBuffer pBuffer, int iLine, int iCol, out string? pbstrName, out int piLineOffset) { - using (Logger.LogBlock(FunctionId.Debugging_VsLanguageDebugInfo_GetNameOfLocation, CancellationToken.None)) - { - string? name = null; - var lineOffset = 0; + (pbstrName, piLineOffset) = GetNameOfLocationWorker(); - if (_languageDebugInfo != null) - { - _uiThreadOperationExecutor.Execute( - title: ServicesVSResources.Debugger, - defaultDescription: ServicesVSResources.Determining_breakpoint_location, - allowCancellation: true, - showProgress: false, - action: waitContext => - { - var cancellationToken = waitContext.UserCancellationToken; - var textBuffer = _languageService.EditorAdaptersFactoryService.GetDataBuffer(pBuffer); - if (textBuffer != null) - { - var nullablePoint = textBuffer.CurrentSnapshot.TryGetPoint(iLine, iCol); - if (nullablePoint.HasValue) - { - var point = nullablePoint.Value; - var document = point.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); - - if (document != null) - { - // NOTE(cyrusn): We have to wait here because the debuggers' - // GetNameOfLocation is a blocking call. In the future, it - // would be nice if they could make it async. - _threadingContext.JoinableTaskFactory.Run(async () => - { - var debugLocationInfo = await _languageDebugInfo.GetLocationInfoAsync(document, point, cancellationToken).ConfigureAwait(false); - - if (!debugLocationInfo.IsDefault) - { - name = debugLocationInfo.Name; - lineOffset = debugLocationInfo.LineOffset; - } - }); - } - } - } - }); + // Note(DustinCa): Docs say that GetNameOfLocation should return S_FALSE if a name could not be found. + // Also, that's what the old native code does, so we should do it here. + return pbstrName != null ? VSConstants.S_OK : VSConstants.S_FALSE; - if (name != null) + (string name, int lineOffset) GetNameOfLocationWorker() + { + return this.ThreadingContext.JoinableTaskFactory.Run(async () => + { + using (Logger.LogBlock(FunctionId.Debugging_VsLanguageDebugInfo_GetNameOfLocation, CancellationToken.None)) { - pbstrName = name; - piLineOffset = lineOffset; - return VSConstants.S_OK; - } - } + if (_languageDebugInfo == null) + return default; - // Note(DustinCa): Docs say that GetNameOfLocation should return S_FALSE if a name could not be found. - // Also, that's what the old native code does, so we should do it here. - pbstrName = null; - piLineOffset = 0; - return VSConstants.S_FALSE; + using var waitContext = _uiThreadOperationExecutor.BeginExecute( + title: ServicesVSResources.Debugger, + defaultDescription: ServicesVSResources.Determining_breakpoint_location, + allowCancellation: true, + showProgress: false); + + var cancellationToken = waitContext.UserCancellationToken; + var textBuffer = _languageService.EditorAdaptersFactoryService.GetDataBuffer(pBuffer); + if (textBuffer == null) + return default; + + var nullablePoint = textBuffer.CurrentSnapshot.TryGetPoint(iLine, iCol); + if (!nullablePoint.HasValue) + return default; + + var point = nullablePoint.Value; + var document = point.Snapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document == null) + return default; + + // NOTE(cyrusn): We have to wait here because the debuggers' + // GetNameOfLocation is a blocking call. In the future, it + // would be nice if they could make it async. + var debugLocationInfo = await _languageDebugInfo.GetLocationInfoAsync(document, point, cancellationToken).ConfigureAwait(true); + + if (debugLocationInfo.IsDefault) + return default; + + return (debugLocationInfo.Name, debugLocationInfo.LineOffset); + } + }); } } public int GetProximityExpressions(IVsTextBuffer pBuffer, int iLine, int iCol, int cLines, out IVsEnumBSTR? ppEnum) { - // NOTE(cyrusn): cLines is ignored. This is to match existing dev10 behavior. - using (Logger.LogBlock(FunctionId.Debugging_VsLanguageDebugInfo_GetProximityExpressions, CancellationToken.None)) + ppEnum = this.ThreadingContext.JoinableTaskFactory.Run(async () => { - VsEnumBSTR? enumBSTR = null; - - if (_proximityExpressionsService != null) + // NOTE(cyrusn): cLines is ignored. This is to match existing dev10 behavior. + using (Logger.LogBlock(FunctionId.Debugging_VsLanguageDebugInfo_GetProximityExpressions, CancellationToken.None)) { - _uiThreadOperationExecutor.Execute( + using var context = _uiThreadOperationExecutor.BeginExecute( title: ServicesVSResources.Debugger, defaultDescription: ServicesVSResources.Determining_autos, allowCancellation: true, - showProgress: false, - action: context => - { - var textBuffer = _languageService.EditorAdaptersFactoryService.GetDataBuffer(pBuffer); + showProgress: false); - if (textBuffer != null) - { - var snapshot = textBuffer.CurrentSnapshot; - var nullablePoint = snapshot.TryGetPoint(iLine, iCol); - if (nullablePoint.HasValue) - { - var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); - if (document != null) - { - var point = nullablePoint.Value; - var proximityExpressions = _proximityExpressionsService.GetProximityExpressionsAsync(document, point.Position, context.UserCancellationToken).WaitAndGetResult(context.UserCancellationToken); - - if (proximityExpressions != null) - { - enumBSTR = new VsEnumBSTR(proximityExpressions); - } - } - } - } - }); + if (_proximityExpressionsService == null) + return null; + + var textBuffer = _languageService.EditorAdaptersFactoryService.GetDataBuffer(pBuffer); + if (textBuffer == null) + return null; + + var snapshot = textBuffer.CurrentSnapshot; + var nullablePoint = snapshot.TryGetPoint(iLine, iCol); + if (!nullablePoint.HasValue) + return null; + + var document = snapshot.GetOpenDocumentInCurrentContextWithChanges(); + if (document == null) + return null; + + var point = nullablePoint.Value; + var proximityExpressions = await _proximityExpressionsService.GetProximityExpressionsAsync( + document, point.Position, context.UserCancellationToken).ConfigureAwait(true); + + if (proximityExpressions == null) + return null; + + return new VsEnumBSTR(proximityExpressions); } + }); - ppEnum = enumBSTR; - return ppEnum != null ? VSConstants.S_OK : VSConstants.E_FAIL; - } + return ppEnum != null ? VSConstants.S_OK : VSConstants.E_FAIL; } public int IsMappedLocation(IVsTextBuffer pBuffer, int iLine, int iCol) @@ -181,51 +168,49 @@ public int IsMappedLocation(IVsTextBuffer pBuffer, int iLine, int iCol) public int ResolveName(string pszName, uint dwFlags, out IVsEnumDebugName? ppNames) { - using (Logger.LogBlock(FunctionId.Debugging_VsLanguageDebugInfo_ResolveName, CancellationToken.None)) + // In VS, this method frequently get's called with an empty string to test if the language service + // supports this method (some language services, like F#, implement IVsLanguageDebugInfo but don't + // implement this method). In that scenario, there's no sense doing work, so we'll just return + // S_FALSE (as the old VB language service did). + if (string.IsNullOrEmpty(pszName)) { - // In VS, this method frequently get's called with an empty string to test if the language service - // supports this method (some language services, like F#, implement IVsLanguageDebugInfo but don't - // implement this method). In that scenario, there's no sense doing work, so we'll just return - // S_FALSE (as the old VB language service did). - if (string.IsNullOrEmpty(pszName)) - { - ppNames = null; - return VSConstants.S_FALSE; - } + ppNames = null; + return VSConstants.S_FALSE; + } - VsEnumDebugName? enumName = null; - _uiThreadOperationExecutor.Execute( - title: ServicesVSResources.Debugger, - defaultDescription: ServicesVSResources.Resolving_breakpoint_location, - allowCancellation: true, - showProgress: false, - action: waitContext => + ppNames = this.ThreadingContext.JoinableTaskFactory.Run(async () => + { + using (Logger.LogBlock(FunctionId.Debugging_VsLanguageDebugInfo_ResolveName, CancellationToken.None)) { - _threadingContext.JoinableTaskFactory.Run(async () => + using var waitContext = _uiThreadOperationExecutor.BeginExecute( + title: ServicesVSResources.Debugger, + defaultDescription: ServicesVSResources.Resolving_breakpoint_location, + allowCancellation: true, + showProgress: false); + + var cancellationToken = waitContext.UserCancellationToken; + if (dwFlags == (uint)RESOLVENAMEFLAGS.RNF_BREAKPOINT) { - var cancellationToken = waitContext.UserCancellationToken; - if (dwFlags == (uint)RESOLVENAMEFLAGS.RNF_BREAKPOINT) - { - var solution = _languageService.Workspace.CurrentSolution; + 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(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. + if (_breakpointService != null) + { + var breakpoints = await _breakpointService.ResolveBreakpointsAsync( + solution, pszName, cancellationToken).ConfigureAwait(false); + var debugNames = await breakpoints.SelectAsArrayAsync( + bp => CreateDebugNameAsync(bp, cancellationToken)).ConfigureAwait(true); - enumName = new VsEnumDebugName(debugNames); - } + return new VsEnumDebugName(debugNames); } - }); - }); + } + } - ppNames = enumName; - return ppNames != null ? VSConstants.S_OK : VSConstants.E_NOTIMPL; - } + return null; + }); + + return ppNames != null ? VSConstants.S_OK : VSConstants.E_NOTIMPL; } private async ValueTask CreateDebugNameAsync( @@ -238,7 +223,7 @@ private async ValueTask CreateDebugNameAsync( // 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( - _threadingContext, document.Id, cancellationToken).ConfigureAwait(false); + this.ThreadingContext, document.Id, cancellationToken).ConfigureAwait(true); if (mappedSpan != null) span = mappedSpan.Value; @@ -247,24 +232,23 @@ private async ValueTask CreateDebugNameAsync( public int ValidateBreakpointLocation(IVsTextBuffer pBuffer, int iLine, int iCol, VsTextSpan[] pCodeSpan) { - using (Logger.LogBlock(FunctionId.Debugging_VsLanguageDebugInfo_ValidateBreakpointLocation, CancellationToken.None)) + return this.ThreadingContext.JoinableTaskFactory.Run(async () => { - var result = VSConstants.E_NOTIMPL; - _uiThreadOperationExecutor.Execute( - title: ServicesVSResources.Debugger, - defaultDescription: ServicesVSResources.Validating_breakpoint_location, - allowCancellation: true, - showProgress: false, - action: waitContext => + using (Logger.LogBlock(FunctionId.Debugging_VsLanguageDebugInfo_ValidateBreakpointLocation, CancellationToken.None)) { - result = ValidateBreakpointLocationWorker(pBuffer, iLine, iCol, pCodeSpan, waitContext.UserCancellationToken); - }); + using var waitContext = _uiThreadOperationExecutor.BeginExecute( + title: ServicesVSResources.Debugger, + defaultDescription: ServicesVSResources.Validating_breakpoint_location, + allowCancellation: true, + showProgress: false); - return result; - } + return await ValidateBreakpointLocationAsync( + pBuffer, iLine, iCol, pCodeSpan, waitContext.UserCancellationToken).ConfigureAwait(true); + } + }); } - private int ValidateBreakpointLocationWorker( + private async Task ValidateBreakpointLocationAsync( IVsTextBuffer pBuffer, int iLine, int iCol, @@ -272,9 +256,7 @@ private int ValidateBreakpointLocationWorker( CancellationToken cancellationToken) { if (_breakpointService == null) - { return VSConstants.E_FAIL; - } var textBuffer = _languageService.EditorAdaptersFactoryService.GetDataBuffer(pBuffer); if (textBuffer != null) @@ -336,7 +318,8 @@ private int ValidateBreakpointLocationWorker( // NOTE(cyrusn): we need to wait here because ValidateBreakpointLocation is // synchronous. In the future, it would be nice for the debugger to provide // an async entry point for this. - var breakpoint = _breakpointService.ResolveBreakpointAsync(document, new TextSpan(point.Position, length), cancellationToken).WaitAndGetResult(cancellationToken); + var breakpoint = await _breakpointService.ResolveBreakpointAsync( + document, new TextSpan(point.Position, length), cancellationToken).ConfigureAwait(true); if (breakpoint == null) { // There should *not* be a breakpoint here. E_FAIL to let the debugger know @@ -357,9 +340,7 @@ private int ValidateBreakpointLocationWorker( // There should be a breakpoint at the location passed back. if (pCodeSpan != null && pCodeSpan.Length > 0) - { pCodeSpan[0] = breakpoint.TextSpan.ToSnapshotSpan(snapshot).ToVsTextSpan(); - } return VSConstants.S_OK; } diff --git a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs index bac0507b76bf7..6044e5dc3227e 100644 --- a/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs +++ b/src/VisualStudio/Core/Def/LanguageService/AbstractLanguageService`2.cs @@ -87,6 +87,8 @@ protected AbstractLanguageService(TPackage package) this._languageDebugInfo = CreateLanguageDebugInfo(); } + private IThreadingContext ThreadingContext => this.Package.ComponentModel.GetService(); + public override IServiceProvider SystemServiceProvider => Package; @@ -223,7 +225,6 @@ private VsLanguageDebugInfo CreateLanguageDebugInfo() this.DebuggerLanguageId, (TLanguageService)this, languageServices, - this.Package.ComponentModel.GetService(), this.Package.ComponentModel.GetService()); }