Skip to content

Commit 2b4b54d

Browse files
committed
Fix subfolder handling. Closes #4715
1 parent b4ea011 commit 2b4b54d

File tree

5 files changed

+649
-457
lines changed

5 files changed

+649
-457
lines changed

Rubberduck.Core/Navigation/CodeExplorer/CodeExplorerCustomFolderViewModel.cs

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using System.Collections.Generic;
2+
using System.Diagnostics;
23
using System.Linq;
34
using Rubberduck.Navigation.Folders;
45
using Rubberduck.Parsing.Symbols;
@@ -7,6 +8,7 @@
78

89
namespace Rubberduck.Navigation.CodeExplorer
910
{
11+
[DebuggerDisplay("{Name}")]
1012
public sealed class CodeExplorerCustomFolderViewModel : CodeExplorerItemViewModel
1113
{
1214
private static readonly DeclarationType[] ComponentTypes =
@@ -27,8 +29,8 @@ public CodeExplorerCustomFolderViewModel(
2729
IEnumerable<Declaration> declarations) : base(parent, parent?.Declaration)
2830
{
2931
_vbe = vbe;
30-
31-
FullPath = fullPath ?? string.Empty;
32+
FolderDepth = parent is CodeExplorerCustomFolderViewModel folder ? folder.FolderDepth + 1 : 1;
33+
FullPath = fullPath?.Trim('"') ?? string.Empty;
3234
Name = name.Replace("\"", string.Empty);
3335

3436
AddNewChildren(declarations.ToList());
@@ -44,6 +46,11 @@ public CodeExplorerCustomFolderViewModel(
4446

4547
public string FolderAttribute => $"@Folder(\"{FullPath.Replace("\"", string.Empty)}\")";
4648

49+
/// <summary>
50+
/// One-based depth in the folder hierarchy.
51+
/// </summary>
52+
public int FolderDepth { get; }
53+
4754
public override QualifiedSelection? QualifiedSelection => null;
4855

4956
public override bool IsErrorState
@@ -58,18 +65,20 @@ public override bool IsErrorState
5865

5966
protected override void AddNewChildren(List<Declaration> declarations)
6067
{
61-
var subfolders = declarations.Where(declaration => declaration.IsInSubFolder(FullPath)).ToList();
68+
var children = declarations.Where(declaration => declaration.IsInSubFolder(FullPath)).ToList();
6269

63-
foreach (var folder in subfolders.GroupBy(declaration => declaration.CustomFolder))
70+
foreach (var folder in children.GroupBy(declaration => declaration.CustomFolder.SubFolderRoot(FullPath)))
6471
{
65-
AddChild(new CodeExplorerCustomFolderViewModel(this, folder.Key.SubFolderRoot(Name), folder.Key, _vbe, folder));
72+
AddChild(new CodeExplorerCustomFolderViewModel(this, folder.Key, $"{FullPath}.{folder.Key}", _vbe, folder));
73+
foreach (var declaration in folder)
74+
{
75+
declarations.Remove(declaration);
76+
}
6677
}
6778

68-
var components = declarations.Except(subfolders).ToList();
69-
70-
foreach (var component in components.GroupBy(item => item.ComponentName))
79+
foreach (var declaration in declarations.GroupBy(item => item.ComponentName))
7180
{
72-
var moduleName = component.Key;
81+
var moduleName = declaration.Key;
7382
var parent = declarations.SingleOrDefault(item =>
7483
ComponentTypes.Contains(item.DeclarationType) && item.ComponentName == moduleName);
7584

Rubberduck.Core/Navigation/Folders/FolderExtensions.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ namespace Rubberduck.Navigation.Folders
66
{
77
public static class FolderExtensions
88
{
9-
private const char FolderDelimiter = '.';
9+
public const char FolderDelimiter = '.';
1010

1111
public static string RootFolder(this Declaration declaration)
1212
{
@@ -17,12 +17,12 @@ public static string RootFolder(this Declaration declaration)
1717

1818
public static string SubFolderRoot(this string folder, string subfolder)
1919
{
20-
if (folder is null || subfolder is null || !subfolder.Trim('"').StartsWith(folder.Trim('"')))
20+
if (folder is null || subfolder is null || !folder.Trim('"').StartsWith(subfolder.Trim('"')))
2121
{
2222
return string.Empty;
2323
}
2424

25-
var subPath = subfolder.Trim('"').Substring(folder.Length - 1);
25+
var subPath = folder.Trim('"').Substring(subfolder.Length + 1);
2626
return subPath.Split(FolderDelimiter).FirstOrDefault() ?? string.Empty;
2727
}
2828

@@ -51,7 +51,7 @@ public static bool IsInSubFolder(this Declaration declaration, string folder)
5151
return false;
5252
}
5353

54-
return declarationPath.Take(declarationPath.Length).SequenceEqual(folderPath, StringComparer.Ordinal);
54+
return declarationPath.Take(folderPath.Length).SequenceEqual(folderPath, StringComparer.Ordinal);
5555
}
5656

5757
public static bool IsInFolderOrSubFolder(this Declaration declaration, string folder)
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
using NUnit.Framework;
2+
using Rubberduck.Navigation.CodeExplorer;
3+
using Rubberduck.VBEditor.SafeComWrappers;
4+
using System.Linq;
5+
6+
namespace RubberduckTests.CodeExplorer
7+
{
8+
[TestFixture]
9+
public class CodeExplorerFolderTests
10+
{
11+
[Category("Code Explorer")]
12+
[Test]
13+
public void DefaultProjectFolderIsCreated()
14+
{
15+
const string inputCode =
16+
@"Sub Foo()
17+
Dim d As Boolean
18+
d = True
19+
End Sub";
20+
21+
using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, ComponentType.StandardModule, inputCode)
22+
.SelectFirstProject())
23+
{
24+
var project = explorer.ViewModel.SelectedItem;
25+
var folder = project.Children.OfType<CodeExplorerCustomFolderViewModel>().Single();
26+
27+
Assert.NotNull(folder);
28+
Assert.AreEqual(project.Declaration.IdentifierName, folder.Name);
29+
}
30+
}
31+
32+
[Category("Code Explorer")]
33+
[Test]
34+
public void SubFolderAnnotationCreatesSubFolders()
35+
{
36+
const string inputCode =
37+
@"'@Folder(""First.Second.Third"")
38+
39+
Sub Foo()
40+
Dim d As Boolean
41+
d = True
42+
End Sub";
43+
44+
using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, new[] { ComponentType.StandardModule }, new[] { inputCode })
45+
.SelectFirstCustomFolder())
46+
{
47+
var folder = (CodeExplorerCustomFolderViewModel)explorer.ViewModel.SelectedItem;
48+
var subfolder = folder.Children.OfType<CodeExplorerCustomFolderViewModel>().Single();
49+
var subsubfolder = subfolder.Children.OfType<CodeExplorerCustomFolderViewModel>().Single();
50+
51+
Assert.AreEqual("First", folder.Name);
52+
Assert.AreEqual("Second", subfolder.Name);
53+
Assert.AreEqual("Third", subsubfolder.Name);
54+
}
55+
}
56+
57+
[Category("Code Explorer")]
58+
[Test]
59+
public void SubFolderModuleIsChildOfDeepestSubFolder()
60+
{
61+
const string inputCode =
62+
@"'@Folder(""First.Second.Third"")
63+
64+
Sub Foo()
65+
Dim d As Boolean
66+
d = True
67+
End Sub";
68+
69+
using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, ComponentType.StandardModule, inputCode)
70+
.SelectFirstCustomFolder())
71+
{
72+
var folder = explorer.ViewModel.SelectedItem;
73+
while (folder.Children.OfType<CodeExplorerCustomFolderViewModel>().Any())
74+
{
75+
folder = folder.Children.OfType<CodeExplorerCustomFolderViewModel>().Single();
76+
}
77+
78+
var component = folder.Children.OfType<CodeExplorerComponentViewModel>().Single();
79+
80+
Assert.AreEqual("TestModule", component.Name);
81+
}
82+
}
83+
84+
[Category("Code Explorer")]
85+
[Test]
86+
public void SubFoldersForkOnCorrectDepth()
87+
{
88+
string[] folders = new[]
89+
{
90+
"Foo.Bar.Baz",
91+
"Foo.Bar",
92+
"Bar.Bar",
93+
"Bar.Baz"
94+
};
95+
96+
var modules = folders.Select(folder => $@"'@Folder(""{folder}"")").ToArray();
97+
var components = folders.Select(_ => ComponentType.StandardModule).ToArray();
98+
99+
using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, components, modules)
100+
.SelectFirstProject())
101+
{
102+
var project = explorer.ViewModel.SelectedItem;
103+
var custom = project.Children.OfType<CodeExplorerCustomFolderViewModel>().Where(folder => !folder.Name.Equals(project.Name)).ToList();
104+
105+
var foo = custom.Last();
106+
var foobar = foo.Children.OfType<CodeExplorerCustomFolderViewModel>().Single();
107+
var foobarbaz = foobar.Children.OfType<CodeExplorerCustomFolderViewModel>().Single();
108+
109+
var bar = custom.First();
110+
var barbar = bar.Children.OfType<CodeExplorerCustomFolderViewModel>().First();
111+
var barbaz = bar.Children.OfType<CodeExplorerCustomFolderViewModel>().Last();
112+
113+
Assert.AreEqual(3, project.Children.OfType<CodeExplorerCustomFolderViewModel>().Count());
114+
Assert.AreEqual(1, foo.Children.OfType<CodeExplorerCustomFolderViewModel>().Count());
115+
Assert.AreEqual(1, foobar.Children.OfType<CodeExplorerCustomFolderViewModel>().Count());
116+
Assert.AreEqual(2, bar.Children.OfType<CodeExplorerCustomFolderViewModel>().Count());
117+
118+
Assert.AreEqual("Foo", foo.Name);
119+
Assert.AreEqual("Bar", foobar.Name);
120+
Assert.AreEqual("Baz", foobarbaz.Name);
121+
Assert.AreEqual("Bar", bar.Name);
122+
Assert.AreEqual("Bar", barbar.Name);
123+
Assert.AreEqual("Baz", barbaz.Name);
124+
}
125+
}
126+
127+
[Category("Code Explorer")]
128+
[Test]
129+
public void FoldersSortByName()
130+
{
131+
string[] folders = new[]
132+
{
133+
"AFolder",
134+
"BFolder",
135+
"CFolder",
136+
};
137+
138+
var modules = folders.Select(folder => $@"'@Folder(""{folder}"")").ToArray();
139+
var components = folders.Select(_ => ComponentType.StandardModule).ToArray();
140+
141+
using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, components, modules)
142+
.SelectFirstProject())
143+
{
144+
var project = explorer.ViewModel.SelectedItem;
145+
var custom = project.Children.OfType<CodeExplorerCustomFolderViewModel>().Where(folder => !folder.Name.Equals(project.Name)).Select(folder => folder.Name).ToList();
146+
Assert.IsTrue(custom.OrderBy(_ => _).SequenceEqual(folders.OrderBy(_ => _)));
147+
}
148+
}
149+
150+
[Category("Code Explorer")]
151+
[Test]
152+
public void FoldersNamesAreCaseSensitive()
153+
{
154+
string[] folders = new[]
155+
{
156+
"foo",
157+
"Foo"
158+
};
159+
160+
var modules = folders.Select(folder => $@"'@Folder(""{folder}"")").ToArray();
161+
var components = folders.Select(_ => ComponentType.StandardModule).ToArray();
162+
163+
using (var explorer = new MockedCodeExplorer(ProjectType.HostProject, components, modules)
164+
.SelectFirstProject())
165+
{
166+
var project = explorer.ViewModel.SelectedItem;
167+
Assert.AreEqual(3, project.Children.Count);
168+
}
169+
}
170+
}
171+
}

0 commit comments

Comments
 (0)