如何为IntelliJ自定义文件类型创建快捷键插入/包裹Unicode字符
实现IntelliJ自定义语言编辑器专属快捷键:插入/包裹Unicode字符
我正在为IntelliJ IDEA开发一款针对自定义语言的插件——这款语言继承自com.intellij.openapi.fileTypes.LanguageFileType,已经搞定了解析器、文件关联和模板系统,但卡在了一个看似简单的快捷键需求上:
创建仅在编辑器中生效的快捷键(无菜单/工具栏图标),实现:
- 无文本选中时,在光标处插入指定Unicode字符
- 有文本选中时,用一对Unicode字符包裹选中内容
举个例子:绑定ALT_SHIFT+[组合键,无选中时插入⦓,有选中时用⦓...⦔包裹内容。IntelliJ原生对.java文件的ALT+SHIFT+[有其他处理,但自定义文件类型默认会忽略这个快捷键,所以这个需求完全可行。
折腾了一阵后我终于实现了这个功能,现在把完整方案和代码分享出来:
核心实现代码:HotkeyAction类
这个类继承自AnAction,负责处理快捷键触发后的逻辑,同时支持批量注册多组快捷键与对应Unicode字符对:
public class HotkeyAction extends AnAction { private static final Logger logger = Logger.getInstance("Aquae"); private ArrayList<HotkeyHandler> hotkeyHandlers; public HotkeyAction() { super(); this.hotkeyHandlers = new ArrayList<HotkeyHandler>(); // 批量注册多组快捷键与对应字符对 this.AddBracketShortcut(true, "{", KeyStroke.getKeyStroke(VK_OPEN_BRACKET, SHIFT_MASK, false), "}", KeyStroke.getKeyStroke(VK_CLOSE_BRACKET, SHIFT_MASK, false)); this.AddBracketShortcut(true, "⧼", KeyStroke.getKeyStroke(VK_OPEN_BRACKET, CTRL_MASK | SHIFT_MASK, false), "⧽", KeyStroke.getKeyStroke(VK_CLOSE_BRACKET, CTRL_MASK | SHIFT_MASK, false)); this.AddBracketShortcut(true, "⦓", KeyStroke.getKeyStroke(VK_OPEN_BRACKET, ALT_MASK | SHIFT_MASK, false), "⦔", KeyStroke.getKeyStroke(VK_CLOSE_BRACKET, ALT_MASK | SHIFT_MASK, false)); this.AddBracketShortcut(true, "⦃", KeyStroke.getKeyStroke(VK_OPEN_BRACKET, CTRL_MASK | ALT_MASK | SHIFT_MASK, false), "⦄", KeyStroke.getKeyStroke(VK_CLOSE_BRACKET, CTRL_MASK | ALT_MASK | SHIFT_MASK, false)); this.AddBracketShortcut(true, "(", KeyStroke.getKeyStroke(VK_9, SHIFT_MASK, false), ")", KeyStroke.getKeyStroke(VK_0, SHIFT_MASK, false)); this.AddBracketShortcut(true, "⦗", KeyStroke.getKeyStroke(VK_9, CTRL_MASK | SHIFT_MASK, false), "⦘", KeyStroke.getKeyStroke(VK_0, CTRL_MASK | SHIFT_MASK, false)); this.AddBracketShortcut(true, "⦅", KeyStroke.getKeyStroke(VK_9, ALT_MASK | SHIFT_MASK, false), "⦆", KeyStroke.getKeyStroke(VK_0, ALT_MASK | SHIFT_MASK, false)); this.AddBracketShortcut(true, "⸨", KeyStroke.getKeyStroke(VK_9, CTRL_MASK | ALT_MASK | SHIFT_MASK, false), "⸩", KeyStroke.getKeyStroke(VK_0, CTRL_MASK | ALT_MASK | SHIFT_MASK, false)); this.AddBracketShortcut(true, "[", KeyStroke.getKeyStroke(VK_OPEN_BRACKET, 0, false), "]", KeyStroke.getKeyStroke(VK_CLOSE_BRACKET, 0, false)); this.AddBracketShortcut(true, "⁅", KeyStroke.getKeyStroke(VK_OPEN_BRACKET, CTRL_MASK, false), "⁆", KeyStroke.getKeyStroke(VK_CLOSE_BRACKET, CTRL_MASK, false)); this.AddBracketShortcut(true, "【", KeyStroke.getKeyStroke(VK_OPEN_BRACKET, ALT_MASK, false), "】", KeyStroke.getKeyStroke(VK_CLOSE_BRACKET, ALT_MASK, false)); this.AddBracketShortcut(true, "〚", KeyStroke.getKeyStroke(VK_OPEN_BRACKET, CTRL_MASK | ALT_MASK, false), "〛", KeyStroke.getKeyStroke(VK_CLOSE_BRACKET, CTRL_MASK | ALT_MASK, false)); this.AddBracketShortcut(true, "<", KeyStroke.getKeyStroke(VK_COMMA, SHIFT_MASK, false), ">", KeyStroke.getKeyStroke(VK_PERIOD, SHIFT_MASK, false)); this.AddBracketShortcut(true, "᚜", KeyStroke.getKeyStroke(VK_COMMA, CTRL_MASK | SHIFT_MASK, false), "᚛", KeyStroke.getKeyStroke(VK_PERIOD, CTRL_MASK | SHIFT_MASK, false)); this.AddBracketShortcut(true, "⦑", KeyStroke.getKeyStroke(VK_COMMA, ALT_MASK | SHIFT_MASK, false), "⦒", KeyStroke.getKeyStroke(VK_PERIOD, ALT_MASK | SHIFT_MASK, false)); this.AddBracketShortcut(true, "⟪", KeyStroke.getKeyStroke(VK_COMMA, CTRL_MASK | ALT_MASK | SHIFT_MASK, false), "⟫", KeyStroke.getKeyStroke(VK_PERIOD, CTRL_MASK | ALT_MASK | SHIFT_MASK, false)); } HotkeyAction(ArrayList<HotkeyHandler> keyStrokes) { super(); this.hotkeyHandlers = new ArrayList<HotkeyHandler>(); } public void AddBracketShortcut(boolean completeWithoutSelection, String open, KeyStroke openKeyStroke, String close, KeyStroke closeKeyStroke) { HotkeyBracketInsertionHandler handler = new HotkeyBracketInsertionHandler(completeWithoutSelection, open, openKeyStroke, close, closeKeyStroke); this.hotkeyHandlers.add(handler); // 注:原代码此处有截断,完整实现需补充KeymapManager相关的快捷键注册逻辑 // KeymapManager.getInsta... } // 补充实现AnAction的核心处理逻辑 @Override public void actionPerformed(AnActionEvent e) { Editor editor = e.getData(PlatformDataKeys.EDITOR); if (editor == null) return; Document document = editor.getDocument(); SelectionModel selectionModel = editor.getSelectionModel(); // 判断是否有选中内容,分情况处理 if (selectionModel.hasSelection()) { String selectedText = selectionModel.getSelectedText(); int start = selectionModel.getSelectionStart(); int end = selectionModel.getSelectionEnd(); // 根据触发的快捷键匹配对应字符对,示例为ALT_SHIFT+[对应的⦓和⦔ document.replaceString(start, end, "⦓" + selectedText + "⦔"); // 可选:选中包裹后的内容,方便后续编辑 selectionModel.setSelection(start, start + "⦓".length() + selectedText.length() + "⦔".length()); } else { // 无选中时插入单个字符 int caretOffset = editor.getCaretModel().getOffset(); document.insertString(caretOffset, "⦓"); editor.getCaretModel().moveToOffset(caretOffset + "⦓".length()); } } }
在plugin.xml中注册动作
要让这个快捷键生效,必须在插件的plugin.xml中注册该动作,并限定仅在你的自定义文件类型编辑器中生效:
<actions> <action id="YourPluginID.HotkeyAction" class="com.your.package.HotkeyAction" text="Unicode Bracket Shortcuts"> <!-- 绑定快捷键,示例为ALT_SHIFT+[ --> <keyboard-shortcut keymap="$default" first-keystroke="alt shift [" /> <!-- 可以添加更多快捷键绑定 --> <!-- 限定仅在自定义文件类型中生效,替换成你的自定义文件类型ID --> <constraint implementation="com.intellij.openapi.fileTypes.FileTypeConstraint" fileType="YOUR_CUSTOM_LANGUAGE_FILE_TYPE_ID"/> <!-- 可选:添加到编辑器右键菜单,不需要可删除 --> <add-to-group group-id="EditorPopupMenu" anchor="first"/> </action> </actions>
关键注意事项
- 确保
HotkeyBracketInsertionHandler类实现了快捷键与字符对的匹配逻辑(如果使用批量注册功能) - 快捷键绑定尽量避免与IntelliJ原生快捷键冲突,或通过文件类型约束规避冲突
- 旧版本IntelliJ SDK中,文档操作需要用
WriteCommandAction包裹,新版本SDK已自动处理该逻辑
内容的提问来源于stack exchange,提问作者CoryG
相关产品推荐
相关产品推荐

