diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/SpringXMLCompletionEngine.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/SpringXMLCompletionEngine.java index 517996012..b94a8e1a7 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/SpringXMLCompletionEngine.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/SpringXMLCompletionEngine.java @@ -58,6 +58,7 @@ import org.eclipse.lemminx.dom.parser.XMLScanner; import org.springframework.ide.vscode.boot.app.BootJavaConfig; import org.springframework.ide.vscode.boot.app.SpringSymbolIndex; import org.springframework.ide.vscode.boot.xml.completions.BeanRefCompletionProposalProvider; +import org.springframework.ide.vscode.boot.xml.completions.ConstructorArgNameCompletionProposalProvider; 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; @@ -95,6 +96,7 @@ public class SpringXMLCompletionEngine implements ICompletionEngine, LanguageSpe BeanRefCompletionProposalProvider beanRefProvider = new BeanRefCompletionProposalProvider(projectFinder, symbolIndex); PropertyNameCompletionProposalProvider propertyNameProvider = new PropertyNameCompletionProposalProvider(projectFinder); + ConstructorArgNameCompletionProposalProvider constructorArgNameProvider = new ConstructorArgNameCompletionProposalProvider(projectFinder); this.completionProviders.put(new XMLElementKey(BEANS_NAMESPACE, null, BEAN_ELEMENT, CLASS_ATTRIBUTE), classesOnlyProvider); this.completionProviders.put(new XMLElementKey(BEANS_NAMESPACE, null, CONSTRUCTOR_ARG_ELEMENT, TYPE_ATTRIBUTE), classesAndInterfacesProvider); @@ -115,6 +117,7 @@ public class SpringXMLCompletionEngine implements ICompletionEngine, LanguageSpe this.completionProviders.put(new XMLElementKey(BEANS_NAMESPACE, BEAN_ELEMENT, PROPERTY_ELEMENT, REF_ATTRIBUTE), beanRefProvider); this.completionProviders.put(new XMLElementKey(BEANS_NAMESPACE, BEAN_ELEMENT, PROPERTY_ELEMENT, NAME_ATTRIBUTE), propertyNameProvider); + this.completionProviders.put(new XMLElementKey(BEANS_NAMESPACE, null, CONSTRUCTOR_ARG_ELEMENT, NAME_ATTRIBUTE), constructorArgNameProvider); this.completionProviders.put(new XMLElementKey(UTIL_NAMESPACE, null, null, VALUE_TYPE_ATTRIBUTE), classesAndInterfacesProvider); this.completionProviders.put(new XMLElementKey(UTIL_NAMESPACE, null, null, KEY_TYPE_ATTRIBUTE), classesAndInterfacesProvider); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/BeanRefCompletionProposalProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/BeanRefCompletionProposalProvider.java index 7a38e4f25..0d7017833 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/BeanRefCompletionProposalProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/BeanRefCompletionProposalProvider.java @@ -89,7 +89,7 @@ public class BeanRefCompletionProposalProvider implements XMLCompletionProvider Renderable renderable = null; - return new TypeCompletionProposal(beanID, kind, edits, beanID, renderable, score); + return new GenericXMLCompletionProposal(beanID, kind, edits, beanID, renderable, score); } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/ConstructorArgNameCompletionProposalProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/ConstructorArgNameCompletionProposalProvider.java new file mode 100644 index 000000000..0b11c4cad --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/ConstructorArgNameCompletionProposalProvider.java @@ -0,0 +1,160 @@ +/******************************************************************************* + * Copyright (c) 2020 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 static org.springframework.ide.vscode.boot.xml.XmlConfigConstants.BEAN_ELEMENT; +import static org.springframework.ide.vscode.boot.xml.XmlConfigConstants.CLASS_ATTRIBUTE; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import org.apache.commons.lang3.tuple.Pair; +import org.eclipse.lemminx.dom.DOMAttr; +import org.eclipse.lemminx.dom.DOMNode; +import org.eclipse.lemminx.dom.parser.Scanner; +import org.eclipse.lsp4j.CompletionItemKind; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.ide.vscode.boot.metadata.types.Type; +import org.springframework.ide.vscode.boot.xml.XMLCompletionProvider; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.java.IJavaType; +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.text.TextDocument; + +/** + * @author Martin Lippert + */ +public class ConstructorArgNameCompletionProposalProvider implements XMLCompletionProvider { + + private static final Logger log = LoggerFactory.getLogger(ConstructorArgNameCompletionProposalProvider.class); + + private final JavaProjectFinder projectFinder; + + public ConstructorArgNameCompletionProposalProvider(JavaProjectFinder projectFinder) { + this.projectFinder = projectFinder; + } + + @Override + public Collection 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(); + + log.info("Stating calculating completions for {} at offset {}.", doc.getId().getUri(), offset); + + + Optional foundProject = this.projectFinder.find(doc.getId()); + if (foundProject.isPresent()) { + IJavaProject project = foundProject.get(); + log.info("Project found is {}", project.getElementName()); + + String prefix = tokenText.substring(0, offset - tokenOffset); + if (prefix.startsWith("\"")) { + prefix = prefix.substring(1); + } + + log.info("Prefix is '{}'", prefix); + + String beanClass = identifyBeanClass(node); + if (beanClass != null && beanClass.length() > 0) { + + log.info("Bean class '{}'", beanClass); + + final String searchPrefix = prefix; + return constructorArgNameCandidates(project, beanClass) + .filter(constructorArg -> constructorArg.getRight().startsWith(searchPrefix)) + .map(constructorArg -> createProposal(constructorArg, doc, offset, tokenOffset, tokenEnd)) + .collect(Collectors.toList()); + } + }; + + return Collections.emptyList(); + } + + public static String identifyBeanClass(DOMNode node) { + DOMNode parentNode = node.getParentNode(); + if (parentNode != null) { + if (BEAN_ELEMENT.equals(parentNode.getLocalName())) { + String beanClassAttribute = parentNode.getAttribute(CLASS_ATTRIBUTE); + return beanClassAttribute; + } + } + return null; + } + + private ICompletionProposal createProposal(Pair constructorArg, TextDocument doc, int offset, int tokenStart, int tokenEnd) { + String label = constructorArg.getRight(); + CompletionItemKind kind = CompletionItemKind.Reference; + + DocumentEdits edits = new DocumentEdits(doc, false); + + String replaceString = "\"" + label + "\""; + int replaceStart = tokenStart; + + if (tokenStart < offset) { + replaceStart = offset; + replaceString = replaceString.substring(offset - tokenStart); + } + edits.replace(replaceStart, tokenEnd, replaceString); + + Renderable renderable = null; + String detail = label; + + IJavaType paramType = constructorArg.getLeft(); + if (paramType != null) { + Type type = Type.fromJavaType(paramType); + if (type != null) { + detail = constructorArg.getRight() + " - " + type.toString(); + } + } + + return new GenericXMLCompletionProposal(label, kind, edits, detail, renderable, 1d); + } + + public static Stream> constructorArgNameCandidates(IJavaProject project, String beanClassFqName) { + IType type = project.getIndex().findType(beanClassFqName); + if (type != null) { + return type.getMethods() + .filter(method -> method.isConstructor()) + .flatMap(method -> getConstructorArgs(method)); + } + else { + return Stream.empty(); + } + } + + private static Stream> getConstructorArgs(IMethod method) { + List parameterNames = method.getParameterNames(); + List parameterTypes = method.parameters().collect(Collectors.toList()); + + if (parameterNames != null && parameterTypes != null && parameterNames.size() == parameterTypes.size()) { + return IntStream.range(0, parameterNames.size()) + .mapToObj(i -> Pair.of(parameterTypes.get(i), parameterNames.get(i))); + } + else { + return Stream.empty(); + } + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/TypeCompletionProposal.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/GenericXMLCompletionProposal.java similarity index 86% rename from headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/TypeCompletionProposal.java rename to headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/GenericXMLCompletionProposal.java index 27aae876d..0870dff7f 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/TypeCompletionProposal.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/GenericXMLCompletionProposal.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2019 Pivotal, Inc. + * Copyright (c) 2019, 2020 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 @@ -18,7 +18,7 @@ import org.springframework.ide.vscode.commons.util.Renderable; /** * @author Martin Lippert */ -public class TypeCompletionProposal extends ScoreableProposal { +public class GenericXMLCompletionProposal extends ScoreableProposal { private final String label; private final CompletionItemKind kind; @@ -27,7 +27,7 @@ public class TypeCompletionProposal extends ScoreableProposal { private final Renderable documentation; private final double score; - public TypeCompletionProposal(String label, CompletionItemKind kind, DocumentEdits edits, String detail, Renderable documentation, double score) { + public GenericXMLCompletionProposal(String label, CompletionItemKind kind, DocumentEdits edits, String detail, Renderable documentation, double score) { super(); this.label = label; this.kind = kind; diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/PropertyNameCompletionProposalProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/PropertyNameCompletionProposalProvider.java index a1c6a2784..773a6f610 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/PropertyNameCompletionProposalProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/PropertyNameCompletionProposalProvider.java @@ -115,7 +115,7 @@ public class PropertyNameCompletionProposalProvider implements XMLCompletionProv Renderable renderable = null; - return new TypeCompletionProposal(label, kind, edits, label, renderable, 1d); + return new GenericXMLCompletionProposal(label, kind, edits, label, renderable, 1d); } private static boolean isPropertyWriteMethod(IMethod method) { diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/TypeCompletionProposalProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/TypeCompletionProposalProvider.java index 17ec73515..df4185213 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/TypeCompletionProposalProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/xml/completions/TypeCompletionProposalProvider.java @@ -124,7 +124,7 @@ public class TypeCompletionProposalProvider implements XMLCompletionProvider { } } - private TypeCompletionProposal createPackageProposal(JavaCodeCompleteData proposal, TextDocument doc, String prefix, int offset) { + private GenericXMLCompletionProposal createPackageProposal(JavaCodeCompleteData proposal, TextDocument doc, String prefix, int offset) { String fqName = proposal.getFullyQualifiedName(); String label = fqName; CompletionItemKind kind = CompletionItemKind.Module; @@ -138,10 +138,10 @@ public class TypeCompletionProposalProvider implements XMLCompletionProvider { Renderable renderable = null; - return new TypeCompletionProposal(label, kind, edits, fqName, renderable, proposal.getRelevance()); + return new GenericXMLCompletionProposal(label, kind, edits, fqName, renderable, proposal.getRelevance()); } - private TypeCompletionProposal createTypeProposal(JavaCodeCompleteData proposal, TextDocument doc, String prefix, int offset) { + private GenericXMLCompletionProposal createTypeProposal(JavaCodeCompleteData proposal, TextDocument doc, String prefix, int offset) { String fqName = proposal.getFullyQualifiedName(); String label = fqName; @@ -168,7 +168,7 @@ public class TypeCompletionProposalProvider implements XMLCompletionProvider { Renderable renderable = null; - return new TypeCompletionProposal(label, kind, edits, fqName, renderable, proposal.getRelevance()); + return new GenericXMLCompletionProposal(label, kind, edits, fqName, renderable, proposal.getRelevance()); } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/PropertyReferenceFinderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/PropertyReferenceFinderTest.java index 7beda60dc..d89176a1c 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/PropertyReferenceFinderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/PropertyReferenceFinderTest.java @@ -18,7 +18,6 @@ import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; import java.util.List; -import java.util.concurrent.CompletableFuture; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.WorkspaceFolder;