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.
Bulb Action
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.
Bulb Menu
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 CreateBulbItems
method.
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 ArrangeContextAction()
or 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 inArrangeContextAction()
and similar methods.Use the
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 BulbMenuItem
s. 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
null
to avoid having an icon altogether.A rich text definition. Use the
RichText
class or, if you don’t need any formatting, simply cast an ordinary string toRichText
.
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 BulbMenu
.
Brief recap of the way things are structured:
You have a top-level
BulbMenu
that you can items directly to.A
BulbMenu
has one or moreBulbGroup
elements in aGroups
collection.Each group has one or more
BulbMenuItem
elements in aMenuItems
collection.Each
BulbMenuItem
can itself have aSubmenu
property of typeBulbMenu
.
Context Action
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
IContextAction
interfaceIt is decorated with the
[ContextAction]
attributeIt 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 IContextActionDataProvider
.
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 ITextControl
, Selection
and 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 IMethodDeclaration
or IAssignmentExpression
. We can also get the tree nodes before and after the caret, using the provider's TokenBeforeCaret
and 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.
Quick-Fix
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:
Implements the
IQuickFix
interfaceIs decorated with the
QuickFix
attributeHas 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.
Note that 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:
Base Classes
In the majority of cases, implementing IBulbAction
, IQuickFix
or 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
QuickFixBase
orContextActionBase
respectively.If you really need to have a separate class for a bulb action, inherit from
BulbActionBase
. This class implementsIBulbAction
and has a lot of useful plumbing necessary for modifying documents.