File: ValueTracking\ValueTrackingCommandHandler.cs
Web Access
Project: src\src\VisualStudio\Core\Def\Microsoft.VisualStudio.LanguageServices_pxr0p0dn_wpftmp.csproj (Microsoft.VisualStudio.LanguageServices)
// 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;
    }
}