From d9905c40cef59a14fcf8ce3485b940e381255a78 Mon Sep 17 00:00:00 2001 From: "nsingh@pivotal.io" Date: Wed, 13 Feb 2019 14:19:30 -0800 Subject: [PATCH] PT 163178235 - Highlight hints for basic @Value cases Properties in @Value now have highlight hints if they have live hover information. Only basic cases are supported. Complex SpEL expressions are not yet supported if multiple properties appear in the expression. --- .../boot/java/value/ValueHoverProvider.java | 134 +++++++++++++++++- 1 file changed, 128 insertions(+), 6 deletions(-) diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java index 6789ad80c..a4bcd6664 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueHoverProvider.java @@ -10,17 +10,21 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.value; +import java.util.ArrayList; +import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.ASTVisitor; import org.eclipse.jdt.core.dom.Annotation; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.MemberValuePair; import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.StringLiteral; import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.lsp4j.CodeLens; import org.eclipse.lsp4j.Hover; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.jsonrpc.messages.Either; @@ -48,7 +52,8 @@ public class ValueHoverProvider implements HoverProvider { IJavaProject project, SpringBootApp[] runningApps) { try { - ASTNode exactNode = getExactNode(node, offset); + ASTNode foundNode = NodeFinder.perform(node, offset, 0); + ASTNode exactNode = getExactNode(foundNode); if (exactNode != null) { return provideHover(exactNode.toString(), offset - exactNode.getStartPosition(), @@ -61,8 +66,7 @@ public class ValueHoverProvider implements HoverProvider { return null; } - private ASTNode getExactNode(ASTNode node, int offset) { - ASTNode exactNode = NodeFinder.perform(node, offset, 0); + private ASTNode getExactNode(ASTNode exactNode) { if (exactNode != null) { // case: @Value("prefix<*>") if (exactNode instanceof StringLiteral && exactNode.getParent() instanceof Annotation) { @@ -84,7 +88,7 @@ public class ValueHoverProvider implements HoverProvider { private Hover provideHover(String value, int offset, int nodeStartOffset, TextDocument doc, SpringBootApp[] runningApps) { try { - LocalRange range = getPropertyRange(value, offset); + LocalRange range = parsePropertyOnHoverOffset(value, offset); if (range != null) { String propertyKey = value.substring(range.getStart(), range.getEnd()); @@ -123,6 +127,72 @@ public class ValueHoverProvider implements HoverProvider { return null; } + /** + * Highlight hints for live hover information is provided separately from the + * actual live hovers. To provide highlight hints, we only need to find if, for + * the given String literal node, it contains a property that has live + * properties, but we don't need to compute all the live properties for the + * hint. We only need to compute a code lens with the range of the property that + * has matching live properties. + * + * @param doc + * @param node + * @param runningApps + * @return + */ + private List provideHighlightHints(TextDocument doc, StringLiteral node, SpringBootApp[] runningApps) { + ASTNode exactNode = getExactNode(node); + + if (exactNode != null) { + try { + String propFromValue = null; + LocalRange propRange = null; + + // Get the escaped value that INCLUDES the quotes as we want to compute + // the hint range in the editor of the property and need to take into account + // all characters in the node value. + // For example: @Value(value = "${a.prop}") + // to highlight a.prop we need to take into account the starting '"' after the '=' + // to get the correct range of a.prop + String nodeValue = node.getEscapedValue(); + if (nodeValue != null) { + // Get actual range and property from the node, as to highlight it + propRange = parseProperty(nodeValue); + if (propRange != null) { + propFromValue = nodeValue.substring(propRange.getStart(), propRange.getEnd()); + } + } + + // Now find live information for the property. If found, highlight that property via a Code Lens + if (propFromValue != null && propRange != null) { + List matchingLiveProperties = findMatchingLiveProperties(runningApps, propFromValue); + + if (matchingLiveProperties != null && !matchingLiveProperties.isEmpty()) { + Range hoverRange = doc.toRange(exactNode.getStartPosition() + propRange.getStart(), + propRange.getEnd() - propRange.getStart()); + return ImmutableList.of(new CodeLens(hoverRange)); + } + } + } catch (Exception e) { + logger.error("Error while generating highlight hints for properties in @Value", e); + } + } + return ImmutableList.of(); + } + + private List findMatchingLiveProperties(SpringBootApp[] runningApps, String propFromValue) { + Map allProperties = getPropertiesFromProcesses(runningApps); + + for (SpringBootApp app : allProperties.keySet()) { + LiveProperties properties = allProperties.get(app); + List matchingLiveProperties = properties.getProperties(propFromValue); + if (matchingLiveProperties != null && !matchingLiveProperties.isEmpty()) { + return matchingLiveProperties; + } + } + return null; + } + public Map getPropertiesFromProcesses(SpringBootApp[] runningApps) { Map result = new HashMap<>(); @@ -142,14 +212,23 @@ public class ValueHoverProvider implements HoverProvider { } public String getPropertyKey(String value, int offset) { - LocalRange range = getPropertyRange(value, offset); + LocalRange range = parsePropertyOnHoverOffset(value, offset); if (range != null) { return value.substring(range.getStart(), range.getEnd()); } return null; } - public LocalRange getPropertyRange(String value, int offset) { + /** + * Find the range of a property in the given value, starting from an offset point, where the offset is + * the hover cursor location. This will only attempt to parse the property from the value, IFF the offset is on the + * property itself, but not on '$', '{', or '}'. For example, for "${a.prop}", property "a.prop" will only be parsed + * if offset is anywhere within the range of the "a.prop", but not '$', '{', or '}'. + * @param value + * @param offset + * @return + */ + private LocalRange parsePropertyOnHoverOffset(String value, int offset) { int start = -1; int end = -1; @@ -180,6 +259,32 @@ public class ValueHoverProvider implements HoverProvider { return null; } + private LocalRange parseProperty(String value) { + int start = -1; + int end = -1; + + for (int i = value.length() - 1; i >= 0; i--) { + if (value.charAt(i) == '{') { + start = i + 1; + break; + } + } + + for(int i = 0; i < value.length(); i++) { + + if (value.charAt(i) == '}') { + end = i; + break; + } + } + + if (start > 0 && start < value.length() && end > 0 && end <= value.length() && start < end) { + return new LocalRange(start, end); + } + + return null; + } + public static class LocalRange { private int start; private int end; @@ -205,4 +310,21 @@ public class ValueHoverProvider implements HoverProvider { return null; } + @Override + public Collection getLiveHintCodeLenses(IJavaProject project, Annotation annotation, TextDocument doc, SpringBootApp[] runningApps) { + // Show highlight hints for properties in @Value that have live information + + List lenses = new ArrayList<>(); + annotation.accept(new ASTVisitor() { + @Override + public boolean visit(StringLiteral node) { + List provideHighlightHints = provideHighlightHints(doc, node, runningApps); + lenses.addAll(provideHighlightHints); + return super.visit(node); + } + }); + + return lenses; + } + }