/// <summary> /// Move project elements based on the given project tree, reference project tree and move action. /// Will modify the project if successful, but not save; only dirty. /// </summary> private static bool TryMove(Project project, IProjectTree projectTree, IProjectTree?referenceProjectTree, MoveAction moveAction) { if (!HasValidDisplayOrder(projectTree) || !HasValidDisplayOrder(referenceProjectTree)) { return(false); } if (projectTree == referenceProjectTree) { return(false); } if (referenceProjectTree != null) { // The reference element is the element for which moved items will be above or below it. ProjectItemElement?referenceElement = TryGetReferenceElement(project, referenceProjectTree, ImmutableArray <string> .Empty, moveAction); if (referenceElement != null) { ImmutableArray <ProjectItemElement> elements = GetItemElements(project, projectTree, ImmutableArray <string> .Empty); return(TryMoveElements(elements, referenceElement, moveAction)); } } return(false); }
/// <summary> /// Move project elements based on the given project tree and move action. /// Will modify the project if successful, but not save; only dirty. /// </summary> private static bool TryMove(Project project, IProjectTree projectTree, MoveAction moveAction) { // Determine what sibling we want to look at based on if we are moving up or down. IProjectTree?sibling = GetSiblingByMoveAction(projectTree, moveAction); return(TryMove(project, projectTree, sibling, moveAction)); }
/// <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); }
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); }
/// <summary> /// Gets the display order for a project tree. /// </summary> public static int GetDisplayOrder(IProjectTree?projectTree) { if (projectTree is IProjectTree2 projectTree2) { return(projectTree2.DisplayOrder); } // It's safe to return zero here. Project trees that do not have a display order are always assumed zero. return(0); }
public override IProjectTree?FindByPath(IProjectTree root, string path) { // We are _usually_ passed the project root here, and we know that our tree items are limited to the // "Dependencies" subtree, so scope the search to that node. // // If we are passed a root which is not the project node, we will not find any search results. // This does not appear to be an issue, but may one day be required. IProjectTree?dependenciesRootNode = root.FindChildWithFlags(DependencyTreeFlags.DependenciesRootNode); return(dependenciesRootNode?.GetSelfAndDescendentsDepthFirst().FirstOrDefault((node, p) => StringComparers.Paths.Equals(node.FilePath, p), path)); }
protected override async Task <IProjectTree?> FindFileAsync(IProjectTreeProvider provider, IProjectTree root) { // First look for the actual App.xaml first IProjectTree?node = FindAppXamlFile(root); if (node == null) { // Otherwise, find a candidate that we might be able to add to the project node = await base.FindFileAsync(provider, root); } return(node); }
private string?FindAppDesignerFolder() { IProjectTree?root = _projectTree.Value.CurrentTree; IProjectTree?folder = root?.GetSelfAndDescendentsBreadthFirst().FirstOrDefault(child => child.Flags.HasFlag(ProjectTreeFlags.Common.AppDesignerFolder)); if (folder == null) { return(null); } return(_projectTree.Value.TreeProvider.GetRootedAddNewItemDirectory(folder)); }
protected override Task <CommandStatusResult> GetCommandStatusAsync(IProjectTree node, bool focused, string?commandText, CommandStatus progressiveStatus) { IProjectTree?nodeToAddTo = GetNodeToAddTo(node); if (nodeToAddTo != null && _addItemDialogService.CanAddNewOrExistingItemTo(nodeToAddTo) && CanAdd(node)) { return(GetCommandStatusResult.Handled(commandText, CommandStatus.Enabled)); } else { return(GetCommandStatusResult.Unhandled); } }
protected override async Task <IProjectTree?> FindFileAsync(IProjectTreeProvider provider, IProjectTree root) { // First look for the actual AppDesigner folder IProjectTree?folder = FindAppDesignerFolder(root); if (folder == null) { // Otherwise, find a location that is a candidate folder = await FindAppDesignerFolderCandidateAsync(provider, root); } return(folder); }
private async Task <IProjectTree?> GetParentFolderAsync(bool createIfNotExists) { if (CreatedByDefaultUnderAppDesignerFolder) { IProjectTree?tree = await GetAppDesignerFolderAsync(createIfNotExists); if (tree != null) { return(tree); } } return(_projectTree.CurrentTree); }
public IProjectTree?FindByPath(IProjectTree?root, string path) { if (root == null) { return(null); } IProjectTree?dependenciesNode = root.Flags.Contains(DependencyTreeFlags.DependenciesRootNodeFlags) ? root : root.GetSubTreeNode(DependencyTreeFlags.DependenciesRootNodeFlags); return(dependenciesNode?.GetSelfAndDescendentsBreadthFirst() .FirstOrDefault((node, p) => string.Equals(node.FilePath, p, StringComparisons.Paths), path)); }
// TODO remove suppression once CPS annotation corrected #pragma warning disable CS8613 // Nullability of reference types in return type doesn't match implicitly implemented member. public string?GetExtenderCATID(ExtenderCATIDType extenderCATIDType, IProjectTree?treeNode) #pragma warning restore CS8613 // Nullability of reference types in return type doesn't match implicitly implemented member. { // CPS's implementation of ExtenderCATIDType incorrectly treats the same "instances" as distinct items based // where they are accessed in CPS. It also incorrectly maps "HierarchyExtensionObject" and "HierarchyBrowseObject" // as only applying to the hierarchy, when they take ITEMIDs indicating the node they apply to. // // See https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/extending-the-object-model-of-the-base-project. // // The latter issue we can do nothing about, however, to address the former, map these types to a truer form it makes // it easier on implementors and maintainers of this to understand the objects we're talking about. switch (extenderCATIDType) { case ExtenderCATIDType.HierarchyExtensionObject: // IVsHierarchy.GetProperty(VSITEMID.Root, VSHPROPID_ExtObjectCATID) case ExtenderCATIDType.AutomationProject: // DTE.Project return(GetExtenderCATID(ExtendeeObject.Project)); case ExtenderCATIDType.HierarchyBrowseObject: // IVsHierarchy.GetProperty(VSHPROPID_BrowseObjectCATID) case ExtenderCATIDType.ProjectBrowseObject: // EnvDTE.Project.Properties return(GetExtenderCATID(ExtendeeObject.ProjectBrowseObject)); case ExtenderCATIDType.ConfigurationBrowseObject: // IVsCfgProvider2.GetCfgProviderProperty(VSCFGPROPID_IntrinsicExtenderCATID)/DTE.Configuration return(GetExtenderCATID(ExtendeeObject.Configuration)); case ExtenderCATIDType.HierarchyConfigurationBrowseObject: // IVsHierarchy.GetProperty(VSHPROPID_CfgBrowseObjectCATID) case ExtenderCATIDType.ProjectConfigurationBrowseObject: // EnvDTE.Configuration.Properties return(GetExtenderCATID(ExtendeeObject.ConfigurationBrowseObject)); case ExtenderCATIDType.AutomationProjectItem: // EnvDTE.ProjectItem return(GetExtenderCATID(ExtendeeObject.ProjectItem)); case ExtenderCATIDType.AutomationReference: // VSLangProject.Reference case ExtenderCATIDType.ReferenceBrowseObject: // EnvDTE.ProjectItem.Properties (when reference) return(GetExtenderCATID(ExtendeeObject.ReferenceBrowseObject)); case ExtenderCATIDType.FileBrowseObject: // EnvDTE.ProjectItem.Properties (when file) return(GetExtenderCATID(ExtendeeObject.FileBrowseObject)); case ExtenderCATIDType.AutomationFolderProperties: // FolderProperties case ExtenderCATIDType.FolderBrowseObject: // EnvDTE.ProjectItem.Properties (when folder) return(GetExtenderCATID(ExtendeeObject.FolderBrowseObject)); default: case ExtenderCATIDType.Unknown: // EnvDTE.ProjectItem.Properties (when not file, folder, reference) BCLDebug.Assert(extenderCATIDType == ExtenderCATIDType.Unknown, $"Unrecognized CATID type {extenderCATIDType}"); return(null); } }
/// <summary> /// Efficiently finds a descendent with the given path in the given tree. /// </summary> /// <param name="root">The root of the tree.</param> /// <param name="path">The absolute or project-relative path to the item sought.</param> /// <returns>The item in the tree if found; otherwise <c>null</c>.</returns> public override IProjectTree?FindByPath(IProjectTree root, string path) { // We override this since we need to find children under either: // // - our dependencies root node // - dependency sub tree nodes // - dependency sub tree top level nodes // // Deeper levels will be attached items with additional info, not direct dependencies // specified in the project file. IProjectTree?projectTree = _viewProviders.FirstOrDefault()?.Value.FindByPath(root, path); return(projectTree); }
/// <summary> /// When the task runs, if the receiver picks up that we will be adding an item, it will capture the MSBuild project's includes. /// If any items were added as a result of the task running, the hint receiver will perform the specified action on those items. /// </summary> public async Task Capture(OrderingMoveAction action, IProjectTree target, Func <Task> task) { Requires.NotNull(target, nameof(target)); Requires.NotNull(task, nameof(task)); _action = action; _target = target; await task(); // We need to be sure we are not hinting before we reset, otherwise everything would get reset before HintedAsync gets called. // This is for sanity. if (!_isHinting) { Reset(); } }
protected override async Task <bool> TryHandleCommandAsync(IProjectTree node, bool focused, long commandExecuteOptions, IntPtr variantArgIn, IntPtr variantArgOut) { IProjectTree?nodeToAddTo = GetNodeToAddTo(node); if (nodeToAddTo == null) { return(false); } // We use a hint receiver that listens for when a file gets added. // The reason is so we can modify the MSBuild project inside the same write lock of when a file gets added internally in CPS. // This ensures that we only perform actions on the items that were added as result of a e.g. a add new/existing item dialog. await _orderAddItemHintReceiver.Capture(Action, node, () => OnAddingNodesAsync(nodeToAddTo)); return(true); }
public void Search(IRelationshipSearchParameters parameters, Action <ISearchResult> resultAccumulator) { Requires.NotNull(parameters, nameof(parameters)); Requires.NotNull(resultAccumulator, nameof(resultAccumulator)); if (_providers.Length == 0) { // No providers registered return; } if (!parameters.Options.SearchExternalItems) { // Consider the dependencies tree as containing 'external items', allowing the // tree to be excluded from search results via this option. return; } using var context = new DependenciesTreeSearchContext(parameters, resultAccumulator); _joinableTaskContext.Factory.Run(SearchSolutionAsync); Task SearchSolutionAsync() { // Search projects concurrently return(Task.WhenAll(_projectServiceAccessor.GetProjectService().LoadedUnconfiguredProjects.Select(SearchProjectAsync))); } async Task SearchProjectAsync(UnconfiguredProject unconfiguredProject) { IUnconfiguredProjectVsServices? projectVsServices = unconfiguredProject.Services.ExportProvider.GetExportedValue <IUnconfiguredProjectVsServices>(); IActiveConfigurationGroupService?configurationGroupService = unconfiguredProject.Services.ExportProvider.GetExportedValue <IActiveConfigurationGroupService>(); IProjectTree?dependenciesNode = projectVsServices?.ProjectTree.CurrentTree?.FindChildWithFlags(DependencyTreeFlags.DependenciesRootNode); if (projectVsServices != null && dependenciesNode != null && configurationGroupService is IActiveConfigurationGroupService2 activeConfigurationGroupService) { IConfigurationGroup <ConfiguredProject> configuredProjects = await activeConfigurationGroupService.GetActiveLoadedConfiguredProjectGroupAsync(); var projectContext = new DependenciesTreeProjectSearchContext(context, unconfiguredProject, dependenciesNode, _hierarchyItemManager, projectVsServices, _relationProvider); // Search providers concurrently await Task.WhenAll(_providers.Select(provider => provider.SearchAsync(projectContext))); } } }
public string?GetExtenderCATID(ExtenderCATIDType extenderCATIDType, IProjectTree?treeNode) { // CPS's implementation of ExtenderCATIDType incorrectly treats the same "instances" as distinct items based // where they are accessed in CPS. It also incorrectly maps "HierarchyExtensionObject" and "HierarchyBrowseObject" // as only applying to the hierarchy, when they take ITEMIDs indicating the node they apply to. // // See https://docs.microsoft.com/en-us/visualstudio/extensibility/internals/extending-the-object-model-of-the-base-project. // // The latter issue we can do nothing about, however, to address the former, map these types to a truer form it makes // it easier on implementors and maintainers of this to understand the objects we're talking about. return(extenderCATIDType switch { ExtenderCATIDType.HierarchyExtensionObject or // IVsHierarchy.GetProperty(VSITEMID.Root, VSHPROPID_ExtObjectCATID) ExtenderCATIDType.AutomationProject => // DTE.Project GetExtenderCATID(ExtendeeObject.Project), ExtenderCATIDType.HierarchyBrowseObject or // IVsHierarchy.GetProperty(VSHPROPID_BrowseObjectCATID) ExtenderCATIDType.ProjectBrowseObject => // EnvDTE.Project.Properties GetExtenderCATID(ExtendeeObject.ProjectBrowseObject), ExtenderCATIDType.ConfigurationBrowseObject => // IVsCfgProvider2.GetCfgProviderProperty(VSCFGPROPID_IntrinsicExtenderCATID)/DTE.Configuration GetExtenderCATID(ExtendeeObject.Configuration), ExtenderCATIDType.HierarchyConfigurationBrowseObject or // IVsHierarchy.GetProperty(VSHPROPID_CfgBrowseObjectCATID) ExtenderCATIDType.ProjectConfigurationBrowseObject => // EnvDTE.Configuration.Properties GetExtenderCATID(ExtendeeObject.ConfigurationBrowseObject), ExtenderCATIDType.AutomationProjectItem => // EnvDTE.ProjectItem GetExtenderCATID(ExtendeeObject.ProjectItem), ExtenderCATIDType.AutomationReference or // VSLangProject.Reference ExtenderCATIDType.ReferenceBrowseObject => // EnvDTE.ProjectItem.Properties (when reference) GetExtenderCATID(ExtendeeObject.ReferenceBrowseObject), ExtenderCATIDType.FileBrowseObject => // EnvDTE.ProjectItem.Properties (when file) GetExtenderCATID(ExtendeeObject.FileBrowseObject), ExtenderCATIDType.AutomationFolderProperties or // FolderProperties ExtenderCATIDType.FolderBrowseObject => // EnvDTE.ProjectItem.Properties (when folder) GetExtenderCATID(ExtendeeObject.FolderBrowseObject), ExtenderCATIDType.Unknown or _ => // EnvDTE.ProjectItem.Properties (when not file, folder, reference) UnknownOrDefault() });
/// <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); }
protected override async Task <IProjectTree?> FindFileAsync(IProjectTreeProvider provider, IProjectTree root) { // Search AppDesigner folder first if it exists IProjectTree?appDesignerFolder = await GetAppDesignerFolderAsync(provider, root); if (appDesignerFolder != null) { IProjectTree?node = await base.FindFileAsync(provider, appDesignerFolder); if (node != null) { return(node); } } // Then fallback to project root return(await base.FindFileAsync(provider, root)); }
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 (dependencyNode != null && 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(currentNodes != null // shouldCleanup ? CleanupOldNodes(rootNode, currentNodes) : rootNode); }
private async Task <string?> FindFileAsync(IProjectTreeProvider provider, IProjectTree root, SpecialFileFlags flags) { IProjectTree?node = await FindFileAsync(provider, root); if (node == null) { return(null); } string?path = GetFilePath(provider, node); if (path != null && flags.HasFlag(SpecialFileFlags.CreateIfNotExist)) { // Similar to legacy, we only verify state if we've been asked to create it await VerifyStateAsync(node, path); } return(path); }
private async Task <string?> GetDefaultAppDesignerFolderPathAsync() { IProjectTree?currentTree = _projectTree.Value.CurrentTree; if (currentTree == null) { return(null); } string?rootPath = _projectTree.Value.TreeProvider.GetRootedAddNewItemDirectory(currentTree); string folderName = await GetDefaultAppDesignerFolderNameAsync(); if (string.IsNullOrEmpty(folderName)) { return(null); // Developer has set the AppDesigner path to empty } return(Path.Combine(rootPath, folderName)); }
private async Task <IProjectTree> CreateOrUpdateNodeAsync( IProjectTree?node, IDependency dependency, TargetedDependenciesSnapshot targetedSnapshot, bool isProjectItem, ProjectTreeFlags?additionalFlags = null, ProjectTreeFlags?excludedFlags = null) { IRule?browseObjectProperties = dependency.Flags.Contains(DependencyTreeFlags.SupportsRuleProperties) ? await _treeServices.GetBrowseObjectRuleAsync(dependency, targetedSnapshot.Catalogs) : null; return(CreateOrUpdateNode( node, dependency.ToViewModel(targetedSnapshot), browseObjectProperties, isProjectItem, additionalFlags, excludedFlags)); }
public static IProjectTreeService Create(IProjectTree?tree = null, IProjectTreeProvider?treeProvider = null) { var mock = new Mock <IProjectTreeService>(); var treeState = IProjectTreeServiceStateFactory.ImplementTree(() => tree, () => treeProvider ?? IProjectTreeProviderFactory.Create()); mock.Setup(s => s.PublishAnyNonLoadingTreeAsync(It.IsAny <CancellationToken>())) .ReturnsAsync(treeState); mock.Setup(s => s.PublishAnyNonNullTreeAsync(It.IsAny <CancellationToken>())) .ReturnsAsync(treeState); mock.Setup(s => s.PublishLatestTreeAsync(It.IsAny <bool>(), It.IsAny <bool>(), It.IsAny <CancellationToken>())) .ReturnsAsync(treeState); mock.SetupGet(s => s.CurrentTree) .Returns(treeState); return(mock.Object); }
public async Task <IDependenciesTreeConfiguredProjectSearchContext?> ForConfiguredProjectAsync(ConfiguredProject configuredProject, CancellationToken cancellationToken = default) { Requires.NotNull(configuredProject, nameof(configuredProject)); IProjectTree targetRootNode; if (_dependenciesNode.FindChildWithFlags(DependencyTreeFlags.TargetNode) == null) { // Tree does not show any target nodes targetRootNode = _dependenciesNode; } else { if (configuredProject.Services.ProjectSubscription == null) { return(null); } IProjectSubscriptionUpdate subscriptionUpdate = (await configuredProject.Services.ProjectSubscription.ProjectRuleSource.GetLatestVersionAsync(configuredProject, cancellationToken: cancellationToken)).Value; if (!subscriptionUpdate.CurrentState.TryGetValue(ConfigurationGeneral.SchemaName, out IProjectRuleSnapshot configurationGeneralSnapshot) || !configurationGeneralSnapshot.Properties.TryGetValue(ConfigurationGeneral.TargetFrameworkProperty, out string tf)) { return(null); } IProjectTree?targetNode = _dependenciesNode.FindChildWithFlags(ProjectTreeFlags.Create("$TFM:" + tf)); if (targetNode == null) { TraceUtilities.TraceError("Should not fail to find the target node."); return(null); } targetRootNode = targetNode; } return(new DependenciesTreeConfiguredProjectSearchContext(_inner, targetRootNode, _hierarchyItemManager, _projectVsServices, _relationProvider)); }
/// <summary> /// Gets a sibling based on the given project tree. Can return null. /// </summary> /// <param name="projectTree">the given project tree</param> /// <param name="returnSibling">passes the index of the given project tree from the given ordered sequence, expecting to return a sibling</param> /// <returns>a sibling</returns> private static IProjectTree2?GetSiblingByDisplayOrder(IProjectTree projectTree, Func <int, ImmutableArray <IProjectTree>, IProjectTree2?> returnSibling) { IProjectTree?parent = projectTree.Parent; int displayOrder = GetDisplayOrder(projectTree); if (!IsValidDisplayOrder(displayOrder) || parent == null) { return(null); } ImmutableArray <IProjectTree> orderedChildren = GetChildren(parent); for (int i = 0; i < orderedChildren.Length; ++i) { IProjectTree sibling = orderedChildren[i]; if (GetDisplayOrder(sibling) == displayOrder) { return(returnSibling(i, orderedChildren)); } } return(null); }
/// <summary> /// We follow this algorithm for looking up files: /// /// if (not asked to create) /// Look in AppDesigner folder /// Look in root folder /// /// if (asked to create) /// Look in AppDesigner folder /// Look in root folder /// Force-create in app-designer folder unless that file is not created there by default. /// In that case create under the root node. /// </summary> public async Task <string?> GetFileAsync(SpecialFiles fileId, SpecialFileFlags flags, CancellationToken cancellationToken = default) { // Search for the file in the app designer and root folders. IProjectTree?specialFileNode = await FindFileAsync(Name); if (specialFileNode != null) { if (await IsNodeInSyncWithDiskAsync(specialFileNode, forceSync: flags.HasFlag(SpecialFileFlags.CreateIfNotExist), cancellationToken)) { return(specialFileNode.FilePath); } } // File doesn't exist. Create it if we've been asked to. if (flags.HasFlag(SpecialFileFlags.CreateIfNotExist)) { string createdFilePath = await CreateFileAsync(Name); if (createdFilePath != null) { return(createdFilePath); } } // We haven't found the file but return the default file path as that's the contract. IProjectTree?rootNode = _projectTree.CurrentTree; if (rootNode == null) { return(null); } string rootFilePath = _projectTree.TreeProvider.GetPath(rootNode); string fullPath = Path.Combine(Path.GetDirectoryName(rootFilePath), Name); return(fullPath); }
/// <summary> /// Move the respective item elements to the top of the target's children. /// </summary> public static bool TryMoveElementsToTop(Project project, ImmutableArray <ProjectItemElement> elements, IProjectTree target) { Requires.NotNull(project, nameof(project)); Requires.NotNull(target, nameof(target)); IProjectTree?newTarget = target; // This is to handle adding files to empty folders since empty folders do not have a valid display order yet. // We need to find a target up the tree that has a valid display order, because it most likely will have our reference element that we want. while (!HasValidDisplayOrder(newTarget) && !newTarget !.Flags.Contains(ProjectTreeFlags.ProjectRoot)) { newTarget = newTarget.Parent; } var excludeIncludes = elements.Select(x => x.Include).ToImmutableArray(); ProjectItemElement?referenceElement = GetChildren(newTarget !).Select(x => TryGetReferenceElement(project, x, excludeIncludes, MoveAction.Above)).FirstOrDefault(x => x != null); if (referenceElement == null) { return(false); } return(TryMoveElements(elements, referenceElement, MoveAction.Above)); }
public bool CanCopy(IImmutableSet <IProjectTree> nodes, IProjectTree?receiver, bool deleteOriginal = false) { throw new NotImplementedException(); }