added content-assist proposal computer for property names in Spring XML config files

This commit is contained in:
Martin Lippert
2019-04-12 16:39:09 +02:00
parent 02986cc99b
commit ca88c6de4e
6 changed files with 168 additions and 7 deletions

View File

@@ -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");

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -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) {