|
// Licensed to the .NET Foundation under one or more agreements.
// 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.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeStyle;
using Microsoft.CodeAnalysis.Completion;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Editor.VisualBasic.LineCommit;
using Microsoft.CodeAnalysis.Host;
using Microsoft.CodeAnalysis.NavigateTo;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.Extensions;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.SolutionCrawler;
using Microsoft.VisualStudio.LanguageServices;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Threading;
using Roslyn.Utilities;
namespace Microsoft.VisualStudio.Extensibility.Testing
{
internal partial class WorkspaceInProcess
{
private static bool s_initializedAsyncSaveListener;
private static IVsRunningDocTableEvents? s_runningDocTableEventListener;
#pragma warning disable IDE0052 // Remove unread private members
private static uint s_runningDocTableEventListenerCookie;
#pragma warning restore IDE0052 // Remove unread private members
internal static void EnableAsynchronousOperationTracking()
{
AsynchronousOperationListenerProvider.Enable(true, diagnostics: true);
}
protected override async Task InitializeCoreAsync()
{
await base.InitializeCoreAsync();
if (s_initializedAsyncSaveListener)
return;
s_initializedAsyncSaveListener = true;
await JoinableTaskFactory.SwitchToMainThreadAsync();
var threadingContext = await GetComponentModelServiceAsync<IThreadingContext>(CancellationToken.None);
var listenerProvider = await GetComponentModelServiceAsync<IAsynchronousOperationListenerProvider>(CancellationToken.None);
var rdtEvents = await GetRequiredGlobalServiceAsync<SVsRunningDocumentTable, IVsRunningDocumentTable>(CancellationToken.None);
s_runningDocTableEventListener = new RunningDocumentTableEventListener(threadingContext, listenerProvider.GetListener(FeatureAttribute.Workspace));
ErrorHandler.ThrowOnFailure(rdtEvents.AdviseRunningDocTableEvents(s_runningDocTableEventListener, out s_runningDocTableEventListenerCookie));
}
public async Task<bool> IsPrettyListingOnAsync(string languageName, CancellationToken cancellationToken)
{
var globalOptions = await GetComponentModelServiceAsync<IGlobalOptionService>(cancellationToken);
return globalOptions.GetOption(LineCommitOptionsStorage.PrettyListing, languageName);
}
public async Task SetPrettyListingAsync(string languageName, bool value, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var globalOptions = await GetComponentModelServiceAsync<IGlobalOptionService>(cancellationToken);
globalOptions.SetGlobalOption(LineCommitOptionsStorage.PrettyListing, languageName, value);
}
public async Task SetTriggerCompletionInArgumentListsAsync(string languageName, bool value, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var globalOptions = await GetComponentModelServiceAsync<IGlobalOptionService>(cancellationToken);
globalOptions.SetGlobalOption(CompletionOptionsStorage.TriggerInArgumentLists, languageName, value);
}
public async Task SetFileScopedNamespaceAsync(bool value, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync<IGlobalOptionService>(cancellationToken);
globalOptions.SetGlobalOption(Microsoft.CodeAnalysis.CSharp.CodeStyle.CSharpCodeStyleOptions.NamespaceDeclarations,
new CodeStyleOption2<NamespaceDeclarationPreference>(value ? NamespaceDeclarationPreference.FileScoped : NamespaceDeclarationPreference.BlockScoped, NotificationOption2.Suggestion));
}
public async Task SetFullSolutionAnalysisAsync(bool value, CancellationToken cancellationToken)
{
await JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
var globalOptions = await TestServices.Shell.GetComponentModelServiceAsync<IGlobalOptionService>(cancellationToken);
var scope = value ? BackgroundAnalysisScope.FullSolution : BackgroundAnalysisScope.Default;
globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.CSharp, scope);
globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.BackgroundAnalysisScopeOption, LanguageNames.VisualBasic, scope);
var compilerScope = value ? CompilerDiagnosticsScope.FullSolution : CompilerDiagnosticsScope.OpenFiles;
globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption, LanguageNames.CSharp, compilerScope);
globalOptions.SetGlobalOption(SolutionCrawlerOptionsStorage.CompilerDiagnosticsScopeOption, LanguageNames.VisualBasic, compilerScope);
}
public Task WaitForAsyncOperationsAsync(string featuresToWaitFor, CancellationToken cancellationToken)
=> WaitForAsyncOperationsAsync(featuresToWaitFor, waitForWorkspaceFirst: true, cancellationToken);
public async Task WaitForAsyncOperationsAsync(string featuresToWaitFor, bool waitForWorkspaceFirst, CancellationToken cancellationToken)
{
if (waitForWorkspaceFirst || featuresToWaitFor == FeatureAttribute.Workspace)
{
await WaitForProjectSystemAsync(cancellationToken);
await TestServices.Shell.WaitForFileChangeNotificationsAsync(cancellationToken);
await TestServices.Editor.WaitForEditorOperationsAsync(cancellationToken);
}
var listenerProvider = await GetComponentModelServiceAsync<AsynchronousOperationListenerProvider>(cancellationToken);
if (waitForWorkspaceFirst)
{
var workspaceWaiter = listenerProvider.GetWaiter(FeatureAttribute.Workspace);
await workspaceWaiter.ExpeditedWaitAsync().WithCancellation(cancellationToken);
}
var featureWaiter = listenerProvider.GetWaiter(featuresToWaitFor);
await featureWaiter.ExpeditedWaitAsync().WithCancellation(cancellationToken);
}
public async Task WaitForAllAsyncOperationsAsync(string[] featureNames, CancellationToken cancellationToken)
{
if (featureNames.Contains(FeatureAttribute.Workspace))
{
await WaitForProjectSystemAsync(cancellationToken);
await TestServices.Shell.WaitForFileChangeNotificationsAsync(cancellationToken);
await TestServices.Editor.WaitForEditorOperationsAsync(cancellationToken);
}
var listenerProvider = await GetComponentModelServiceAsync<AsynchronousOperationListenerProvider>(cancellationToken);
var workspace = await GetComponentModelServiceAsync<VisualStudioWorkspace>(cancellationToken);
if (featureNames.Contains(FeatureAttribute.NavigateTo))
{
var statusService = workspace.Services.GetRequiredService<IWorkspaceStatusService>();
Contract.ThrowIfFalse(await statusService.IsFullyLoadedAsync(cancellationToken));
// Make sure the "priming" operation has started for Nav To
var threadingContext = await GetComponentModelServiceAsync<IThreadingContext>(cancellationToken);
var asyncListener = listenerProvider.GetListener(FeatureAttribute.NavigateTo);
var searchHost = new DefaultNavigateToSearchHost(workspace.CurrentSolution, asyncListener, threadingContext.DisposalToken);
// Calling DefaultNavigateToSearchHost.IsFullyLoadedAsync starts the fire-and-forget asynchronous
// operation to populate the remote host. The call to WaitAllAsync below will wait for that operation to
// complete.
await searchHost.IsFullyLoadedAsync(cancellationToken);
}
await listenerProvider.WaitAllAsync(workspace, featureNames).WithCancellation(cancellationToken);
}
public async Task WaitForRenameAsync(CancellationToken cancellationToken)
{
await WaitForAllAsyncOperationsAsync(
new[]
{
FeatureAttribute.Rename,
FeatureAttribute.RenameTracking,
FeatureAttribute.InlineRenameFlyout,
},
cancellationToken);
}
/// <summary>
/// This event listener is an adapter to expose asynchronous file save operations to Roslyn via its standard
/// workspace event waiters.
/// </summary>
private sealed class RunningDocumentTableEventListener : IVsRunningDocTableEvents, IVsRunningDocTableEvents7
{
private readonly IThreadingContext _threadingContext;
private readonly IAsynchronousOperationListener _asynchronousOperationListener;
public RunningDocumentTableEventListener(IThreadingContext threadingContext, IAsynchronousOperationListener asynchronousOperationListener)
{
_threadingContext = threadingContext;
_asynchronousOperationListener = asynchronousOperationListener;
}
int IVsRunningDocTableEvents.OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
=> VSConstants.S_OK;
int IVsRunningDocTableEvents.OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
=> VSConstants.S_OK;
int IVsRunningDocTableEvents.OnAfterSave(uint docCookie)
=> VSConstants.S_OK;
int IVsRunningDocTableEvents.OnAfterAttributeChange(uint docCookie, uint grfAttribs)
=> VSConstants.S_OK;
int IVsRunningDocTableEvents.OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
=> VSConstants.S_OK;
int IVsRunningDocTableEvents.OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame)
=> VSConstants.S_OK;
#pragma warning disable CS8768 // Nullability of reference types in return type doesn't match implemented member (possibly because of nullability attributes). (Signature was corrected in https://devdiv.visualstudio.com/DevDiv/_git/VS/pullrequest/390178)
IVsTask? IVsRunningDocTableEvents7.OnBeforeSaveAsync(uint cookie, uint flags, IVsTask? saveTask)
#pragma warning restore CS8768 // Nullability of reference types in return type doesn't match implemented member (possibly because of nullability attributes).
{
if (saveTask is not null)
{
_ = _threadingContext.JoinableTaskFactory.RunAsync(async () =>
{
// Track asynchronous save operations via Roslyn's Workspace events
using var _ = _asynchronousOperationListener.BeginAsyncOperation("OnBeforeSaveAsync");
await saveTask;
});
}
// No additional work for the caller to handle
return null;
}
#pragma warning disable CS8768 // Nullability of reference types in return type doesn't match implemented member (possibly because of nullability attributes). (Signature was corrected in https://devdiv.visualstudio.com/DevDiv/_git/VS/pullrequest/390178)
IVsTask? IVsRunningDocTableEvents7.OnAfterSaveAsync(uint cookie, uint flags)
#pragma warning restore CS8768 // Nullability of reference types in return type doesn't match implemented member (possibly because of nullability attributes).
{
// No additional work for the caller to handle
return null;
}
}
}
}
|