Making intentions more accessible for domain experts
The intention menu plays a key role in most DSLs developed with JetBrains MPS. The popup menu usually provides quick access to operations such as context-sensitive refactorings.
The only possibility to open the menu is by pressing alt enter
. I would like to argue that having only a shortcut as an option is not the best solution for domain experts who are usually not used to JetBrains IDEs or controlling an application by the keyboard. Wouldn't it be better to also open the intention menu with the mouse?
In this blog, I will suggest a small MPS plugin to support domain experts by letting them open the intentions via the editor popup menu (the one which opens on right-clicking on a node). The image shows the outcome.
Implementing the "Intentions..."-action
Every action must be placed inside an MPS plugin. As a preparation step add a new plugin or choose an existing one from your MPS project (a tutorial can be found in the MPS user guide).
If the plugin is selected, add an MPS action to the plugin model and fill in common fields such as name and caption as you prefer.
action ShowIntentionPopupMenu {
required access: editor command
also available in: << ... >>
caption: Intentions...
mnemonic: <no mnemonic>
description: Opens the Intention menu
required access
To open the menu, access to the current EditorComponent
is needed as this owns an instance of IntentionsSupport
. Having an action context parameter of type EditorComponent
forces the action to be an editor command (required access: editor command
).
execute block
The intentions menu can be opened programmatically by invoking IntentionsSupport
s method checkAndShowMenu()
. Sadly, the EditorComponent
field myIntentionsSupport
is private as well as the method checkAndShowMenu()
. Thus we have to use Java reflection. To simplify our lives, we use the language de.slisson.mps.reflection
which is part of the MPS-extensions platform. The language is marked as deprecated as it's generally not a good solution to use MPS or IDEA internal implementations (I created a ticket and asked JetBrains to provide a public API).
execute(event)->void {
this.editorComponent.[myIntentionsSupport].[checkAndShowMenu()];
}
(The code pieces surrounded by square brackets must be invoked using Java reflections.)
If you are using the MPS-extensions platform, there is another solution for the execute block below.
Action Group
The next step is to add an action group to make the action accessible in the editor context actions. I placed the action under the placeholder refactoring
so that the intention menu and refactoring submenu are located side by side.
group IntentionPopupMenu_Group
is popup: false
contents
ShowIntentionPopupMenu
modifications
add to EditorPopup at position refactoring
So far, all required parts are implemented and after building the plugin the action should already be visible in the editor context menu and working as expected, but there are further improvements we could apply to the intention.
Keymap Changes
A good usability plus is telling the user the action's shortcut so that he or she can learn and use it next time. To achieve that a KeymapChangesDeclaration
is added to our plugin.
keymap changes IntentionPopupMenu_KeyMap for Default
ShowIntentionPopupMenu <alt>+<VK_ENTER>
The key is now displayed to actions right in the menu.
Side notes: There are now multiple alt enter
shortcuts to different actions registered. It's not guaranteed that the IDEA platform will choose our new action by pressing alt enter
(as we only indent to supply a new menu item, we don't bother with IDEA's heuristic for now; both actions are similar). Further, the original action to open the intention menu is unfortunately not registered in the IDEA action manager. Hence, you cannot change or remove the key binding to avoid the duplicated shortcut. However, if the new action is triggered, the Presentation Assistant plugin can detect that, which is not the case for the original action as it is not registered in the action manager.
Action Icon
Action icons enable the user to spot actions faster once they are used to the icons. It would be handy to reuse the intention bulb icon, which is also shown in the editor when an intention is available for the selected node.
The following workaround is required to reuse the intention bulb icon:
Add a
file
resource (conceptFileIcon
) aticon:
Execute the action
Show reflective editor
on the created FileIcon nodeSet child
iconExpression
toAllIcons.Actions.IntentionBulb
The iconExpression
is deprecated but there is no better alternative so far (see MPS ticket). You can of course, also add an icon image to your solution instead.
is applicable block
Another improvement could be to enable the action only if intentions are available for the selected node. I haven't implemented it yet, but if you like, take a look at OriginalIntentionMenu
.getIntentionsGroup()
of com.mbeddr.mpsutil.intentions.runtime.plugin
(which is also part of the MPS-extensions platform)
execute block if the MPS-extensions platform is used
If all steps above are followed and intention grouping is used, you will discover that the action is opening the original MPS intention menu and not IntentionsMenuWithGroups
from the MPS-extensions platform. If you would like to understand the cause, take a look at the constructor of OriginalIntentionMenu
(which is not the original MPS core implementation of the popup menu as the name may imply).
The OriginalIntentionMenu
overrides the original action stored in myShowIntentionsAction
of IntentionsSupport
but the field is not used in the method checkAndShowMenu()
. Hence, we have to invoke the action directly instead of the method. (If you don't use MPS-extensions, you can also use this solution as the original action is stored in myShowIntentionsAction
and invokes checkAndShowMenu()
.
execute(event)->void {
this.editorComponent.[myIntentionsSupport].[myShowIntentionsAction].actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, null)
Complete action
Last but not least a full example of the action:
action ShowIntentionPopupMenu {
required access: editor command
also available in: << ... >>
caption: Intentions...
mnemonic: <no mnemonic>
description: Opens the Intention menu
icon: <no icon> AllIcons.Actions.IntentionBulb
construction parameters
<< ... >>
action context parameters ( always visible = false )
EditorComponent editorComponent key: EDITOR_COMPONENT required
<update block>
execute(event)->void {
this.editorComponent.[myIntentionsSupport].[checkAndShowMenu()];
// OR if MPS-extensions platform is integrated
this.editorComponent.[myIntentionsSupport].[myShowIntentionsAction].actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, null))
}
}
And your plugin should contain at least these three nodes:
Do you like this extension? Leave a comment or send me a message ๐
Update
I was pointed to the intention light bulb which appears on the leftmost editor cell if an intention is available for the selected node. So there are two possibilities in MPS to open the intention menu and not just one as I wrote above. It is, however, easy to miss the icon as it can be quite far from the cursor and it forces you to move the mouse and your intention away from the current node. It also shows up after some milliseconds which is not ideal if you know that you are going to invoke an intention when clicking on a node.