ReSharper has a very handy feature that's particularly useful when coding against abstractions. Since coding against abstractions is something that can also be done in VBA, the next release of rubberduck is going to include that functionality as well:
The click handler for the code pane context menu simply calls the paremeterless overload of FindAllImplementations
:
private void _findAllImplementationsContextMenu_Click(CommandBarButton Ctrl, ref bool CancelDefault)
{
FindAllImplementations();
}
private void FindAllImplementations()
{
var selection = IDE.ActiveCodePane.GetSelection();
var progress = new ParsingProgressPresenter();
var parseResult = progress.Parse(_parser, IDE.ActiveVBProject);
var implementsStatement = parseResult.Declarations.FindInterfaces()
.SelectMany(i => i.References.Where(reference => reference.Context.Parent is VBAParser.ImplementsStmtContext))
.SingleOrDefault(r => r.QualifiedModuleName == selection.QualifiedName && r.Selection.Contains(selection.Selection));
if (implementsStatement != null)
{
FindAllImplementations(implementsStatement.Declaration, parseResult);
}
var member = parseResult.Declarations.FindInterfaceImplementationMembers()
.SingleOrDefault(m => m.Selection.Contains(selection.Selection));
if (member == null)
{
member = parseResult.Declarations.FindInterfaceMembers()
.SingleOrDefault(m => m.Project == selection.QualifiedName.Project
&& m.ComponentName == selection.QualifiedName.ComponentName
&& m.Selection.Contains(selection.Selection));
}
if (member == null)
{
return;
}
FindAllImplementations(member, parseResult);
}
There's also a public overload that takes a Declaration
target, so that the functionality can be used from the Code Explorer's own context menu:
public void FindAllImplementations(Declaration target)
{
var progress = new ParsingProgressPresenter();
var parseResult = progress.Parse(_parser, IDE.ActiveVBProject);
FindAllImplementations(target, parseResult);
}
And lastly an overload that takes parse results, in case there's a readily usable and resolved parse tree to use:
public void FindAllImplementations(Declaration target, VBProjectParseResult parseResult)
{
IEnumerable<Declaration> implementations;
if (target.DeclarationType == DeclarationType.Class)
{
implementations = FindAllImplementationsOfClass(target, parseResult);
}
else
{
implementations = FindAllImplementationsOfMember(target, parseResult);
}
if (implementations == null)
{
implementations = new List<Declaration>();
}
var declarations = implementations as IList<Declaration> ?? implementations.ToList();
var implementationsCount = declarations.Count();
if (implementationsCount == 1)
{
// if there's only 1 implementation, just jump to it:
ImplementationsListDockablePresenter.OnNavigateImplementation(IDE, declarations.First());
}
else if (implementationsCount > 1)
{
// if there's more than one implementation, show the dockable navigation window:
try
{
ShowImplementationsToolwindow(target, declarations);
}
catch (COMException)
{
// the exception is related to the docked control host instance,
// trying again will work (I know, that's bad bad bad code)
ShowImplementationsToolwindow(target, declarations);
}
}
else
{
var message = string.Format(RubberduckUI.AllImplementations_NoneFound, target.IdentifierName);
var caption = string.Format(RubberduckUI.AllImplementations_Caption, target.IdentifierName);
MessageBox.Show(message, caption, MessageBoxButtons.OK, MessageBoxIcon.Information);
}
}
Then we have specialized methods, depending on whether we're looking for implementations of an interface, or of a specific member:
private IEnumerable<Declaration> FindAllImplementationsOfClass(Declaration target, VBProjectParseResult parseResult)
{
if (target.DeclarationType != DeclarationType.Class)
{
return null;
}
var result = target.References
.Where(reference => reference.Context.Parent is VBAParser.ImplementsStmtContext)
.SelectMany(reference => parseResult.Declarations[reference.QualifiedModuleName.ComponentName])
.ToList();
return result;
}
private IEnumerable<Declaration> FindAllImplementationsOfMember(Declaration target, VBProjectParseResult parseResult)
{
if (!target.DeclarationType.HasFlag(DeclarationType.Member))
{
return null;
}
var isInterface = parseResult.Declarations.FindInterfaces()
.Select(i => i.QualifiedName.QualifiedModuleName.ToString())
.Contains(target.QualifiedName.QualifiedModuleName.ToString());
if (isInterface)
{
return parseResult.Declarations.FindInterfaceImplementationMembers(target.IdentifierName);
}
var member = parseResult.Declarations.FindInterfaceMember(target);
return parseResult.Declarations.FindInterfaceImplementationMembers(member.IdentifierName);
}
Here's the presenter implementation that handles the UI part:
public class ImplementationsListDockablePresenter : DockablePresenterBase
{
public ImplementationsListDockablePresenter(VBE vbe, AddIn addin, SimpleListControl control, IEnumerable<Declaration> implementations)
: base(vbe, addin, control)
{
BindTarget(implementations);
}
private void BindTarget(IEnumerable<Declaration> implementations)
{
var listBox = Control.ResultBox;
listBox.DataSource = implementations.Select(implementation => new ImplementationListItem(implementation)).ToList();
listBox.DisplayMember = "DisplayString";
listBox.ValueMember = "Selection";
Control.Navigate += ControlNavigate;
}
public static void OnNavigateImplementation(VBE vbe, Declaration implementation)
{
vbe.SetSelection(new QualifiedSelection(implementation.QualifiedName.QualifiedModuleName, implementation.Selection));
}
private void ControlNavigate(object sender, ListItemActionEventArgs e)
{
var implementation = e.SelectedItem as ImplementationListItem;
if (implementation != null)
{
OnNavigateImplementation(VBE, implementation.GetDeclaration());
}
}
SimpleListControl Control { get { return UserControl as SimpleListControl; } }
}
It works beautifully. How would you improve this code?