Skip to content

Commit 83960e9

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 83960e9

21 files changed

+860
-11
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
}
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: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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.treeStructure.Tree;
7+
8+
import javax.swing.*;
9+
import javax.swing.tree.DefaultMutableTreeNode;
10+
import javax.swing.tree.DefaultTreeModel;
11+
import javax.swing.tree.TreePath;
12+
13+
public abstract class QuarkusActionNode extends DefaultMutableTreeNode {
14+
15+
private final String name;
16+
17+
private final QuarkusProjectNode projectNode;
18+
19+
public QuarkusActionNode(String name, QuarkusProjectNode projectNode) {
20+
this.name = name;
21+
this.projectNode = projectNode;
22+
}
23+
24+
public String getName() {
25+
return name;
26+
}
27+
28+
public Module getModule() {
29+
return projectNode.getModule();
30+
}
31+
32+
public QuarkusProjectNode getProjectNode() {
33+
return projectNode;
34+
}
35+
36+
public String getDisplayName() {
37+
return name;
38+
}
39+
40+
public Icon getIcon() {
41+
return AllIcons.Actions.InlayGear;
42+
}
43+
44+
public void refreshNode(boolean nodeStructureChanged) {
45+
invokeLater(() -> {
46+
var tree = projectNode.getTree();
47+
if (nodeStructureChanged) {
48+
((DefaultTreeModel) tree.getModel()).nodeStructureChanged(this);
49+
} else {
50+
((DefaultTreeModel) tree.getModel()).nodeChanged(this);
51+
}
52+
var treePath = new TreePath(this.getPath());
53+
tree.expandPath(treePath);
54+
});
55+
}
56+
57+
private static void invokeLater(Runnable runnable) {
58+
if (ApplicationManager.getApplication().isDispatchThread()) {
59+
runnable.run();
60+
} else {
61+
ApplicationManager.getApplication().invokeLater(runnable);
62+
}
63+
}
64+
65+
public abstract String getActionId();
66+
}
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)