Quick-Fixes and Context Actions
One of the ways that ReSharper offers to correct or modify your code is by using a pop-up menu that shows up on the left-hand side of the screen:
This menu (typically triggered with the
Alt+Enter shortcut) contains a set of menu items that plugin writers can provide. There are two separate ReSharper constructs that both can provide items to appear in this menu:
Context actions are simply possible code modifications that might be applicable in a particular point in code. Context actions typically offer a chance to improve the code without dramatically changing it. CA items are displayed with pencil icons.
Quick-fixes are possible modifications that appear associated with a particular highlighting (i.e., a warning or an error). These typically offer a chance to correct a particular problem in your code. Quick-fix items are displayed with red or yellow light bulb icons (where quick-fixes with red bulbs are meant to fix errors and quick-fixes with yellow bulbs fix warnings, suggestions and hints).
In each case, a context action (CA) or quick-fix (QF) can provide zero or more menu entries for the user to act upon. As of ReSharper 7, these entries can also be hierarchical in nature, i.e. one can have nested/submenu items in addition to top-level ones.
Each item provided by a QF or CA is called a bulb action. A bulb action is any class that implements the
IBulbAction interface. This interface has two members:
Text- this property needs to contain the text that is displayed in the menu item.
Execute()- this method is called when the menu item is chosen.
Whether or not to create separate classes for bulb actions (in addition to QF/CA classes) is a design decision. Typically, if your QF or CA only intends to display one item, it may not be necessary.
In addition to ‘plain text’ offered by
IBulbAction, your bulb action can also provide rich text, i.e., text that has some formatting. For example:
To support it, your bulb action must also implement the
IBulbItemRichText interface. This interface has a property called
RichText that contains a definition of the text shown in the menu, including formatting changes such as bold text or a different text colour. Here’s an example:
Check out the
TextStyle class for a range of possible options.
A bulb menu is a collection of bulb actions provided by various CAs and QFs, all arranged in a particular list or hierarchy. Bulb menus are created internally by ReSharper, but they should be populated explicitly in the QFs and CAs via the
A bulb menu is represented by the
BulbMenu class. The contents of this class can be modified in several ways. The simplest way, if you want to add just one item to the root level of the menu is to call a method such as
ArrangeQuickFix(). Both of these methods takes one bulb action and puts it at the top level.
A singular call such as
ArrangeContextAction() has a corresponding method
ArrangeContextActions() - note the
s at the end. This basically takes several bulb items and puts them at the top level.
Now let’s talk about the hierarchical aspects of menus. Essentially, a bulb menu keeps information about its structure in a property called
Groups which is an enumeration of
BulbGroup objects. Rather than creating groups, it is recommended that you use the
BulbMenu.GetOrCreateGroup() method to avoid creating duplicate groups. This method takes an
Anchor which relates to the location where the group is placed - take a look at the static members of
AnchorsForBulbMenuGroups for some well-known anchors.
Having acquired or created a group, you can do one of two things:
Add a single bulb action using the
AddBulbAction()method. This is precisely what happens behind the scenes in
ArrangeContextAction()and similar methods.
GetOrCreateSubmenu()method in order to create a submenu for a particular menu.
Hierarchical Menu (a.k.a. Submenu)
This is where an explanation of submenus is in order. You see, one of the properties of a
BulbGroup is called
MenuItems and it contains, you guessed it, a collection of
BulbMenuItems. What happens behind the scenes in the
GetOrCreateSubmenu() method is that a new
BulbMenuItem is acquired or created and then added to the collection of existing items.
Both the explicit creation of the
BulbMenuItem and its addition via
GetOrCreateSubmenu() require you to initialize and pass in a
BulbMenuItemViewDescription structure. For this structure, you need to define:
An anchor (as per bulb groups)
An icon. You can take one of the existing icons in ReSharper or pass
nullto avoid having an icon altogether.
A rich text definition. Use the
RichTextclass or, if you don’t need any formatting, simply cast an ordinary string to
You’ll also note that the
BulbMenuItem constructor has two additional parameters. The
bulbAction parameter lets you define a bulb action to execute when this menu item is triggered. The
withSubmenu parameter defines whether you need to have a submenu for this menu item. If you do, it initializes the
Submenu property of the
BulbMenuItem to a new
Brief recap of the way things are structured:
You have a top-level
BulbMenuthat you can items directly to.
BulbMenuhas one or more
BulbGroupelements in a
Each group has one or more
BulbMenuItemelements in a
BulbMenuItemcan itself have a
Submenuproperty of type
As mentioned previously, a context action is meant to offer an opportunity to change code in a particular context. For example, offering to change a number from hexadecimal to decimal should be a context action - this is a convenience method.
A context action is a class that follows the following rules:
It implements the
It is decorated with the
It has a constructor that takes the CA data provider as a parameter
Let’s start with the CA constructor. As mentioned above, it should have one parameter corresponding to a data provider for the context action. The data provider is how we can get information about where the context action is possibly going to be displayed. In other words, it's the context for the context action. The data provider is a language-specific interface derived from
Here is an example from a C# context action:
Now it’s time to implement the interface members. The first, an
IsAvailable() method is used to check whether this context action is available. If it is, the
CreateBulbItems() method will be called. If not, it's not called, and no items are added for this action. The
IsAvailable() method is implemented by looking at the context data provider we were passed in the constructor, and deciding if our action is valid for the current location. There are several properties on the method that are useful, for example, we can get at the current
CaretOffset. More importantly, we can look at the current node in the PSI syntax tree - the
SelectedElement property gives us the
ITreeNode of the PSI syntax tree at the current caret offset. We can downcast this to a more specific type, such as
IAssignmentExpression. We can also get the tree nodes before and after the caret, using the provider's
TokenAfterCaret. However, the
GetSelectedElement<T> method is most useful, because it will walk up the syntax tree looking for a containing node of the correct type. For example, we can use
GetSelectedElement<IMethodDeclaration>(true, true) to get the containing method declaration, even if the node we're currently on is an expression. We can check to see if the value is null to ensure we're in the right place, and we can walk the contents of the method declaration node to look inside the method body or parameters, etc.
The next interface member is the one that populates the bulb menu. The method is defined as
and the idea is that you use the
menu parameter to define the structure of the menu, as described in the previous section. For instance, the implementation can be as simple as
if you’ve only got one bulb action that you want to add.
A quick-fix is just like a context action. The only difference is that a quick-fix appears in response to a highlighting. A quick-fix is called this because its purpose is to fix a particular problem, a problem typically found by a daemon and highlighted in code.
The creation of a quick-fix is very similar to the creation of a context action. You need a class that:
Is decorated with the
Has at least one constructor with one parameter corresponding to a particular highlighting
The constructor parameter that the quick-fix takes must correspond to the highlighting which corresponds to this quick-fix. As far as interface members are concerned, the situation is identical to that of the context action, with the only difference that the
CreateBulbItems() method takes an extra parameter indicating the
Severity of the associated highlighting.
BulbMenu.ArrangeQuickFixes() requires a set of pairs of bulb actions and severities. Thus, if you keep an internal list of
IBulbAction elements, you can convert it to a set of pairs using such code as:
In the majority of cases, implementing
IContextAction is not recommended. Instead, we suggest that you do the following:
If your QF or CA publishes only a single bulb action, consider inheriting your class from
If you really need to have a separate class for a bulb action, inherit from
BulbActionBase. This class implements
IBulbActionand has a lot of useful plumbing necessary for modifying documents.