diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProcessor.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProcessor.java index fa970c989..d467c1f08 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProcessor.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/annotations/AnnotationAttributeCompletionProcessor.java @@ -141,7 +141,15 @@ public class AnnotationAttributeCompletionProcessor implements CompletionProvide String prefix = identifyPropertyPrefix(node.toString(), offset - node.getStartPosition()); int startOffset = node.getStartPosition(); - int endOffset = node.getStartPosition() + node.getLength(); + + // special adjustment for the case of a $missing$ SimpleName node, e.g. @DependsOn(value = <*>) + // where the node starts right after the "=", not at the offset + if (node instanceof SimpleName && "$missing$".equals(((SimpleName)node).getIdentifier())) { + startOffset = offset; + prefix = ""; + } + + int endOffset = startOffset + node.getLength(); String proposalPrefix = "\""; String proposalPostfix = "\""; 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 952295d0e..cbcfe225c 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 @@ -17,13 +17,16 @@ import java.util.Map; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.CompilationUnit; import org.eclipse.jdt.core.dom.ITypeBinding; import org.eclipse.jdt.core.dom.NodeFinder; +import org.eclipse.jdt.core.dom.SimpleName; import org.springframework.ide.vscode.boot.java.snippets.JavaSnippetManager; import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache; 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.util.LanguageSpecific; +import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -48,7 +51,7 @@ public class BootJavaCompletionEngine implements ICompletionEngine, LanguageSpec public Collection getCompletions(TextDocument document, int offset) throws Exception { return cuCache.withCompilationUnit(document, cu -> { if (cu != null) { - ASTNode node = NodeFinder.perform(cu, offset, 0); + ASTNode node = findNode(document, offset, cu); if (node != null) { Collection completions = new ArrayList(); @@ -63,6 +66,36 @@ public class BootJavaCompletionEngine implements ICompletionEngine, LanguageSpec }); } + private ASTNode findNode(TextDocument document, int offset, CompilationUnit cu) { + ASTNode node = NodeFinder.perform(cu, offset, 0); + + // take special case into account, e.g. @DependsOn(value = <*>) + // in this case the NodeFinder returns the outer AST node (the one where the annotation belongs to + // (e.g. the type declaration). This happens because the $missing$ SimpleName node is inserted right after + // the "=". Therefore, the NodeFinder doesn't find the SimpleName at the position behind the space + // and the information about the surrounding annotation is lost. + // + // For that case, we look for spaces before the offset and a $missing$ SimpleName to identify this + // exact situation and pass on the ASTNode for the SimpleName. + try { + int numberOfSpaces = 0; + while (Character.isSpaceChar(document.getChar(offset - numberOfSpaces - 1))) { + numberOfSpaces++; + } + + if (numberOfSpaces > 0) { + ASTNode leftNode = NodeFinder.perform(cu, offset - numberOfSpaces, 0); + if (leftNode instanceof SimpleName && "$missing$".equals(((SimpleName)leftNode).getIdentifier())) { + node = leftNode; + } + } + } catch (BadLocationException e) { + // ignore, keep the original node + } + + return node; + } + private void collectCompletionsForAnnotations(ASTNode node, int offset, TextDocument doc, Collection completions) { Annotation annotation = null; ASTNode exactNode = node; diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnCompletionProviderTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnCompletionProviderTest.java index c2de42c04..fad38e2ee 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnCompletionProviderTest.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/DependsOnCompletionProviderTest.java @@ -108,11 +108,15 @@ public class DependsOnCompletionProviderTest { assertCompletions("@DependsOn(value=<*>)", 2, "@DependsOn(value=\"bean1\"<*>)"); } - // TODO: not yet working, needs more groundwork due to the parser skipping these non-valid parts of the AST -// @Test -// public void testDependsOnCompletionWithoutQuotesWithAttributeNameAndDefaultSpaces() throws Exception { -// assertCompletions("@DependsOn(value = <*>)", 2, "@DependsOn(value = \"bean1\"<*>)"); -// } + @Test + public void testDependsOnCompletionWithoutQuotesWithAttributeNameAndDefaultSpaces() throws Exception { + assertCompletions("@DependsOn(value = <*>)", 2, "@DependsOn(value = \"bean1\"<*>)"); + } + + @Test + public void testDependsOnCompletionWithoutQuotesWithAttributeNameAndManySpaces() throws Exception { + assertCompletions("@DependsOn(value = <*> )", 2, "@DependsOn(value = \"bean1\"<*> )"); + } @Test public void testDependsOnCompletionInsideOfQuotesWithoutPrefix() throws Exception {