private AggregateContainsRelationCollection(IRelatableItem item, ImmutableArray <IRelation> containsRelations) { _item = item; _spans = containsRelations .Select(relation => new AggregateContainsRelationCollectionSpan(this, relation)) .ToArray(); }
void IRelation.UpdateContainsCollection(IRelatableItem parent, AggregateContainsRelationCollectionSpan span) { if (parent is TParent typedParent) { UpdateContainsCollection(typedParent, span); } }
protected override bool TryGetProjectNode(IProjectTree targetRootNode, IRelatableItem item, [NotNullWhen(returnValue: true)] out IProjectTree?projectTree) { IProjectTree?typeGroupNode = targetRootNode.FindChildWithFlags(DependencyTreeFlags.ProjectDependencyGroup); projectTree = typeGroupNode?.FindChildWithFlags(ProjectTreeFlags.Create("$ID:" + Library.Name)); return(projectTree != null); }
IEnumerable <IRelatableItem>?IRelation.CreateContainedByItems(IRelatableItem child) { if (child is TChild typedChild) { return(CreateContainedByItems(typedChild)); } return(null); }
/// <summary> /// Attempts to create a collection for the children of <paramref name="parentItem"/>. /// Fails only when no relations exist to produce child items for the given item's type. /// </summary> public static bool TryCreate(IRelatableItem parentItem, IRelationProvider relationProvider, [NotNullWhen(returnValue: true)] out AggregateContainsRelationCollection?collection) { ImmutableArray <IRelation> containsRelations = relationProvider.GetContainsRelationsFor(parentItem.GetType()); if (containsRelations.IsEmpty) { collection = null; return(false); } collection = new AggregateContainsRelationCollection(parentItem, containsRelations); return(true); }
bool IRelation.HasContainedItem(IRelatableItem parent) => HasContainedItems((TParent)parent);
protected virtual bool TryGetProjectNode(IProjectTree targetRootNode, IRelatableItem item, [NotNullWhen(returnValue: true)] out IProjectTree?projectTree) { projectTree = null; return(false); }
bool IRelatableItem.TryGetProjectNode(IProjectTree targetRootNode, IRelatableItem item, [NotNullWhen(returnValue: true)] out IProjectTree?projectTree) { return(TryGetProjectNode(targetRootNode, item, out projectTree)); }
/// <summary> /// Updates the items contained within this span, which ultimately contributes to the /// parent <see cref="AggregateContainsRelationCollection"/>. /// </summary> /// <remarks> /// <para> /// This method runs on the UI thread, recursively across all materialized collections in the tree. /// In order to provide linear time complexity, <paramref name="sources"/> must have a stable sorted order across invocations /// of this method. Failing this requirement will result in increased numbers of updates to items, degrading performance. /// </para> /// <para> /// This method operates over the ordered sequence of <typeparamref name="TData"/> values in <paramref name="sources"/>, /// comparing them each in turn (via <paramref name="comparer"/>) to any existing <typeparamref name="TItem"/> items /// in the span. This comparison only considers the 'identity' of its operands, not their state. The comparison determines /// what happens for that data/item pair: /// <list type="bullet"> /// <item> /// <paramref name="comparer"/> returns zero -- the source value and existing item match. <paramref name="update"/> is /// called with both, allowing the data value to update the item in-place. If <paramref name="update"/> returns /// <see langword="true"/> then any materialized descendents of the item are updated recursively via their relations. /// </item> /// <item> /// <paramref name="comparer"/> returns negative -- the source value does not exist in the current collection and should /// be added. <paramref name="factory"/> is called to produce the new item to insert. /// </item> /// <item> /// <paramref name="comparer"/> returns positive -- the source no longer contains the item, and it should be removed. /// </item> /// </list> /// This method ensures the appropriate <see cref="INotifyCollectionChanged"/> events are triggered on the parent /// <see cref="AggregateContainsRelationCollection"/> in response to these updates. /// </para> /// </remarks> /// <param name="sources"> /// The sequence of data values that the resulting items in this span will reflect when this method completes. The sequence must /// be ordered in a way that <paramref name="comparer"/>. /// </param> /// <param name="comparer"> /// Compares a source value with an existing item to determine whether they have equivalent identity. /// Does not consider the state within either type while producing its result. /// </param> /// <param name="update"> /// Updates a tree item based on a data value. Returns <see langword="true"/> is the update mutated the tree item in /// some way, otherwise <see langword="false"/>.</param> /// <param name="factory"> /// Creates a new item for a given data value. /// </param> public void UpdateContainsItems <TData, TItem>( IEnumerable <TData> sources, Func <TData, TItem, int> comparer, Func <TData, TItem, bool> update, Func <TData, TItem> factory) where TItem : class, IRelatableItem { using IEnumerator <TData> src = sources.GetEnumerator(); bool?srcConsumed = null; for (int itemIndex = 0; Items != null && itemIndex < Items.Count; itemIndex++) { if (srcConsumed != false && !src.MoveNext()) { // Source stream ended, all remaining items are invalid. Remove each in turn. // Note we do not reset as that reset would refresh the entire parent collection, not just this span of items. // We remove items in reverse order to reduce shuffling items in collections. for (int removeAtIndex = Items.Count - 1; removeAtIndex >= itemIndex; removeAtIndex--) { IRelatableItem removedItem = Items[removeAtIndex]; Items.RemoveAt(removeAtIndex); _parent.RaiseChange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, removedItem, BaseIndex + removeAtIndex)); } srcConsumed = true; break; } TData source = src.Current; var item = (TItem)Items[itemIndex]; int comparison = comparer(source, item); if (comparison == 0) { // Items match, update in place if (update(source, item)) { // The update changed state, so notify its contains collection to update any materialized children via its relations item.ContainsCollection?.OnStateUpdated(); } srcConsumed = true; } else if (comparison < 0) { // Source contains a new item to insert TItem newItem = factory(source); Items.Insert(itemIndex, newItem); _parent.RaiseChange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItem, BaseIndex + itemIndex)); srcConsumed = true; } else { // Source is missing this item, remove it Items.RemoveAt(itemIndex); _parent.RaiseChange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item, BaseIndex + itemIndex)); // decrement the index as we've removed the item from this index and need to consider the one which is now at this index itemIndex--; srcConsumed = false; } } while (srcConsumed == false || src.MoveNext()) { // Add extra source items to end of list TData source = src.Current; TItem newItem = factory(source); Items ??= new List <IRelatableItem>(); Items.Add(newItem); _parent.RaiseChange(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, newItem, BaseIndex + Items.Count - 1)); srcConsumed = true; } if (Items?.Count == 0) { Items = null; } }
public void SubmitResult(IRelatableItem?item) { if (item == null) { return; } item = DeduplicateItem(item); PopulateAncestors(item); _inner.SubmitResult(item); void PopulateAncestors(IRelatableItem childItem) { if (childItem.ContainedByCollection != null) { // We've already populated this item's ancestors. It's likely an ancestor of // another search result. This also prevents runaway in case of cycles. return; } ImmutableArray <IRelation> containedByRelations = _relationProvider.GetContainedByRelationsFor(childItem.GetType()); if (containedByRelations.IsEmpty) { // We should never have a scenario where an item type does not have a parent. TraceUtilities.TraceError($"No IRelation exports exist that provide parent (ContainedBy) items for type {childItem.GetType()}."); return; } var allParentItems = new List <object>(); foreach (IRelation relation in containedByRelations) { IEnumerable <IRelatableItem>?relationParentItems = relation.CreateContainedByItems(childItem); if (relationParentItems != null) { foreach (IRelatableItem parentItem in relationParentItems) { IRelatableItem deduplicateItem = DeduplicateItem(parentItem); allParentItems.Add(deduplicateItem); if (deduplicateItem.TryGetProjectNode(_targetRootNode, parentItem, out IProjectTree? projectNode)) { uint itemId = (uint)projectNode.Identity.ToInt32(); IVsHierarchyItem hierarchyItem = _hierarchyItemManager.GetHierarchyItem(_projectVsServices.VsHierarchy, itemId); allParentItems.Add(hierarchyItem); } if (deduplicateItem.ContainedByCollection == null) { PopulateAncestors(deduplicateItem); } } } } childItem.ContainedByCollection = new AggregateContainedByRelationCollection(allParentItems); return; } IRelatableItem DeduplicateItem(IRelatableItem item) { object key = GetItemKey(item); if (_itemByKey.TryGetValue(key, out IRelatableItem existingItem)) { return(existingItem); } _itemByKey.Add(key, item); return(item); } }
private static object GetItemKey(IRelatableItem item) => (item.GetType(), item.Identity);