Skip to content

Commit

Permalink
feat: Create a Quarkus Dev Explorer in the IDE
Browse files Browse the repository at this point in the history
Fixes redhat-developer#1175

Signed-off-by: azerr <[email protected]>
  • Loading branch information
angelozerr committed Sep 25, 2023
1 parent 9c95979 commit fd5554a
Show file tree
Hide file tree
Showing 14 changed files with 570 additions and 4 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*******************************************************************************
* Copyright (c) 2023 Red Hat Inc. and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package com.redhat.devtools.intellij.quarkus;

import com.intellij.DynamicBundle;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.PropertyKey;

import java.util.function.Supplier;

/**
* Quarkus messages bundle.
*/
public final class QuarkusBundle extends DynamicBundle {

@NonNls public static final String BUNDLE = "messages.QuarkusBundle";
private static final QuarkusBundle INSTANCE = new QuarkusBundle();

private QuarkusBundle() {
super(BUNDLE);
}

@NotNull
public static @Nls String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) {
return INSTANCE.getMessage(key, params);
}

@NotNull
public static Supplier<@Nls String> messagePointer(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, Object @NotNull ... params) {
return INSTANCE.getLazyMessage(key, params);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.redhat.devtools.intellij.quarkus.explorer;

import com.intellij.icons.AllIcons;
import com.intellij.openapi.module.Module;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;

public class QuarkusActionNode extends DefaultMutableTreeNode {

private final String name;

private final Module module;

public QuarkusActionNode(String name, Module module) {
this.name = name;
this.module = module;
}

public String getName() {
return name;
}

public Module getModule() {
return module;
}

public String getDisplayName() {
return name;
}

public Icon getIcon() {
return AllIcons.Actions.InlayGear;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
package com.redhat.devtools.intellij.quarkus.explorer;

import com.intellij.ide.DataManager;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.*;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.SimpleToolWindowPanel;
import com.intellij.ui.AnimatedIcon;
import com.intellij.ui.DoubleClickListener;
import com.intellij.ui.treeStructure.Tree;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.redhat.devtools.intellij.lsp4ij.console.explorer.LanguageServerTreeRenderer;
import com.redhat.devtools.intellij.quarkus.explorer.actions.QuarkusDevStartAction;
import com.redhat.microprofile.psi.quarkus.PsiQuarkusUtils;
import org.jetbrains.annotations.NotNull;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;

public class QuarkusExplorer extends SimpleToolWindowPanel implements Disposable {

private final Tree tree;
private final Project project;

public QuarkusExplorer(@NotNull Project project) {
super(true, true);
this.project = project;
tree = buildTree();
this.setContent(tree);
load();
}

/**
* Builds the Language server tree
*
* @return Tree object of all language servers
*/
private Tree buildTree() {

DefaultMutableTreeNode top = new DefaultMutableTreeNode("Quarkus projects");

Tree tree = new Tree(top);
tree.setRootVisible(false);
tree.setCellRenderer(new QuarkusTreeRenderer());

tree.putClientProperty(AnimatedIcon.ANIMATION_IN_RENDERER_ALLOWED, true);

((DefaultTreeModel) tree.getModel()).reload(top);


var doubleClickListener = new DoubleClickListener() {
@Override
protected boolean onDoubleClick(@NotNull MouseEvent event) {
executeAction(tree);
return false;
}
};
doubleClickListener.installOn(tree);

tree.addKeyListener(new KeyAdapter() {
@Override
public void keyPressed(KeyEvent e) {
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
executeAction(tree);
}
}
});

return tree;
}

private void load() {
var action = ReadAction.nonBlocking(() -> {
DefaultMutableTreeNode root = (DefaultMutableTreeNode) ((DefaultTreeModel) tree.getModel()).getRoot();
Module[] modules = ModuleManager.getInstance(project).getModules();
for (Module javaProject : modules) {
if (PsiQuarkusUtils.isQuarkusProject(javaProject)) {
QuarkusProjectNode projectNode = new QuarkusProjectNode(javaProject);
root.add(projectNode);
// Fill Quarkus actions
projectNode.add(new QuarkusActionNode("Start", javaProject));
}
}
((DefaultTreeModel) tree.getModel()).reload(root);
});
var executeInSmartMode = DumbService.getInstance(project).isDumb();
if (executeInSmartMode) {
action = action.inSmartMode(project);
}
action
.submit(AppExecutorUtil.getAppExecutorService());
}

private static void executeAction(Tree tree) {
final TreePath path = tree.getSelectionPath();
Object node = path.getLastPathComponent();
if (node instanceof QuarkusActionNode) {
ActionManager am = ActionManager.getInstance();
String actionNodeName = ((QuarkusActionNode) node).getName();

String actionId = QuarkusDevStartAction.ACTION_ID;

AnAction action = am.getAction(actionId);
if (action != null) {
action.actionPerformed(new AnActionEvent(null,
DataManager.getInstance().getDataContext(tree),
ActionPlaces.UNKNOWN, new Presentation(),
am, 0));
}

}
}

@Override
public void dispose() {

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.redhat.devtools.intellij.quarkus.explorer;

import com.intellij.openapi.module.Module;
import com.redhat.devtools.intellij.quarkus.lang.QuarkusIcons;

import javax.swing.*;
import javax.swing.tree.DefaultMutableTreeNode;

public class QuarkusProjectNode extends DefaultMutableTreeNode {

private final Module module;

public QuarkusProjectNode(Module module) {
this.module = module;
}

public String getDisplayName() {
return module.getName();
}

public Icon getIcon() {
return QuarkusIcons.Quarkus;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.redhat.devtools.intellij.quarkus.explorer;

import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowFactory;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
import com.redhat.devtools.intellij.lsp4ij.LanguageServerBundle;
import com.redhat.devtools.intellij.quarkus.QuarkusBundle;
import org.jetbrains.annotations.NotNull;

public class QuarkusToolWindowFactory implements ToolWindowFactory {

@Override
public void createToolWindowContent(@NotNull Project project, @NotNull ToolWindow toolWindow) {
QuarkusExplorer explorer = new QuarkusExplorer(project);
ContentManager contentManager = toolWindow.getContentManager();
Content content = contentManager.getFactory().createContent(explorer,
QuarkusBundle.message("quarkus.tool.window.display.name"), false);
content.setDisposer(explorer);
contentManager.addContent(content);

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*******************************************************************************
* Copyright (c) 2023 Red Hat Inc. and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
* which is available at https://www.apache.org/licenses/LICENSE-2.0.
*
* SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
*
* Contributors:
* Red Hat Inc. - initial API and implementation
*******************************************************************************/
package com.redhat.devtools.intellij.quarkus.explorer;

import com.intellij.ide.ui.UISettings;
import com.intellij.ui.ColoredTreeCellRenderer;
import com.intellij.ui.RelativeFont;
import com.intellij.ui.SimpleTextAttributes;
import com.intellij.util.ui.UIUtil;
import com.redhat.devtools.intellij.lsp4ij.ServerStatus;
import com.redhat.devtools.intellij.lsp4ij.console.explorer.LanguageServerProcessTreeNode;
import com.redhat.devtools.intellij.lsp4ij.console.explorer.LanguageServerTreeNode;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;

import javax.swing.*;
import java.awt.*;

/**
* Quarkus tree nodes renderer.
* <p>
* Some piece of code has been copied/pasted from https://github.com/JetBrains/intellij-community/blob/master/platform/smRunner/src/com/intellij/execution/testframework/sm/runner/ui/TestTreeRenderer.java and adapted for Language Server case.
*/
public class QuarkusTreeRenderer extends ColoredTreeCellRenderer {

@NonNls
private static final String SPACE_STRING = " ";
private String myDurationText;
private Color myDurationColor;
private int myDurationWidth;
private int myDurationOffset;

@Override
public void customizeCellRenderer(@NotNull final JTree tree,
final Object value,
final boolean selected,
final boolean expanded,
final boolean leaf,
final int row,
final boolean hasFocus) {
myDurationText = null;
myDurationColor = null;
myDurationWidth = 0;
myDurationOffset = 0;

if (value instanceof QuarkusProjectNode) {
// Render of Quarkus project
QuarkusProjectNode projectNode = (QuarkusProjectNode) value;
setIcon(projectNode.getIcon());
append(projectNode.getDisplayName());
return;
}

if (value instanceof QuarkusActionNode) {
// Render of Quarkus action
QuarkusActionNode actionNode = (QuarkusActionNode) value;
setIcon(actionNode.getIcon());
append(actionNode.getDisplayName());
return;
}

/*if (value instanceof LanguageServerProcessTreeNode) {
// Render of language server process
LanguageServerProcessTreeNode languageProcessTreeNode = (LanguageServerProcessTreeNode) value;
setIcon(languageProcessTreeNode.getIcon());
append(languageProcessTreeNode.getDisplayName());
if (languageProcessTreeNode.getServerStatus() == ServerStatus.starting
|| languageProcessTreeNode.getServerStatus() == ServerStatus.stopping) {
// Display elapsed time when language server is starting/stopping
myDurationText = languageProcessTreeNode.getElapsedTime();
final var durationText = myDurationText;
if (durationText != null) {
FontMetrics metrics = getFontMetrics(RelativeFont.SMALL.derive(getFont()));
myDurationWidth = metrics.stringWidth(durationText);
myDurationOffset = metrics.getHeight() / 2; // an empty area before and after the text
myDurationColor = selected ? UIUtil.getTreeSelectionForeground(hasFocus) : SimpleTextAttributes.GRAYED_ATTRIBUTES.getFgColor();
}
}
return;
}*/
//strange node
final String text = value.toString();
//no icon
append(text != null ? text : SPACE_STRING, SimpleTextAttributes.GRAYED_ATTRIBUTES);
}

@NotNull
@Override
public Dimension getPreferredSize() {
final Dimension preferredSize = super.getPreferredSize();
if (myDurationWidth > 0) preferredSize.width += myDurationWidth + myDurationOffset;
return preferredSize;
}

@Override
protected void paintComponent(Graphics g) {
UISettings.setupAntialiasing(g);
Shape clip = null;
int width = getWidth();
int height = getHeight();
if (isOpaque()) {
// paint background for expanded row
g.setColor(getBackground());
g.fillRect(0, 0, width, height);
}
if (myDurationWidth > 0) {
width -= myDurationWidth + myDurationOffset;
if (width > 0 && height > 0) {
g.setColor(myDurationColor);
g.setFont(RelativeFont.SMALL.derive(getFont()));
g.drawString(myDurationText, width + myDurationOffset / 2, getTextBaseLine(g.getFontMetrics(), height));
clip = g.getClip();
g.clipRect(0, 0, width, height);
}
}
super.paintComponent(g);
// restore clip area if needed
if (clip != null) g.setClip(clip);
}
}

Loading

0 comments on commit fd5554a

Please sign in to comment.