(二十四)IntelliJ 插件开发——Idea下方工具窗口

Github

https://github.com/kungyutucheng/my_gradle_plugin

运行环境

macOS 10.14.5
IntelliJ idea 2019.2.4

参考

sonarlint-intellij
mybatis-log-plugin

前言

最近在编写自己的一个idea插件,其中有个功能需要在idea下方弹出一个tool window,效果和mybatis log的tool window窗口类似:


效果图

接下来,一步步实现以下功能:

  • 主体tool window
  • 左侧菜单栏Restart和Stop按钮
  • 左侧菜单栏idea其他类型按钮
  • 左侧菜单栏自定义按钮

源码

1. 主体tool window功能

首先简单实现主体tool window的显示,先增加一个action入口,点击触发tool window弹出,注册action如下:

        
            
        

接着实现ConsoleViewAction类:

public class ConsoleViewAction extends AnAction {
    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        CustomExecutor executor = new CustomExecutor(e.getProject());
        executor.run();
    }
}

可以看到,仅仅是在actionPerformed方法中创建了一个CustomExecutor对象,并调用了run方法。CustomExecutor是我们自定义的执行器,run方法被调用之后,会构建一个tool window并展示,具体代码如下:

public class CustomExecutor implements Disposable {

    private ConsoleView consoleView = null;

    private Project project = null;

    public CustomExecutor(@NotNull Project project) {
        this.project = project;
        this.consoleView = createConsoleView(project);
    }

    private ConsoleView createConsoleView(Project project) {
        TextConsoleBuilder consoleBuilder = TextConsoleBuilderFactory.getInstance().createBuilder(project);
        ConsoleView console = consoleBuilder.getConsole();
        return console;
    }

    @Override
    public void dispose() {
        Disposer.dispose(this);
    }

    public void run() {
        if (project.isDisposed()) {
            return;
        }

        Executor executor = CustomRunExecutor.getRunExecutorInstance();
        if (executor == null) {
            return;
        }

        final RunnerLayoutUi.Factory factory = RunnerLayoutUi.Factory.getInstance(project);
        RunnerLayoutUi layoutUi = factory.create("runnerId", "runnerTitle", "sessionName", project);
        final JPanel consolePanel = createConsolePanel(consoleView);

        RunContentDescriptor descriptor = new RunContentDescriptor(new RunProfile() {
            @Nullable
            @Override
            public RunProfileState getState(@NotNull Executor executor, @NotNull ExecutionEnvironment environment) throws ExecutionException {
                return null;
            }

            @NotNull
            @Override
            public String getName() {
                return "name";
            }

            @Nullable
            @Override
            public Icon getIcon() {
                return null;
            }
        }, new DefaultExecutionResult(), layoutUi);
        descriptor.setExecutionId(System.nanoTime());

        final Content content = layoutUi.createContent("contentId", consolePanel, "displayName", AllIcons.Debugger.Console, consolePanel);
        content.setCloseable(false);
        layoutUi.addContent(content);

        Disposer.register(descriptor,this);

        Disposer.register(content, consoleView);

        ExecutionManager.getInstance(project).getContentManager().showRunContent(executor, descriptor);
    }
}

CustomRunExecutor类继承了Executor,主要是定义了tool window的相关静态信息,代码如下:

public class CustomRunExecutor extends Executor {

    public static final String TOOL_WINDOW_ID = "tool window plugin";

    @Override
    public String getToolWindowId() {
        return TOOL_WINDOW_ID;
    }

    @Override
    public Icon getToolWindowIcon() {
        return IconUtil.ICON;
    }

    @NotNull
    @Override
    public Icon getIcon() {
        return IconUtil.ICON;
    }

    @Override
    public Icon getDisabledIcon() {
        return IconUtil.ICON;
    }

    @Override
    public String getDescription() {
        return TOOL_WINDOW_ID;
    }

    @NotNull
    @Override
    public String getActionName() {
        return TOOL_WINDOW_ID;
    }

    @NotNull
    @Override
    public String getId() {
        return StringConst.PLUGIN_ID;
    }

    @NotNull
    @Override
    public String getStartActionText() {
        return TOOL_WINDOW_ID;
    }

    @Override
    public String getContextActionId() {
        return "custom context action id";
    }

    @Override
    public String getHelpId() {
        return TOOL_WINDOW_ID;
    }

    public static Executor getRunExecutorInstance() {
        return ExecutorRegistry.getInstance().getExecutorById(StringConst.PLUGIN_ID);
    }
}

之后注册executor扩展:

    
        
    

运行效果如下:


入口
主体tool window

2. 新增Restart和Stop按钮

首先run方法中增加如下代码,代表添加左边工具条:

layoutUi.getOptions().setLeftToolbar(createActionToolbar(consolePanel, consoleView, layoutUi, descriptor, executor), "RunnerToolbar");

createActionToolbar方法定义如下:

    private ActionGroup createActionToolbar(JPanel consolePanel, ConsoleView consoleView, RunnerLayoutUi layoutUi, RunContentDescriptor descriptor, Executor executor) {
        final DefaultActionGroup actionGroup = new DefaultActionGroup();
        actionGroup.add(new RerunAction(consolePanel, consoleView));
        actionGroup.add(new StopAction());
        return actionGroup;
    }

RetunrnAction定义如下:

    private class RerunAction extends AnAction implements DumbAware {
        private final ConsoleView consoleView;

        public RerunAction(JComponent consolePanel, ConsoleView consoleView) {
            super("Rerun", "Rerun", AllIcons.Actions.Restart);
            this.consoleView = consoleView;
            registerCustomShortcutSet(CommonShortcuts.getRerun(), consolePanel);
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            Disposer.dispose(consoleView);
            rerunAction.run();
        }

        @Override
        public void update(AnActionEvent e) {
            e.getPresentation().setVisible(rerunAction != null);
            e.getPresentation().setIcon(AllIcons.Actions.Restart);
        }
    }

StopAction定义如下:

    private class StopAction extends AnAction implements DumbAware {
        public StopAction() {
            super("Stop", "Stop", AllIcons.Actions.Suspend);
        }

        @Override
        public void actionPerformed(AnActionEvent e) {
            stopAction.run();
        }

        @Override
        public void update(AnActionEvent e) {
            e.getPresentation().setVisible(stopAction != null);
            e.getPresentation().setEnabled(stopEnabled != null && stopEnabled.compute());
        }
    }

可以看到,在update方法中,通过rerunAction != nullstopAction != null来控制其可见性,通过stopEnabled.compute()来声明stop按钮是否可用,故在CustomExecutor新增全局变量:

    private Runnable rerunAction;
    private Runnable stopAction;

    private Computable stopEnabled;

同时,修改ConsoleViewAction,令其在初始化CustomExecutor的时候设置好returnActionstopActionstopEnabled

public class ConsoleViewAction extends AnAction {
    @Override
    public void actionPerformed(@NotNull AnActionEvent e) {
        runExecutor(e.getProject());

    }

    public void runExecutor(Project project) {
        if (project == null) {
            return;
        }
        CustomExecutor executor = new CustomExecutor(project);
        // 设置restart和stop
        executor.withReturn(() -> runExecutor(project)).withStop(() -> ConfigUtil.setRunning(project,false), () ->
            ConfigUtil.getRunning(project));
        executor.run();
    }
}

其中,ConfigUtil定义如下,主要用来持久化数据:

public class ConfigUtil {

    public static void setRunning(Project project, boolean value) {
        PropertiesComponent.getInstance(project).setValue(StringConst.RUNNING_KEY, value);
    }

    public static boolean getRunning(Project project){
        return PropertiesComponent.getInstance(project).getBoolean(StringConst.RUNNING_KEY);
    }
}

运行效果如下:


return和stop

3. idea其他类型按钮

可以通过在第二点中的createActionToolbar中增加其他类型的idea按钮,代码如下:

    private ActionGroup createActionToolbar(JPanel consolePanel, ConsoleView consoleView, RunnerLayoutUi layoutUi, RunContentDescriptor descriptor, Executor executor) {
        final DefaultActionGroup actionGroup = new DefaultActionGroup();
        actionGroup.add(new RerunAction(consolePanel, consoleView));
        actionGroup.add(new StopAction());
        actionGroup.add(consoleView.createConsoleActions()[2]);
        actionGroup.add(consoleView.createConsoleActions()[3]);
        actionGroup.add(consoleView.createConsoleActions()[5]);
        return actionGroup;
    }

效果如下:


idea其他类型按钮

通过debug可以发现,createConsoleActions返回下面这样一个数组:

createConsoleActions

用了idea这么久,具体是哪些按钮我想都很清楚了,其实就是文章一开始效果图里面左侧第二栏那6个按钮

效果图

4. 自定义按钮

同理,也是修改createActionToolbar

    private ActionGroup createActionToolbar(JPanel consolePanel, ConsoleView consoleView, RunnerLayoutUi layoutUi, RunContentDescriptor descriptor, Executor executor) {
        final DefaultActionGroup actionGroup = new DefaultActionGroup();
        actionGroup.add(new RerunAction(consolePanel, consoleView));
        actionGroup.add(new StopAction());
        actionGroup.add(consoleView.createConsoleActions()[2]);
        actionGroup.add(consoleView.createConsoleActions()[3]);
        actionGroup.add(consoleView.createConsoleActions()[5]);
        actionGroup.add(new CustomAction("custom action", "custom action", IconUtil.ICON));
        return actionGroup;
    }

效果如下:


自定义按钮

总结

总体而言,这节内容还是比较简单的,最麻烦的莫过于需要去阅读其他插件的代码,效率偏低且感觉不系统,很好奇官方文档写得这么差,为何idea生态貌似挺好的(一堆插件)?

你可能感兴趣的:((二十四)IntelliJ 插件开发——Idea下方工具窗口)