CodeRush Plugin - Navigate to Implementation Part 2
In the previous post we just defined what we want the plugin to do. Let's start to walk through the process of creating a plugin with CodeRush.
There's some plumbing that needs to be done every time you create a plugin and that is not directly related to the functionality of the plugin. For this part I'll just point you to a plugin tutorial by Mark Miller. Since our plugin will handle navigation it seems natural to hook into the Navigation provider system provided by CodeRush.
- Open the plugin designer
- The NavigationProvider does not show up in the toolbox by default so we need to add it.
- Place a NavigationProvider on the designer and subscribe to the CheckAvailability and Navigate events.
Here's a dump of all of the code in the code-behind of the plugin.
namespace CR_NavigationContrib
{
public partial class NavigationContribPlugIn : StandardPlugIn
{
private Implementors _implementors;
// DXCore-generated code...
#region InitializePlugIn
public override void InitializePlugIn()
{
base.InitializePlugIn();
_implementors = new Implementors();
//
// TODO: Add your initialization code here.
//
}
#endregion
#region FinalizePlugIn
public override void FinalizePlugIn()
{
//
// TODO: Add your finalization code here.
//
base.FinalizePlugIn();
}
#endregion
private void navigationProvider1_CheckAvailability(object sender, CheckContentAvailabilityEventArgs ea)
{
_implementors.Load(ea.Element);
ea.Available = _implementors.Count > 0;
if (!ea.Available)
return;
foreach (var item in _implementors)
{
ea.AddSubMenuItem(item.FullName, item.Name);
}
}
private void navigationProvider1_Navigate(object sender, NavigationEventArgs ea)
{
SubMenuItem selectedMenu = ea.SelectedSubMenuItem;
if (selectedMenu == null)
return;
Class implementor = _implementors.Where(c => c.FullName == selectedMenu.Name).FirstOrDefault();
if (implementor != null)
{
Navigator navigator = new Navigator(implementor, ea.Element);
navigator.Navigate();
}
}
}
}
What we see in the code above, are two methods InitializePlugin() and FinalizePlugin() which are standard boilerplate with every plugin and two event handlers navigationProvider1_CheckAvailability and navigationProvider1_Navigate for the navigation provider. In addition to the plugin class, the plugin has two additional classes that we write. Implementors and Navigator. The Implementors class is responsible for storing which classes implement the Interface we're on if any. The Navigator class will take us to the implementation when the navigation method is called.
A few more issues that are worth to mention.
Here's a screenshot of the event arguments for the CheckAvailability event. As you can see there's quite a lot going on. This turns out to be both a blessing and a curse. On the blessing side you can achieve pretty much everything you want. On the curse side, it is not easy to find what you want.
I wonder why the CodeRush team created such a flat object model. I would much rather prefer a deeper, but more categorized hierarchy. I'm not complaining, don't get me wrong, but the current object model without documentation is not easy to navigate (for me, that is). Maybe if I had a lot of experience with VS extensibility much of what I struggle with would be moot. Time will tell.
In the CheckAvailability event handler we get a list of classes that implement the interface we're on. If the Implementors class found at least one class that implements the Interface, we tell CodeRush that our navigation provider should be available to the user. In addition we populate the navigation menu with sub menu items, one for each class. We use the fully qualified name of the class as the key for the menu.
private void navigationProvider1_CheckAvailability(object sender, CheckContentAvailabilityEventArgs ea)
{
_implementors.Load(ea.Element);
ea.Available = _implementors.Count > 0;
if (!ea.Available)
return;
foreach (var item in _implementors)
{
ea.AddSubMenuItem(item.FullName, item.Name);
}
}
The Navigate event handler will be called if the user selected one of the sub menu items we created in the CheckAvailability handler. Here we check that we got a menu (maybe not necessary since we couldn't get here without clicking one). Next we query the Implementors class for a class that matches the key we stored as the menu name earlier. If we find a class that implements the interface, we instantiate a new Navigator that will handle the navigation to the location of the implementation. (eg. open and activate files etc.)
private void navigationProvider1_Navigate(object sender, NavigationEventArgs ea)
{
SubMenuItem selectedMenu = ea.SelectedSubMenuItem;
if (selectedMenu == null)
return;
Class implementor = _implementors.Where(c => c.FullName == selectedMenu.Name).FirstOrDefault();
if (implementor != null)
{
Navigator navigator = new Navigator(implementor, ea.Element);
navigator.Navigate();
}
}
In the next part we'll walk through the Implementors and Navigator classes. (At least one of them)