YON Blog

IntelliJ Plugin - Action 获取 Tree 上节点

开发插件时使用 SimpleTree 来做数据的展示,SimpleTree 上可以配置节点上的右键菜单:

// 代码引用自 IDEA 社区版源码
// com.intellij.openapi.externalSystem.view.ExternalProjectsViewImpl#initTree
myTree.addMouseListener(new PopupHandler() {
    @Override
    public void invokePopup(final Component comp, final int x, final int y) {
        final String id = getMenuId(getSelectedNodes(ExternalSystemNode.class));
        if (id != null) {
            final ActionGroup actionGroup = (ActionGroup)actionManager.getAction(id);
            if (actionGroup != null) {
                actionManager.createActionPopupMenu(ExternalProjectsViewImpl.this.getName(), actionGroup).getComponent().show(comp, x, y);
            }
        }
    }

    @Nullable
    private String getMenuId(Collection<? extends ExternalSystemNode> nodes) {
        String id = null;
        for (ExternalSystemNode node : nodes) {
            String menuId = node.getMenuId();
            if (menuId == null) {
                return null;
            }
            if (id == null) {
                id = menuId;
            }
            else if (!id.equals(menuId)) {
                return null;
            }
        }
        return id;
    }
});

接下来的问题就是,在 Action 里如何获取右击时被选中的节点?继续查看 IDEA 源码,可以看到 Gradle 插件里最终可以执行的任务的 Node 是 RunConfigurationNode,该节点的 MenuId 是 ExternalSystemView.RunConfigurationMenu,最终找到相关的 Action,例如 EditExternalSystemRunConfigurationAction,里面获取选中节点是这样实现:

@Override
public void actionPerformed(@NotNull AnActionEvent e) {
    // 省略
    final List<ExternalSystemNode> selectedNodes = e.getData(ExternalSystemDataKeys.SELECTED_NODES);
    // 省略
}

那么 AnActionEvent 的数据又是谁放的呢?使用 ExternalSystemDataKeys.SELECTED_NODES 做关键字继续查找,会发现是在 ExternalProjectsViewImpl 实现的 com.intellij.openapi.actionSystem.DataProvider#getData接口里使用:

@Nullable
@Override
public Object getData(@NotNull @NonNls String dataId) {
    // 省略
    if (ExternalSystemDataKeys.SELECTED_NODES.is(dataId)) return getSelectedNodes(ExternalSystemNode.class);
    // 省略

    return super.getData(dataId);
}

阅读 DataProvider 接口的注释,可以知道 AnActionEvent 获取 Data 会在 UI 树上向上遍历,如果 UI 组件实现了 DataProvider 接口就会询问其能否返回对应的数据,如果有组件返回了非 null 数据那么查找就结束。
Gradle 插件的实现里 ExternalProjectsViewImpl 是最外层的界面组件,所以在这里实现 DataProvider 接口来提供数据。我们也可以继承 SimpleTree 来实现 DataProvider 接口提供数据。