Have you noticed those pop-ups with icons that appear to the left of your code when you move your caret on something?
These pop-ups are triggered by two types of artifacts that you can make in ReSharper - Quick-Fixes and Context Items. However, both of these features rely on the concept of Bulb Items.
A Bulb Item is a general name given to one popup item element. The idea is that a context action can show several bulb items. Thus, it is critical for the developer to realize that you can either have, e.g., a context action and a bulb item in separate classes or, if you have a 1-to-1 correspondence, the easiest thing is to make sure that the context action also is a bulb item.
Let's talk about bulb items living in separate classes. The simplest way to create such a class is to inherit it from BulbItemImpl. This base class lets you override members which change the way the bulb item behaves. There are two members worth noting:
Text property lets you define the text that actually appears on the bulb item.
You need to override the
ExecutePsiTransaction() method to handle the bulb item actually being 'fired'. This method takes two parameters:
- The current solution
- An IProgressIndicator interface - you can use this to indicate how much of your code is 'done'. This is typically only useful if your bulb item performs a lengthy operation.
After you perform the action itself, this method needs to return an Action<ITextControl> containing any post-transaction logic you may have. Typically, you will return null, but in certain cases, you can return a delegate that performs some action on the control. For example, let's imagine that after generating a blog of text, you want to execute Refactor|Rename on the generated identifier. The returned action is exactly the place to do it.
Hint: if you are using separate bulb item classes, use the constructor to pass in all the variables that the bulb item needs in order to operate.
A bulb action is, essentially, a container which returns one or more bulb items depending on whether or not they are applicable at a particular location. These bulb items will subsequently appear in the popup menu on the left hand side of the editor panel. ReSharper supports two types of bulb actions - context actions and quick-fixes - both of which are described below.
Bulb actions are defined via the
IBulbAction interface, from which both
IQuickFix derive. You are unlikely to ever need to use the
IBulbAction interface directly in your code.
A context item gets a pop-up to appear when the cursor is over a particular element (i.e., in a particular context). In fact, it core behavior is to get several items to appear. These items are called bulb items, and we have already met them.
The fundamental requirements for a Context Action is to
- Implement the IContextAction interface
- Be decorated with the ContextAction attribute
The ContextAction attributes is used to supply metadata to ReSharper. At the very least, you need to provide its name and description - without these attributes, ReSharper will issue an error and won't load your plugin.
Now let's talk about the IContextAction interface. This interface has just two members that you need to define:
- The IsAvailable() method determines whether the context action is available. If it is, it presents its items in the popup menu, if it's not, then it doesn't.
- The Items property of type IBulbItem returns an array of bulb items that need to be shown (assuming that IsAvailable() is true).
This interface is just about all you need to create your own context actions, but there's one more thing: the data provider. Basically, the context action data provider is a helpful class that lets you navigate various ReSharper features such as, e.g., figuring out what code element the caret is placed on. You can easily inject it via the constructor as follows:
Single-Item Context Action Helper
As I mentioned before, if your context action has just one item, it makes sense to make sure that the context action is the item. The simplest way to do this is to define a base class which both inherits from BulbItemImpl and implements the IContextAction interface. The implementation can be something similar to the following class, from which single-item context actions can derive:
Please note that ReSharper also contains deprecated classes such as CSharpContextActionBase that were meant to serve a similar purpose. It is best not to use them, but instead to use an implementation similar to the one above.
Let's take a look at an example of a context action using the above base class. Our context action will only fire when the caret is inside an empty pair of double quotes, and will offer us the option to insert a GUID.
We begin by specifying the class, generating the constructor (you need to call the base class) and we'll also define the Text value:
Now, we override the IsAvailable() method to ensure that the context action is only available for an empty string. Note the user of the provider variable:
Notice how in the above code we cache the acquired literal. In the subsequent ExecutePsiTransaction() implementation, we actually replace this literal with the newly generated GUID:
And that's it! Our GUID-generating context action is ready.
A Quick Fix is in many ways identical to a context action. In fact, both the IContextAction interface for context actions and the IQuickFix that quick fixes use are empty interfaces that both derive from IBulbAction. So, what is the difference?
The difference is that context actions can pop up practically anywhere in your code, whereas quick fixes can only pop up on errors (i.e., on highlightings). Another, more direct difference, is that context actions are typically decorated with the pencil icon, whereas quick-fixes use yellow and red lightbulbs as indicators of warnings and errors:
As a result, instead of taking some ContextActionDataProvider as a constructor parameter, the quick fix takes a parameter of type IHighlighting, i.e., it actually gets passed the error that triggered it.
Quick fixes follow the same principles as context actions in terms of activation. In the same vein, you could also define a quick fix that is also a bulb item by implementing IQuickFix and inheriting from BulbItemImpl in a single class. Also, while quick fixes do require to be decorated with the QuickFix attribute, they do not have any necessary parameters, so the attribute can be applied to a base class (if you are using one).
Popup Bulb Actions
In addition to bulb actions that show up on the left-hand side of the editor window, there is another type of action that ReSharper supports: the popup bulb action. These actions do not appear in a list, but instead show up as a blue rectangle right above the location where they are meant to be applied.
In order to write your own popup bulb action, you need to implement the
IPopupBulbAction interface. Please note that you might need to implement the interface explicitly, as some names clash with names from, e.g.,
IPopupBulbAction interface defines the following members:
PopupMenuCaption-- returns a string that gets displayed on the pop-up item.
FillMenuItemDescriptor(IBulbItem key, IMenuItemDescriptor descriptor)-- in this method, you need to specify the parameters of the provided
IMenuItemDescriptor. Apart from
Style, all other properties can theoretically be set to
OnPopupClosed()-- these methods get fired when the popup is shown and closed respectively. This is where, for example, you can show and hide the highlighting for the code that is being affected.
PopupAnchorings-- returns a collection of anchoring points that define the location the popup will appear. Typically, one of the
Anchoring2D.AnchorAside) is used here.
PopupKey-- if the user presses Escape to close this popup, we need a key that will associate with this particular item.