8
\$\begingroup\$

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 is going to include that functionality as well:

right-click on an interface method and select "Go to implementation" to list all implementations of that method


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?

\$\endgroup\$
4
\$\begingroup\$
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));
}

This has a crash-causing bug. You need to change the top .SingleOrDefault() to match the bottom one.


Right here, you need to ensure the members are of the same interface - not just have the same name:

if (isInterface)
{
    return parseResult.Declarations.FindInterfaceImplementationMembers(target.IdentifierName);
}

var member = parseResult.Declarations.FindInterfaceMember(target);
return parseResult.Declarations.FindInterfaceImplementationMembers(member.IdentifierName);

Otherwise, you return all the IClass2_DoSomethings along with the IClass1_DoSomethings.

A simple .Where() will fix it:

if (isInterface)
{
    return parseResult.Declarations.FindInterfaceImplementationMembers(target.IdentifierName)
           .Where(item => item.IdentifierName == target.ComponentName + "_" + target.IdentifierName);
}

var member = parseResult.Declarations.FindInterfaceMember(target);
return parseResult.Declarations.FindInterfaceImplementationMembers(member.IdentifierName)
       .Where(item => item.IdentifierName == member.ComponentName + "_" + member.IdentifierName);

Thanks for helping me develop patches for these problems, @Mat's Mug

\$\endgroup\$
  • \$\begingroup\$ Well, thanks for finding them in the first place! \$\endgroup\$ – Mathieu Guindon May 26 '15 at 17:20
1
\$\begingroup\$

Small note. In some places we're referring to the editor as vbe or VBE. In others we're referring to it as IDE. We should pick one or the other.

\$\endgroup\$
  • 1
    \$\begingroup\$ yeah.. remnants of RD 1.0... \$\endgroup\$ – Mathieu Guindon May 26 '15 at 18:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.