added content-assist proposal computer for property names in Spring XML config files
This commit is contained in:
@@ -141,6 +141,13 @@ public class StringUtil {
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String lowerCaseFirstChar(String string) {
|
||||
if (StringUtil.hasText(string)) {
|
||||
return Character.toLowerCase(string.charAt(0)) + string.substring(1);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public static String datestamp() {
|
||||
Date d = new Date();
|
||||
SimpleDateFormat f = new SimpleDateFormat("yyyyMMdd");
|
||||
|
||||
@@ -22,6 +22,8 @@ import org.eclipse.lsp4xml.dom.DOMParser;
|
||||
import org.eclipse.lsp4xml.dom.parser.Scanner;
|
||||
import org.eclipse.lsp4xml.dom.parser.TokenType;
|
||||
import org.eclipse.lsp4xml.dom.parser.XMLScanner;
|
||||
import org.springframework.ide.vscode.boot.xml.completions.PropertyNameCompletionProposalProvider;
|
||||
import org.springframework.ide.vscode.boot.xml.completions.TypeCompletionProposalProvider;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
@@ -37,12 +39,15 @@ public class SpringXMLCompletionEngine implements ICompletionEngine {
|
||||
private static final String BEAN_ELEMENT = "bean";
|
||||
private static final String BEAN_CLASS_ATTRIBUTE = "class";
|
||||
|
||||
private static final String PROPERTY_ELEMENT = "property";
|
||||
private static final String PROPERTY_NAME_ATTRIBUTE = "name";
|
||||
|
||||
private final Map<XMLCompletionProviderKey, XMLCompletionProvider> completionProviders;
|
||||
|
||||
public SpringXMLCompletionEngine(SpringXMLLanguageServerComponents springXMLLanguageServerComponents, JavaProjectFinder projectFinder) {
|
||||
this.completionProviders = new HashMap<>();
|
||||
this.completionProviders.put(new XMLCompletionProviderKey(BEANS_NAMESPACE, BEAN_ELEMENT, BEAN_CLASS_ATTRIBUTE), new TypeCompletionProposalProvider(projectFinder));
|
||||
this.completionProviders.put(new XMLCompletionProviderKey(BEANS_NAMESPACE, null, BEAN_ELEMENT, BEAN_CLASS_ATTRIBUTE), new TypeCompletionProposalProvider(projectFinder, true));
|
||||
this.completionProviders.put(new XMLCompletionProviderKey(BEANS_NAMESPACE, BEAN_ELEMENT, PROPERTY_ELEMENT, PROPERTY_NAME_ATTRIBUTE), new PropertyNameCompletionProposalProvider(projectFinder));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -66,7 +71,14 @@ public class SpringXMLCompletionEngine implements ICompletionEngine {
|
||||
DOMAttr attributeAt = dom.findAttrAt(offset);
|
||||
|
||||
if (attributeAt != null) {
|
||||
XMLCompletionProviderKey key = new XMLCompletionProviderKey(namespace, node.getLocalName(), attributeAt.getNodeName());
|
||||
XMLCompletionProviderKey key = new XMLCompletionProviderKey(namespace, null, node.getLocalName(), attributeAt.getNodeName());
|
||||
|
||||
if (!this.completionProviders.containsKey(key)) {
|
||||
DOMNode parentNode = node.getParentNode();
|
||||
String parentNodeName = parentNode != null ? parentNode.getLocalName() : null;
|
||||
key = new XMLCompletionProviderKey(namespace, parentNodeName, node.getLocalName(), attributeAt.getNodeName());
|
||||
}
|
||||
|
||||
XMLCompletionProvider completionProvider = this.completionProviders.get(key);
|
||||
if (completionProvider != null) {
|
||||
Collection<ICompletionProposal> completions = completionProvider.getCompletions(doc, namespace, node, attributeAt, scanner, offset);
|
||||
|
||||
@@ -18,10 +18,12 @@ public class XMLCompletionProviderKey {
|
||||
private final String namespaceURI;
|
||||
private final String elementName;
|
||||
private final String attributeName;
|
||||
private final String parentNodeName;
|
||||
|
||||
public XMLCompletionProviderKey(String namespaceURI, String elementName, String attributeName) {
|
||||
public XMLCompletionProviderKey(String namespaceURI, String parentNodeName, String elementName, String attributeName) {
|
||||
super();
|
||||
this.namespaceURI = namespaceURI;
|
||||
this.parentNodeName = parentNodeName;
|
||||
this.elementName = elementName;
|
||||
this.attributeName = attributeName;
|
||||
}
|
||||
@@ -30,6 +32,10 @@ public class XMLCompletionProviderKey {
|
||||
return namespaceURI;
|
||||
}
|
||||
|
||||
public String getParentNodeName() {
|
||||
return parentNodeName;
|
||||
}
|
||||
|
||||
public String getElementName() {
|
||||
return elementName;
|
||||
}
|
||||
@@ -45,6 +51,7 @@ public class XMLCompletionProviderKey {
|
||||
result = prime * result + ((attributeName == null) ? 0 : attributeName.hashCode());
|
||||
result = prime * result + ((elementName == null) ? 0 : elementName.hashCode());
|
||||
result = prime * result + ((namespaceURI == null) ? 0 : namespaceURI.hashCode());
|
||||
result = prime * result + ((parentNodeName == null) ? 0 : parentNodeName.hashCode());
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -72,6 +79,11 @@ public class XMLCompletionProviderKey {
|
||||
return false;
|
||||
} else if (!namespaceURI.equals(other.namespaceURI))
|
||||
return false;
|
||||
if (parentNodeName == null) {
|
||||
if (other.parentNodeName != null)
|
||||
return false;
|
||||
} else if (!parentNodeName.equals(other.parentNodeName))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/*******************************************************************************
|
||||
* Copyright (c) 2019 Pivotal, Inc.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* https://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.xml.completions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.eclipse.lsp4xml.dom.DOMAttr;
|
||||
import org.eclipse.lsp4xml.dom.DOMNode;
|
||||
import org.eclipse.lsp4xml.dom.parser.Scanner;
|
||||
import org.springframework.ide.vscode.boot.xml.XMLCompletionProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.IMethod;
|
||||
import org.springframework.ide.vscode.commons.java.IType;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
|
||||
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
|
||||
import org.springframework.ide.vscode.commons.util.Renderable;
|
||||
import org.springframework.ide.vscode.commons.util.StringUtil;
|
||||
import org.springframework.ide.vscode.commons.util.text.TextDocument;
|
||||
|
||||
/**
|
||||
* @author Martin Lippert
|
||||
*/
|
||||
public class PropertyNameCompletionProposalProvider implements XMLCompletionProvider {
|
||||
|
||||
private final JavaProjectFinder projectFinder;
|
||||
|
||||
public PropertyNameCompletionProposalProvider(JavaProjectFinder projectFinder) {
|
||||
this.projectFinder = projectFinder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ICompletionProposal> getCompletions(TextDocument doc, String namespace, DOMNode node, DOMAttr attributeAt,
|
||||
Scanner scanner, int offset) {
|
||||
|
||||
int tokenOffset = scanner.getTokenOffset();
|
||||
int tokenEnd = scanner.getTokenEnd();
|
||||
String tokenText = scanner.getTokenText();
|
||||
|
||||
Optional<IJavaProject> foundProject = this.projectFinder.find(doc.getId());
|
||||
if (foundProject.isPresent()) {
|
||||
IJavaProject project = foundProject.get();
|
||||
|
||||
String prefix = tokenText.substring(0, offset - tokenOffset);
|
||||
if (prefix.startsWith("\"")) {
|
||||
prefix = prefix.substring(1);
|
||||
}
|
||||
|
||||
String beanClass = identifyBeanClass(node);
|
||||
if (beanClass != null) {
|
||||
IType beanType = project.getIndex().findType(beanClass);
|
||||
|
||||
final String searchPrefix = prefix;
|
||||
return beanType.getMethods()
|
||||
.filter(method -> isPropertyWriteMethod(method))
|
||||
.filter(method -> getPropertyName(method).startsWith(searchPrefix))
|
||||
.map(method -> createProposal(method, doc, offset, tokenOffset, tokenEnd))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
};
|
||||
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private String identifyBeanClass(DOMNode node) {
|
||||
DOMNode parentNode = node.getParentNode();
|
||||
if (parentNode != null) {
|
||||
if ("bean".equals(parentNode.getLocalName())) {
|
||||
String beanClassAttribute = parentNode.getAttribute("class");
|
||||
return beanClassAttribute;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private ICompletionProposal createProposal(IMethod method, TextDocument doc, int offset, int tokenStart, int tokenEnd) {
|
||||
String label = getPropertyName(method);
|
||||
CompletionItemKind kind = CompletionItemKind.Method;
|
||||
|
||||
DocumentEdits edits = new DocumentEdits(doc);
|
||||
|
||||
String replaceString = "\"" + label + "\"";
|
||||
int replaceStart = tokenStart;
|
||||
|
||||
if (tokenStart < offset) {
|
||||
replaceStart = offset;
|
||||
replaceString = replaceString.substring(offset - tokenStart);
|
||||
}
|
||||
edits.replace(replaceStart, tokenEnd, replaceString);
|
||||
|
||||
Renderable renderable = null;
|
||||
|
||||
return new TypeCompletionProposal(label, kind, edits, label, renderable, 1d);
|
||||
}
|
||||
|
||||
private boolean isPropertyWriteMethod(IMethod method) {
|
||||
return method != null
|
||||
&& method.getElementName().startsWith("set")
|
||||
&& method.getElementName().length() > 3;
|
||||
}
|
||||
|
||||
private String getPropertyName(IMethod method) {
|
||||
String methodName = method.getElementName();
|
||||
if (methodName.startsWith("set")) {
|
||||
String propertyName = methodName.substring(3);
|
||||
if (propertyName.length() > 0) {
|
||||
return StringUtil.lowerCaseFirstChar(propertyName);
|
||||
}
|
||||
}
|
||||
return methodName;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -8,7 +8,7 @@
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.xml;
|
||||
package org.springframework.ide.vscode.boot.xml.completions;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
@@ -8,15 +8,17 @@
|
||||
* Contributors:
|
||||
* Pivotal, Inc. - initial API and implementation
|
||||
*******************************************************************************/
|
||||
package org.springframework.ide.vscode.boot.xml;
|
||||
package org.springframework.ide.vscode.boot.xml.completions;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.eclipse.lsp4j.CompletionItemKind;
|
||||
import org.eclipse.lsp4xml.dom.DOMAttr;
|
||||
import org.eclipse.lsp4xml.dom.DOMNode;
|
||||
import org.eclipse.lsp4xml.dom.parser.Scanner;
|
||||
import org.springframework.ide.vscode.boot.xml.XMLCompletionProvider;
|
||||
import org.springframework.ide.vscode.commons.java.IJavaProject;
|
||||
import org.springframework.ide.vscode.commons.java.IType;
|
||||
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
|
||||
@@ -34,9 +36,11 @@ import reactor.util.function.Tuple2;
|
||||
public class TypeCompletionProposalProvider implements XMLCompletionProvider {
|
||||
|
||||
private final JavaProjectFinder projectFinder;
|
||||
private final boolean classesOnly;
|
||||
|
||||
public TypeCompletionProposalProvider(JavaProjectFinder projectFinder) {
|
||||
public TypeCompletionProposalProvider(JavaProjectFinder projectFinder, boolean classesOnly) {
|
||||
this.projectFinder = projectFinder;
|
||||
this.classesOnly = classesOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,11 +65,12 @@ public class TypeCompletionProposalProvider implements XMLCompletionProvider {
|
||||
|
||||
return types
|
||||
.filter(result -> result.getT1() != null && result.getT1().getElementName() != null && result.getT1().getElementName().length() > 0)
|
||||
.filter(result -> classesOnly ? result.getT1().isClass() : true)
|
||||
.map(t -> createProposal(t, doc, offset, tokenOffset, tokenEnd))
|
||||
.collectList().block();
|
||||
};
|
||||
|
||||
return null;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
private ICompletionProposal createProposal(Tuple2<IType, Double> t, TextDocument doc, int offset, int tokenStart, int tokenEnd) {
|
||||
Reference in New Issue
Block a user