Skip to content

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:

Editor Basics 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.