Skip to content

Commit 2ea8d22

Browse files
committed
feat: Create a Quarkus Dev Explorer in the IDE
Fixes #1175 Signed-off-by: azerr <azerr@redhat.com>
1 parent 9c95979 commit 2ea8d22

16 files changed

+733
-6
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2023 Red Hat Inc. and others.
3+
*
4+
* This program and the accompanying materials are made available under the
5+
* terms of the Eclipse Public License v. 2.0 which is available at
6+
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7+
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
8+
*
9+
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10+
*
11+
* Contributors:
12+
* Red Hat Inc. - initial API and implementation
13+
*******************************************************************************/
14+
package com.redhat.devtools.intellij.quarkus;
15+
16+
import com.intellij.DynamicBundle;
17+
import org.jetbrains.annotations.Nls;
18+
import org.jetbrains.annotations.NonNls;
19+
import org.jetbrains.annotations.NotNull;
20+
import org.jetbrains.annotations.PropertyKey;
21+
22+
import java.util.function.Supplier;
23+
24+
/**
25+
* Quarkus messages bundle.
26+
*/
27+
public final class QuarkusBundle extends DynamicBundle {
28+
29+
@NonNls public static final String BUNDLE = "messages.QuarkusBundle";
30+
private static final QuarkusBundle INSTANCE = new QuarkusBundle();
31+
32+
private QuarkusBundle() {
33+
super(BUNDLE);
34+
}
35+
36+
@NotNull
37+
public static @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) {
38+
return INSTANCE.getMessage(key, params);
39+
}
40+
41+
@NotNull
42+
public static Supplier<@Nls String> messagePointer(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) {
43+
return INSTANCE.getLazyMessage(key, params);
44+
}
45+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.redhat.devtools.intellij.quarkus.explorer;
2+
3+
import com.intellij.icons.AllIcons;
4+
import com.intellij.openapi.module.Module;
5+
6+
import javax.swing.*;
7+
import javax.swing.tree.DefaultMutableTreeNode;
8+
import javax.swing.tree.DefaultTreeModel;
9+
10+
public class QuarkusActionNode extends DefaultMutableTreeNode {
11+
12+
private final String name;
13+
14+
private final Module module;
15+
16+
private final DefaultTreeModel treeModel;
17+
public QuarkusActionNode(String name, Module module, DefaultTreeModel treeModel) {
18+
this.name = name;
19+
this.module = module;
20+
this.treeModel = treeModel;
21+
}
22+
23+
public String getName() {
24+
return name;
25+
}
26+
27+
public Module getModule() {
28+
return module;
29+
}
30+
31+
public String getDisplayName() {
32+
return name;
33+
}
34+
35+
public Icon getIcon() {
36+
return AllIcons.Actions.InlayGear;
37+
}
38+
39+
public void refreshNode() {
40+
treeModel.nodeChanged(this);
41+
}
42+
}
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
package com.redhat.devtools.intellij.quarkus.explorer;
2+
3+
import com.intellij.execution.ExecutionListener;
4+
import com.intellij.execution.ExecutionManager;
5+
import com.intellij.execution.RunManagerListener;
6+
import com.intellij.execution.RunnerAndConfigurationSettings;
7+
import com.intellij.execution.process.ProcessHandler;
8+
import com.intellij.execution.runners.ExecutionEnvironment;
9+
import com.intellij.ide.DataManager;
10+
import com.intellij.openapi.Disposable;
11+
import com.intellij.openapi.actionSystem.*;
12+
import com.intellij.openapi.application.ReadAction;
13+
import com.intellij.openapi.module.Module;
14+
import com.intellij.openapi.module.ModuleManager;
15+
import com.intellij.openapi.project.DumbService;
16+
import com.intellij.openapi.project.Project;
17+
import com.intellij.openapi.ui.SimpleToolWindowPanel;
18+
import com.intellij.ui.AnimatedIcon;
19+
import com.intellij.ui.DoubleClickListener;
20+
import com.intellij.ui.treeStructure.Tree;
21+
import com.intellij.util.concurrency.AppExecutorUtil;
22+
import com.intellij.util.messages.MessageBusConnection;
23+
import com.redhat.devtools.intellij.quarkus.explorer.actions.QuarkusDevStartAction;
24+
import com.redhat.devtools.intellij.quarkus.run.QuarkusRunConfiguration;
25+
import com.redhat.microprofile.psi.quarkus.PsiQuarkusUtils;
26+
import org.jetbrains.annotations.NotNull;
27+
import org.jetbrains.annotations.Nullable;
28+
29+
import javax.swing.tree.DefaultMutableTreeNode;
30+
import javax.swing.tree.DefaultTreeModel;
31+
import javax.swing.tree.TreeNode;
32+
import javax.swing.tree.TreePath;
33+
import java.awt.event.KeyAdapter;
34+
import java.awt.event.KeyEvent;
35+
import java.awt.event.MouseEvent;
36+
import java.util.Enumeration;
37+
38+
import static com.redhat.devtools.intellij.quarkus.run.QuarkusRunConfiguration.QUARKUS_CONFIGURATION;
39+
40+
public class QuarkusExplorer extends SimpleToolWindowPanel implements Disposable {
41+
42+
private final Tree tree;
43+
private final Project project;
44+
45+
public QuarkusExplorer(@NotNull Project project) {
46+
super(true, true);
47+
this.project = project;
48+
tree = buildTree();
49+
this.setContent(tree);
50+
load();
51+
}
52+
53+
/**
54+
* Builds the Language server tree
55+
*
56+
* @return Tree object of all language servers
57+
*/
58+
private Tree buildTree() {
59+
60+
DefaultMutableTreeNode top = new DefaultMutableTreeNode("Quarkus projects");
61+
62+
Tree tree = new Tree(top);
63+
tree.setRootVisible(false);
64+
tree.setCellRenderer(new QuarkusTreeRenderer());
65+
66+
tree.putClientProperty(AnimatedIcon.ANIMATION_IN_RENDERER_ALLOWED, true);
67+
68+
((DefaultTreeModel) tree.getModel()).reload(top);
69+
70+
71+
var doubleClickListener = new DoubleClickListener() {
72+
@Override
73+
protected boolean onDoubleClick(@NotNull MouseEvent event) {
74+
executeAction(tree);
75+
return false;
76+
}
77+
};
78+
doubleClickListener.installOn(tree);
79+
80+
tree.addKeyListener(new KeyAdapter() {
81+
@Override
82+
public void keyPressed(KeyEvent e) {
83+
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
84+
executeAction(tree);
85+
}
86+
}
87+
});
88+
89+
MessageBusConnection connection = project.getMessageBus().connect(project);
90+
connection.subscribe(RunManagerListener.TOPIC, new RunManagerListener() {
91+
@Override
92+
public void runConfigurationSelected(@Nullable RunnerAndConfigurationSettings settings) {
93+
// Do nothing
94+
}
95+
96+
@Override
97+
public void runConfigurationAdded(@NotNull RunnerAndConfigurationSettings settings) {
98+
// TODO: refresh tree
99+
}
100+
});
101+
connection.subscribe(ExecutionManager.EXECUTION_TOPIC, new ExecutionListener() {
102+
103+
@Override
104+
public void processStarting(@NotNull String executorId, @NotNull ExecutionEnvironment env, @NotNull ProcessHandler handler) {
105+
QuarkusRunApplicationNode application = findQuarkusApplication(env);
106+
if (application != null) {
107+
application.setApplicationStatus(QuarkusRunApplicationNode.QuarkusApplicationStatus.starting);
108+
}
109+
}
110+
111+
@Override
112+
public void processNotStarted(@NotNull String executorId, @NotNull ExecutionEnvironment env) {
113+
QuarkusRunApplicationNode application = findQuarkusApplication(env);
114+
if (application != null) {
115+
application.setApplicationStatus(QuarkusRunApplicationNode.QuarkusApplicationStatus.stopped);
116+
}
117+
}
118+
119+
@Override
120+
public void processStarted(@NotNull String executorId, @NotNull ExecutionEnvironment env, final @NotNull ProcessHandler handler) {
121+
QuarkusRunApplicationNode application = findQuarkusApplication(env);
122+
if (application != null) {
123+
application.setApplicationStatus(QuarkusRunApplicationNode.QuarkusApplicationStatus.started);
124+
}
125+
}
126+
127+
@Override
128+
public void processTerminating(@NotNull String executorId, @NotNull ExecutionEnvironment env, @NotNull ProcessHandler handler) {
129+
QuarkusRunApplicationNode application = findQuarkusApplication(env);
130+
if (application != null) {
131+
application.setApplicationStatus(QuarkusRunApplicationNode.QuarkusApplicationStatus.stopping);
132+
}
133+
}
134+
135+
@Override
136+
public void processTerminated(@NotNull String executorId, @NotNull ExecutionEnvironment env, @NotNull ProcessHandler handler, int exitCode) {
137+
QuarkusRunApplicationNode application = findQuarkusApplication(env);
138+
if (application != null) {
139+
application.setApplicationStatus(QuarkusRunApplicationNode.QuarkusApplicationStatus.stopped);
140+
}
141+
}
142+
143+
private @Nullable QuarkusRunApplicationNode findQuarkusApplication(@NotNull ExecutionEnvironment env) {
144+
QuarkusRunConfiguration runConfiguration = env.getDataContext() != null ? (QuarkusRunConfiguration) env.getDataContext().getData(QUARKUS_CONFIGURATION) : null;
145+
if (runConfiguration == null) {
146+
return null;
147+
}
148+
149+
Module module = runConfiguration.getModule();
150+
DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
151+
Enumeration<TreeNode> children = root.children();
152+
while (children.hasMoreElements()) {
153+
TreeNode node = children.nextElement();
154+
if (node instanceof QuarkusProjectNode && module.equals(((QuarkusProjectNode) node).getModule())) {
155+
QuarkusProjectNode project = (QuarkusProjectNode) node;
156+
Enumeration<TreeNode> children2 = project.children();
157+
while (children2.hasMoreElements()) {
158+
TreeNode node2 = children2.nextElement();
159+
if (node2 instanceof QuarkusRunApplicationNode && runConfiguration.equals(((QuarkusRunApplicationNode) node2).getConfiguration())) {
160+
return (QuarkusRunApplicationNode) node2;
161+
}
162+
}
163+
break;
164+
}
165+
}
166+
return null;
167+
}
168+
});
169+
return tree;
170+
}
171+
172+
private void load() {
173+
var action = ReadAction.nonBlocking(() -> {
174+
DefaultMutableTreeNode root = (DefaultMutableTreeNode) ((DefaultTreeModel) tree.getModel()).getRoot();
175+
Module[] modules = ModuleManager.getInstance(project).getModules();
176+
for (Module javaProject : modules) {
177+
if (PsiQuarkusUtils.isQuarkusProject(javaProject)) {
178+
QuarkusProjectNode projectNode = new QuarkusProjectNode(javaProject);
179+
root.add(projectNode);
180+
// Fill Quarkus actions
181+
projectNode.add(new QuarkusRunApplicationNode(javaProject, (DefaultTreeModel) tree.getModel()));
182+
}
183+
}
184+
((DefaultTreeModel) tree.getModel()).reload(root);
185+
});
186+
var executeInSmartMode = DumbService.getInstance(project).isDumb();
187+
if (executeInSmartMode) {
188+
action = action.inSmartMode(project);
189+
}
190+
action
191+
.submit(AppExecutorUtil.getAppExecutorService());
192+
}
193+
194+
private static void executeAction(Tree tree) {
195+
final TreePath path = tree.getSelectionPath();
196+
Object node = path.getLastPathComponent();
197+
if (node instanceof QuarkusActionNode) {
198+
ActionManager am = ActionManager.getInstance();
199+
String actionNodeName = ((QuarkusActionNode) node).getName();
200+
201+
String actionId = QuarkusDevStartAction.ACTION_ID;
202+
203+
AnAction action = am.getAction(actionId);
204+
if (action != null) {
205+
action.actionPerformed(new AnActionEvent(null,
206+
DataManager.getInstance().getDataContext(tree),
207+
ActionPlaces.UNKNOWN, new Presentation(),
208+
am, 0));
209+
}
210+
211+
}
212+
}
213+
214+
@Override
215+
public void dispose() {
216+
217+
}
218+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.redhat.devtools.intellij.quarkus.explorer;
2+
3+
import com.intellij.openapi.module.Module;
4+
import com.redhat.devtools.intellij.quarkus.lang.QuarkusIcons;
5+
6+
import javax.swing.*;
7+
import javax.swing.tree.DefaultMutableTreeNode;
8+
9+
public class QuarkusProjectNode extends DefaultMutableTreeNode {
10+
11+
private final Module module;
12+
13+
public QuarkusProjectNode(Module module) {
14+
this.module = module;
15+
}
16+
17+
public String getDisplayName() {
18+
return module.getName();
19+
}
20+
21+
public Icon getIcon() {
22+
return QuarkusIcons.Quarkus;
23+
}
24+
25+
public Module getModule() {
26+
return module;
27+
}
28+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.redhat.devtools.intellij.quarkus.explorer;
2+
3+
import com.intellij.execution.configurations.RunConfiguration;
4+
import com.intellij.openapi.module.Module;
5+
import com.redhat.devtools.intellij.quarkus.run.QuarkusRunConfiguration;
6+
7+
import javax.swing.tree.DefaultTreeModel;
8+
9+
public class QuarkusRunApplicationNode extends QuarkusActionNode{
10+
11+
private RunConfiguration configuration;
12+
13+
public void setConfiguration(RunConfiguration configuration) {
14+
this.configuration = configuration;
15+
}
16+
17+
public RunConfiguration getConfiguration() {
18+
return configuration;
19+
}
20+
21+
public static enum QuarkusApplicationStatus {
22+
none,
23+
starting,
24+
started,
25+
stopping,
26+
stopped;
27+
}
28+
29+
private QuarkusApplicationStatus applicationStatus;
30+
31+
public QuarkusRunApplicationNode(Module module, DefaultTreeModel treeModel) {
32+
super("Run...", module, treeModel);
33+
this.applicationStatus = QuarkusApplicationStatus.none;
34+
}
35+
36+
public void setApplicationStatus(QuarkusApplicationStatus applicationStatus) {
37+
this.applicationStatus = applicationStatus;
38+
refreshNode();
39+
}
40+
41+
@Override
42+
public String getDisplayName() {
43+
return super.getDisplayName() + " [" + applicationStatus + "]";
44+
}
45+
}

0 commit comments

Comments
 (0)