Skip to content

Commit 446f1e1

Browse files
committed
Handle folder component sync correctly. Closes #4720
1 parent 8ab45d8 commit 446f1e1

File tree

3 files changed

+175
-8
lines changed

3 files changed

+175
-8
lines changed

Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerCustomFolderViewModel.cs

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public CodeExplorerCustomFolderViewModel(
4444

4545
public string FullPath { get; }
4646

47-
public string FolderAttribute => $"@Folder(\"{FullPath.Replace("\"", string.Empty)}\")";
47+
public string FolderAttribute => $"'@Folder(\"{FullPath.Replace("\"", string.Empty)}\")";
4848

4949
/// <summary>
5050
/// One-based depth in the folder hierarchy.
@@ -76,7 +76,7 @@ protected override void AddNewChildren(List<Declaration> declarations)
7676
}
7777
}
7878

79-
foreach (var declaration in declarations.GroupBy(item => item.ComponentName))
79+
foreach (var declaration in declarations.Where(child => child.IsInFolder(FullPath)).GroupBy(item => item.ComponentName))
8080
{
8181
var moduleName = declaration.Key;
8282
var parent = declarations.SingleOrDefault(item =>
@@ -91,25 +91,56 @@ protected override void AddNewChildren(List<Declaration> declarations)
9191
!ComponentTypes.Contains(item.DeclarationType) && item.ComponentName == moduleName);
9292

9393
AddChild(new CodeExplorerComponentViewModel(this, parent, members, _vbe));
94+
declarations.Remove(parent);
9495
}
9596
}
9697

9798
public override void Synchronize(List<Declaration> updated)
9899
{
99-
var declarations = updated.Where(declaration => declaration.IsInFolderOrSubFolder(FullPath)).ToList();
100+
SynchronizeChildren(updated);
101+
}
100102

103+
protected override void SynchronizeChildren(List<Declaration> updated)
104+
{
105+
var declarations = updated.Where(declaration => declaration.IsInFolderOrSubFolder(FullPath)).ToList();
106+
101107
if (!declarations.Any())
102108
{
103109
Declaration = null;
104110
return;
105111
}
106112

107-
foreach (var declaration in declarations)
113+
var synchronizing = declarations.ToList();
114+
115+
foreach (var subfolder in Children.OfType<CodeExplorerCustomFolderViewModel>().ToList())
116+
{
117+
subfolder.SynchronizeChildren(declarations);
118+
if (subfolder.Declaration is null)
119+
{
120+
RemoveChild(subfolder);
121+
continue;
122+
}
123+
124+
var synchronized = synchronizing.Where(child => !declarations.Contains(child)).ToList();
125+
foreach (var declaration in synchronized)
126+
{
127+
updated.Remove(declaration);
128+
}
129+
}
130+
131+
foreach (var child in Children.OfType<CodeExplorerComponentViewModel>().ToList())
108132
{
109-
updated.Remove(declaration);
133+
child.Synchronize(updated);
134+
if (child.Declaration is null)
135+
{
136+
RemoveChild(child);
137+
continue;
138+
}
139+
140+
updated.Remove(child.Declaration);
110141
}
111142

112-
SynchronizeChildren(declarations);
143+
AddNewChildren(updated);
113144
}
114145
}
115146
}

Rubberduck.Core/UI/CodeExplorer/Commands/AddComponentCommand.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ protected void AddComponent(CodeExplorerItemViewModel node)
7979
? nodeProject.VBComponents
8080
: ComponentsCollectionFromActiveProject())
8181
{
82-
var folderAnnotation = $"'@Folder(\"{GetFolder(node)}\")";
82+
var folderAnnotation = (node is CodeExplorerCustomFolderViewModel folder) ? folder.FolderAttribute : $"'@Folder(\"{GetFolder(node)}\")";
8383

8484
using (var newComponent = components.Add(ComponentType))
8585
{
@@ -110,7 +110,7 @@ protected void AddComponent(CodeExplorerItemViewModel node, string moduleText)
110110
? nodeProject.VBComponents
111111
: ComponentsCollectionFromActiveProject())
112112
{
113-
var folderAnnotation = $"'@Folder(\"{GetFolder(node)}\")";
113+
var folderAnnotation = (node is CodeExplorerCustomFolderViewModel folder) ? folder.FolderAttribute : $"'@Folder(\"{GetFolder(node)}\")";
114114
var fileName = CreateTempTextFile(moduleText);
115115

116116
using (var newComponent = components.Import(fileName))

RubberduckTests/CodeExplorer/CodeExplorerFolderTests.cs

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
using NUnit.Framework;
22
using Rubberduck.Navigation.CodeExplorer;
3+
using Rubberduck.Parsing.Annotations;
4+
using Rubberduck.Parsing.Symbols;
5+
using Rubberduck.VBEditor;
36
using Rubberduck.VBEditor.SafeComWrappers;
7+
using Rubberduck.VBEditor.Utility;
8+
using System;
49
using System.Linq;
10+
using System.Runtime.InteropServices;
11+
using System.Runtime.InteropServices.ComTypes;
512

613
namespace RubberduckTests.CodeExplorer
714
{
@@ -54,6 +61,123 @@ Dim d As Boolean
5461
}
5562
}
5663

64+
[Category("Code Explorer")]
65+
[Test]
66+
public void AddedModuleIsAtCorrectDepth_DefaultNode()
67+
{
68+
const string inputCode =
69+
@"'@Folder(""First.Second.Third"")
70+
71+
Sub Foo()
72+
Dim d As Boolean
73+
d = True
74+
End Sub";
75+
76+
using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, new[] { ComponentType.StandardModule }, new[] { inputCode })
77+
.SelectFirstProject())
78+
{
79+
var project = (CodeExplorerProjectViewModel)explorer.ViewModel.SelectedItem;
80+
var folder = project.Children.OfType<CodeExplorerCustomFolderViewModel>().First(node => node.Name.Equals(project.Declaration.IdentifierName));
81+
var declarations = project.State.AllUserDeclarations.ToList();
82+
declarations.Add(GetNewClassDeclaration(project.Declaration, "Foo"));
83+
84+
project.Synchronize(declarations);
85+
var added = folder.Children.OfType<CodeExplorerComponentViewModel>().Single();
86+
87+
Assert.AreEqual(DeclarationType.ClassModule, added.Declaration.DeclarationType);
88+
Assert.AreEqual(project.Declaration.IdentifierName, added.Declaration.CustomFolder);
89+
}
90+
}
91+
92+
[Category("Code Explorer")]
93+
[Test]
94+
public void AddedModuleIsAtCorrectDepth_RootNode()
95+
{
96+
const string inputCode =
97+
@"'@Folder(""First.Second.Third"")
98+
99+
Sub Foo()
100+
Dim d As Boolean
101+
d = True
102+
End Sub";
103+
104+
using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, new[] { ComponentType.StandardModule }, new[] { inputCode })
105+
.SelectFirstCustomFolder())
106+
{
107+
var project = explorer.ViewModel.Projects.OfType<CodeExplorerProjectViewModel>().First();
108+
var folder = (CodeExplorerCustomFolderViewModel)explorer.ViewModel.SelectedItem;
109+
var declarations = project.State.AllUserDeclarations.ToList();
110+
declarations.Add(GetNewClassDeclaration(project.Declaration, "Foo", "\"First\""));
111+
112+
project.Synchronize(declarations);
113+
var added = folder.Children.OfType<CodeExplorerComponentViewModel>().Single();
114+
115+
Assert.AreEqual(DeclarationType.ClassModule, added.Declaration.DeclarationType);
116+
Assert.AreEqual("\"First\"", added.Declaration.CustomFolder);
117+
}
118+
}
119+
120+
[Category("Code Explorer")]
121+
[Test]
122+
public void AddedModuleIsAtCorrectDepth_SubNode()
123+
{
124+
const string inputCode =
125+
@"'@Folder(""First.Second.Third"")
126+
127+
Sub Foo()
128+
Dim d As Boolean
129+
d = True
130+
End Sub";
131+
132+
using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, new[] { ComponentType.StandardModule }, new[] { inputCode })
133+
.SelectFirstCustomFolder())
134+
{
135+
var project = explorer.ViewModel.Projects.OfType<CodeExplorerProjectViewModel>().First();
136+
var folder = (CodeExplorerCustomFolderViewModel)explorer.ViewModel.SelectedItem;
137+
var subfolder = folder.Children.OfType<CodeExplorerCustomFolderViewModel>().Single();
138+
var declarations = project.State.AllUserDeclarations.ToList();
139+
declarations.Add(GetNewClassDeclaration(project.Declaration, "Foo", "\"First.Second\""));
140+
141+
project.Synchronize(declarations);
142+
var added = subfolder.Children.OfType<CodeExplorerComponentViewModel>().Single();
143+
144+
Assert.AreEqual(DeclarationType.ClassModule, added.Declaration.DeclarationType);
145+
Assert.AreEqual("\"First.Second\"", added.Declaration.CustomFolder);
146+
}
147+
}
148+
149+
[Category("Code Explorer")]
150+
[Test]
151+
public void AddedModuleIsAtCorrectDepth_TerminalNode()
152+
{
153+
const string inputCode =
154+
@"'@Folder(""First.Second.Third"")
155+
156+
Sub Foo()
157+
Dim d As Boolean
158+
d = True
159+
End Sub";
160+
161+
using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, new[] { ComponentType.StandardModule }, new[] { inputCode })
162+
.SelectFirstCustomFolder())
163+
{
164+
var project = explorer.ViewModel.Projects.OfType<CodeExplorerProjectViewModel>().First();
165+
var folder = (CodeExplorerCustomFolderViewModel)explorer.ViewModel.SelectedItem;
166+
var subfolder = folder.Children.OfType<CodeExplorerCustomFolderViewModel>().Single()
167+
.Children.OfType<CodeExplorerCustomFolderViewModel>().Single();
168+
var declarations = project.State.AllUserDeclarations.ToList();
169+
declarations.Add(GetNewClassDeclaration(project.Declaration, "Foo", "\"First.Second.Third\""));
170+
171+
project.Synchronize(declarations);
172+
173+
var added = subfolder.Children.OfType<CodeExplorerComponentViewModel>()
174+
.SingleOrDefault(node => node.Declaration.DeclarationType == DeclarationType.ClassModule);
175+
176+
Assert.IsNotNull(added);
177+
Assert.AreEqual("\"First.Second.Third\"", added.Declaration.CustomFolder);
178+
}
179+
}
180+
57181
[Category("Code Explorer")]
58182
[Test]
59183
public void SubFolderModuleIsChildOfDeepestSubFolder()
@@ -167,5 +291,17 @@ public void FoldersNamesAreCaseSensitive()
167291
Assert.AreEqual(3, project.Children.Count);
168292
}
169293
}
294+
295+
private static Declaration GetNewClassDeclaration(Declaration project, string name, string folder = "")
296+
{
297+
var annotations = string.IsNullOrEmpty(folder)
298+
? Enumerable.Empty<IAnnotation>()
299+
: new[] { new FolderAnnotation(new QualifiedSelection(project.QualifiedModuleName, new Selection(1, 1)), null, new[] { folder }) };
300+
301+
var declaration =
302+
new ClassModuleDeclaration(new QualifiedMemberName(project.QualifiedModuleName, name), project, name, true, annotations, new Attributes());
303+
304+
return declaration;
305+
}
170306
}
171307
}

0 commit comments

Comments
 (0)