操作(Actions)
操作(Actions)
IntelliJ Platform允许用户定制菜单和工具栏。例如 File | Open File..菜单 和 Open... 工具按钮
在IntelliJ Platform注册了实现的Action后,用户执行相关操作时,将会调用Action的实现类
新建Actions 教程是如何新建一个Action
分组Actions 教程主要是介绍Action分组的3种类型
实现Action
新建Action需要继承抽象类AnAction
注意
AnAction的子类不能有任何类型的成员变量,这是因为 AnAction 子类的实例存在于应用程序的整个生命周期中。如果AnAction 子类使用成员变量存储了某些数据,但是没有及时清除,将会发生数据泄漏。
需要重写的方法
必须重写 AnAction.actionPerformed()方法,如果需要也可以重写 AnAction.update()方法。
AnAction.update(): IntelliJ Platform更新Action状态(state)的时候会调用AnAction.update() 方法,状态(state)有2个值enabled, visible,可以控制是否显示该Action。调用该方法的时候会传入AnActionEvent 并包含当前Action的上下文信息。通过改变事件上下文中Presentation 的状态来启用或禁用该Action,该方法需要很快的执行速度
AnAction.actionPerformed() :当Action处于可用状态,并被用户使用的时候,会调用该Action的 AnAction.actionPerformed() 方法。调用该方法的时候会传入AnActionEvent ,可以获取到上下文信息,例如 projects, files, selection等。
重写AnAction.update()方法
IntelliJ Platform会定期调用 AnAction.update() 方法。这个方法会评估Action的状态来确定禁用/启用该Action。该方法必须保证能执行完成(不会异常),否则,该Action将会 "stuck"(卡住)
注意
AnAction.update() 方法将在 UI 线程上频繁调用。该方法必须很快的执行,不要执行耗时长的业务操作
提示
如果不能很快的确定并修改action的状态,那么应该在该Action的 AnAction.actionPerformed()方法中进行判断,并通知用户如果不满足某些条件,该Action将不会执行
确定Action上下文
调用AnAction.update() 方法的时候,会传入AnActionEvent 参数,通过AnActionEvent 参数可以获取到Action的上下文信息,例如 Presentation,以及该Action是否是从哪里触发的。也可以通过 AnActionEvent.getData() 方法获取其它信息。AnActionEvent.getData() 方法传入不同的key可以获取到不同的信息,可以在CommonDataKeys 中查看所有的key,包含 Project, Editor, PsiFile等。获取此类信息执行速度很快,可以在AnAction.update() 方法中调用 (AnAction.update() 方法要尽可能的快,不要执行耗时长的操作)
激活Action
根据Action的上下文信息,AnAction.update() 可以激活,禁用,或隐藏该Action。Action的状态通过 AnActionEvent.getPresentation() 方法获取到Presentation对象来进行控制,
默认的Presentation 对象是一组有关菜单或工具栏操作的描述性信息。所有的Action对象(菜单,工具栏,导航搜索等)都拥有一个唯一的Presentation。Presentation对象里包含Action的文本,描述,图标,是否可见和状态(enable/disable)等。Presentation 的属性从注册Action的信息中初始化的。
Presentation.setEnabled() 可以修改Action的enabled/disabled状态
Presentation.setVisible() 可以修改Action是否可见
如果Action的状态是enabled,用户触发该Action 的时候将会调用AnAction.actionPerformed() 方法。菜单Action的显示位置根据Action的注册信息来确定。工具栏Action会显示它的图标
如果Action的状态是disabled。AnAction.actionPerformed() 将不会被执行,菜单Action会显示他的disabled状态的图标。
如果Action 配置了compact 属性(compact=true),那么,该Action被禁用的时候,将不会显示在工具栏中(compact属性相关查看分组Actions )
提示
有任何用户活动或焦点转移时都会调用工具栏中Action的 update()方法。如果没有任何用户活动或焦点转移,Action可用性发生变化,可以调用 ActivityTracker.getInstance().inc() 以通知操作子系统更新所有工具栏操作。
示例 PopupDialogAction.update() 演示了根据项目是否打开来启用菜单操作的示例。
重写AnAction.actionPerformed方法
用户选择某个菜单或工具栏的时候,该菜单或工具栏绑定的 Action 中的 AnAction.actionPerformed 方法将会被执行,这个方法执行真正的业务逻辑。
使用 AnActionEvent.getData(CommonDataKeys) 可以获取到 Project, Editor, PsiFile等信息。例如:可以在 AnAction.actionPerformed 方法中修改,删除或在打开的文档中增加 PSI(Program Structure Interface)元素。
和AnAction.update() 相比,AnAction.actionPerformed方法对执行速度要求并没那么严格,但依然需要尽量提升执行速度。
action_basics PopupDialogAction.actionPerformed() 示例中演示了检查 PSI 元素的功能
Action IDs
每一个 action和action group都有唯一的id. IntelliJ Platform中已定义的 Action ID可以在 IdeActions 中找到
分组Actions
Actions可以进行分组,多个Action组合成一个工具栏或一个菜单。
Action可以同时被加入多个分组,从而出现在不同的地方。同一个Action可以注册在不同的地方,但是需要配置不同的id。
Presentation
配置在不同位置的同一个Action具有不同的 Presentation 对象。所以同一个Action出现在不同的地方的时候,有不同的文本和图标。通过 AnAction.getTemplatePresentation() 方法可以为Action复制不同的 Presentation
Compact属性
Group Action的 compact 属性可以控制Action 在disabled 状态时是否显示在工具栏中。在 注册Action 可以查看在注册Action Group时如何设置compact 属性。 compact 设置为true时,Action 只有在enabled 和visible 状态时才显示。
分组Actions 教程中提供了示例
注册Action
有2种方式可以注册Action
- 在plugin.xml中注册
- 通过代码注册
在plugin.xml中注册
下面的示例介绍了如何在 plugin.xml中注册Action
设置显示文本
从2020.1版本开始,可以根据Action出现的位置配置不同的文本。根据Action的位置(如菜单,工具栏等),可以使用 <override-text> 为Action定义不同的文本。 在2020.3及之后的版本中 Group Action也支持 <override-text> 元素
详情查看下面示例
设置Action别名
2020.3版本以后,用户可以通过 Help -> Find Action 来定位Action. 如果需要通过别名搜索到该Action,可以在 <action> 或 <reference> 元素中配置一个或多个 <synonym> 元素
<action id="MyAction" text="My Action Name" >
<!-- 如果text要国际化,需要把 text属性替换成 key属性, 属性值配置为国际化文本的key (国际化文本的key) ,如何定义key,见下文-->
<!-- <synonym key="MyAction.synonym.key"/>-->
<synonym text="Another Search Term"/>
</action>
如果文本需要国际化,在你的国际化语言包 定义key
禁用组搜索
2020.3以后,设置 searchable="false",用户通过 Help -> Find Action results 搜索Action时,将搜索不到当前 Action
Actions和Groups的国际化
Action 和 group的国际化,需要在国际化配置文件 $NAME$Bundle.properties 里定义对应的key。可以参考示例插件action_basics。
国际化的文本需要配置在资源包里
国际化的文本资源包需要在 plugin.xml 中声明,示例插件action_basics 中,只提供了一个国际化文本资源包,如下所示:
<resource-bundle>messages.BasicActionsBundle</resource-bundle>
如果需要在 2020.1版本中定义国际化文本资源包,需要在Action中进行配置,如下所示:
<actions resource-bundle="messages.MyActionsBundle">
<!-- action/group defined here will use keys from MyActionsBundle.properties -->
</actions>
Action注册参考示例
ActionPlaces 类中的常量定义了Action 可以配置的的显示位置
IntelliJ Platform中的Action都在 PlatformActions.xml 中进行了注册
通过 Code Completion , Quick Definition 和 Quick Documentation 功能可以查看更多相关信息
提示
通过 UI Inspector 可以查找平台的Action Id
plugin.xml 中 Action 和 Group 的配置如下:
<actions>
<!--
注册Action,配置描述如下
- "id" (必填) - action id,需要保持IDE中的唯一性
- "class" (必填) - action实现类的全限定命名
- "text" (必填) - 菜单中的action显示名称,工具栏action的文本提示
- "use-shortcut-of" (可选) - 快捷键
- "description" (可选) - action获得焦点时,显示的描述文本
- "icon" (可选) - 图标
-->
<action
id="VssIntegration.GarbageCollection"
class="com.example.impl.CollectGarbage"
text="Garbage Collector: Collect _Garbage"
description="Run garbage collector"
icon="icons/garbage.png">
<!--
<override-text>为菜单定义了别名:
- "text" (必填) - action的显示文本
- "place" (必填) - action的显示位置,下面的示例中,当Action显示在主菜单时,会显示为 “Collect _Garbage"
第二个 <override-text> 的配置表示,当Action显示在EditorPopup时,和在主菜单中显示的文本保持一致
-->
<override-text place="MainMenu" text="Collect _Garbage"/>
<override-text place="EditorPopup" use-text-of-place="MainMenu"/>
<!-- 搜索action时,可以用 GC进行搜索 -->
<synonym text="GC"/>
<!--
配置<add-to-group> 属性,可以把Action加入某个group里,一个action可以加入多个group
- "group-id" (必填) - 要加入的group的id,该group必须实现 DefaultActionGroup 类
- "anchor" (必填) - action在group中的位置,可以配置为: "first", "last","before", and "after".
- "relative-to-action" 如果anchor配置为"before" 或 "after"时,必须配置该属性,表示位于该属性的before或after
-->
<add-to-group
group-id="ToolsMenu"
relative-to-action="GenerateJavadoc"
anchor="after"/>
<!--
<keyboard-shortcut> 该action的快捷键,一个Action可以配置多个快捷键
- "first-keystroke" (必填) - 快捷链
- "second-keystroke" (可选) - 第2种快捷键
- "keymap" (可选) - 键盘映射(keymap), com.intellij.openapi.keymap.KeymapManager定义了keymap的列表
-->
<!-- 给所有的keymap增加第一,第二快捷键 -->
<keyboard-shortcut
keymap="$default"
first-keystroke="control alt G"
second-keystroke="C"/>
<!-- keymap为 Mac OS X时生效
- "remove" (可选) - 移除配置的快捷锓
-->
<keyboard-shortcut
keymap="Mac OS X"
first-keystroke="control alt G"
second-keystroke="C"
remove="true"/>
<!--
在"Mac OS X 10.5+"键盘中。删除其它配置的快捷键,只留这一种快捷键
- "replace-all" (可选) - 在添加指定快捷键之前从action中删除所有键盘和鼠标快捷方式
-->
<keyboard-shortcut
keymap="Mac OS X 10.5+"
first-keystroke="control alt G"
second-keystroke="C"
replace-all="true"/>
<!--
<mouse-shortcut> 鼠标快捷键,可以有多个
- "keystroke" (必选) - 指定鼠标按键和修饰符
* 鼠标按钮: "button1", "button2", "button3"
* 修饰符: "shift", "control", "meta", "alt", "altGraph"
* 双击: "doubleClick"
- "keymap" (必选) - 键盘映射(keymap), 哪种键盘时激活该快捷键,com.intellij.openapi.keymap.KeymapManager定义了keymap的列表
也可以配置 "remove" and "replace-all" 属性,详见看上面的配置介绍
-->
<mouse-shortcut
keymap="$default"
keystroke="control button3 doubleClick"/>
</action>
<!--
该action没有配置文本和描述属性. 如果它有资源包(resource bundle),将根据id自动匹配资源包中配置的文本和描述
-->
<action
id="sdk.action.PopupDialogAction"
class="sdk.action.PopupDialogAction"
icon="SdkIcons.Sdk_default_icon"/>
<!--
<group> 元素定义了action group。<action>, <group>和 <separator>元素将被自动包含进该group
- "id" (必选) - group的id,唯一
- "class" (可选) - 指定group实现类的全限定命名(FQN),如果不配置,将会使用 com.intellij.openapi.actionSystem.DefaultActionGroup
- "text" (可选) - group的文本 (显示子菜单中菜单项的文本).
- "description" (可选) 当该group获取焦点时,显示的文本
- "icon" (可选) - 图标
- "popup" (可选) - 配置group在菜单中的显示方式
* "true" - group actions放在子菜单中
* "false" - 放在当前菜单中,由分隔线分隔.
- "compact" (optional) - compact="true" 时,如果action不可用,则不显示该action
-->
<group
class="com.example.impl.MyActionGroup"
id="TestActionGroup"
text="Test Group"
description="Group with test actions"
icon="icons/testgroup.png"
popup="true"
compact="true">
<action
id="VssIntegration.TestAction"
class="com.example.impl.TestAction"
text="My Test Action"
description="My test action"/>
<!-- action之间的分隔线 -->
<separator/>
<group id="TestActionSubGroup"/>
<!-- 把另一个action加入该group, ref配置为另一个action的id -->
<reference ref="EditorCopy"/>
<add-to-group
group-id="MainMenu"
relative-to-action="HelpMenu"
anchor="before"/>
</group>
</actions>
用代码注册Action
用代码注册Action,需要2步:
通过 ActionManager.registerAction() 注册action ,参数包含**action id 和 AnAction 的实现类
需要把action加了一个或多个group中。通过 ActionManager.getAction(group id) 可以获取到 group (ActionManager.getAction(group id)返回值可以强转为 DefaultActionGroup)
构建ActionUI
如果插件需要包含一个工具栏或一个弹出菜单,可以使用 ActionPopupMenu 和 ActionToolbar 。通过 ActionManager.createActionPopupMenu() 和 createActionToolbar() 方法可以创建弹出菜单对象和工具栏对象。可以使用 getComponent() 方法从弹出菜单对象和工具栏对象中获取Swing 组件。
如果一个action 需要加入某个组件(例如工具窗口中的面板),使用 ActionToolbar.setTargetComponent(目标组件) 方法。设置目标组件保证了该action工具栏的状态和目标组件保持一致.
查看 Toolbar 文档,了解更多。
常用action相关类
Toggle/Selection
带有"selected"/"pressed"的action 可以使用ToggleAction (例如: 带有复选框的菜单项, 工具栏操作按钮)。 也可以了解 ToggleOptionAction。
Back/Forward 导航
BackAction 提供了回到上一步编辑位置的功能,ForwardAction 提供了回到后一步编辑位置的功能。
History history = anActionEvent.getData(History.KEY);
history.back();
history.forward();
Action运行时占位符
如果Action需要在运行时注册,可以使用注册一个EmptyAction 方便在 Settings/Preferences -> Keymap 中对该Action进行设置