Catching Multiple Hotkeys in Editor?

I'm writing a plugin for IntelliJ to handle a custom language in a specific file type and it includes about 20 unicode characters which need to be bound to hotkeys for usability.  I have two to three problems getting these wired up:

  1. I can't find any code on how to utilize EditorActionHandler or EditorAction to bind a key combination and insert a character at the caret when the file has focus in the editor (or a pair of characters around a selection if there is a selection.)
  2. I'm not sure if that's the right approach with so many hotkeys (I'd prefer to have them in an array or even an if/else-if block than to make 40+ separate classes for actions and action handlers - I'd also like to be able to adjust these dynamically based on the output from the lexer since the language itself may define new hotkeys for interaction.)
  3. Thus far I've only been able to catch typed keys in the editor, but not key combinations.

Any help would be appreciated, especially with code samples since I'm new to plugin development for IntelliJ and can't find any related code samples in the tutorials or a comprehensive API reference - just some short writeups and samples on the more generic seeming AnAction overrides to add menu items.

10 comments

One possible source for examples is a source code for IDEA Community Edition - you can get it on Github. E.g. a quite simple example of an action, that modifies code in editor is HungryBackspaceAction. Another example of an editor action is given in IntelliJ Platform SDK DevGuide here.

An action can be assigned multiple shortcuts. To be able to do different things on different shortcuts in the same action, you'll need to dispatch on InputEvent contents in your action code. EditorAction doesn't expose this data, but if you extend from AnAction directly, you are passed AnActionEvent instance, which contains a reference to InputEvent. You can also assign shortcuts to actions programmatically, using KeymapManager (KeymapManager.getInstance().getActiveKeyMap().addShortcut(...)).

Not sure I understand the problem with handling key combinations. Is it about assigning shortcuts like Ctrl+O? That is done in the same way - in your plugin XML, just set "control O" as a keystroke for an action.

 

0

This seems to be getting closer, but I'm still at a loss as to how to interact with addShortcut, Shortcut, KeyStroke, and actually register the event.  Is there any actual reference/documentation on this aside from the tutorials which don't exactly match the specific task?

 

In my AnAction-overriding constructor I have the incomplete code:

KeyStroke keyStroke = new KeyStroke("A", 65, )
Shortcut s = new KeyboardShortcut("ALT [");
KeymapManager.getInstance().getActiveKeymap().addShortcut("test");

While in the actionPerformed call I have:

String s = "";
InputEvent inputEvent = anActionEvent.getInputEvent();
if (inputEvent.isAltDown()) {
s = "ALT" + (s.length() > 0 ? " + " + s : "");
}
if (inputEvent.isControlDown()) {
s = "CTRL" + (s.length() > 0 ? " + " + s : "");
}
inputEvent.

But am also at a lost as to how I get the actual key pressed in the actionPerformed function (though the modifiers appear to all be included in the inputEvent.)

0

InputEvent and KeyStroke are AWT/Swing classes, I don't think we have any documentation on using them, you can read corresponding javadocs in JDK, or, probably, in some other AWT/Swing resources.

If InputEvent represents a keyboard event, you can cast it to KeyEvent (after instanceof check), and get more information from it.

If you think something is missing in IntelliJ Platform classes' javadocs, you can mention it here, we'll try to add missing information.

 

0

I think this got me a lot closer but am still missing something, my class currently looks like:

public class HotkeyAction extends AnAction {
HotkeyAction() {
super();
KeyStroke keyStroke = KeyStroke.getKeyStroke(97, 0, false);
Shortcut s = new KeyboardShortcut(keyStroke, null);
KeymapManager.getInstance().getActiveKeymap().addShortcut("test", s);
Messages.showMessageDialog(s.toString(), "test0", Messages.getInformationIcon());
}

@Override
public void actionPerformed(AnActionEvent anActionEvent) {
String s = "";
InputEvent inputEvent = anActionEvent.getInputEvent();
Messages.showMessageDialog(inputEvent.toString(), "test", Messages.getInformationIcon());
if (inputEvent instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent) inputEvent;
s = String.valueOf(keyEvent.getKeyChar());
if (inputEvent.isAltDown()) {
s = "ALT" + (s.length() > 0 ? " + " + s : "");
}
if (inputEvent.isControlDown()) {
s = "CTRL" + (s.length() > 0 ? " + " + s : "");
}
Messages.showMessageDialog(s, "test2", Messages.getInformationIcon());
}
}
}

and the registration looks like:

<action id="org.aquae.slip.HotkeyAction" class="org.aquae.slip.HotkeyAction" text="HotkeyAction"/>

Is this being registered incorrectly?  I'm not seeing any message boxes when I run the plugin.

0

The actions are initialized in a background thread, and showing message dialog is only possible from event dispatch thread - you should have exception in log about that.

Also, you're using wrong actionId when you're calling addShortcut - it doesn't match action id you're using when registering your action.

0

This seems to be the information I needed, thanks.

0

For anyone else who comes across this and wants a code sample, this works up through keybinding and outputting the result to the log:

<action id="org.aquae.slip.HotkeyAction" class="org.aquae.slip.HotkeyAction" text="HotkeyAction"/>

 

package org.aquae.slip;

import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.keymap.KeymapManager;

import java.awt.event.InputEvent;
import java.awt.event.KeyEvent;
import java.util.ArrayList;

import static java.awt.event.KeyEvent.*;

public class HotkeyAction extends AnAction {
private static final Logger logger = Logger.getInstance("Aquae");
private ArrayList<HotkeyHandler> keyStrokes;

public HotkeyAction() {
super();
this.keyStrokes = new ArrayList<HotkeyHandler>();
this.AddShortcut(VK_OPEN_BRACKET, CTRL_DOWN_MASK | ALT_DOWN_MASK | SHIFT_DOWN_MASK, false, "?", "?");
this.AddShortcut(VK_CLOSE_BRACKET, CTRL_DOWN_MASK | ALT_DOWN_MASK | SHIFT_DOWN_MASK, false, "?", "");
}

HotkeyAction(ArrayList<HotkeyHandler> keyStrokes) {
super();
this.keyStrokes = new ArrayList<HotkeyHandler>();
}

public void AddShortcut(int keyCode, int modifiers, boolean onKeyRelease, String insertBefore, String insertAfter) {
HotkeyHandler handler = new HotkeyInsertionHandler(keyCode, modifiers, onKeyRelease, insertBefore, insertAfter);
this.keyStrokes.add(handler);
KeymapManager.getInstance().getActiveKeymap().addShortcut("org.aquae.slip.HotkeyAction", handler.getShortcut());
}

@Override
public void actionPerformed(AnActionEvent anActionEvent) {
String s = "";
InputEvent inputEvent = anActionEvent.getInputEvent();
logger.warn(inputEvent.toString());
if (inputEvent instanceof KeyEvent) {
KeyEvent keyEvent = (KeyEvent) inputEvent;
s = String.valueOf(keyEvent.getKeyChar());
if (inputEvent.isShiftDown()) {
s = "SHIFT" + (s.length() > 0 ? " + " + s : "");
}
if (inputEvent.isAltDown()) {
s = "ALT" + (s.length() > 0 ? " + " + s : "");
}
if (inputEvent.isControlDown()) {
s = "CTRL" + (s.length() > 0 ? " + " + s : "");
}
logger.warn(s);
}
}
}

 

package org.aquae.slip;

import com.intellij.openapi.actionSystem.KeyboardShortcut;
import com.intellij.openapi.actionSystem.Shortcut;

import javax.swing.*;

public class HotkeyHandler {
private int _keyCode;
private int _modifiers;
private boolean _onKeyRelease;
private KeyStroke _keyStroke;
private Shortcut _shortcut;

public HotkeyHandler(int keyCode, int modifiers, boolean onKeyRelease) {
this._keyCode = keyCode;
this._modifiers = modifiers;
this._onKeyRelease = onKeyRelease;
this._keyStroke = KeyStroke.getKeyStroke(keyCode, modifiers, onKeyRelease);
this._shortcut = new KeyboardShortcut(this._keyStroke, null);
}

public int getKeyCode() { return (this._keyCode); }
public KeyStroke getKeyStroke() { return (this._keyStroke); }
public int getModifiers() { return (this._modifiers); }
public boolean getOnKeyRelease() { return (this._onKeyRelease); }
public Shortcut getShortcut() { return (this._shortcut); }
}

 

package org.aquae.slip;

public class HotkeyInsertionHandler extends HotkeyHandler {
private String _insertBefore;
private String _insertAfter;

public HotkeyInsertionHandler(int keyCode, int modifiers, boolean onKeyRelease, String insertBefore, String insertAfter) {
super(keyCode, modifiers, onKeyRelease);
this._insertBefore = insertBefore;
this._insertAfter = insertAfter;
}

public String getInsertBefore() { return (this._insertBefore); }
public String getInsertAfter() { return (this._insertAfter); }
}
0

I spoke a bit soon, it seems no matter how I try to implement it I'm getting the error:

    2018-05-31 11:45:08,984 [  12688]  ERROR - plication.impl.ApplicationImpl - Assertion failed: Write access is allowed inside write-action only (see com.intellij.openapi.application.Application.runWriteAction())
    java.lang.Throwable: Assertion failed: Write access is allowed inside write-action only (see com.intellij.openapi.application.Application.runWriteAction())

Is there a special way to go about this?  I've tried implementing the HotkeyHandler class such that it overrides a variety of different EditorActions and EditorActionHandlers called by the HotkeyAction which inherits from AnAction.

What's the appropriate way to make changes in the editor from within a class inheriting from AnAction?

0

I suggest you go through IntelliJ Platform SDK DevGuide again - it has an answer to this question (and possibly some future questions).

To modify document you should be holding a write lock (using Application.runWriteAction()) and executing a command (CommandProcessor.executeCommand). Or you can combine both, like in the example I've mentioned previously, by using WriteCommandAction class.

0

Thanks, I think I have it after reading that, just issues moving carets and selections around now but I should be able to figure that out.

0

Please sign in to leave a comment.