Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Property location and jump to location in mouse over popup #370 #374

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
import java.util.Map;
import java.util.Optional;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import org.apache.maven.Maven;
import org.apache.maven.model.Dependency;
Expand Down Expand Up @@ -158,16 +157,18 @@ private LocationLink findMavenPropertyLocation(IDefinitionRequest request) {
DOMNode propertyDeclaration = null;
Predicate<DOMNode> isMavenProperty = (node) -> PROPERTIES_ELT.equals(node.getParentNode().getLocalName());

if (childProj.getFile().toURI().toString().equals(xmlDocument.getDocumentURI())) {
URI childProjectUri = ParticipantUtils.normalizedUri(childProj.getFile().toURI().toString());
URI thisProjectUri = ParticipantUtils.normalizedUri(xmlDocument.getDocumentURI());
if (childProjectUri.equals(thisProjectUri)) {
// Property is defined in the same file as the request
propertyDeclaration = DOMUtils.findNodesByLocalName(xmlDocument, mavenProperty.getValue()).stream()
.filter(isMavenProperty).collect(Collectors.toList()).get(0);
.filter(isMavenProperty).findFirst().orElse(null);
} else {
DOMDocument propertyDeclaringDocument = org.eclipse.lemminx.utils.DOMUtils.loadDocument(
childProj.getFile().toURI().toString(),
request.getNode().getOwnerDocument().getResolverExtensionManager());
propertyDeclaration = DOMUtils.findNodesByLocalName(propertyDeclaringDocument, mavenProperty.getValue())
.stream().filter(isMavenProperty).collect(Collectors.toList()).get(0);
.stream().filter(isMavenProperty).findFirst().orElse(null);
}

if (propertyDeclaration == null) {
Expand Down Expand Up @@ -198,4 +199,4 @@ private static LocationLink toLocation(File target, DOMNode targetNode, Range or
Range targetRange = XMLPositionUtility.createRange(targetNode);
return new LocationLink(target.toURI().toString(), targetRange, targetRange, originRange);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@
import static org.eclipse.lemminx.extensions.maven.DOMConstants.DEPENDENCY_ELT;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.PLUGINS_ELT;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.PLUGIN_ELT;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.PROPERTIES_ELT;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.VERSION_ELT;

import java.io.File;
import java.net.URI;
import java.text.MessageFormat;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import java.util.function.UnaryOperator;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -57,6 +59,7 @@
import org.eclipse.lemminx.services.extensions.HoverParticipantAdapter;
import org.eclipse.lemminx.services.extensions.IHoverRequest;
import org.eclipse.lemminx.services.extensions.IPositionRequest;
import org.eclipse.lemminx.utils.XMLPositionUtility;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.MarkupContent;
import org.eclipse.lsp4j.MarkupKind;
Expand Down Expand Up @@ -139,6 +142,7 @@ yield hoverForProject(request,
private static final String PomTextHover_managed_version_missing = "The managed version could not be determined.";
private static final String PomTextHover_managed_location = "The artifact is managed in {0}";
private static final String PomTextHover_managed_location_missing = "The managed definition location could not be determined, probably defined by \"import\" scoped dependencies.";
private static final String PomTextHover_property_location = "The property is defined in {0}";

private static String getActualVersionText(boolean supportsMarkdown, MavenProject project) {
if (project == null) {
Expand Down Expand Up @@ -427,25 +431,56 @@ private Hover collectPluginConfiguration(IHoverRequest request) {

private Hover collectProperty(IHoverRequest request, Map.Entry<Range, String> property) {
boolean supportsMarkdown = request.canSupportMarkupKind(MarkupKind.MARKDOWN);
UnaryOperator<String> toBold = supportsMarkdown ? MarkdownUtils::toBold : UnaryOperator.identity();
String lineBreak = MarkdownUtils.getLineBreak(supportsMarkdown);
DOMDocument doc = request.getXMLDocument();
MavenProject project = plugin.getProjectCache().getLastSuccessfulMavenProject(doc);
if (project != null) {
Map<String, String> allProps = ParticipantUtils.getMavenProjectProperties(project);
UnaryOperator<String> toBold = supportsMarkdown ? MarkdownUtils::toBold : UnaryOperator.identity();
return allProps.entrySet().stream()
.filter(prop -> property.getValue().equals(prop.getKey()))
.map(prop -> {
StringBuilder message = new StringBuilder();
message.append(toBold.apply("Property: ")).append(prop.getKey()).append(lineBreak)
.append(toBold.apply("Value: ")).append(prop.getValue()).append(lineBreak);

// Find location
MavenProject parentProject = project, childProj = project;
while (parentProject != null && parentProject.getProperties().containsKey(property.getValue())) {
childProj = parentProject;
parentProject = parentProject.getParent();
}

DOMNode propertyDeclaration = null;
Predicate<DOMNode> isMavenProperty = (node) -> PROPERTIES_ELT.equals(node.getParentNode().getLocalName());

URI childProjectUri = ParticipantUtils.normalizedUri(childProj.getFile().toURI().toString());
URI thisProjectUri = ParticipantUtils.normalizedUri(doc.getDocumentURI());
if (childProjectUri.equals(thisProjectUri)) {
// Property is defined in the same file as the request
propertyDeclaration = DOMUtils.findNodesByLocalName(doc, property.getValue()).stream()
.filter(isMavenProperty).findFirst().orElse(null);
} else {
DOMDocument propertyDeclaringDocument = org.eclipse.lemminx.utils.DOMUtils.loadDocument(
childProj.getFile().toURI().toString(),
request.getNode().getOwnerDocument().getResolverExtensionManager());
propertyDeclaration = DOMUtils.findNodesByLocalName(propertyDeclaringDocument, property.getValue())
.stream().filter(isMavenProperty).findFirst().orElse(null);
}

for (Entry<String, String> prop : allProps.entrySet()) {
String mavenProperty = prop.getKey();
if (property.getValue().equals(mavenProperty)) {
String message = toBold.apply("Property: ") + mavenProperty + lineBreak + toBold.apply("Value: ")
+ prop.getValue() + lineBreak;
if (propertyDeclaration != null) {
String uri = childProj.getFile().toURI().toString();
Range targetRange = XMLPositionUtility.createRange(propertyDeclaration);
String sourceModelId = childProj.getGroupId() + ':' + childProj.getArtifactId() + ':' + childProj.getVersion();
message.append(toBold.apply(MessageFormat.format(PomTextHover_property_location,
supportsMarkdown ? MarkdownUtils.toLink(uri, targetRange, sourceModelId, null) : sourceModelId)));
}

Hover hover = new Hover(
new MarkupContent(supportsMarkdown ? MarkupKind.MARKDOWN : MarkupKind.PLAINTEXT, message));
new MarkupContent(supportsMarkdown ? MarkupKind.MARKDOWN : MarkupKind.PLAINTEXT, message.toString()));
hover.setRange(property.getKey());
return hover;
}
}
}).findAny().orElse(null);
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*******************************************************************************
* Copyright (c) 2020, 2022 Red Hat Inc. and others.
* Copyright (c) 2020, 2023 Red Hat Inc. and others.
* This program and the accompanying materials are made
* available under the terms of the Eclipse Public License 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
Expand All @@ -8,6 +8,11 @@
*******************************************************************************/
package org.eclipse.lemminx.extensions.maven.utils;

import java.util.Arrays;
import java.util.Objects;

import org.eclipse.lsp4j.Range;

public class MarkdownUtils {
public static final String LINE_BREAK = "\n\n";

Expand Down Expand Up @@ -47,12 +52,30 @@ public static String htmlXMLToMarkdown(String description) {
}

public static String toLink(String uri, String message, String title) {
return toLink(uri, null, message, title);
}

public static String toLink(String uri, Range range, String message, String title) {
StringBuilder link = new StringBuilder();

// [Message](http://example.com/ "Title")
link.append('[').append(message != null ? message : "This link").append(']');
if (uri != null) {
link.append('(').append(uri);
if(range != null && (range.getStart() != null || range.getEnd() != null)) {
// #L34,1-L35,3
StringBuilder selection = new StringBuilder();
Arrays.asList(range.getStart(), range.getEnd()).stream().filter(Objects::nonNull)
.forEach(r -> {
if (!selection.isEmpty()) {
selection.append('-');
}
selection.append('L').append(r.getLine() + 1).append(',').append(r.getCharacter() + 1);
});
if (!selection.isEmpty()) {
link.append('#').append(selection);
}
}
if (title != null && title.trim().length() > 0) {
link.append(' ').append('"').append(title.trim()).append('"');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import static org.eclipse.lemminx.extensions.maven.DOMConstants.RELATIVE_PATH_ELT;
import static org.eclipse.lemminx.extensions.maven.DOMConstants.VERSION_ELT;

import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
Expand Down Expand Up @@ -329,4 +330,13 @@ public static boolean match(Diagnostic diagnostic, String code) {
return code == null ? diagnostic.getCode().getLeft() == null :
code.equals(diagnostic.getCode().getLeft());
}

public static URI normalizedUri(String uriString) {
try {
return URI.create(uriString).normalize();
} catch (IllegalArgumentException e) {
LOGGER.log(Level.SEVERE, e.getMessage(), e);
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*******************************************************************************
* 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 2.0
* which is available at https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*******************************************************************************/
package org.eclipse.lemminx.extensions.maven.participants.hover;

import static org.eclipse.lemminx.extensions.maven.utils.MavenLemminxTestsUtils.createDOMDocument;
import static org.eclipse.lemminx.XMLAssert.assertHover;
import static org.eclipse.lemminx.XMLAssert.r;

import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.util.concurrent.ExecutionException;

import org.eclipse.lemminx.commons.BadLocationException;
import org.eclipse.lemminx.dom.DOMDocument;
import org.eclipse.lemminx.extensions.contentmodel.settings.ContentModelSettings;
import org.eclipse.lemminx.extensions.maven.NoMavenCentralExtension;
import org.eclipse.lemminx.extensions.maven.utils.ParticipantUtils;
import org.eclipse.lemminx.services.XMLLanguageService;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(NoMavenCentralExtension.class)
public class MavenPropertyHoverTest {
private XMLLanguageService languageService;

@BeforeEach
public void setUp() throws IOException {
languageService = new XMLLanguageService();
}

@AfterEach
public void tearDown() throws InterruptedException, ExecutionException {
languageService.dispose();
languageService = null;
}

@Test
public void testHpverForVariablePropertyDefinedInSameDocument()
throws IOException, InterruptedException, ExecutionException, URISyntaxException, BadLocationException {
DOMDocument document = createDOMDocument("/pom-with-properties-in-parent-for-definition.xml", languageService);
vrubezhny marked this conversation as resolved.
Show resolved Hide resolved
String text = document.getText();
int offset = text.indexOf("${anotherProperty}") + "${".length();
text = text.substring(0, offset) + '|' + text.substring(offset);
String expectedHoverText = """
**Property:** anotherProperty

**Value:** $

**The property is defined in [org.test:child:0.0.1-SNAPSHOT](%s#L16,3-L16,39)**""";

ContentModelSettings settings = new ContentModelSettings();
settings.setUseCache(false);
assertHover(languageService, text, null, document.getDocumentURI(),
String.format(expectedHoverText, document.getDocumentURI()),
r(28, 15,28, 27), settings);
}

@Test
public void testHpverForVariablePropertyDefinedInParentDocument()
throws IOException, InterruptedException, ExecutionException, URISyntaxException, BadLocationException {
DOMDocument document = createDOMDocument("/pom-with-properties-in-parent-for-definition.xml", languageService);
String text = document.getText();
int offset = text.indexOf("${myProperty}") + "${".length();
text = text.substring(0, offset) + '|' + text.substring(offset);
String expectedHoverText = """
**Property:** myProperty

**Value:** $

**The property is defined in [org.test:test:0.0.1-SNAPSHOT](%s#L12,3-L12,29)**""";

File documentFile = new File(ParticipantUtils.normalizedUri(document.getDocumentURI()).getPath());
File expectedFile = new File(documentFile.getParent(), "pom-with-properties-for-definition.xml");
ContentModelSettings settings = new ContentModelSettings();
settings.setUseCache(false);
assertHover(languageService, text, null, document.getDocumentURI(),
String.format(expectedHoverText, expectedFile.toURI().toString()),
r(23, 15, 23, 22), settings);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@
<artifactId>someAritfact</artifactId>
<version>${myProperty}</version>
</plugin>
<plugin>
<groupId>test</groupId>
<artifactId>anotherAritfact</artifactId>
<version>${anotherProperty}</version>
</plugin>
</plugins>
</build>
</project>