/// <summary> /// Builds a sub tree under root: target framework or Dependencies node when there is only one target. /// </summary> private async Task <IProjectTree> BuildSubTreeAsync( IProjectTree rootNode, TargetedDependenciesSnapshot targetedSnapshot, List <IDependency> dependencies, bool isActiveTarget, bool shouldCleanup) { HashSet <IProjectTree>?currentNodes = shouldCleanup ? new HashSet <IProjectTree>(capacity: dependencies.Count) : null; foreach (IDependency dependency in dependencies) { IProjectTree?dependencyNode = rootNode.FindChildWithCaption(dependency.Caption); bool isNewDependencyNode = dependencyNode == null; if (dependencyNode != null && dependency.Flags.Contains(DependencyTreeFlags.SupportsHierarchy)) { if ((dependency.Resolved && dependencyNode.Flags.Contains(DependencyTreeFlags.Unresolved)) || (!dependency.Resolved && dependencyNode.Flags.Contains(DependencyTreeFlags.Resolved))) { // when transition from unresolved to resolved or vise versa - remove old node // and re-add new one to allow GraphProvider to recalculate children isNewDependencyNode = true; rootNode = dependencyNode.Remove(); dependencyNode = null; } } // NOTE this project system supports multiple implicit configuration dimensions (such as target framework) // which is a concept not modelled by DTE/VSLangProj. In order to produce a sensible view of the project // via automation, we expose only the active target framework at any given time. // // This is achieved by using IProjectItemTree for active target framework items, and IProjectTree for inactive // target frameworks. CPS only creates automation objects for items with "Reference" flag if they implement // IProjectItemTree. See SimpleItemNode.Initialize (in CPS) for details. dependencyNode = await CreateOrUpdateNodeAsync( dependencyNode, dependency, targetedSnapshot, isProjectItem : isActiveTarget); currentNodes?.Add(dependencyNode); IProjectTree?parent = isNewDependencyNode ? rootNode.Add(dependencyNode).Parent : dependencyNode.Parent; Assumes.NotNull(parent); rootNode = parent !; } return(currentNodes != null // shouldCleanup ? CleanupOldNodes(rootNode, currentNodes) : rootNode); }
/// <summary> /// Builds a sub tree under root: target framework or Dependencies node when there is only one target. /// </summary> private async Task <IProjectTree> BuildSubTreeAsync( IProjectTree rootNode, ITargetedDependenciesSnapshot targetedSnapshot, IEnumerable <IDependency> dependencies, IProjectCatalogSnapshot catalogs, bool isActiveTarget, bool shouldCleanup) { var currentNodes = new List <IProjectTree>(); foreach (IDependency dependency in dependencies) { IProjectTree dependencyNode = rootNode.FindNodeByCaption(dependency.Caption); bool isNewDependencyNode = dependencyNode == null; if (!isNewDependencyNode && dependency.Flags.Contains(DependencyTreeFlags.SupportsHierarchy)) { if ((dependency.Resolved && dependencyNode.Flags.Contains(DependencyTreeFlags.UnresolvedFlags)) || (!dependency.Resolved && dependencyNode.Flags.Contains(DependencyTreeFlags.ResolvedFlags))) { // when transition from unresolved to resolved or vise versa - remove old node // and re-add new one to allow GraphProvider to recalculate children isNewDependencyNode = true; rootNode = dependencyNode.Remove(); dependencyNode = null; } } dependencyNode = await CreateOrUpdateNodeAsync(dependencyNode, dependency, targetedSnapshot, catalogs, isActiveTarget); currentNodes.Add(dependencyNode); if (isNewDependencyNode) { rootNode = rootNode.Add(dependencyNode).Parent; } else { rootNode = dependencyNode.Parent; } } if (shouldCleanup) { rootNode = CleanupOldNodes(rootNode, currentNodes); } return(rootNode); }
/// <summary> /// Builds a sub tree under root: target framework or Dependencies node when there is only one target. /// </summary> private async Task <IProjectTree> BuildSubTreeAsync( IProjectTree rootNode, TargetedDependenciesSnapshot targetedSnapshot, List <IDependency> dependencies, bool isActiveTarget, bool shouldCleanup) { HashSet <IProjectTree>?currentNodes = shouldCleanup ? new HashSet <IProjectTree>(capacity: dependencies.Count) : null; foreach (IDependency dependency in dependencies) { IProjectTree?dependencyNode = rootNode.FindChildWithCaption(dependency.Caption); bool isNewDependencyNode = dependencyNode == null; // NOTE this project system supports multiple implicit configuration dimensions (such as target framework) // which is a concept not modelled by DTE/VSLangProj. In order to produce a sensible view of the project // via automation, we expose only the active target framework at any given time. // // This is achieved by using IProjectItemTree for active target framework items, and IProjectTree for inactive // target frameworks. CPS only creates automation objects for items with "Reference" flag if they implement // IProjectItemTree. See SimpleItemNode.Initialize (in CPS) for details. dependencyNode = await CreateOrUpdateNodeAsync( dependencyNode, dependency, targetedSnapshot, isProjectItem : isActiveTarget); currentNodes?.Add(dependencyNode); IProjectTree?parent = isNewDependencyNode ? rootNode.Add(dependencyNode).Parent : dependencyNode.Parent; Assumes.NotNull(parent); rootNode = parent; } return(currentNodes != null // shouldCleanup ? CleanupOldNodes(rootNode, currentNodes) : rootNode); }
/// <summary> /// Builds a sub tree under root: target framework or Dependencies node when there is only one target. /// </summary> private async Task <IProjectTree> BuildSubTreeAsync( IProjectTree rootNode, ITargetedDependenciesSnapshot targetedSnapshot, List <IDependency> dependencies, bool isActiveTarget, bool shouldCleanup) { List <IProjectTree> currentNodes = shouldCleanup ? new List <IProjectTree>(capacity: dependencies.Count) : null; foreach (IDependency dependency in dependencies) { IProjectTree dependencyNode = rootNode.FindChildWithCaption(dependency.Caption); bool isNewDependencyNode = dependencyNode == null; if (!isNewDependencyNode && dependency.Flags.Contains(DependencyTreeFlags.SupportsHierarchy)) { if ((dependency.Resolved && dependencyNode.Flags.Contains(DependencyTreeFlags.UnresolvedFlags)) || (!dependency.Resolved && dependencyNode.Flags.Contains(DependencyTreeFlags.ResolvedFlags))) { // when transition from unresolved to resolved or vise versa - remove old node // and re-add new one to allow GraphProvider to recalculate children isNewDependencyNode = true; rootNode = dependencyNode.Remove(); dependencyNode = null; } } dependencyNode = await CreateOrUpdateNodeAsync(dependencyNode, dependency, targetedSnapshot, isActiveTarget); currentNodes?.Add(dependencyNode); rootNode = isNewDependencyNode ? rootNode.Add(dependencyNode).Parent : dependencyNode.Parent; } return(shouldCleanup ? CleanupOldNodes(rootNode, currentNodes) : rootNode); }
/// <summary> /// Adds a givel list of elements to IProjectTree /// </summary> /// <param name="tree"></param> /// <param name="itemsToAdd"></param> /// <param name="itemFilter"></param> /// <param name="nodeCreator"></param> /// <returns></returns> public static IProjectTree AddElementsToTree(this IProjectTree tree, IEnumerable <string> itemsToAdd, Func <string, bool> itemFilter, Func <string, IProjectTree> nodeCreator) { foreach (string item in itemsToAdd) { if (itemFilter(item)) { // Double check we don't already have this one - we shouldn't var existingNode = tree.FindImmediateChildByPath(item); System.Diagnostics.Debug.Assert(existingNode == null); if (existingNode == null) { tree = tree.Add(nodeCreator(item)).Parent; } } } return(tree); }
/// <summary> /// Creates or updates a project tree for a given IProjectDependenciesSubTreeProvider /// </summary> /// <param name="dependenciesNode"></param> /// <param name="subTreeProvider"></param> /// <param name="changes"></param> /// <param name="catalogs">Can be null if sub tree provider does not use design time build</param> /// <param name="cancellationToken"></param> /// <returns>IProjectTree for root Dependencies node</returns> private IProjectTree CreateOrUpdateSubTreeProviderNode(IProjectTree dependenciesNode, IProjectDependenciesSubTreeProvider subTreeProvider, IDependenciesChangeDiff changes, IProjectCatalogSnapshot catalogs, CancellationToken cancellationToken) { Requires.NotNull(dependenciesNode, nameof(dependenciesNode)); Requires.NotNull(subTreeProvider, nameof(subTreeProvider)); Requires.NotNull(subTreeProvider.RootNode, nameof(subTreeProvider.RootNode)); Requires.NotNullOrEmpty(subTreeProvider.RootNode.Caption, nameof(subTreeProvider.RootNode.Caption)); Requires.NotNullOrEmpty(subTreeProvider.ProviderType, nameof(subTreeProvider.ProviderType)); var projectFolder = Path.GetDirectoryName(UnconfiguredProject.FullPath); var providerRootTreeNode = GetSubTreeRootNode(dependenciesNode, subTreeProvider.RootNode.Flags); if (subTreeProvider.RootNode.HasChildren || subTreeProvider.ShouldBeVisibleWhenEmpty) { bool newNode = false; if (providerRootTreeNode == null) { providerRootTreeNode = NewTree( caption: subTreeProvider.RootNode.Caption, visible: true, filePath: subTreeProvider.RootNode.Id.ToString(), browseObjectProperties: null, flags: subTreeProvider.RootNode.Flags, icon: subTreeProvider.RootNode.Icon.ToProjectSystemType(), expandedIcon: subTreeProvider.RootNode.ExpandedIcon.ToProjectSystemType()); newNode = true; } if (changes != null) { foreach (var removedItem in changes.RemovedNodes) { if (cancellationToken.IsCancellationRequested) { return(dependenciesNode); } var treeNode = FindProjectTreeNode(providerRootTreeNode, removedItem, projectFolder); if (treeNode != null) { providerRootTreeNode = treeNode.Remove(); } } foreach (var updatedItem in changes.UpdatedNodes) { if (cancellationToken.IsCancellationRequested) { return(dependenciesNode); } var treeNode = FindProjectTreeNode(providerRootTreeNode, updatedItem, projectFolder); if (treeNode != null) { var updatedNodeParentContext = GetCustomPropertyContext(treeNode.Parent); var updatedValues = new ReferencesProjectTreeCustomizablePropertyValues { Caption = updatedItem.Caption, Flags = updatedItem.Flags, Icon = updatedItem.Icon.ToProjectSystemType(), ExpandedIcon = updatedItem.ExpandedIcon.ToProjectSystemType() }; ApplyProjectTreePropertiesCustomization(updatedNodeParentContext, updatedValues); // update existing tree node properties treeNode = treeNode.SetProperties( caption: updatedItem.Caption, flags: updatedItem.Flags, icon: updatedItem.Icon.ToProjectSystemType(), expandedIcon: updatedItem.ExpandedIcon.ToProjectSystemType()); providerRootTreeNode = treeNode.Parent; } } foreach (var addedItem in changes.AddedNodes) { if (cancellationToken.IsCancellationRequested) { return(dependenciesNode); } var treeNode = FindProjectTreeNode(providerRootTreeNode, addedItem, projectFolder); if (treeNode == null) { treeNode = CreateProjectItemTreeNode(providerRootTreeNode, addedItem, catalogs); providerRootTreeNode = providerRootTreeNode.Add(treeNode).Parent; } } } if (newNode) { dependenciesNode = dependenciesNode.Add(providerRootTreeNode).Parent; } else { dependenciesNode = providerRootTreeNode.Parent; } } else { if (providerRootTreeNode != null) { dependenciesNode = dependenciesNode.Remove(providerRootTreeNode); } } return(dependenciesNode); }
public async Task <IProjectTree> BuildTreeAsync( IProjectTree dependenciesTree, DependenciesSnapshot snapshot, CancellationToken cancellationToken = default) { // Keep a reference to the original tree to return in case we are cancelled. IProjectTree originalTree = dependenciesTree; bool hasSingleTarget = snapshot.DependenciesByTargetFramework.Count(x => !x.Key.Equals(TargetFramework.Any)) == 1; var currentTopLevelNodes = new HashSet <IProjectTree>(); if (hasSingleTarget) { await BuildSingleTargetTreeAsync(); } else { await BuildMultiTargetTreeAsync(); } if (cancellationToken.IsCancellationRequested) { return(originalTree); } dependenciesTree = CleanupOldNodes(dependenciesTree, currentTopLevelNodes); ProjectImageMoniker rootIcon = _viewModelFactory.GetDependenciesRootIcon(snapshot.HasReachableVisibleUnresolvedDependency).ToProjectSystemType(); return(dependenciesTree.SetProperties(icon: rootIcon, expandedIcon: rootIcon)); async Task BuildSingleTargetTreeAsync() { foreach ((ITargetFramework _, TargetedDependenciesSnapshot targetedSnapshot) in snapshot.DependenciesByTargetFramework) { if (cancellationToken.IsCancellationRequested) { return; } dependenciesTree = await BuildSubTreesAsync( rootNode : dependenciesTree, snapshot.ActiveTargetFramework, targetedSnapshot, RememberNewNodes); } } async Task BuildMultiTargetTreeAsync() { foreach ((ITargetFramework targetFramework, TargetedDependenciesSnapshot targetedSnapshot) in snapshot.DependenciesByTargetFramework) { if (cancellationToken.IsCancellationRequested) { return; } if (targetFramework.Equals(TargetFramework.Any)) { dependenciesTree = await BuildSubTreesAsync( rootNode : dependenciesTree, snapshot.ActiveTargetFramework, targetedSnapshot, RememberNewNodes); } else { IProjectTree? node = dependenciesTree.FindChildWithCaption(targetFramework.FriendlyName); bool shouldAddTargetNode = node == null; IDependencyViewModel targetViewModel = _viewModelFactory.CreateTargetViewModel(targetedSnapshot); node = CreateOrUpdateNode( node, targetViewModel, browseObjectProperties: null, isProjectItem: false, additionalFlags: ProjectTreeFlags.Create(ProjectTreeFlags.Common.BubbleUp)); node = await BuildSubTreesAsync( rootNode : node, snapshot.ActiveTargetFramework, targetedSnapshot, CleanupOldNodes); dependenciesTree = shouldAddTargetNode ? dependenciesTree.Add(node).Parent ! : node.Parent !; Assumes.NotNull(dependenciesTree); currentTopLevelNodes.Add(node); } } } IProjectTree RememberNewNodes(IProjectTree rootNode, IEnumerable <IProjectTree> currentNodes) { if (currentNodes != null) { currentTopLevelNodes.AddRange(currentNodes); } return(rootNode); } }
/// <summary> /// Builds all available sub trees under root: target framework or Dependencies node /// when there is only one target. /// </summary> private async Task <IProjectTree> BuildSubTreesAsync( IProjectTree rootNode, ITargetFramework activeTarget, TargetedDependenciesSnapshot targetedSnapshot, Func <IProjectTree, HashSet <IProjectTree>, IProjectTree> syncFunc) { var groupedByProviderType = new Dictionary <string, List <IDependency> >(StringComparers.DependencyProviderTypes); foreach (IDependency dependency in targetedSnapshot.TopLevelDependencies) { if (!dependency.Visible) { // If a dependency is not visible we will still register a top-level group if it // has the ShowEmptyProviderRootNode flag. if (!dependency.Flags.Contains(DependencyTreeFlags.ShowEmptyProviderRootNode)) { // No such flag, so skip it completely. continue; } } if (!groupedByProviderType.TryGetValue(dependency.ProviderType, out List <IDependency> dependencies)) { dependencies = new List <IDependency>(); groupedByProviderType.Add(dependency.ProviderType, dependencies); } // Only add visible dependencies. See note above. if (dependency.Visible) { dependencies.Add(dependency); } } var currentNodes = new HashSet <IProjectTree>(capacity: groupedByProviderType.Count); bool isActiveTarget = targetedSnapshot.TargetFramework.Equals(activeTarget); foreach ((string providerType, List <IDependency> dependencies) in groupedByProviderType) { IDependencyViewModel?subTreeViewModel = _viewModelFactory.CreateRootViewModel( providerType, targetedSnapshot.CheckForUnresolvedDependencies(providerType)); if (subTreeViewModel == null) { // In theory this should never happen, as it means we have a dependency model of a type // that no provider claims. https://github.com/dotnet/project-system/issues/3653 continue; } IProjectTree?subTreeNode = rootNode.FindChildWithCaption(subTreeViewModel.Caption); bool isNewSubTreeNode = subTreeNode == null; ProjectTreeFlags excludedFlags = targetedSnapshot.TargetFramework.Equals(TargetFramework.Any) ? ProjectTreeFlags.Create(ProjectTreeFlags.Common.BubbleUp) : ProjectTreeFlags.Empty; subTreeNode = CreateOrUpdateNode( subTreeNode, subTreeViewModel, browseObjectProperties: null, isProjectItem: false, excludedFlags: excludedFlags); subTreeNode = await BuildSubTreeAsync( subTreeNode, targetedSnapshot, dependencies, isActiveTarget, shouldCleanup : !isNewSubTreeNode); currentNodes.Add(subTreeNode); rootNode = isNewSubTreeNode ? rootNode.Add(subTreeNode).Parent ! : subTreeNode.Parent !; Assumes.NotNull(rootNode); } return(syncFunc(rootNode, currentNodes)); }
/// <summary> /// Builds Dependencies tree for given dependencies snapshot /// </summary> public override async Task <IProjectTree> BuildTreeAsync( IProjectTree dependenciesTree, IDependenciesSnapshot snapshot, CancellationToken cancellationToken = default) { IProjectTree originalTree = dependenciesTree; var currentTopLevelNodes = new List <IProjectTree>(); IProjectTree RememberNewNodes(IProjectTree rootNode, IEnumerable <IProjectTree> currentNodes) { if (currentNodes != null) { currentTopLevelNodes.AddRange(currentNodes); } return(rootNode); } if (snapshot.Targets.Count(x => !x.Key.Equals(TargetFramework.Any)) == 1) { foreach ((ITargetFramework _, ITargetedDependenciesSnapshot targetedSnapshot) in snapshot.Targets) { if (cancellationToken.IsCancellationRequested) { return(originalTree); } dependenciesTree = await BuildSubTreesAsync( rootNode : dependenciesTree, snapshot.ActiveTarget, targetedSnapshot, RememberNewNodes); } } else { foreach ((ITargetFramework targetFramework, ITargetedDependenciesSnapshot targetedSnapshot) in snapshot.Targets) { if (cancellationToken.IsCancellationRequested) { return(originalTree); } if (targetFramework.Equals(TargetFramework.Any)) { dependenciesTree = await BuildSubTreesAsync( rootNode : dependenciesTree, snapshot.ActiveTarget, targetedSnapshot, RememberNewNodes); } else { IProjectTree node = dependenciesTree.FindChildWithCaption(targetFramework.FriendlyName); bool shouldAddTargetNode = node == null; IDependencyViewModel targetViewModel = ViewModelFactory.CreateTargetViewModel(targetedSnapshot); node = CreateOrUpdateNode( node, targetViewModel, rule: null, isProjectItem: false, additionalFlags: ProjectTreeFlags.Create(ProjectTreeFlags.Common.BubbleUp)); node = await BuildSubTreesAsync( rootNode : node, snapshot.ActiveTarget, targetedSnapshot, CleanupOldNodes); dependenciesTree = shouldAddTargetNode ? dependenciesTree.Add(node).Parent : node.Parent; currentTopLevelNodes.Add(node); } } } dependenciesTree = CleanupOldNodes(dependenciesTree, currentTopLevelNodes); // now update root Dependencies node status ProjectImageMoniker rootIcon = ViewModelFactory.GetDependenciesRootIcon(snapshot.HasUnresolvedDependency).ToProjectSystemType(); return(dependenciesTree.SetProperties(icon: rootIcon, expandedIcon: rootIcon)); }
/// <summary> /// Builds all available sub trees under root: target framework or Dependencies node /// when there is only one target. /// </summary> private async Task <IProjectTree> BuildSubTreesAsync( IProjectTree rootNode, ITargetFramework activeTarget, ITargetedDependenciesSnapshot targetedSnapshot, Func <IProjectTree, IEnumerable <IProjectTree>, IProjectTree> syncFunc) { var groupedByProviderType = new Dictionary <string, List <IDependency> >(StringComparers.DependencyProviderTypes); foreach (IDependency dependency in targetedSnapshot.TopLevelDependencies) { if (!dependency.Visible) { if (dependency.Flags.Contains(DependencyTreeFlags.ShowEmptyProviderRootNode)) { // if provider sends special invisible node with flag ShowEmptyProviderRootNode, we // need to show provider node even if it does not have any dependencies. groupedByProviderType.Add(dependency.ProviderType, new List <IDependency>()); } continue; } if (!groupedByProviderType.TryGetValue(dependency.ProviderType, out List <IDependency> dependencies)) { dependencies = new List <IDependency>(); groupedByProviderType.Add(dependency.ProviderType, dependencies); } dependencies.Add(dependency); } var currentNodes = new List <IProjectTree>(capacity: groupedByProviderType.Count); bool isActiveTarget = targetedSnapshot.TargetFramework.Equals(activeTarget); foreach ((string providerType, List <IDependency> dependencies) in groupedByProviderType) { IDependencyViewModel subTreeViewModel = ViewModelFactory.CreateRootViewModel( providerType, targetedSnapshot.CheckForUnresolvedDependencies(providerType)); IProjectTree subTreeNode = rootNode.FindChildWithCaption(subTreeViewModel.Caption); bool isNewSubTreeNode = subTreeNode == null; ProjectTreeFlags excludedFlags = targetedSnapshot.TargetFramework.Equals(TargetFramework.Any) ? ProjectTreeFlags.Create(ProjectTreeFlags.Common.BubbleUp) : ProjectTreeFlags.Empty; subTreeNode = CreateOrUpdateNode( subTreeNode, subTreeViewModel, rule: null, isProjectItem: false, excludedFlags: excludedFlags); subTreeNode = await BuildSubTreeAsync( subTreeNode, targetedSnapshot, dependencies, isActiveTarget, shouldCleanup : !isNewSubTreeNode); currentNodes.Add(subTreeNode); rootNode = isNewSubTreeNode ? rootNode.Add(subTreeNode).Parent : subTreeNode.Parent; } return(syncFunc(rootNode, currentNodes)); }
/// <summary> /// Creates or updates a project tree for a given IProjectDependenciesSubTreeProvider /// </summary> /// <param name="dependenciesNode"></param> /// <param name="subTreeProvider"></param> /// <param name="changes"></param> /// <param name="catalogs">Can be null if sub tree provider does not use design time build</param> /// <param name="cancellationToken"></param> /// <returns>IProjectTree for root Dependencies node</returns> private IProjectTree CreateOrUpdateSubTreeProviderNode(IProjectTree dependenciesNode, IProjectDependenciesSubTreeProvider subTreeProvider, IDependenciesChangeDiff changes, IProjectCatalogSnapshot catalogs, CancellationToken cancellationToken) { Requires.NotNull(dependenciesNode, nameof(dependenciesNode)); Requires.NotNull(subTreeProvider, nameof(subTreeProvider)); Requires.NotNull(subTreeProvider.RootNode, nameof(subTreeProvider.RootNode)); Requires.NotNullOrEmpty(subTreeProvider.RootNode.Caption, nameof(subTreeProvider.RootNode.Caption)); Requires.NotNullOrEmpty(subTreeProvider.ProviderType, nameof(subTreeProvider.ProviderType)); var providerRootTreeNode = GetSubTreeRootNode(dependenciesNode, subTreeProvider.RootNode.Flags); if (subTreeProvider.RootNode.HasChildren || subTreeProvider.ShouldBeVisibleWhenEmpty) { bool newNode = false; if (providerRootTreeNode == null) { providerRootTreeNode = NewTree( caption: subTreeProvider.RootNode.Caption, visible: true, filePath: subTreeProvider.RootNode.Id.ToString(), browseObjectProperties: null, flags: subTreeProvider.RootNode.Flags, icon: subTreeProvider.RootNode.Icon.ToProjectSystemType(), expandedIcon: subTreeProvider.RootNode.ExpandedIcon.ToProjectSystemType()); newNode = true; } if (changes != null) { foreach (var removedItem in changes.RemovedNodes) { if (cancellationToken.IsCancellationRequested) { return(dependenciesNode); } var treeNode = providerRootTreeNode.FindNodeByPath(removedItem.Id.ToString()); if (treeNode != null) { providerRootTreeNode = treeNode.Remove(); } } foreach (var updatedItem in changes.UpdatedNodes) { if (cancellationToken.IsCancellationRequested) { return(dependenciesNode); } var treeNode = providerRootTreeNode.FindNodeByPath(updatedItem.Id.ToString()); if (treeNode != null) { var updatedNodeParentContext = GetCustomPropertyContext(treeNode.Parent); var updatedValues = new ReferencesProjectTreeCustomizablePropertyValues { Caption = updatedItem.Caption, Flags = updatedItem.Flags, Icon = updatedItem.Icon.ToProjectSystemType(), ExpandedIcon = updatedItem.ExpandedIcon.ToProjectSystemType() }; ApplyProjectTreePropertiesCustomization(updatedNodeParentContext, updatedValues); // update existing tree node properties treeNode = treeNode.SetProperties( caption: updatedItem.Caption, flags: updatedItem.Flags, icon: updatedItem.Icon.ToProjectSystemType(), expandedIcon: updatedItem.ExpandedIcon.ToProjectSystemType()); providerRootTreeNode = treeNode.Parent; } } var configuredProjectExports = GetActiveConfiguredProjectExports(ActiveConfiguredProject); foreach (var addedItem in changes.AddedNodes) { if (cancellationToken.IsCancellationRequested) { return(dependenciesNode); } var treeNode = providerRootTreeNode.FindNodeByPath(addedItem.Id.ToString()); if (treeNode == null) { IRule rule = null; if (addedItem.Properties != null) { // when itemSpec is not in valid absolute path format, property page does not show // item name correctly. var itemSpec = addedItem.Flags.Contains(DependencyNode.CustomItemSpec) ? addedItem.Caption : addedItem.Id.ItemSpec; var itemContext = ProjectPropertiesContext.GetContext(UnconfiguredProject, addedItem.Id.ItemType, itemSpec); if (addedItem.Resolved) { rule = GetRuleForResolvableReference( itemContext, new KeyValuePair <string, IImmutableDictionary <string, string> >( addedItem.Id.ItemSpec, addedItem.Properties), catalogs, configuredProjectExports); } else { rule = GetRuleForUnresolvableReference( itemContext, catalogs, configuredProjectExports); } } // Notify about tree changes to customization context var customTreePropertyContext = GetCustomPropertyContext(providerRootTreeNode); var customTreePropertyValues = new ReferencesProjectTreeCustomizablePropertyValues { Caption = addedItem.Caption, Flags = addedItem.Flags, Icon = addedItem.Icon.ToProjectSystemType() }; ApplyProjectTreePropertiesCustomization(customTreePropertyContext, customTreePropertyValues); treeNode = NewTree(caption: addedItem.Caption, visible: true, filePath: addedItem.Id.ToString(), browseObjectProperties: rule, flags: addedItem.Flags, icon: addedItem.Icon.ToProjectSystemType(), expandedIcon: addedItem.ExpandedIcon.ToProjectSystemType()); providerRootTreeNode = providerRootTreeNode.Add(treeNode).Parent; } } } if (newNode) { dependenciesNode = dependenciesNode.Add(providerRootTreeNode).Parent; } else { dependenciesNode = providerRootTreeNode.Parent; } } else { if (providerRootTreeNode != null) { dependenciesNode = dependenciesNode.Remove(providerRootTreeNode); } } return(dependenciesNode); }
/// <summary> /// Builds Dependencies tree for given dependencies snapshot /// </summary> public override IProjectTree BuildTree( IProjectTree dependenciesTree, IDependenciesSnapshot snapshot, CancellationToken cancellationToken = default(CancellationToken)) { var originalTree = dependenciesTree; var currentTopLevelNodes = new List <IProjectTree>(); Func <IProjectTree, IEnumerable <IProjectTree>, IProjectTree> rememberNewNodes = (rootNode, currentNodes) => { if (currentNodes != null) { currentTopLevelNodes.AddRange(currentNodes); } return(rootNode); }; if (snapshot.Targets.Where(x => !x.Key.Equals(TargetFramework.Any)).Count() == 1) { foreach (var target in snapshot.Targets) { if (cancellationToken.IsCancellationRequested) { return(originalTree); } dependenciesTree = BuildSubTrees( dependenciesTree, snapshot.ActiveTarget, target.Value, target.Value.Catalogs, rememberNewNodes); } } else { foreach (var target in snapshot.Targets) { if (cancellationToken.IsCancellationRequested) { return(originalTree); } if (target.Key.Equals(TargetFramework.Any)) { dependenciesTree = BuildSubTrees(dependenciesTree, snapshot.ActiveTarget, target.Value, target.Value.Catalogs, rememberNewNodes); } else { var node = dependenciesTree.FindNodeByCaption(target.Key.FriendlyName); var shouldAddTargetNode = node == null; var targetViewModel = ViewModelFactory.CreateTargetViewModel(target.Value); node = CreateOrUpdateNode(node, targetViewModel, rule: null, isProjectItem: false, additionalFlags: ProjectTreeFlags.Create(ProjectTreeFlags.Common.BubbleUp)); node = BuildSubTrees(node, snapshot.ActiveTarget, target.Value, target.Value.Catalogs, CleanupOldNodes); if (shouldAddTargetNode) { dependenciesTree = dependenciesTree.Add(node).Parent; } else { dependenciesTree = node.Parent; } currentTopLevelNodes.Add(node); } } } dependenciesTree = CleanupOldNodes(dependenciesTree, currentTopLevelNodes); // now update root Dependencies node status var rootIcon = ViewModelFactory.GetDependenciesRootIcon(snapshot.HasUnresolvedDependency).ToProjectSystemType(); return(dependenciesTree.SetProperties(icon: rootIcon, expandedIcon: rootIcon)); }
/// <summary> /// Builds all available sub trees under root: target framework or Dependencies node /// when there is only one target. /// </summary> private IProjectTree BuildSubTrees( IProjectTree rootNode, ITargetFramework activeTarget, ITargetedDependenciesSnapshot targetedSnapshot, IProjectCatalogSnapshot catalogs, Func <IProjectTree, IEnumerable <IProjectTree>, IProjectTree> syncFunc) { var currentNodes = new List <IProjectTree>(); var grouppedByProviderType = new Dictionary <string, List <IDependency> >(StringComparer.OrdinalIgnoreCase); foreach (var dependency in targetedSnapshot.TopLevelDependencies) { if (!dependency.Visible) { if (dependency.Flags.Contains(DependencyTreeFlags.ShowEmptyProviderRootNode)) { // if provider sends special invisible node with flag ShowEmptyProviderRootNode, we // need to show provider node even if it does not have any dependencies. grouppedByProviderType.Add(dependency.ProviderType, new List <IDependency>()); } continue; } if (!grouppedByProviderType.TryGetValue(dependency.ProviderType, out List <IDependency> dependencies)) { dependencies = new List <IDependency>(); grouppedByProviderType.Add(dependency.ProviderType, dependencies); } dependencies.Add(dependency); } var isActiveTarget = targetedSnapshot.TargetFramework.Equals(activeTarget); foreach (var dependencyGroup in grouppedByProviderType) { var subTreeViewModel = ViewModelFactory.CreateRootViewModel( dependencyGroup.Key, targetedSnapshot.CheckForUnresolvedDependencies(dependencyGroup.Key)); var subTreeNode = rootNode.FindNodeByCaption(subTreeViewModel.Caption); var isNewSubTreeNode = subTreeNode == null; var excludedFlags = ProjectTreeFlags.Empty; if (targetedSnapshot.TargetFramework.Equals(TargetFramework.Any)) { excludedFlags = ProjectTreeFlags.Create(ProjectTreeFlags.Common.BubbleUp); } subTreeNode = CreateOrUpdateNode( subTreeNode, subTreeViewModel, rule: null, isProjectItem: false, excludedFlags: excludedFlags); subTreeNode = BuildSubTree( subTreeNode, targetedSnapshot, dependencyGroup.Value, catalogs, isActiveTarget, shouldCleanup: !isNewSubTreeNode); currentNodes.Add(subTreeNode); if (isNewSubTreeNode) { rootNode = rootNode.Add(subTreeNode).Parent; } else { rootNode = subTreeNode.Parent; } } return(syncFunc(rootNode, currentNodes)); }