Skip to content

Commit 1ff1864

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 1ff1864

23 files changed

+887
-34
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: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
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.run.QuarkusRunConfiguration;
24+
import com.redhat.microprofile.psi.quarkus.PsiQuarkusUtils;
25+
import org.jetbrains.annotations.NotNull;
26+
import org.jetbrains.annotations.Nullable;
27+
28+
import javax.swing.tree.DefaultMutableTreeNode;
29+
import javax.swing.tree.DefaultTreeModel;
30+
import javax.swing.tree.TreeNode;
31+
import javax.swing.tree.TreePath;
32+
import java.awt.event.KeyAdapter;
33+
import java.awt.event.KeyEvent;
34+
import java.awt.event.MouseEvent;
35+
import java.util.Enumeration;
36+
37+
import static com.redhat.devtools.intellij.quarkus.run.QuarkusRunConfiguration.QUARKUS_CONFIGURATION;
38+
39+
public class QuarkusExplorer extends SimpleToolWindowPanel implements Disposable {
40+
41+
private final Tree tree;
42+
private final Project project;
43+
44+
public QuarkusExplorer(@NotNull Project project) {
45+
super(true, true);
46+
this.project = project;
47+
tree = buildTree();
48+
this.setContent(tree);
49+
load();
50+
}
51+
52+
/**
53+
* Builds the Language server tree
54+
*
55+
* @return Tree object of all language servers
56+
*/
57+
private Tree buildTree() {
58+
59+
DefaultMutableTreeNode top = new DefaultMutableTreeNode("Quarkus projects");
60+
61+
Tree tree = new Tree(top);
62+
tree.setRootVisible(false);
63+
tree.setCellRenderer(new QuarkusTreeRenderer());
64+
65+
tree.putClientProperty(AnimatedIcon.ANIMATION_IN_RENDERER_ALLOWED, true);
66+
67+
((DefaultTreeModel) tree.getModel()).reload(top);
68+
69+
70+
var doubleClickListener = new DoubleClickListener() {
71+
@Override
72+
protected boolean onDoubleClick(@NotNull MouseEvent event) {
73+
executeAction(tree);
74+
return false;
75+
}
76+
};
77+
doubleClickListener.installOn(tree);
78+
79+
tree.addKeyListener(new KeyAdapter() {
80+
@Override
81+
public void keyPressed(KeyEvent e) {
82+
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
83+
executeAction(tree);
84+
}
85+
}
86+
});
87+
88+
MessageBusConnection connection = project.getMessageBus().connect(project);
89+
connection.subscribe(RunManagerListener.TOPIC, new RunManagerListener() {
90+
@Override
91+
public void runConfigurationSelected(@Nullable RunnerAndConfigurationSettings settings) {
92+
// Do nothing
93+
}
94+
95+
@Override
96+
public void runConfigurationAdded(@NotNull RunnerAndConfigurationSettings settings) {
97+
// TODO: refresh tree
98+
}
99+
});
100+
connection.subscribe(ExecutionManager.EXECUTION_TOPIC, new ExecutionListener() {
101+
102+
@Override
103+
public void processNotStarted(@NotNull String executorId, @NotNull ExecutionEnvironment env) {
104+
QuarkusRunDevNode application = findQuarkusApplication(env);
105+
if (application != null) {
106+
application.setApplicationStatus(QuarkusRunDevNode.QuarkusApplicationStatus.stopped);
107+
}
108+
}
109+
110+
@Override
111+
public void processStarted(@NotNull String executorId, @NotNull ExecutionEnvironment env, final @NotNull ProcessHandler handler) {
112+
QuarkusRunDevNode application = findQuarkusApplication(env);
113+
if (application != null) {
114+
application.setApplicationStatus(QuarkusRunDevNode.QuarkusApplicationStatus.starting);
115+
}
116+
}
117+
118+
@Override
119+
public void processTerminating(@NotNull String executorId, @NotNull ExecutionEnvironment env, @NotNull ProcessHandler handler) {
120+
QuarkusRunDevNode application = findQuarkusApplication(env);
121+
if (application != null) {
122+
application.setApplicationStatus(QuarkusRunDevNode.QuarkusApplicationStatus.stopping);
123+
}
124+
}
125+
126+
@Override
127+
public void processTerminated(@NotNull String executorId, @NotNull ExecutionEnvironment env, @NotNull ProcessHandler handler, int exitCode) {
128+
QuarkusRunDevNode application = findQuarkusApplication(env);
129+
if (application != null) {
130+
application.setApplicationStatus(QuarkusRunDevNode.QuarkusApplicationStatus.stopped);
131+
}
132+
}
133+
134+
private @Nullable QuarkusRunDevNode findQuarkusApplication(@NotNull ExecutionEnvironment env) {
135+
QuarkusRunConfiguration runConfiguration = env.getDataContext() != null ? (QuarkusRunConfiguration) env.getDataContext().getData(QUARKUS_CONFIGURATION) : null;
136+
if (runConfiguration == null) {
137+
return null;
138+
}
139+
140+
Module module = runConfiguration.getModule();
141+
DefaultMutableTreeNode root = (DefaultMutableTreeNode) tree.getModel().getRoot();
142+
Enumeration<TreeNode> children = root.children();
143+
while (children.hasMoreElements()) {
144+
TreeNode node = children.nextElement();
145+
if (node instanceof QuarkusProjectNode && module.equals(((QuarkusProjectNode) node).getModule())) {
146+
QuarkusProjectNode project = (QuarkusProjectNode) node;
147+
Enumeration<TreeNode> children2 = project.children();
148+
while (children2.hasMoreElements()) {
149+
TreeNode node2 = children2.nextElement();
150+
if (node2 instanceof QuarkusRunDevNode && runConfiguration.equals(((QuarkusRunDevNode) node2).getConfiguration())) {
151+
return (QuarkusRunDevNode) node2;
152+
}
153+
}
154+
break;
155+
}
156+
}
157+
return null;
158+
}
159+
});
160+
return tree;
161+
}
162+
163+
private void load() {
164+
var action = ReadAction.nonBlocking(() -> {
165+
DefaultMutableTreeNode root = (DefaultMutableTreeNode) ((DefaultTreeModel) tree.getModel()).getRoot();
166+
Module[] modules = ModuleManager.getInstance(project).getModules();
167+
for (Module javaProject : modules) {
168+
if (PsiQuarkusUtils.isQuarkusProject(javaProject)) {
169+
QuarkusProjectNode projectNode = new QuarkusProjectNode(javaProject, tree);
170+
root.add(projectNode);
171+
// Fill Quarkus actions
172+
projectNode.add(new QuarkusRunDevNode(projectNode));
173+
}
174+
}
175+
((DefaultTreeModel) tree.getModel()).reload(root);
176+
});
177+
var executeInSmartMode = DumbService.getInstance(project).isDumb();
178+
if (executeInSmartMode) {
179+
action = action.inSmartMode(project);
180+
}
181+
action
182+
.submit(AppExecutorUtil.getAppExecutorService());
183+
}
184+
185+
private static void executeAction(Tree tree) {
186+
final TreePath path = tree.getSelectionPath();
187+
Object node = path.getLastPathComponent();
188+
if (node instanceof QuarkusActionNode) {
189+
ActionManager am = ActionManager.getInstance();
190+
String actionId = ((QuarkusActionNode) node).getActionId();
191+
if (actionId == null) {
192+
return;
193+
}
194+
AnAction action = am.getAction(actionId);
195+
if (action != null) {
196+
action.actionPerformed(new AnActionEvent(null,
197+
DataManager.getInstance().getDataContext(tree),
198+
ActionPlaces.UNKNOWN, new Presentation(),
199+
am, 0));
200+
}
201+
202+
}
203+
}
204+
205+
@Override
206+
public void dispose() {
207+
208+
}
209+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.redhat.devtools.intellij.quarkus.explorer;
2+
3+
import com.redhat.devtools.intellij.quarkus.run.QuarkusOpenAppInBrowserAction;
4+
import com.redhat.devtools.intellij.quarkus.run.QuarkusOpenDevUIAction;
5+
6+
public class QuarkusOpenApplicationNode extends QuarkusActionNode {
7+
public QuarkusOpenApplicationNode(QuarkusProjectNode projectNode) {
8+
super("Open Application", projectNode);
9+
}
10+
11+
@Override
12+
public String getActionId() {
13+
return QuarkusOpenAppInBrowserAction.ACTION_ID;
14+
}
15+
}
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+
}

0 commit comments

Comments
 (0)