Skip to content

Commit 7e7e87b

Browse files
authored
Merge pull request #2825 from retailcoder/rd-next
SelectedDeclaration
2 parents 45d91c8 + 60838a0 commit 7e7e87b

File tree

7 files changed

+160
-154
lines changed

7 files changed

+160
-154
lines changed

Rubberduck.Parsing/Symbols/DeclarationFinder.cs

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
using Antlr4.Runtime;
1111
using Rubberduck.Parsing.Grammar;
1212
using Rubberduck.VBEditor.Application;
13+
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
1314

1415
namespace Rubberduck.Parsing.Symbols
1516
{
@@ -48,6 +49,8 @@ public class DeclarationFinder
4849
private readonly ConcurrentDictionary<QualifiedModuleName, ConcurrentBag<IAnnotation>> _annotations;
4950
private readonly ConcurrentDictionary<Declaration, ConcurrentBag<Declaration>> _parametersByParent;
5051
private readonly ConcurrentDictionary<DeclarationType, ConcurrentBag<Declaration>> _userDeclarationsByType;
52+
private readonly IDictionary<QualifiedSelection, IEnumerable<Declaration>> _declarationsBySelection;
53+
private readonly IDictionary<QualifiedSelection, IEnumerable<IdentifierReference>> _referencesBySelection;
5154

5255
private readonly Lazy<ConcurrentDictionary<Declaration, Declaration[]>> _handlersByWithEventsField;
5356
private readonly Lazy<ConcurrentDictionary<VBAParser.ImplementsStmtContext, Declaration[]>> _membersByImplementsContext;
@@ -58,12 +61,29 @@ public class DeclarationFinder
5861

5962
private readonly object threadLock = new object();
6063

64+
private static QualifiedSelection GetGroupingKey(Declaration declaration)
65+
{
66+
// we want the procedures' whole body, not just their identifier:
67+
return declaration.DeclarationType.HasFlag(DeclarationType.Member)
68+
? new QualifiedSelection(
69+
declaration.QualifiedName.QualifiedModuleName,
70+
declaration.Context.GetSelection())
71+
: declaration.QualifiedSelection;
72+
}
73+
6174
public DeclarationFinder(IReadOnlyList<Declaration> declarations, IEnumerable<IAnnotation> annotations, IReadOnlyList<UnboundMemberDeclaration> unresolvedMemberDeclarations, IHostApplication hostApp = null)
6275
{
6376
_hostApp = hostApp;
6477
_annotations = annotations.GroupBy(node => node.QualifiedSelection.QualifiedName).ToConcurrentDictionary();
6578
_declarations = declarations.GroupBy(item => item.QualifiedName.QualifiedModuleName).ToConcurrentDictionary();
6679
_declarationsByName = declarations.GroupBy(declaration => declaration.IdentifierName.ToLowerInvariant()).ToConcurrentDictionary();
80+
_declarationsBySelection = declarations.Where(declaration => !declaration.IsBuiltIn)
81+
.GroupBy(GetGroupingKey)
82+
.ToDictionary(group => group.Key, group => group.AsEnumerable());
83+
_referencesBySelection = declarations
84+
.SelectMany(declaration => declaration.References)
85+
.GroupBy(reference => new QualifiedSelection(reference.QualifiedModuleName, reference.Selection))
86+
.ToDictionary(group => group.Key, group => group.AsEnumerable());
6787
_parametersByParent = declarations.Where(declaration => declaration.DeclarationType == DeclarationType.Parameter)
6888
.GroupBy(declaration => declaration.ParentDeclaration).ToConcurrentDictionary();
6989
_userDeclarationsByType = declarations.Where(declaration => !declaration.IsBuiltIn).GroupBy(declaration => declaration.DeclarationType).ToConcurrentDictionary();
@@ -148,6 +168,62 @@ public DeclarationFinder(IReadOnlyList<Declaration> declarations, IEnumerable<IA
148168
,true);
149169
}
150170

171+
public Declaration FindSelectedDeclaration(ICodePane activeCodePane)
172+
{
173+
if (activeCodePane == null || activeCodePane.IsWrappingNullReference)
174+
{
175+
return null;
176+
}
177+
178+
var qualifiedSelection = activeCodePane.GetQualifiedSelection();
179+
if (!qualifiedSelection.HasValue || qualifiedSelection.Value.Equals(default(QualifiedSelection)))
180+
{
181+
return null;
182+
}
183+
184+
var selection = qualifiedSelection.Value.Selection;
185+
186+
// statistically we'll be on an IdentifierReference more often than on a Declaration:
187+
var matches = _referencesBySelection
188+
.Where(kvp => kvp.Key.QualifiedName.Equals(qualifiedSelection.Value.QualifiedName)
189+
&& kvp.Key.Selection.ContainsFirstCharacter(qualifiedSelection.Value.Selection))
190+
.SelectMany(kvp => kvp.Value)
191+
.OrderByDescending(reference => reference.Declaration.DeclarationType)
192+
.Select(reference => reference.Declaration)
193+
.Distinct()
194+
.ToArray();
195+
196+
if (!matches.Any())
197+
{
198+
matches = _declarationsBySelection
199+
.Where(kvp => kvp.Key.QualifiedName.Equals(qualifiedSelection.Value.QualifiedName)
200+
&& kvp.Key.Selection.ContainsFirstCharacter(selection))
201+
.SelectMany(kvp => kvp.Value)
202+
.OrderByDescending(declaration => declaration.DeclarationType)
203+
.Distinct()
204+
.ToArray();
205+
}
206+
207+
switch (matches.Length)
208+
{
209+
case 0:
210+
ConcurrentBag<Declaration> modules;
211+
return _declarations.TryGetValue(qualifiedSelection.Value.QualifiedName, out modules)
212+
? modules.SingleOrDefault(declaration => declaration.DeclarationType.HasFlag(DeclarationType.Module))
213+
: null;
214+
215+
case 1:
216+
var match = matches.Single();
217+
return match.DeclarationType == DeclarationType.ModuleOption
218+
? match.ParentScopeDeclaration
219+
: match;
220+
221+
default:
222+
// they're sorted by type, so a local comes before the procedure it's in
223+
return matches.FirstOrDefault();
224+
}
225+
}
226+
151227
public IEnumerable<Declaration> FreshUndeclared
152228
{
153229
get { return _newUndeclared.AllValues(); }

Rubberduck.Parsing/Symbols/DeclarationType.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ public enum DeclarationType
1616
[DebuggerDisplay("ClassModule")]
1717
ClassModule = 1 << 3 | Module,
1818
[DebuggerDisplay("UserForm")]
19-
UserForm = 1 << 4,
19+
UserForm = 1 << 4 | ClassModule,
2020
[DebuggerDisplay("Document")]
21-
Document = 1 << 5,
21+
Document = 1 << 5 | ClassModule,
2222
[DebuggerDisplay("ModuleOption")]
2323
ModuleOption = 1 << 6,
2424
[DebuggerDisplay("Member")]

Rubberduck.Parsing/VBA/RubberduckParserState.cs

Lines changed: 2 additions & 137 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
using System.Linq;
66
using System.Runtime.InteropServices;
77
using System.Threading;
8+
using System.Threading.Tasks;
89
using Antlr4.Runtime;
910
using Antlr4.Runtime.Tree;
1011
using Rubberduck.Parsing.ComReflection;
@@ -1004,143 +1005,7 @@ public void RebuildSelectionCache()
10041005

10051006
public Declaration FindSelectedDeclaration(ICodePane activeCodePane, bool procedureLevelOnly = false)
10061007
{
1007-
if (activeCodePane.IsWrappingNullReference)
1008-
{
1009-
return null;
1010-
}
1011-
1012-
var selection = activeCodePane.GetQualifiedSelection();
1013-
if (selection.Equals(_lastSelection))
1014-
{
1015-
return _selectedDeclaration;
1016-
}
1017-
1018-
if (selection == null)
1019-
{
1020-
return _selectedDeclaration;
1021-
}
1022-
1023-
_lastSelection = selection.Value;
1024-
_selectedDeclaration = null;
1025-
1026-
if (!selection.Equals(default(QualifiedSelection)))
1027-
{
1028-
var matches = new List<Tuple<Declaration, Selection, QualifiedModuleName>>();
1029-
lock (_declarationSelections)
1030-
{
1031-
foreach (var item in _declarationSelections)
1032-
{
1033-
if (item.Item3.Equals(selection.Value.QualifiedName) &&
1034-
item.Item2.ContainsFirstCharacter(selection.Value.Selection) &&
1035-
item.Item1.DeclarationType != DeclarationType.ModuleOption)
1036-
{
1037-
matches.Add(item);
1038-
}
1039-
}
1040-
}
1041-
try
1042-
{
1043-
if (matches.Count == 1)
1044-
{
1045-
_selectedDeclaration = matches[0].Item1;
1046-
}
1047-
else
1048-
{
1049-
Declaration match = null;
1050-
if (procedureLevelOnly)
1051-
{
1052-
foreach (var item in matches)
1053-
{
1054-
if (item.Item1.DeclarationType.HasFlag(DeclarationType.Member))
1055-
{
1056-
match = match != null ? null : item.Item1;
1057-
}
1058-
}
1059-
}
1060-
1061-
// No match
1062-
if (matches.Count == 0)
1063-
{
1064-
if (match == null)
1065-
{
1066-
foreach (var item in AllUserDeclarations)
1067-
{
1068-
if ((item.DeclarationType == DeclarationType.ClassModule ||
1069-
item.DeclarationType == DeclarationType.ProceduralModule) &&
1070-
item.QualifiedName.QualifiedModuleName.Equals(selection.Value.QualifiedName))
1071-
{
1072-
// var line = selection.Value.Selection.StartLine;
1073-
// var procType = activeCodePane.CodeModule.GetProcKindOfLine(line);
1074-
// var procName = activeCodePane.CodeModule.GetProcOfLine(line);
1075-
// if (!string.IsNullOrEmpty(procName))
1076-
// {
1077-
// switch (procType)
1078-
// {
1079-
// case ProcKind.PropertyGet:
1080-
// match = DeclarationFinder.Members(item).SingleOrDefault(m => m.IdentifierName == procName && m.DeclarationType == DeclarationType.PropertyGet);
1081-
// break;
1082-
// case ProcKind.PropertyLet:
1083-
// match = DeclarationFinder.Members(item).SingleOrDefault(m => m.IdentifierName == procName && m.DeclarationType == DeclarationType.PropertyLet);
1084-
// break;
1085-
// case ProcKind.PropertySet:
1086-
// match = DeclarationFinder.Members(item).SingleOrDefault(m => m.IdentifierName == procName && m.DeclarationType == DeclarationType.PropertySet);
1087-
// break;
1088-
// default:
1089-
// match = DeclarationFinder.Members(item).SingleOrDefault(m => m.IdentifierName == procName);
1090-
// break;
1091-
// }
1092-
// }
1093-
// else
1094-
// {
1095-
match = match != null ? null : item;
1096-
// }
1097-
}
1098-
}
1099-
}
1100-
}
1101-
else
1102-
{
1103-
// Idiotic approach to find the best declaration out of a set of overlapping declarations.
1104-
// The one closest to the start of the user selection with the smallest width wins.
1105-
var userSelection = selection.Value.Selection;
1106-
1107-
var currentSelection = matches[0].Item2;
1108-
match = matches[0].Item1;
1109-
1110-
foreach (var item in matches)
1111-
{
1112-
var itemDifferenceInStart = Math.Abs(userSelection.StartLine - item.Item2.StartLine);
1113-
var currentSelectionDifferenceInStart = Math.Abs(userSelection.StartLine - currentSelection.StartLine);
1114-
1115-
if (itemDifferenceInStart < currentSelectionDifferenceInStart)
1116-
{
1117-
currentSelection = item.Item2;
1118-
match = item.Item1;
1119-
}
1120-
1121-
if (itemDifferenceInStart == currentSelectionDifferenceInStart)
1122-
{
1123-
if (Math.Abs(userSelection.StartColumn - item.Item2.StartColumn) <
1124-
Math.Abs(userSelection.StartColumn - currentSelection.StartColumn))
1125-
{
1126-
currentSelection = item.Item2;
1127-
match = item.Item1;
1128-
}
1129-
}
1130-
1131-
}
1132-
}
1133-
1134-
_selectedDeclaration = match;
1135-
}
1136-
}
1137-
catch (InvalidOperationException exception)
1138-
{
1139-
Logger.Error(exception);
1140-
}
1141-
}
1142-
1143-
return _selectedDeclaration;
1008+
return DeclarationFinder.FindSelectedDeclaration(activeCodePane);
11441009
}
11451010

11461011
public void RemoveBuiltInDeclarations(IReference reference)

Rubberduck.VBEEditor/QualifiedSelection.cs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
namespace Rubberduck.VBEditor
44
{
5-
public struct QualifiedSelection : IComparable<QualifiedSelection>
5+
public struct QualifiedSelection : IComparable<QualifiedSelection>, IEquatable<QualifiedSelection>
66
{
77
public QualifiedSelection(QualifiedModuleName qualifiedName, Selection selection)
88
{
@@ -26,6 +26,11 @@ public int CompareTo(QualifiedSelection other)
2626
return Selection.CompareTo(other.Selection);
2727
}
2828

29+
public bool Equals(QualifiedSelection other)
30+
{
31+
return other.Selection.Equals(_selection) && other.QualifiedName.Equals(_qualifiedName);
32+
}
33+
2934
public override string ToString()
3035
{
3136
return string.Concat(QualifiedName, " ", Selection);
@@ -48,11 +53,11 @@ public override int GetHashCode()
4853

4954
public override bool Equals(object obj)
5055
{
51-
if (obj == null) { return false; }
52-
53-
var other = (QualifiedSelection)obj;
54-
return other.QualifiedName.Equals(_qualifiedName)
55-
&& other.Selection.Equals(_selection);
56+
if (obj is QualifiedSelection)
57+
{
58+
return Equals((QualifiedSelection) obj);
59+
}
60+
return false;
5661
}
5762
}
5863
}

Rubberduck.VBEEditor/SafeComWrappers/Office.Core/CommandBars.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections;
33
using System.Collections.Generic;
4+
using System.Linq;
45
using System.Runtime.InteropServices;
56
using Rubberduck.VBEditor.SafeComWrappers.Abstract;
67
using Rubberduck.VBEditor.SafeComWrappers.MSForms;
@@ -31,7 +32,7 @@ private void DeleteExistingCommandBar(string name)
3132
{
3233
try
3334
{
34-
var existing = Target[name];
35+
var existing = Target.Cast<Microsoft.Office.Core.CommandBar>().FirstOrDefault(bar => bar.Name == name);
3536
if (existing != null)
3637
{
3738
existing.Delete();

Rubberduck.VBEEditor/Selection.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ namespace Rubberduck.VBEditor
44
{
55
public struct Selection : IEquatable<Selection>, IComparable<Selection>
66
{
7+
public Selection(int line, int column) : this(line, column, line, column) { }
8+
79
public Selection(int startLine, int startColumn, int endLine, int endColumn)
810
{
911
_startLine = startLine;

0 commit comments

Comments
 (0)