|
// 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;
using System.Collections.Immutable;
using System.ComponentModel.Composition;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
using Microsoft.CodeAnalysis.Host.Mef;
using Microsoft.CodeAnalysis.Internal.Log;
using Microsoft.CodeAnalysis.Options;
using Microsoft.CodeAnalysis.Shared.TestHooks;
using Microsoft.CodeAnalysis.Text;
using Microsoft.CodeAnalysis.ValueTracking;
using Microsoft.VisualStudio.Commanding;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.LanguageServices.Setup;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Text.Classification;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Utilities;
using Roslyn.Utilities;
using IAsyncServiceProvider = Microsoft.VisualStudio.Shell.IAsyncServiceProvider;
using Task = System.Threading.Tasks.Task;
namespace Microsoft.VisualStudio.LanguageServices.ValueTracking;
[Export(typeof(ICommandHandler))]
[ContentType(ContentTypeNames.RoslynContentType)]
[Name(PredefinedCommandHandlerNames.ShowValueTracking)]
internal class ValueTrackingCommandHandler : ICommandHandler<ValueTrackingEditorCommandArgs>
{
private readonly IAsyncServiceProvider _serviceProvider;
private readonly IThreadingContext _threadingContext;
private readonly ClassificationTypeMap _typeMap;
private readonly IClassificationFormatMapService _classificationFormatMapService;
private readonly IGlyphService _glyphService;
private readonly IEditorFormatMapService _formatMapService;
private readonly IGlobalOptionService _globalOptions;
private readonly IUIThreadOperationExecutor _threadOperationExecutor;
private readonly IAsynchronousOperationListener _listener;
private readonly Workspace _workspace;
[ImportingConstructor]
[Obsolete(MefConstruction.ImportingConstructorMessage, error: true)]
public ValueTrackingCommandHandler(
SVsServiceProvider serviceProvider,
IThreadingContext threadingContext,
ClassificationTypeMap typeMap,
IClassificationFormatMapService classificationFormatMapService,
IGlyphService glyphService,
IEditorFormatMapService formatMapService,
IGlobalOptionService globalOptions,
IAsynchronousOperationListenerProvider listenerProvider,
IUIThreadOperationExecutor threadOperationExecutor,
VisualStudioWorkspace workspace)
{
_serviceProvider = (IAsyncServiceProvider)serviceProvider;
_threadingContext = threadingContext;
_typeMap = typeMap;
_classificationFormatMapService = classificationFormatMapService;
_glyphService = glyphService;
_formatMapService = formatMapService;
_globalOptions = globalOptions;
_threadOperationExecutor = threadOperationExecutor;
_listener = listenerProvider.GetListener(FeatureAttribute.ValueTracking);
_workspace = workspace;
}
public string DisplayName => "Go to value tracking";
public CommandState GetCommandState(ValueTrackingEditorCommandArgs args)
=> CommandState.Available;
public bool ExecuteCommand(ValueTrackingEditorCommandArgs args, CommandExecutionContext executionContext)
{
using var logger = Logger.LogBlock(FunctionId.ValueTracking_Command, CancellationToken.None, LogLevel.Information);
var cancellationToken = executionContext.OperationContext.UserCancellationToken;
var caretPosition = args.TextView.GetCaretPoint(args.SubjectBuffer);
if (!caretPosition.HasValue)
{
return false;
}
var textSpan = new TextSpan(caretPosition.Value.Position, 0);
var sourceTextContainer = args.SubjectBuffer.AsTextContainer();
var document = sourceTextContainer.GetOpenDocumentInCurrentContext();
if (document is null)
{
return false;
}
_threadingContext.JoinableTaskFactory.RunAsync(async () =>
{
var service = document.Project.Solution.Services.GetRequiredService<IValueTrackingService>();
var items = await service.TrackValueSourceAsync(textSpan, document, cancellationToken).ConfigureAwait(false);
if (items.Length == 0)
{
return;
}
await ShowToolWindowAsync(args.TextView, document, items, cancellationToken).ConfigureAwait(false);
});
return true;
}
private async Task ShowToolWindowAsync(ITextView textView, Document document, ImmutableArray<ValueTrackedItem> items, CancellationToken cancellationToken)
{
var toolWindow = await GetOrCreateToolWindowAsync(textView, cancellationToken).ConfigureAwait(false);
if (toolWindow?.ViewModel is null)
{
return;
}
var classificationFormatMap = _classificationFormatMapService.GetClassificationFormatMap(textView);
var solution = document.Project.Solution;
var valueTrackingService = solution.Services.GetRequiredService<IValueTrackingService>();
var rootItemMap = items.GroupBy(i => i.Parent, resultSelector: (key, items) => (parent: key, children: items));
using var _ = CodeAnalysis.PooledObjects.ArrayBuilder<TreeItemViewModel>.GetInstance(out var rootItems);
foreach (var (parent, children) in rootItemMap)
{
if (parent is null)
{
foreach (var child in children)
{
var root = await ValueTrackedTreeItemViewModel.CreateAsync(
solution, child, children: ImmutableArray<TreeItemViewModel>.Empty, toolWindow.ViewModel, _glyphService, valueTrackingService, _globalOptions, _threadingContext, _listener, _threadOperationExecutor, cancellationToken).ConfigureAwait(false);
rootItems.Add(root);
}
}
else
{
using var _1 = CodeAnalysis.PooledObjects.ArrayBuilder<TreeItemViewModel>.GetInstance(out var childItems);
foreach (var child in children)
{
var childViewModel = await ValueTrackedTreeItemViewModel.CreateAsync(
solution, child, children: ImmutableArray<TreeItemViewModel>.Empty, toolWindow.ViewModel, _glyphService, valueTrackingService, _globalOptions, _threadingContext, _listener, _threadOperationExecutor, cancellationToken).ConfigureAwait(false);
childItems.Add(childViewModel);
}
var root = await ValueTrackedTreeItemViewModel.CreateAsync(
solution, parent, childItems.ToImmutable(), toolWindow.ViewModel, _glyphService, valueTrackingService, _globalOptions, _threadingContext, _listener, _threadOperationExecutor, cancellationToken).ConfigureAwait(false);
rootItems.Add(root);
}
}
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
toolWindow.ViewModel.Roots.Clear();
foreach (var root in rootItems)
{
toolWindow.ViewModel.Roots.Add(root);
}
await ShowToolWindowAsync(cancellationToken).ConfigureAwait(true);
}
private async Task ShowToolWindowAsync(CancellationToken cancellationToken)
{
var roslynPackage = await RoslynPackage.GetOrLoadAsync(_threadingContext, _serviceProvider, cancellationToken).ConfigureAwait(false);
Contract.ThrowIfNull(roslynPackage);
await roslynPackage.ShowToolWindowAsync(
typeof(ValueTrackingToolWindow),
0,
true,
roslynPackage.DisposalToken).ConfigureAwait(false);
}
private async Task<ValueTrackingToolWindow?> GetOrCreateToolWindowAsync(ITextView textView, CancellationToken cancellationToken)
{
var roslynPackage = await RoslynPackage.GetOrLoadAsync(_threadingContext, _serviceProvider, cancellationToken).ConfigureAwait(false);
if (roslynPackage is null)
{
return null;
}
var window = (ValueTrackingToolWindow)await roslynPackage.FindWindowPaneAsync(
typeof(ValueTrackingToolWindow),
0,
create: true,
roslynPackage.DisposalToken).ConfigureAwait(false);
await _threadingContext.JoinableTaskFactory.SwitchToMainThreadAsync(cancellationToken);
if (!window.Initialized)
{
var viewModel = new ValueTrackingTreeViewModel(_classificationFormatMapService.GetClassificationFormatMap(textView), _typeMap, _formatMapService);
window.Initialize(viewModel, _workspace, _threadingContext);
}
return window;
}
}
|