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如下:
<action id="com.kungyu.toolview.ConsoleViewAction" class="com.kungyu.toolview.ConsoleViewAction"
text="ConsoleViewAction" description="ConsoleViewAction">
<add-to-group group-id="ToolsMenu" anchor="last"/>
</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扩展:
<extensions defaultExtensionNs="com.intellij">
<executor implementation="com.kungyu.toolview.CustomRunExecutor" id="CustomRunExecutor"/>
</extensions>
运行效果如下:
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 != null
和stopAction != null
来控制其可见性,通过stopEnabled.compute()
来声明stop
按钮是否可用,故在CustomExecutor
新增全局变量:
private Runnable rerunAction;
private Runnable stopAction;
private Computable<Boolean> stopEnabled;
同时,修改ConsoleViewAction
,令其在初始化CustomExecutor
的时候设置好returnAction
、stopAction
和stopEnabled
:
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);
}
}
运行效果如下:
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;
}
效果如下:
通过debug可以发现,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生态貌似挺好的(一堆插件)?