Github
https://github.com/kungyutucheng/my_gradle_plugin
運(yùn)行環(huán)境
macOS 10.14.5
IntelliJ idea 2019.2.4
參考
sonarlint-intellij
mybatis-log-plugin
前言
最近在編寫(xiě)自己的一個(gè)idea插件,其中有個(gè)功能需要在idea下方彈出一個(gè)tool window,效果和mybatis log的tool window窗口類(lèi)似:

接下來(lái),一步步實(shí)現(xiàn)以下功能:
- 主體tool window
- 左側(cè)菜單欄Restart和Stop按鈕
- 左側(cè)菜單欄idea其他類(lèi)型按鈕
- 左側(cè)菜單欄自定義按鈕
源碼
1. 主體tool window功能
首先簡(jiǎn)單實(shí)現(xiàn)主體tool window的顯示,先增加一個(gè)action入口,點(diǎn)擊觸發(fā)tool window彈出,注冊(cè)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>
接著實(shí)現(xiàn)ConsoleViewAction類(lèi):
public class ConsoleViewAction extends AnAction {
@Override
public void actionPerformed(@NotNull AnActionEvent e) {
CustomExecutor executor = new CustomExecutor(e.getProject());
executor.run();
}
}
可以看到,僅僅是在actionPerformed方法中創(chuàng)建了一個(gè)CustomExecutor對(duì)象,并調(diào)用了run方法。CustomExecutor是我們自定義的執(zhí)行器,run方法被調(diào)用之后,會(huì)構(gòu)建一個(gè)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類(lèi)繼承了Executor,主要是定義了tool window的相關(guān)靜態(tài)信息,代碼如下:
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);
}
}
之后注冊(cè)executor擴(kuò)展:
<extensions defaultExtensionNs="com.intellij">
<executor implementation="com.kungyu.toolview.CustomRunExecutor" id="CustomRunExecutor"/>
</extensions>
運(yùn)行效果如下:


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方法中,通過(guò)rerunAction != null和stopAction != null來(lái)控制其可見(jiàn)性,通過(guò)stopEnabled.compute()來(lái)聲明stop按鈕是否可用,故在CustomExecutor新增全局變量:
private Runnable rerunAction;
private Runnable stopAction;
private Computable<Boolean> stopEnabled;
同時(shí),修改ConsoleViewAction,令其在初始化CustomExecutor的時(shí)候設(shè)置好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);
// 設(shè)置restart和stop
executor.withReturn(() -> runExecutor(project)).withStop(() -> ConfigUtil.setRunning(project,false), () ->
ConfigUtil.getRunning(project));
executor.run();
}
}
其中,ConfigUtil定義如下,主要用來(lái)持久化數(shù)據(jù):
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);
}
}
運(yùn)行效果如下:

3. idea其他類(lèi)型按鈕
可以通過(guò)在第二點(diǎn)中的createActionToolbar中增加其他類(lèi)型的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;
}
效果如下:

通過(guò)debug可以發(fā)現(xiàn),createConsoleActions返回下面這樣一個(gè)數(shù)組:

用了idea這么久,具體是哪些按鈕我想都很清楚了,其實(shí)就是文章一開(kāi)始效果圖里面左側(cè)第二欄那6個(gè)按鈕

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;
}
效果如下:

總結(jié)
總體而言,這節(jié)內(nèi)容還是比較簡(jiǎn)單的,最麻煩的莫過(guò)于需要去閱讀其他插件的代碼,效率偏低且感覺(jué)不系統(tǒng),很好奇官方文檔寫(xiě)得這么差,為何idea生態(tài)貌似挺好的(一堆插件)?