File: SolutionExplorer\SymbolTree\SymbolTreeItem.cs
Web Access
Project: src\src\VisualStudio\Core\Impl\Microsoft.VisualStudio.LanguageServices.Implementation.csproj (Microsoft.VisualStudio.LanguageServices.Implementation)
// 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.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Editor.Shared.Extensions;
using Microsoft.CodeAnalysis.Editor.Wpf;
using Microsoft.CodeAnalysis.SolutionExplorer;
using Microsoft.Internal.VisualStudio.PlatformUI;
using Microsoft.VisualStudio.Imaging.Interop;
using Microsoft.VisualStudio.Shell;
using Roslyn.Utilities;
 
namespace Microsoft.VisualStudio.LanguageServices.Implementation.SolutionExplorer;
 
/// <summary>
/// Actual in-memory object that will be presented in the solution explorer tree.  Note:
/// we attempt to reuse instances of these to maintain visual persistence (like selection state)
/// when items are recomputed.
/// </summary>
internal sealed class SymbolTreeItem : BaseItem,
    IInvocationController,
    IAttachedCollectionSource,
    ISupportExpansionEvents,
    INotifyPropertyChanged
{
    public readonly RootSymbolTreeItemSourceProvider RootProvider;
    public readonly ISolutionExplorerSymbolTreeItemProvider ItemProvider;
    public readonly SymbolTreeItemKey ItemKey;
 
    private readonly SymbolTreeChildCollection _childCollection;
 
    private bool _expanded;
    private SymbolTreeItemSyntax _itemSyntax;
 
    public SymbolTreeItem(
        RootSymbolTreeItemSourceProvider rootProvider,
        ISolutionExplorerSymbolTreeItemProvider itemProvider,
        SymbolTreeItemKey itemKey) : base(itemKey.Name, canPreview: true)
    {
        RootProvider = rootProvider;
        ItemProvider = itemProvider;
        ItemKey = itemKey;
        _childCollection = new(rootProvider, this, hasItemsDefault: ItemKey.HasItems);
    }
 
    private void ThrowIfNotOnUIThread()
        => this.RootProvider.ThreadingContext.ThrowIfNotOnUIThread();
 
    public SymbolTreeItemSyntax ItemSyntax
    {
        get => _itemSyntax;
        set
        {
            ThrowIfNotOnUIThread();
 
            // When the syntax node for this item is changed, we want to recompute the children for it
            // (if this  node is expanded). Otherwise, we can just throw away what we have and recompute
            // the next time when asked.
            _itemSyntax = value;
            UpdateChildren();
        }
    }
 
    public override ImageMoniker IconMoniker => this.ItemKey.Glyph.GetImageMoniker();
 
    // We act as our own invocation controller.
    public override IInvocationController? InvocationController => this;
 
    public bool Invoke(IEnumerable<object> items, InputSource inputSource, bool preview)
    {
        if (items.FirstOrDefault() is not SymbolTreeItem item)
            return false;
 
        RootProvider.NavigationSupport.NavigateTo(
            item.ItemKey.DocumentId, item.ItemSyntax.NavigationToken.SpanStart, preview);
        return true;
    }
 
    public override IContextMenuController? ContextMenuController
        => RootProvider.ContextMenuController;
 
    public void BeforeExpand()
    {
        ThrowIfNotOnUIThread();
        _expanded = true;
        UpdateChildren();
    }
 
    public void AfterCollapse()
    {
        ThrowIfNotOnUIThread();
        _expanded = false;
        UpdateChildren();
    }
 
    private void UpdateChildren()
    {
        ThrowIfNotOnUIThread();
 
        if (_expanded)
        {
            // When we update the item syntax we can reset ourselves to the initial state (if collapsed).
            // Then when we're expanded the next time, we'll recompute the child items properly.  If we 
            // are already expanded, then recompute our children which will recursively push the change
            // down further.
            var items = this.ItemProvider.GetItems(
                this.ItemKey.DocumentId, _itemSyntax.DeclarationNode, this.RootProvider.ThreadingContext.DisposalToken);
            _childCollection.SetItemsAndMarkComputed_OnMainThread(this.ItemProvider, items);
        }
        else
        {
            // Otherwise, return the child collection to the uninitialized state.
            _childCollection.ResetToUncomputedState(this.ItemKey.HasItems);
        }
    }
 
    object IAttachedCollectionSource.SourceItem => _childCollection.SourceItem;
 
    bool IAttachedCollectionSource.HasItems => _childCollection.HasItems;
 
    IEnumerable IAttachedCollectionSource.Items => _childCollection.Items;
 
    event PropertyChangedEventHandler INotifyPropertyChanged.PropertyChanged
    {
        add => _childCollection.PropertyChanged += value;
        remove => _childCollection.PropertyChanged -= value;
    }
}