How to Observe Usage of Bulb Actions/Context Actions/Quick-Fixes?

Hello there,

we're doing research on how developers use Visual Studio and (as those two almost generally come together) ReSharper. Therefore, we want to observe what VS/RS features are used by our test subjects. For RS-Actions we realized such an observation by installing an logging IActionHandler on every UpdateableAction known to the IActionManager. So far so good. Unfortunately, we are unable to find a similar way to treat Bulb Actions, Context Actions, and Quick-Fixes. As we assume their usage is crucial in understanding how developers use RS, we are keen to find some way of automated usage observation. Hence, my questions are:

  • Is there some (possibly generic) way of getting a handle on all Bulp Actions/Context Actions/Quick-Foxes?
  • Can something like an ActionHandler be used here, to be informed about executions?


We're using ReSharper 8 (most recent update) in VS2010.

Thanks in advance!
Best,
Sven

1 comment
Comment actions Permalink

I finally found a solution to my problem! It  involves a little casting and reflection, but actually works stable in  all the tests I've done so far. I proceed as follows:

R# has the interface IBulbItemsProvider, implementations of which are  dynamically loaded and queried for bulb items, i.e., menu items  wrapping bulb actions whenever one of the R#-menus opens. I now  implemented the following provider:

[SolutionComponent]
public class BulbItemInstrumentationComponent : IBulbActionprovider
{
  public int Priority { get{ return int.MaxValue; } }

  public object PreExecute(ITextControl tc) { return null; }

  public void CollectActions(IntentionsBulbItems ibis, ...)
  {
    var bulbItems = ibis.AllBulbMenuItems;
    foreach (var execItem in bulbItems.Select(item => item.ExecutableItem))
    {
      var proxy = execItem as IntentionAction.MyExecutableProxi;
      if (proxy != null)
      {
        proxy.WrapBulbAction();
        continue;
      }

      var exec = execItem as ExecutableItem;
      if (exec != null)
      {
        exec.WrapBulbAction();
        continue;
      }

      throw new Exception("unexpected item type: " + execItem.GetType().FullName);
    }
  }
}

The return value of Priority makes sure that my provider is called  last of all existing providers. This results in the parameter  IntensionsBulbItems of CollectAction() already containing BulbMenuItems  that have been collected from the other providers.
I then iterate over all of these items and install my observation  wrappers. Therefore I need to downcast the item-executables to their  actual type. For bulb items from the R#Core the two mentioned types seem  to be the only ones in use. Providers from R#Extensions might actually  use other types, for which case the Exception at the end of the loop  will be replaced by some more generous handling in the future ;)
The wrapping itself is implemented in the WrapBulbAction() extension methods. These look like follows:


static class BulbItemInstrumentationExtensions {   private static readonly MethodInfo MyExecutableProxiBulbActionSetter =     typeof (IntentionAction.MyExecutableProxi).GetProperty("BulbAction").GetSetMethod(true);   private static readonly FieldInfo ExecutableItemActionField =     typeof (ExecutableItem).GetField("myAction", BindingFlags.NonPublic | BindingFlags.Instance);   public static void WrapBulbAction(this IntentionAction.MyExecutableProxi proxi)   {     var originalBulbAction = proxy.BulbAction;     var bulbActionProxy = new LoggingBulbActionProxy(originalBulbAction);     MyExecutableProxiBulbActionSetter.Invoke(proxy, new object[] {bulbActionProxy});   }   public static void WrapBulbAction(this ExecutableItem exec)   {     var originalAction = (Action) ExecutableItemActionField.GetValue(exec);     var actionProxy = new LoggingActionProxy(originalAction);     var newAction = (Action) (wrapper.Execute);     ExecutableItemActionField.SetValue(exec, newAction);   } }

Where LoggingBulbActionProxy implement IBulbAction itself and  delegates all calls to the IBulbAction passed to its constructor,  thereby logging all calls to Execute(). And LoggingActionProxy is a  class declaring an Execute() method, that takes case of the logging and  then delegates to the Action passed into the constructor.

Now I'm finally able to log all the usages of R#BulbActions. Had a  hard time finding my way, but finally beat the challenge. Hope this  helps someone, somewhere, somewhen!

0

Please sign in to leave a comment.