From 446f1e1c5046a0a95e1915f27b626da05e50700f Mon Sep 17 00:00:00 2001 From: comintern Date: Thu, 17 Jan 2019 23:14:10 -0600 Subject: [PATCH] Handle folder component sync correctly. Closes #4720 --- .../CodeExplorerCustomFolderViewModel.cs | 43 +++++- .../Commands/AddComponentCommand.cs | 4 +- .../CodeExplorer/CodeExplorerFolderTests.cs | 136 ++++++++++++++++++ 3 files changed, 175 insertions(+), 8 deletions(-) diff --git a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerCustomFolderViewModel.cs b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerCustomFolderViewModel.cs index 0b68ad17a2..3cb0dab16f 100644 --- a/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerCustomFolderViewModel.cs +++ b/Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerCustomFolderViewModel.cs @@ -44,7 +44,7 @@ public sealed class CodeExplorerCustomFolderViewModel : CodeExplorerItemViewMode public string FullPath { get; } - public string FolderAttribute => $"@Folder(\"{FullPath.Replace("\"", string.Empty)}\")"; + public string FolderAttribute => $"'@Folder(\"{FullPath.Replace("\"", string.Empty)}\")"; /// /// One-based depth in the folder hierarchy. @@ -76,7 +76,7 @@ protected override void AddNewChildren(List declarations) } } - foreach (var declaration in declarations.GroupBy(item => item.ComponentName)) + foreach (var declaration in declarations.Where(child => child.IsInFolder(FullPath)).GroupBy(item => item.ComponentName)) { var moduleName = declaration.Key; var parent = declarations.SingleOrDefault(item => @@ -91,25 +91,56 @@ protected override void AddNewChildren(List declarations) !ComponentTypes.Contains(item.DeclarationType) && item.ComponentName == moduleName); AddChild(new CodeExplorerComponentViewModel(this, parent, members, _vbe)); + declarations.Remove(parent); } } public override void Synchronize(List updated) { - var declarations = updated.Where(declaration => declaration.IsInFolderOrSubFolder(FullPath)).ToList(); + SynchronizeChildren(updated); + } + protected override void SynchronizeChildren(List updated) + { + var declarations = updated.Where(declaration => declaration.IsInFolderOrSubFolder(FullPath)).ToList(); + if (!declarations.Any()) { Declaration = null; return; } - foreach (var declaration in declarations) + var synchronizing = declarations.ToList(); + + foreach (var subfolder in Children.OfType().ToList()) + { + subfolder.SynchronizeChildren(declarations); + if (subfolder.Declaration is null) + { + RemoveChild(subfolder); + continue; + } + + var synchronized = synchronizing.Where(child => !declarations.Contains(child)).ToList(); + foreach (var declaration in synchronized) + { + updated.Remove(declaration); + } + } + + foreach (var child in Children.OfType().ToList()) { - updated.Remove(declaration); + child.Synchronize(updated); + if (child.Declaration is null) + { + RemoveChild(child); + continue; + } + + updated.Remove(child.Declaration); } - SynchronizeChildren(declarations); + AddNewChildren(updated); } } } diff --git a/Rubberduck.Core/UI/CodeExplorer/Commands/AddComponentCommand.cs b/Rubberduck.Core/UI/CodeExplorer/Commands/AddComponentCommand.cs index e23644abe1..23e5da0736 100644 --- a/Rubberduck.Core/UI/CodeExplorer/Commands/AddComponentCommand.cs +++ b/Rubberduck.Core/UI/CodeExplorer/Commands/AddComponentCommand.cs @@ -79,7 +79,7 @@ protected void AddComponent(CodeExplorerItemViewModel node) ? nodeProject.VBComponents : ComponentsCollectionFromActiveProject()) { - var folderAnnotation = $"'@Folder(\"{GetFolder(node)}\")"; + var folderAnnotation = (node is CodeExplorerCustomFolderViewModel folder) ? folder.FolderAttribute : $"'@Folder(\"{GetFolder(node)}\")"; using (var newComponent = components.Add(ComponentType)) { @@ -110,7 +110,7 @@ protected void AddComponent(CodeExplorerItemViewModel node, string moduleText) ? nodeProject.VBComponents : ComponentsCollectionFromActiveProject()) { - var folderAnnotation = $"'@Folder(\"{GetFolder(node)}\")"; + var folderAnnotation = (node is CodeExplorerCustomFolderViewModel folder) ? folder.FolderAttribute : $"'@Folder(\"{GetFolder(node)}\")"; var fileName = CreateTempTextFile(moduleText); using (var newComponent = components.Import(fileName)) diff --git a/RubberduckTests/CodeExplorer/CodeExplorerFolderTests.cs b/RubberduckTests/CodeExplorer/CodeExplorerFolderTests.cs index 4d3a7cebe9..30feb46057 100644 --- a/RubberduckTests/CodeExplorer/CodeExplorerFolderTests.cs +++ b/RubberduckTests/CodeExplorer/CodeExplorerFolderTests.cs @@ -1,7 +1,14 @@ using NUnit.Framework; using Rubberduck.Navigation.CodeExplorer; +using Rubberduck.Parsing.Annotations; +using Rubberduck.Parsing.Symbols; +using Rubberduck.VBEditor; using Rubberduck.VBEditor.SafeComWrappers; +using Rubberduck.VBEditor.Utility; +using System; using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.InteropServices.ComTypes; namespace RubberduckTests.CodeExplorer { @@ -54,6 +61,123 @@ Sub Foo() } } + [Category("Code Explorer")] + [Test] + public void AddedModuleIsAtCorrectDepth_DefaultNode() + { + const string inputCode = +@"'@Folder(""First.Second.Third"") + +Sub Foo() +Dim d As Boolean +d = True +End Sub"; + + using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, new[] { ComponentType.StandardModule }, new[] { inputCode }) + .SelectFirstProject()) + { + var project = (CodeExplorerProjectViewModel)explorer.ViewModel.SelectedItem; + var folder = project.Children.OfType().First(node => node.Name.Equals(project.Declaration.IdentifierName)); + var declarations = project.State.AllUserDeclarations.ToList(); + declarations.Add(GetNewClassDeclaration(project.Declaration, "Foo")); + + project.Synchronize(declarations); + var added = folder.Children.OfType().Single(); + + Assert.AreEqual(DeclarationType.ClassModule, added.Declaration.DeclarationType); + Assert.AreEqual(project.Declaration.IdentifierName, added.Declaration.CustomFolder); + } + } + + [Category("Code Explorer")] + [Test] + public void AddedModuleIsAtCorrectDepth_RootNode() + { + const string inputCode = +@"'@Folder(""First.Second.Third"") + +Sub Foo() +Dim d As Boolean +d = True +End Sub"; + + using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, new[] { ComponentType.StandardModule }, new[] { inputCode }) + .SelectFirstCustomFolder()) + { + var project = explorer.ViewModel.Projects.OfType().First(); + var folder = (CodeExplorerCustomFolderViewModel)explorer.ViewModel.SelectedItem; + var declarations = project.State.AllUserDeclarations.ToList(); + declarations.Add(GetNewClassDeclaration(project.Declaration, "Foo", "\"First\"")); + + project.Synchronize(declarations); + var added = folder.Children.OfType().Single(); + + Assert.AreEqual(DeclarationType.ClassModule, added.Declaration.DeclarationType); + Assert.AreEqual("\"First\"", added.Declaration.CustomFolder); + } + } + + [Category("Code Explorer")] + [Test] + public void AddedModuleIsAtCorrectDepth_SubNode() + { + const string inputCode = +@"'@Folder(""First.Second.Third"") + +Sub Foo() +Dim d As Boolean +d = True +End Sub"; + + using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, new[] { ComponentType.StandardModule }, new[] { inputCode }) + .SelectFirstCustomFolder()) + { + var project = explorer.ViewModel.Projects.OfType().First(); + var folder = (CodeExplorerCustomFolderViewModel)explorer.ViewModel.SelectedItem; + var subfolder = folder.Children.OfType().Single(); + var declarations = project.State.AllUserDeclarations.ToList(); + declarations.Add(GetNewClassDeclaration(project.Declaration, "Foo", "\"First.Second\"")); + + project.Synchronize(declarations); + var added = subfolder.Children.OfType().Single(); + + Assert.AreEqual(DeclarationType.ClassModule, added.Declaration.DeclarationType); + Assert.AreEqual("\"First.Second\"", added.Declaration.CustomFolder); + } + } + + [Category("Code Explorer")] + [Test] + public void AddedModuleIsAtCorrectDepth_TerminalNode() + { + const string inputCode = +@"'@Folder(""First.Second.Third"") + +Sub Foo() +Dim d As Boolean +d = True +End Sub"; + + using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, new[] { ComponentType.StandardModule }, new[] { inputCode }) + .SelectFirstCustomFolder()) + { + var project = explorer.ViewModel.Projects.OfType().First(); + var folder = (CodeExplorerCustomFolderViewModel)explorer.ViewModel.SelectedItem; + var subfolder = folder.Children.OfType().Single() + .Children.OfType().Single(); + var declarations = project.State.AllUserDeclarations.ToList(); + declarations.Add(GetNewClassDeclaration(project.Declaration, "Foo", "\"First.Second.Third\"")); + + project.Synchronize(declarations); + + var added = subfolder.Children.OfType() + .SingleOrDefault(node => node.Declaration.DeclarationType == DeclarationType.ClassModule); + + Assert.IsNotNull(added); + Assert.AreEqual("\"First.Second.Third\"", added.Declaration.CustomFolder); + } + } + [Category("Code Explorer")] [Test] public void SubFolderModuleIsChildOfDeepestSubFolder() @@ -167,5 +291,17 @@ public void FoldersNamesAreCaseSensitive() Assert.AreEqual(3, project.Children.Count); } } + + private static Declaration GetNewClassDeclaration(Declaration project, string name, string folder = "") + { + var annotations = string.IsNullOrEmpty(folder) + ? Enumerable.Empty() + : new[] { new FolderAnnotation(new QualifiedSelection(project.QualifiedModuleName, new Selection(1, 1)), null, new[] { folder }) }; + + var declaration = + new ClassModuleDeclaration(new QualifiedMemberName(project.QualifiedModuleName, name), project, name, true, annotations, new Attributes()); + + return declaration; + } } }