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 22, 2023
1 parent 9c95979 commit 4e4bed5
Show file tree
Hide file tree
Showing 10 changed files with 341 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,75 @@
package com.redhat.devtools.intellij.quarkus.explorer;

import com.intellij.openapi.Disposable;
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.treeStructure.Tree;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.redhat.devtools.intellij.lsp4ij.console.explorer.LanguageServerTreeRenderer;
import com.redhat.microprofile.psi.quarkus.PsiQuarkusUtils;
import org.jetbrains.annotations.NotNull;

import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;

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);
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);
}
}
});
var executeInSmartMode = DumbService.getInstance(project).isDumb();
if (executeInSmartMode) {
action = action.inSmartMode(project);
}
action
.submit(AppExecutorUtil.getAppExecutorService());
}

@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,125 @@
/*******************************************************************************
* 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 language server
QuarkusProjectNode languageServerTreeNode = (QuarkusProjectNode) value;
setIcon(languageServerTreeNode.getIcon());
append(languageServerTreeNode.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);
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
* Quarkus icon provider.
*/
public class QuarkusIconProvider extends IconProvider {
public static final Icon QUARKUS_ICON = IconLoader.findIcon("/quarkus_icon_rgb_16px_default.png", QuarkusIconProvider.class);

@Nullable
@Override
Expand All @@ -34,7 +33,7 @@ public Icon getIcon(@NotNull PsiElement element, int flags) {
VirtualFile file = element.getContainingFile().getVirtualFile();
if (QuarkusModuleUtil.isQuarkusPropertiesFile(file, element.getProject()) ||
QuarkusModuleUtil.isQuarkusYAMLFile(file, element.getProject())) {
return QUARKUS_ICON;
return QuarkusIcons.Quarkus;
}
}
return null;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/*******************************************************************************
* 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.lang;

import com.intellij.openapi.util.IconLoader;

import javax.swing.*;

/**
* Quarkus icons.
*/
public class QuarkusIcons {

public static final Icon Quarkus = IconLoader.findIcon("/quarkus_icon_rgb_16px_default.png", QuarkusIcons.class);
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ public class QuarkusServerIconProvider implements ServerIconProvider {

@Override
public Icon getIcon() {
return QuarkusIconProvider.QUARKUS_ICON;
return QuarkusIcons.Quarkus;
}
}
5 changes: 4 additions & 1 deletion src/main/resources/META-INF/plugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,10 @@
<projectService serviceImplementation="com.redhat.devtools.intellij.lsp4mp4ij.psi.core.project.PsiMicroProfileProjectManager"/>
<configurationType implementation="com.redhat.devtools.intellij.quarkus.run.QuarkusRunConfigurationType"/>
<consoleActionsPostProcessor implementation="com.redhat.devtools.intellij.quarkus.run.QuarkusRunConsolePostProcessor"/>

<toolWindow id="Quarkus"
anchor="right"
factoryClass="com.redhat.devtools.intellij.quarkus.explorer.QuarkusToolWindowFactory"
icon="com.redhat.devtools.intellij.quarkus.lang.QuarkusIcons.Quarkus" />
<!-- Qute -->
<facetType implementation="com.redhat.devtools.intellij.qute.facet.QuteFacetType"/>
<framework.detector implementation="com.redhat.devtools.intellij.qute.facet.QuteFrameworkDetector"/>
Expand Down
15 changes: 15 additions & 0 deletions src/main/resources/messages/QuarkusBundle.properties
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
###############################################################################
# Copyright (c) 2023 Red Hat Inc. and others.
# All rights reserved. This program and the accompanying materials
# are made available under the terms of the Eclipse Public License v2.0
# which accompanies this distribution, and is available at
# http://www.eclipse.org/legal/epl-v20.html
#
# SPDX-License-Identifier: EPL-2.0
#
# Contributors:
# Red Hat Inc. - initial API and implementation
###############################################################################

## Quarkus explorer
quarkus.tool.window.display.name=Projects

0 comments on commit 4e4bed5

Please sign in to comment.