Skip to content

Commit a384463

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

20 files changed

+875
-33
lines changed

src/main/java/com/redhat/devtools/intellij/lsp4mp4ij/psi/core/project/PsiMicroProfileProject.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,4 +299,7 @@ public String getValue(String key) {
299299
return provider;
300300
}
301301

302+
public Module getJavaProject() {
303+
return javaProject;
304+
}
302305
}

src/main/java/com/redhat/devtools/intellij/quarkus/QuarkusBundle.java

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,22 @@
2626
*/
2727
public final class QuarkusBundle extends DynamicBundle {
2828

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-
}
29+
@NonNls
30+
public static final String BUNDLE = "messages.QuarkusBundle";
31+
private static final QuarkusBundle INSTANCE = new QuarkusBundle();
32+
33+
private QuarkusBundle() {
34+
super(BUNDLE);
35+
}
36+
37+
@NotNull
38+
public static @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) {
39+
return INSTANCE.getMessage(key, params);
40+
}
41+
42+
@NotNull
43+
public static Supplier<@Nls String> messagePointer(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) {
44+
return INSTANCE.getLazyMessage(key, params);
45+
}
46+
4547
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.redhat.devtools.intellij.quarkus.explorer;
2+
3+
import com.intellij.icons.AllIcons;
4+
import com.intellij.openapi.application.ApplicationManager;
5+
import com.intellij.openapi.module.Module;
6+
import com.intellij.ui.AnimatedIcon;
7+
import com.intellij.ui.treeStructure.Tree;
8+
9+
import javax.swing.*;
10+
import javax.swing.tree.DefaultMutableTreeNode;
11+
import javax.swing.tree.DefaultTreeModel;
12+
import javax.swing.tree.TreePath;
13+
14+
public abstract class QuarkusActionNode extends DefaultMutableTreeNode {
15+
16+
private static final Icon RUNNING_ICON = new AnimatedIcon.Default();
17+
private final String name;
18+
19+
private final QuarkusProjectNode projectNode;
20+
21+
public QuarkusActionNode(String name, QuarkusProjectNode projectNode) {
22+
this.name = name;
23+
this.projectNode = projectNode;
24+
}
25+
26+
public String getName() {
27+
return name;
28+
}
29+
30+
public Module getModule() {
31+
return projectNode.getModule();
32+
}
33+
34+
public QuarkusProjectNode getProjectNode() {
35+
return projectNode;
36+
}
37+
38+
public String getDisplayName() {
39+
return name;
40+
}
41+
42+
public Icon getIcon() {
43+
return AllIcons.Actions.InlayGear;
44+
}
45+
46+
public void refreshNode(boolean nodeStructureChanged) {
47+
invokeLater(() -> {
48+
var tree = projectNode.getTree();
49+
if (nodeStructureChanged) {
50+
((DefaultTreeModel) tree.getModel()).nodeStructureChanged(this);
51+
} else {
52+
((DefaultTreeModel) tree.getModel()).nodeChanged(this);
53+
}
54+
var treePath = new TreePath(this.getPath());
55+
tree.expandPath(treePath);
56+
});
57+
}
58+
59+
private static void invokeLater(Runnable runnable) {
60+
if (ApplicationManager.getApplication().isDispatchThread()) {
61+
runnable.run();
62+
} else {
63+
ApplicationManager.getApplication().invokeLater(runnable);
64+
}
65+
}
66+
67+
public abstract String getActionId();
68+
}
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
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 processNotStarted(@NotNull String executorId, @NotNull ExecutionEnvironment env) {
105+
QuarkusRunApplicationNode application = findQuarkusApplication(env);
106+
if (application != null) {
107+
application.setApplicationStatus(QuarkusRunApplicationNode.QuarkusApplicationStatus.stopped);
108+
}
109+
}
110+
111+
@Override
112+
public void processStarted(@NotNull String executorId, @NotNull ExecutionEnvironment env, final @NotNull ProcessHandler handler) {
113+
QuarkusRunApplicationNode application = findQuarkusApplication(env);
114+
if (application != null) {
115+
application.setApplicationStatus(QuarkusRunApplicationNode.QuarkusApplicationStatus.starting);
116+
}
117+
}
118+
119+
@Override
120+
public void processTerminating(@NotNull String executorId, @NotNull ExecutionEnvironment env, @NotNull ProcessHandler handler) {
121+
QuarkusRunApplicationNode application = findQuarkusApplication(env);
122+
if (application != null) {
123+
application.setApplicationStatus(QuarkusRunApplicationNode.QuarkusApplicationStatus.stopping);
124+
}
125+
}
126+
127+
@Override
128+
public void processTerminated(@NotNull String executorId, @NotNull ExecutionEnvironment env, @NotNull ProcessHandler handler, int exitCode) {
129+
QuarkusRunApplicationNode application = findQuarkusApplication(env);
130+
if (application != null) {
131+
application.setApplicationStatus(QuarkusRunApplicationNode.QuarkusApplicationStatus.stopped);
132+
}
133+
}
134+
135+
private @Nullable QuarkusRunApplicationNode findQuarkusApplication(@NotNull ExecutionEnvironment env) {
136+
QuarkusRunConfiguration runConfiguration = env.getDataContext() != null ? (QuarkusRunConfiguration) env.getDataContext().getData(QUARKUS_CONFIGURATION) : null;
137+
if (runConfiguration == null) {
138+
return null;
139+
}
140+
141+
Module module = runConfiguration.getModule();
142+
DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
143+
Enumeration<TreeNode> children = root.children();
144+
while (children.hasMoreElements()) {
145+
TreeNode node = children.nextElement();
146+
if (node instanceof QuarkusProjectNode && module.equals(((QuarkusProjectNode) node).getModule())) {
147+
QuarkusProjectNode project = (QuarkusProjectNode) node;
148+
Enumeration<TreeNode> children2 = project.children();
149+
while (children2.hasMoreElements()) {
150+
TreeNode node2 = children2.nextElement();
151+
if (node2 instanceof QuarkusRunApplicationNode && runConfiguration.equals(((QuarkusRunApplicationNode) node2).getConfiguration())) {
152+
return (QuarkusRunApplicationNode) node2;
153+
}
154+
}
155+
break;
156+
}
157+
}
158+
return null;
159+
}
160+
});
161+
return tree;
162+
}
163+
164+
private void load() {
165+
var action = ReadAction.nonBlocking(() -> {
166+
DefaultMutableTreeNode root = (DefaultMutableTreeNode) ((DefaultTreeModel) tree.getModel()).getRoot();
167+
Module[] modules = ModuleManager.getInstance(project).getModules();
168+
for (Module javaProject : modules) {
169+
if (PsiQuarkusUtils.isQuarkusProject(javaProject)) {
170+
QuarkusProjectNode projectNode = new QuarkusProjectNode(javaProject, tree);
171+
root.add(projectNode);
172+
// Fill Quarkus actions
173+
projectNode.add(new QuarkusRunApplicationNode(projectNode));
174+
}
175+
}
176+
((DefaultTreeModel) tree.getModel()).reload(root);
177+
});
178+
var executeInSmartMode = DumbService.getInstance(project).isDumb();
179+
if (executeInSmartMode) {
180+
action = action.inSmartMode(project);
181+
}
182+
action
183+
.submit(AppExecutorUtil.getAppExecutorService());
184+
}
185+
186+
private static void executeAction(Tree tree) {
187+
final TreePath path = tree.getSelectionPath();
188+
Object node = path.getLastPathComponent();
189+
if (node instanceof QuarkusActionNode) {
190+
ActionManager am = ActionManager.getInstance();
191+
String actionId = ((QuarkusActionNode) node).getActionId();
192+
if (actionId == null) {
193+
return;
194+
}
195+
AnAction action = am.getAction(actionId);
196+
if (action != null) {
197+
action.actionPerformed(new AnActionEvent(null,
198+
DataManager.getInstance().getDataContext(tree),
199+
ActionPlaces.UNKNOWN, new Presentation(),
200+
am, 0));
201+
}
202+
203+
}
204+
}
205+
206+
@Override
207+
public void dispose() {
208+
209+
}
210+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.redhat.devtools.intellij.quarkus.explorer;
2+
3+
import com.intellij.openapi.module.Module;
4+
import com.intellij.ui.treeStructure.Tree;
5+
import com.redhat.devtools.intellij.quarkus.run.QuarkusOpenDevUIAction;
6+
7+
import javax.swing.tree.DefaultTreeModel;
8+
9+
public class QuarkusOpenDevUINode extends QuarkusActionNode {
10+
public QuarkusOpenDevUINode(QuarkusProjectNode projectNode) {
11+
super("Open Dev UI", projectNode);
12+
}
13+
14+
@Override
15+
public String getActionId() {
16+
return QuarkusOpenDevUIAction.ACTION_ID;
17+
}
18+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.redhat.devtools.intellij.quarkus.explorer;
2+
3+
import com.intellij.openapi.module.Module;
4+
import com.intellij.ui.treeStructure.Tree;
5+
import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project.PsiMicroProfileProject;
6+
import com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project.PsiMicroProfileProjectManager;
7+
import com.redhat.devtools.intellij.quarkus.lang.QuarkusIcons;
8+
import com.redhat.devtools.intellij.quarkus.run.QuarkusRunContext;
9+
10+
import javax.swing.*;
11+
import javax.swing.tree.DefaultMutableTreeNode;
12+
13+
public class QuarkusProjectNode extends DefaultMutableTreeNode {
14+
15+
private final QuarkusRunContext runContext;
16+
17+
private final Tree tree;
18+
19+
public QuarkusProjectNode(Module module,Tree tree) {
20+
this.runContext = new QuarkusRunContext(module);
21+
this.tree = tree;
22+
}
23+
24+
public String getDisplayName() {
25+
return getModule().getName();
26+
}
27+
28+
public Icon getIcon() {
29+
return QuarkusIcons.Quarkus;
30+
}
31+
32+
public Module getModule() {
33+
return runContext.getMicroProfileProject().getJavaProject();
34+
}
35+
36+
public QuarkusRunContext getRunContext() {
37+
return runContext;
38+
}
39+
40+
public Tree getTree() {
41+
return tree;
42+
}
43+
}

0 commit comments

Comments
 (0)