diff --git a/build.gradle b/build.gradle index d1a76d7..acba663 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,9 @@ intellij { plugins 'terminal', 'yaml' } +sourceCompatibility = '1.8' +targetCompatibility = '1.8' + java { withSourcesJar() withJavadocJar() diff --git a/gradle.properties b/gradle.properties index 36ce521..38d5fbd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,4 @@ ideaVersion = IC-2018.3 -projectVersion=1.0.1-SNAPSHOT +projectVersion=1.1.0-SNAPSHOT nexusUser=invalid nexusPassword=invalid diff --git a/src/main/java/com/redhat/devtools/intellij/common/tree/LabelAndIconDescriptor.java b/src/main/java/com/redhat/devtools/intellij/common/tree/LabelAndIconDescriptor.java index 9a77223..9d32733 100644 --- a/src/main/java/com/redhat/devtools/intellij/common/tree/LabelAndIconDescriptor.java +++ b/src/main/java/com/redhat/devtools/intellij/common/tree/LabelAndIconDescriptor.java @@ -14,14 +14,22 @@ import com.intellij.ide.util.treeView.NodeDescriptor; import com.intellij.ide.util.treeView.PresentableNodeDescriptor; import com.intellij.openapi.project.Project; +import com.intellij.ui.JBColor; +import com.intellij.ui.SimpleTextAttributes; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.Icon; import java.util.function.Supplier; +import java.util.regex.Matcher; +import java.util.regex.Pattern; public class LabelAndIconDescriptor extends PresentableNodeDescriptor { + public static final Pattern HREF_PATTERN = Pattern.compile("([^<]*)"); + + public static final SimpleTextAttributes LINK_ATTRIBUTES = new SimpleTextAttributes(SimpleTextAttributes.STYLE_UNDERLINE | SimpleTextAttributes.STYLE_CLICKABLE, JBColor.blue); + private final T element; private final Supplier label; private final Supplier location; @@ -53,7 +61,7 @@ public LabelAndIconDescriptor(Project project, T element, Supplier label @Override protected void update(@NotNull PresentationData presentation) { - presentation.setPresentableText(label.get()); + processLabel(presentation); if (location != null && location.get() != null) { presentation.setLocationString(location.get()); } @@ -62,6 +70,29 @@ protected void update(@NotNull PresentationData presentation) { } } + private void processLabel(@NotNull PresentationData presentation) { + String text = label.get(); + Matcher matcher = HREF_PATTERN.matcher(text); + if (matcher.find()) { + int prev = 0; + do { + if (matcher.start() != prev) { + presentation.addText(text.substring(prev, matcher.start()), SimpleTextAttributes.REGULAR_ATTRIBUTES); + } + presentation.addText(matcher.group(2), LINK_ATTRIBUTES); + prev = matcher.end(); + } + while (matcher.find()); + + if (prev < text.length()) { + presentation.addText(text.substring(prev), SimpleTextAttributes.REGULAR_ATTRIBUTES); + } + + } else { + presentation.setPresentableText(label.get()); + } + } + @Override public T getElement() { return element; diff --git a/src/main/java/com/redhat/devtools/intellij/common/tree/LinkElement.java b/src/main/java/com/redhat/devtools/intellij/common/tree/LinkElement.java new file mode 100644 index 0000000..a756c52 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/common/tree/LinkElement.java @@ -0,0 +1,19 @@ +/******************************************************************************* + * Copyright (c) 2021 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is 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 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.intellij.common.tree; + +/** + * Interface to be implemented by tree elements + * to support links. + */ +public interface LinkElement { + void execute(); +} diff --git a/src/main/java/com/redhat/devtools/intellij/common/tree/TreeHelper.java b/src/main/java/com/redhat/devtools/intellij/common/tree/TreeHelper.java new file mode 100644 index 0000000..a9bdf71 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/common/tree/TreeHelper.java @@ -0,0 +1,45 @@ +/******************************************************************************* + * Copyright (c) 2021 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is 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 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.intellij.common.tree; + +import com.intellij.ide.util.treeView.NodeDescriptor; +import com.intellij.ide.util.treeView.PresentableNodeDescriptor; +import com.intellij.util.ui.tree.TreeUtil; + +import javax.swing.JTree; +import javax.swing.tree.TreePath; +import java.awt.Rectangle; +import java.awt.event.MouseAdapter; +import java.awt.event.MouseEvent; + +public class TreeHelper { + public static TreePath getPathForLocation(JTree tree, int x, int y) { + TreePath path = tree.getClosestPathForLocation(x, y); + Rectangle bounds = tree.getPathBounds(path); + return bounds != null && bounds.y <= y && y < bounds.y + bounds.height ? path : null; + } + + public static void addLinkSupport(final JTree tree) { + tree.addMouseListener(new MouseAdapter() { + @Override + public void mouseClicked(MouseEvent e) { + TreePath path = getPathForLocation(tree, e.getX(), e.getY()); + Object object = TreeUtil.getLastUserObject(path); + if (!(object instanceof LinkElement) && object instanceof NodeDescriptor) { + object = ((NodeDescriptor) object).getElement(); + } + if (object instanceof LinkElement) { + ((LinkElement)object).execute(); + } + } + }); + } +} diff --git a/src/test/java/com/redhat/devtools/intellij/common/tree/LabelAndIconDescriptorTest.java b/src/test/java/com/redhat/devtools/intellij/common/tree/LabelAndIconDescriptorTest.java new file mode 100644 index 0000000..6ab603a --- /dev/null +++ b/src/test/java/com/redhat/devtools/intellij/common/tree/LabelAndIconDescriptorTest.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * Copyright (c) 2021 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is 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 + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.intellij.common.tree; + +import com.intellij.ide.projectView.PresentationData; +import com.intellij.ide.util.treeView.PresentableNodeDescriptor; +import com.intellij.ui.SimpleTextAttributes; +import org.junit.Test; + +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +public class LabelAndIconDescriptorTest { + + @Test + public void checkLinkWithoutHref() { + assertTrue(LabelAndIconDescriptor.HREF_PATTERN.matcher("Click here.").find()); + } + + @Test + public void checkLinkWithHref() { + assertTrue(LabelAndIconDescriptor.HREF_PATTERN.matcher("Click here.").find()); + } + + @Test + public void checkTextWithoutHyperLink() { + LabelAndIconDescriptor descriptor = new LabelAndIconDescriptor(null, null, "mytext", null, null); + PresentationData data = new PresentationData(); + descriptor.update(data); + assertEquals("mytext", data.getPresentableText()); + + } + + private void testTextWithOneHyperlink(String text) { + LabelAndIconDescriptor descriptor = new LabelAndIconDescriptor(null, null, text, null, null); + PresentationData data = new PresentationData(); + descriptor.update(data); + assertNull(data.getPresentableText()); + List fragments = data.getColoredText(); + assertNotNull(fragments); + assertEquals(3, fragments.size()); + assertEquals("Click ", fragments.get(0).getText()); + assertEquals(SimpleTextAttributes.REGULAR_ATTRIBUTES, fragments.get(0).getAttributes()); + assertEquals("here", fragments.get(1).getText()); + assertEquals(LabelAndIconDescriptor.LINK_ATTRIBUTES, fragments.get(1).getAttributes()); + assertEquals(".", fragments.get(2).getText()); + assertEquals(SimpleTextAttributes.REGULAR_ATTRIBUTES, fragments.get(2).getAttributes()); + } + + @Test + public void checkTextWithOneHyperLinkAndNoHref() { + testTextWithOneHyperlink("Click here."); + } + + @Test + public void checkTextWithOneHyperLinkAndHref() { + testTextWithOneHyperlink("Click here."); + } + + private void testTextWithTwoHyperlinks(String text) { + LabelAndIconDescriptor descriptor = new LabelAndIconDescriptor(null, null, text, null, null); + PresentationData data = new PresentationData(); + descriptor.update(data); + assertNull(data.getPresentableText()); + List fragments = data.getColoredText(); + assertNotNull(fragments); + assertEquals(5, fragments.size()); + assertEquals("Click ", fragments.get(0).getText()); + assertEquals(SimpleTextAttributes.REGULAR_ATTRIBUTES, fragments.get(0).getAttributes()); + assertEquals("here", fragments.get(1).getText()); + assertEquals(LabelAndIconDescriptor.LINK_ATTRIBUTES, fragments.get(1).getAttributes()); + assertEquals(" or ", fragments.get(2).getText()); + assertEquals(SimpleTextAttributes.REGULAR_ATTRIBUTES, fragments.get(2).getAttributes()); + assertEquals("there", fragments.get(3).getText()); + assertEquals(LabelAndIconDescriptor.LINK_ATTRIBUTES, fragments.get(3).getAttributes()); + assertEquals(".", fragments.get(4).getText()); + assertEquals(SimpleTextAttributes.REGULAR_ATTRIBUTES, fragments.get(4).getAttributes()); + } + + @Test + public void checkTextWithTwoHyperLinkAndNoHref() { + testTextWithTwoHyperlinks("Click here or there."); + } + + @Test + public void checkTextWithTwoHyperLinkAndHref() { + testTextWithTwoHyperlinks("Click here or there."); + } +}