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
Please sign in to leave a comment.
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!