3. Handling Editor Events
The previous tutorial Editor Coordinate Systems described working with caret coordinate systems in an editor window.
Caret position was discussed in terms of Logical Position, Visual Position, and Offset.
This tutorial introduces the Editor Action system, which handles actions activated by keystroke events in the editor.
Two classes from the editor_basics code sample are used to illustrate:
* Using an Consulo EditorActionHandler
to manipulate a caret.
* Creating and registering a custom TypedActionHandler
to intercept keystrokes and change the document.
The tutorial presents the following sections:
- bullet list {:toc}
Using an Consulo EditorActionHandler
In this portion of the tutorial, the editor_basics code sample is used to demonstrate cloning an existing caret.
A custom action class will use EditorActionManager
to access a specific EditorActionHandler
for caret cloning.
The editor_basics
code sample adds an Editor Add Caret menu item to the editor context menu:
{:width="600px"}
Creating the Menu Action Class
The source code for the Java action class is EditorHandlerIllustration, a subclass of AnAction
.
For more information about creating action classes, see the Actions Tutorial which covers the topic in depth.
The EditorHandlerIllustration
action is registered in the editor_basic plugin.xml
file.
Note that this action class is registered to appear on the Editor context menu.
<actions>
<action id="EditorBasics.EditorHandlerIllustration"
class="org.intellij.sdk.editor.EditorHandlerIllustration"
text="Editor Add Caret"
description="Adds a second caret below the existing one."
icon="SdkIcons.Sdk_default_icon">
<add-to-group group-id="EditorPopupMenu" anchor="first"/>
</action>
</action>
Setting Visibility for the Action Menu Entry
Under what conditions should the EditorHandlerIllustration
action be capable of cloning a caret?
Only if the following conditions are met in the EditorHandlerIllustration.update()
method:
* A project is open,
* An editor is available,
* There is at least one caret active in the editor.
After ensuring that Project
and Editor
objects are available, the Editor
object is used to verify there is at least one caret:
public class EditorHandlerIllustration extends AnAction {
@Override
public void update(@NotNull final AnActionEvent e) {
final Project project = e.getProject();
final Editor editor = e.getData(CommonDataKeys.EDITOR);
// Make sure at least one caret is available
boolean menuAllowed = false;
if (editor != null && project != null) {
// Ensure the list of carets in the editor is not empty
menuAllowed = !editor.getCaretModel().getAllCarets().isEmpty();
}
e.getPresentation().setEnabledAndVisible(menuAllowed);
}
}
Acquiring the Correct EditorActionHandler
When the EditorHandlerIllustration.actionPerformed()
method clones the caret, it should use the appropriate Consulo EditorActionHandler
.
An instance of EditorActionManager
is required to obtain the correct EditorActionHandler
.
The EditorActionManager
class provides a static method to do this.
To request the correct EditorActionHandler
from EditorActionManager
, consult the IdeActions
interface for the correct constant to pass into the EditorActionManager.getActionHandler()
method.
For cloning a caret below the primary caret, the constant is ACTION_EDITOR_CLONE_CARET_BELOW
.
Based on that constant, the EditorActionManager
returns an instance of CloneCaretActionHandler
, a subclass of EditorActionHandler
.
// Snippet from EditorHandlerIllustration.actionPerformed()
final EditorActionManager actionManager = EditorActionManager.getInstance();
final EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_CLONE_CARET_BELOW);
Using an EditorActionHandler to Clone the Caret
To clone the caret requires only calling the EditorActionHandler.execute()
method and passing in the appropriate context.
public class EditorHandlerIllustration extends AnAction {
@Override
public void actionPerformed(@NotNull final AnActionEvent e) {
final Editor editor = e.getRequiredData(CommonDataKeys.EDITOR);
final EditorActionManager actionManager = EditorActionManager.getInstance();
final EditorActionHandler actionHandler = actionManager.getActionHandler(IdeActions.ACTION_EDITOR_CLONE_CARET_BELOW);
actionHandler.execute(editor, editor.getCaretModel().getPrimaryCaret(), e.getDataContext());
}
}
Creating a Custom TypedActionHandler
The TypedActionHandler
interface is the basis for classes that handle keystroke events from the editor.
Custom implementations of the class are registered to handle editor keystroke events, and receive a callback for each keystroke.
The steps below explain how to use TypedActionHandler
to customize the behavior of the editor when keystroke events are received.
Implementing a Custom TypedActionHandler Class
First, a subclass such as MyTypedHandler
is created based on TypedActionHandler
.
The class overrides the method TypedActionHandler.execute()
, which is the callback for editor keystroke events.
Implementing the Keystroke Event Handling Logic
Override the TypedActionHandler.execute()
method in MyTypedHandler
to implement the logic for handling keystroke events.
This method is called every time a key is pressed when the Editor Tool Window has focus.
In the following example, the MyTypedHandler.execute()
method inserts "editor_basics\n" at the zero caret Offset position when a keystroke event occurs.
As explained in Working with Text, safe modifications to the document must be in the context of a write action.
So although a method on the Document
interface does the String
insertion, the write action ensures a stable context.
class MyTypedHandler implements TypedActionHandler {
@Override
public void execute(@NotNull Editor editor, char c, @NotNull DataContext dataContext) {
final Document document = editor.getDocument();
final Project project = editor.getProject();
Runnable runnable = () -> document.insertString(0, "editor_basics\n");
WriteCommandAction.runWriteCommandAction(project, runnable);
}
}
Registering a Custom TypedActionHandler
A custom implementation of TypedActionHandler
must be registered to replace the existing typing handler to receive editor keystroke events.
The registration is done through the TypedAction
class.
As is shown in the snippet below, the EditorActionManager
is used to get access to the TypedAction
class.
The method TypedAction.setupHandler()
is used to register the custom MyTypedHandler
class:
public class EditorHandlerIllustration extends AnAction {
static {
final EditorActionManager actionManager = EditorActionManager.getInstance();
final TypedAction typedAction = actionManager.getTypedAction();
typedAction.setupHandler(new MyTypedHandler());
}
}
Placing the registration code in the EditorHandlerIllustration
class is somewhat arbitrary in the sense that the registration of MyTypedHandler
has nothing to do with the EditorHandlerIllustration
class.
However, the EditorHandlerIllustration
class is convenient because as an action it gets instantiated at application startup.
On instantiation, the static
block of code in EditorHandlerIllustration
gets evaluated.
In the editor_basics
code sample any of the AnAction
derived classes would work for registering MyTypedHandler
.