diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java index 48a27e154..f75ddb306 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerComponents.java @@ -26,6 +26,7 @@ import org.springframework.ide.vscode.boot.java.autowired.AutowiredHoverProvider import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider; import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider; import org.springframework.ide.vscode.boot.java.conditionals.ConditionalsLiveHoverProvider; +import org.springframework.ide.vscode.boot.java.data.DataRepositoryCompletionProcessor; import org.springframework.ide.vscode.boot.java.data.DataRepositorySymbolProvider; import org.springframework.ide.vscode.boot.java.handlers.BootJavaCodeLensEngine; import org.springframework.ide.vscode.boot.java.handlers.BootJavaCompletionEngine; @@ -257,45 +258,44 @@ public class BootJavaLanguageServerComponents implements LanguageServerComponent protected ICompletionEngine createCompletionEngine( JavaProjectFinder javaProjectFinder, SpringPropertyIndexProvider indexProvider, - SpringPropertyIndexProvider adHocIndexProvider - ) { + SpringPropertyIndexProvider adHocIndexProvider) + { Map providers = new HashMap<>(); - providers.put(org.springframework.ide.vscode.boot.java.scope.Constants.SPRING_SCOPE, - new ScopeCompletionProcessor()); - providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE, - new ValueCompletionProcessor(indexProvider, adHocIndexProvider)); + providers.put(org.springframework.ide.vscode.boot.java.scope.Constants.SPRING_SCOPE, new ScopeCompletionProcessor()); + providers.put(org.springframework.ide.vscode.boot.java.value.Constants.SPRING_VALUE, new ValueCompletionProcessor(indexProvider, adHocIndexProvider)); + providers.put(Annotations.REPOSITORY, new DataRepositoryCompletionProcessor()); JavaSnippetManager snippetManager = new JavaSnippetManager(server::createSnippetBuilder); - snippetManager.add( - new JavaSnippet("RequestMapping method", JavaSnippetContext.BOOT_MEMBERS, CompletionItemKind.Method, - ImmutableList.of("org.springframework.web.bind.annotation.RequestMapping", - "org.springframework.web.bind.annotation.RequestMethod", - "org.springframework.web.bind.annotation.RequestParam"), - "@RequestMapping(value=\"${path}\", method=RequestMethod.${GET})\n" - + "public ${SomeData} ${requestMethodName}(@RequestParam ${String} ${param}) {\n" - + " return new ${SomeData}(${cursor});\n" + "}\n")); - snippetManager - .add(new JavaSnippet("GetMapping method", JavaSnippetContext.BOOT_MEMBERS, CompletionItemKind.Method, - ImmutableList.of("org.springframework.web.bind.annotation.GetMapping", - "org.springframework.web.bind.annotation.RequestParam"), - "@GetMapping(value=\"${path}\")\n" - + "public ${SomeData} ${getMethodName}(@RequestParam ${String} ${param}) {\n" - + " return new ${SomeData}(${cursor});\n" + "}\n")); - snippetManager.add(new JavaSnippet("PostMapping method", JavaSnippetContext.BOOT_MEMBERS, - CompletionItemKind.Method, - ImmutableList.of("org.springframework.web.bind.annotation.PostMapping", - "org.springframework.web.bind.annotation.RequestBody"), - "@PostMapping(value=\"${path}\")\n" - + "public ${SomeEnityData} ${postMethodName}(@RequestBody ${SomeEnityData} ${entity}) {\n" - + " //TODO: process POST request\n" + " ${cursor}\n" + " return ${entity};\n" + "}\n")); - snippetManager.add(new JavaSnippet("PutMapping method", JavaSnippetContext.BOOT_MEMBERS, - CompletionItemKind.Method, - ImmutableList.of("org.springframework.web.bind.annotation.PutMapping", - "org.springframework.web.bind.annotation.RequestBody", - "org.springframework.web.bind.annotation.PathVariable"), - "@PutMapping(value=\"${path}/{${id}}\")\n" - + "public ${SomeEnityData} ${putMethodName}(@PathVariable ${pvt:String} ${id}, @RequestBody ${SomeEnityData} ${entity}) {\n" - + " //TODO: process PUT request\n" + " ${cursor}\n" + " return ${entity};\n" + "}")); +// snippetManager.add( +// new JavaSnippet("RequestMapping method", JavaSnippetContext.BOOT_MEMBERS, CompletionItemKind.Method, +// ImmutableList.of("org.springframework.web.bind.annotation.RequestMapping", +// "org.springframework.web.bind.annotation.RequestMethod", +// "org.springframework.web.bind.annotation.RequestParam"), +// "@RequestMapping(value=\"${path}\", method=RequestMethod.${GET})\n" +// + "public ${SomeData} ${requestMethodName}(@RequestParam ${String} ${param}) {\n" +// + " return new ${SomeData}(${cursor});\n" + "}\n")); +// snippetManager +// .add(new JavaSnippet("GetMapping method", JavaSnippetContext.BOOT_MEMBERS, CompletionItemKind.Method, +// ImmutableList.of("org.springframework.web.bind.annotation.GetMapping", +// "org.springframework.web.bind.annotation.RequestParam"), +// "@GetMapping(value=\"${path}\")\n" +// + "public ${SomeData} ${getMethodName}(@RequestParam ${String} ${param}) {\n" +// + " return new ${SomeData}(${cursor});\n" + "}\n")); +// snippetManager.add(new JavaSnippet("PostMapping method", JavaSnippetContext.BOOT_MEMBERS, +// CompletionItemKind.Method, +// ImmutableList.of("org.springframework.web.bind.annotation.PostMapping", +// "org.springframework.web.bind.annotation.RequestBody"), +// "@PostMapping(value=\"${path}\")\n" +// + "public ${SomeEnityData} ${postMethodName}(@RequestBody ${SomeEnityData} ${entity}) {\n" +// + " //TODO: process POST request\n" + " ${cursor}\n" + " return ${entity};\n" + "}\n")); +// snippetManager.add(new JavaSnippet("PutMapping method", JavaSnippetContext.BOOT_MEMBERS, +// CompletionItemKind.Method, +// ImmutableList.of("org.springframework.web.bind.annotation.PutMapping", +// "org.springframework.web.bind.annotation.RequestBody", +// "org.springframework.web.bind.annotation.PathVariable"), +// "@PutMapping(value=\"${path}/{${id}}\")\n" +// + "public ${SomeEnityData} ${putMethodName}(@PathVariable ${pvt:String} ${id}, @RequestBody ${SomeEnityData} ${entity}) {\n" +// + " //TODO: process PUT request\n" + " ${cursor}\n" + " return ${entity};\n" + "}")); return new BootJavaCompletionEngine(this, providers, snippetManager); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/Constants.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/Constants.java new file mode 100644 index 000000000..dd4ec8883 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/Constants.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2018 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.data; + +public class Constants { + + public static final String REPOSITORY_TYPE = "org.springframework.data.repository.Repository"; + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryCompletionProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryCompletionProcessor.java new file mode 100644 index 000000000..cd8a49d1f --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryCompletionProcessor.java @@ -0,0 +1,96 @@ +/******************************************************************************* + * Copyright (c) 2018 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.data; + +import java.util.Collection; +import java.util.Optional; + +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.TypeDeclaration; +import org.eclipse.lsp4j.CompletionItemKind; +import org.springframework.ide.vscode.boot.java.handlers.CompletionProvider; +import org.springframework.ide.vscode.boot.java.utils.ASTUtils; +import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; +import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; +import org.springframework.ide.vscode.commons.util.text.IDocument; + +/** + * @author Martin Lippert + */ +public class DataRepositoryCompletionProcessor implements CompletionProvider { + + @Override + public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, + int offset, IDocument doc, Collection completions) { + } + + @Override + public void provideCompletions(ASTNode node, int offset, IDocument doc, Collection completions) { + TypeDeclaration type = ASTUtils.findDeclaringType(node); + DataRepositoryDefinition repo = getDataRepositoryDefinition(type); + if (repo != null) { + DocumentEdits edits = new DocumentEdits(null); + edits.insert(offset, "List findByLastName${1|(String lastName);,And,Or|}"); + + DocumentEdits additionalEdits = new DocumentEdits(null); +// additionalEdits.insert(offset, "(String lastName);"); + + completions.add(new FindByCompletionProposal("findByLastName(String lastName);", CompletionItemKind.Method, edits, null, null, Optional.of(additionalEdits))); + + System.out.println("data completion proposal calculation for: " + node.toString()); + } + } + + private DataRepositoryDefinition getDataRepositoryDefinition(TypeDeclaration type) { + if (type != null) { + ITypeBinding resolvedType = type.resolveBinding(); + return getDataRepositoryDefinition(type, resolvedType); + } + + return null; + } + + private DataRepositoryDefinition getDataRepositoryDefinition(TypeDeclaration type, ITypeBinding resolvedType) { + if (resolvedType != null) { + + // interface analysis + ITypeBinding[] interfaces = resolvedType.getInterfaces(); + for (ITypeBinding resolvedInterface : interfaces) { + String simplifiedType = null; + if (resolvedInterface.isParameterizedType()) { + simplifiedType = resolvedInterface.getBinaryName(); + } + else { + simplifiedType = resolvedType.getQualifiedName(); + } + + if (Constants.REPOSITORY_TYPE.equals(simplifiedType)) { + return new DataRepositoryDefinition(); + } + else { + DataRepositoryDefinition repo = getDataRepositoryDefinition(type, resolvedInterface); + if (repo != null) { + return repo; + } + } + } + + // super type analysis + ITypeBinding superclass = resolvedType.getSuperclass(); + if (superclass != null) { + return getDataRepositoryDefinition(type, superclass); + } + } + return null; + } +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryDefinition.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryDefinition.java new file mode 100644 index 000000000..8f2a2a442 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositoryDefinition.java @@ -0,0 +1,15 @@ +/******************************************************************************* + * Copyright (c) 2018 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.data; + +public class DataRepositoryDefinition { + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositorySymbolProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositorySymbolProvider.java index e4a5d6bc3..2ec66829b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositorySymbolProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/DataRepositorySymbolProvider.java @@ -36,8 +36,6 @@ import reactor.util.function.Tuples; * @author Martin Lippert */ public class DataRepositorySymbolProvider implements SymbolProvider { - - private static final String REPOSITORY_TYPE = "org.springframework.data.repository.Repository"; @Override public Collection getSymbols(TypeDeclaration typeDeclaration, TextDocument doc) { @@ -96,10 +94,10 @@ public class DataRepositorySymbolProvider implements SymbolProvider { simplifiedType = resolvedType.getQualifiedName(); } - if (REPOSITORY_TYPE.equals(simplifiedType)) { + if (Constants.REPOSITORY_TYPE.equals(simplifiedType)) { String beanName = getBeanName(typeDeclaration); String beanType = resolvedInterface.getName(); - + String domainType = null; if (resolvedInterface.isParameterizedType()) { ITypeBinding[] typeParameters = resolvedInterface.getTypeArguments(); diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/FindByCompletionProposal.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/FindByCompletionProposal.java new file mode 100644 index 000000000..74c2ee17e --- /dev/null +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/data/FindByCompletionProposal.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2018 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.data; + +import java.util.Optional; + +import org.eclipse.lsp4j.CompletionItemKind; +import org.eclipse.lsp4j.InsertTextFormat; +import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; +import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.text.IDocument; + +public class FindByCompletionProposal implements ICompletionProposal { + + private String label; + private CompletionItemKind kind; + private DocumentEdits edits; + private String details; + private Renderable doc; + private Optional additionalEdits; + + public FindByCompletionProposal(String label, CompletionItemKind kind, DocumentEdits edits, String details, + Renderable doc, Optional additionalEdits) { + super(); + this.label = label; + this.kind = kind; + this.edits = edits; + this.details = details; + this.doc = doc; + this.additionalEdits = additionalEdits; + } + + @Override + public String getLabel() { + return label; + } + + @Override + public CompletionItemKind getKind() { + return kind; + } + + @Override + public DocumentEdits getTextEdit() { + return edits; + } + + @Override + public String getDetail() { + return details; + } + + @Override + public Renderable getDocumentation() { + return doc; + } + + @Override + public Optional getAdditionalEdit() { + return additionalEdits; + } + + @Override + public InsertTextFormat getInsertTextFormat() { + return InsertTextFormat.Snippet; + } + +} diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java index 0dd720cec..d84abf4a3 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/BootJavaCompletionEngine.java @@ -49,8 +49,9 @@ public class BootJavaCompletionEngine implements ICompletionEngine { if (node != null) { Collection completions = new ArrayList(); - completions.addAll(collectCompletionsForAnnotations(node, offset, document)); - completions.addAll(snippets.getCompletions(document, offset, node, cu)); + collectCompletionsForAnnotations(node, offset, document, completions); + collectCompletions(node, offset, document, completions); + snippets.getCompletions(document, offset, node, cu, completions); return completions; } } @@ -59,7 +60,7 @@ public class BootJavaCompletionEngine implements ICompletionEngine { }); } - private Collection collectCompletionsForAnnotations(ASTNode node, int offset, IDocument doc) { + private void collectCompletionsForAnnotations(ASTNode node, int offset, IDocument doc, Collection completions) { Annotation annotation = null; ASTNode exactNode = node; @@ -75,13 +76,19 @@ public class BootJavaCompletionEngine implements ICompletionEngine { if (qualifiedName != null) { CompletionProvider provider = this.completionProviders.get(qualifiedName); if (provider != null) { - return provider.provideCompletions(exactNode, annotation, type, offset, doc); + provider.provideCompletions(exactNode, annotation, type, offset, doc, completions); } } } } + } - return Collections.emptyList(); + private void collectCompletions(ASTNode node, int offset, TextDocument document, Collection completions) { + if (node != null) { + for (CompletionProvider completionProvider : this.completionProviders.values()) { + completionProvider.provideCompletions(node, offset, document, completions); + } + } } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CompletionProvider.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CompletionProvider.java index ae1ec5148..25ae403cb 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CompletionProvider.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/handlers/CompletionProvider.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2018 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 @@ -23,6 +23,7 @@ import org.springframework.ide.vscode.commons.util.text.IDocument; */ public interface CompletionProvider { - Collection provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, int offset, IDocument doc); + void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, int offset, IDocument doc, Collection completions); + void provideCompletions(ASTNode node, int offset, IDocument doc, Collection completions); } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java index c997ef076..d61ec295b 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/scope/ScopeCompletionProcessor.java @@ -10,9 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java.scope; -import java.util.ArrayList; import java.util.Collection; -import java.util.List; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; @@ -30,10 +28,8 @@ import org.springframework.ide.vscode.commons.util.text.IDocument; public class ScopeCompletionProcessor implements CompletionProvider { @Override - public Collection provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, - int offset, IDocument doc) { - - List result = new ArrayList<>(); + public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, + int offset, IDocument doc, Collection completions) { try { if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair) { @@ -43,7 +39,7 @@ public class ScopeCompletionProcessor implements CompletionProvider { if ("value".equals(memberPair.getName().toString()) && memberPair.getValue().toString().equals("$missing$")) { for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, offset, offset, ""); - result.add(proposal); + completions.add(proposal); } } } @@ -51,7 +47,7 @@ public class ScopeCompletionProcessor implements CompletionProvider { else if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) { for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, offset, offset, ""); - result.add(proposal); + completions.add(proposal); } } else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) { @@ -61,7 +57,7 @@ public class ScopeCompletionProcessor implements CompletionProvider { for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { if (completion.getValue().startsWith(prefix)) { ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, node.getStartPosition(), node.getStartPosition() + node.getLength(), prefix); - result.add(proposal); + completions.add(proposal); } } } @@ -75,7 +71,7 @@ public class ScopeCompletionProcessor implements CompletionProvider { for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { if (completion.getValue().startsWith(prefix)) { ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, node.getStartPosition(), node.getStartPosition() + node.getLength(), prefix); - result.add(proposal); + completions.add(proposal); } } } @@ -84,8 +80,10 @@ public class ScopeCompletionProcessor implements CompletionProvider { catch (Exception e) { e.printStackTrace(); } + } - return result; + @Override + public void provideCompletions(ASTNode node, int offset, IDocument doc, Collection completions) { } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java index bbfd6d5bc..b3bc1d630 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetManager.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2018 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 @@ -47,9 +47,7 @@ public class JavaSnippetManager { } - public Collection getCompletions(IDocument doc, int offset, ASTNode node, CompilationUnit cu) { - Collection completions = new ArrayList<>(); - + public void getCompletions(IDocument doc, int offset, ASTNode node, CompilationUnit cu, Collection completions) { DocumentRegion query = PREFIX_FINDER.getPrefixRegion(doc, offset); for (JavaSnippet javaSnippet : snippets) { @@ -58,8 +56,6 @@ public class JavaSnippetManager { .ifPresent((completion) -> completions.add(completion)); } } - - return completions; } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueCompletionProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueCompletionProcessor.java index 954ce6d75..a9b0d522d 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueCompletionProcessor.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValueCompletionProcessor.java @@ -12,7 +12,6 @@ package org.springframework.ide.vscode.boot.java.value; import static org.springframework.ide.vscode.commons.util.StringUtil.camelCaseToHyphens; -import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.List; @@ -49,10 +48,8 @@ public class ValueCompletionProcessor implements CompletionProvider { } @Override - public Collection provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, - int offset, IDocument doc) { - - List result = new ArrayList<>(); + public void provideCompletions(ASTNode node, Annotation annotation, ITypeBinding type, + int offset, IDocument doc, Collection completions) { try { // case: @Value(<*>) @@ -69,40 +66,42 @@ public class ValueCompletionProcessor implements CompletionProvider { // if it cannot resolve it. If sending this as plain text, then insertion happens correctly ValuePropertyKeyProposal proposal = new ValuePropertyKeyProposal(edits, match, InsertTextFormat.PlainText); - result.add(proposal); + completions.add(proposal); } } // case: @Value(prefix<*>) else if (node instanceof SimpleName && node.getParent() instanceof Annotation) { - computeProposalsForSimpleName(node, result, offset, doc); + computeProposalsForSimpleName(node, completions, offset, doc); } // case: @Value(value=<*>) else if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair && "value".equals(((MemberValuePair)node.getParent()).getName().toString())) { - computeProposalsForSimpleName(node, result, offset, doc); + computeProposalsForSimpleName(node, completions, offset, doc); } // case: @Value("prefix<*>") else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) { if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { - computeProposalsForStringLiteral(node, result, offset, doc); + computeProposalsForStringLiteral(node, completions, offset, doc); } } // case: @Value(value="prefix<*>") else if (node instanceof StringLiteral && node.getParent() instanceof MemberValuePair && "value".equals(((MemberValuePair)node.getParent()).getName().toString())) { if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { - computeProposalsForStringLiteral(node, result, offset, doc); + computeProposalsForStringLiteral(node, completions, offset, doc); } } } catch (Exception e) { e.printStackTrace(); } - - return result; } - private void computeProposalsForSimpleName(ASTNode node, List completions, int offset, + @Override + public void provideCompletions(ASTNode node, int offset, IDocument doc, Collection completions) { + } + + private void computeProposalsForSimpleName(ASTNode node, Collection completions, int offset, IDocument doc) { String prefix = identifyPropertyPrefix(node.toString(), offset - node.getStartPosition()); @@ -125,7 +124,7 @@ public class ValueCompletionProcessor implements CompletionProvider { } } - private void computeProposalsForStringLiteral(ASTNode node, List completions, int offset, + private void computeProposalsForStringLiteral(ASTNode node, Collection completions, int offset, IDocument doc) throws BadLocationException { String prefix = identifyPropertyPrefix(doc.get(node.getStartPosition() + 1, offset - (node.getStartPosition() + 1)), offset - (node.getStartPosition() + 1)); diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryCompletionProcessorTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryCompletionProcessorTest.java new file mode 100644 index 000000000..c214b0c22 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositoryCompletionProcessorTest.java @@ -0,0 +1,81 @@ +/******************************************************************************* + * Copyright (c) 2018 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.data.test; + +import static org.junit.Assert.assertEquals; + +import java.io.InputStream; +import java.util.List; + +import org.apache.commons.io.IOUtils; +import org.eclipse.lsp4j.CompletionItem; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Import; +import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; +import org.springframework.ide.vscode.boot.bootiful.HoverTestConf; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.util.text.LanguageId; +import org.springframework.ide.vscode.languageserver.testharness.Editor; +import org.springframework.ide.vscode.languageserver.testharness.TestAsserts; +import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; +import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import org.springframework.test.context.junit4.SpringRunner; + +/** + * @author Martin Lippert + */ +@RunWith(SpringRunner.class) +@BootLanguageServerTest +@Import(HoverTestConf.class) +public class DataRepositoryCompletionProcessorTest { + + @Autowired private BootLanguageServerHarness harness; + private Editor editor; + + @Before + public void setup() throws Exception { + IJavaProject testProject = ProjectsHarness.INSTANCE.mavenProject("test-spring-data-symbols"); + harness.useProject(testProject); + harness.intialize(null); + } + + @Test + public void testStandardFindByCompletions() throws Exception { + prepareCase("{", "{<*>"); + assertAnnotationCompletions( + "findByLastName<*>(String lastName);"); + } + + private void prepareCase(String selectedAnnotation, String annotationStatementBeforeTest) throws Exception { + InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-spring-data-symbols/src/main/java/org/test/TestCustomerRepositoryForCompletions.java"); + String content = IOUtils.toString(resource); + + content = content.replace(selectedAnnotation, annotationStatementBeforeTest); + editor = new Editor(harness, content, LanguageId.JAVA); + } + + private void assertAnnotationCompletions(String... completedAnnotations) throws Exception { + List completions = editor.getCompletions(); + int i = 0; + for (String expectedCompleted : completedAnnotations) { + Editor clonedEditor = editor.clone(); + clonedEditor.apply(completions.get(i++)); + TestAsserts.assertContains(expectedCompleted, clonedEditor.getText()); + } + + assertEquals(i, completions.size()); + } + + +} diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositorySymbolProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositorySymbolProviderTest.java index f79d04f30..cc7f50fd0 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositorySymbolProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/data/test/DataRepositorySymbolProviderTest.java @@ -63,7 +63,7 @@ public class DataRepositorySymbolProviderTest { } @Test - public void testSimpleReppositorySymbol() throws Exception { + public void testSimpleRepositorySymbol() throws Exception { String docUri = directory.toPath().resolve("src/main/java/org/test/CustomerRepository.java").toUri().toString(); List symbols = indexer.getSymbols(docUri); assertEquals(1, symbols.size()); diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-data-symbols/src/main/java/org/test/TestCustomerRepositoryForCompletions.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-data-symbols/src/main/java/org/test/TestCustomerRepositoryForCompletions.java new file mode 100644 index 000000000..1da41bae8 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-spring-data-symbols/src/main/java/org/test/TestCustomerRepositoryForCompletions.java @@ -0,0 +1,8 @@ +package org.test; + +import java.util.List; + +import org.springframework.data.repository.CrudRepository; + +public interface TestCustomerRepositoryForCompletions extends CrudRepository { +}