From 885977ee7d7fdef2bc340be8f6b395e81834754b Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Mon, 27 Nov 2023 14:15:50 +0100 Subject: [PATCH] GH-758: improved verification of whether to show mapping method snippets or not at a certain position in the code --- .../BootJavaCompletionEngineConfigurer.java | 14 ++++---- .../boot/java/snippets/JavaSnippet.java | 23 ++++++------ .../java/snippets/JavaSnippetContext.java | 12 ++++--- .../java/snippets/JavaSnippetManager.java | 36 +++++++++---------- .../test/RequestMappingSnippetTests.java | 21 +++++++++++ 5 files changed, 62 insertions(+), 44 deletions(-) diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java index 6b4289f02..17542b411 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/app/BootJavaCompletionEngineConfigurer.java @@ -10,7 +10,6 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.app; -import java.util.Collection; import java.util.HashMap; import java.util.Map; @@ -37,7 +36,6 @@ import org.springframework.ide.vscode.boot.java.value.ValueCompletionProcessor; import org.springframework.ide.vscode.boot.metadata.ProjectBasedPropertyIndexProvider; import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; -import org.springframework.ide.vscode.commons.languageserver.util.LspClient; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import com.google.common.collect.ImmutableList; @@ -52,10 +50,10 @@ public class BootJavaCompletionEngineConfigurer { // PT 160529904: Eclipse templates are duplicated, due to templates in Eclipse also being contributed by // STS3 bundle. Therefore do not include templates if client is Eclipse // TODO: REMOVE this check once STS3 is no longer supported - if (LspClient.currentClient() != LspClient.Client.ECLIPSE) { +// if (LspClient.currentClient() != LspClient.Client.ECLIPSE) { JavaSnippetContext webControllerContext = new CompositeJavaSnippetContext( -// JavaSnippetContext.AT_ROOT_LEVEL, + JavaSnippetContext.AT_ROOT_LEVEL, new AnnotatedTypeDeclarationContext(Annotations.CONTROLLER)); snippetManager.add( @@ -92,7 +90,7 @@ public class BootJavaCompletionEngineConfigurer { + "public ${SomeEnityData} ${putMethodName}(@PathVariable ${pvt:String} ${id}, @RequestBody ${SomeEnityData} ${entity}) {\n" + " //TODO: process PUT request\n" + " ${cursor}\n" + " return ${entity};\n" + "}", "PutMapping")); - } +// } return snippetManager; } @@ -126,7 +124,7 @@ public class BootJavaCompletionEngineConfigurer { } @Override - public boolean appliesTo(ASTNode node) { + public boolean appliesTo(ASTNode node, int offset, CharSequence prefix) { TypeDeclaration type = ASTUtils.findDeclaringType(node); if (type != null) { ITypeBinding binding = type.resolveBinding(); @@ -159,9 +157,9 @@ public class BootJavaCompletionEngineConfigurer { } @Override - public boolean appliesTo(ASTNode node) { + public boolean appliesTo(ASTNode node, int offset, CharSequence prefix) { for (JavaSnippetContext context : contexts) { - if (!context.appliesTo(node)) { + if (!context.appliesTo(node, offset, prefix)) { return false; } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java index 8a6496247..b9f5360cb 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippet.java @@ -43,21 +43,14 @@ public class JavaSnippet { this.additionalTriggerPrefix = additionalTriggerPrefix; } - public Optional generateCompletion(Supplier snippetBuilderFactory, + public ICompletionProposal generateCompletion(Supplier snippetBuilderFactory, DocumentRegion query, ASTNode node, CompilationUnit cu, String filterText) { - if (context.appliesTo(node)) { - return Optional.of( - new JavaSnippetCompletion(snippetBuilderFactory, - query, - cu, - this, - filterText - ) - ); - } - - return Optional.empty(); + return new JavaSnippetCompletion(snippetBuilderFactory, + query, + cu, + this, + filterText); } public String getName() { @@ -79,5 +72,9 @@ public class JavaSnippet { public String getAdditionalTriggerPrefix() { return additionalTriggerPrefix; } + + public JavaSnippetContext getContext() { + return context; + } } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetContext.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetContext.java index 8ceebd97e..550508be6 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetContext.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetContext.java @@ -11,16 +11,20 @@ package org.springframework.ide.vscode.boot.java.snippets; import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.NodeFinder; import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.TypeDeclaration; public interface JavaSnippetContext { - JavaSnippetContext BOOT_MEMBERS = (node) -> node instanceof TypeDeclaration || node instanceof SimpleName; + JavaSnippetContext BOOT_MEMBERS = (node, offset, prefix) -> node instanceof TypeDeclaration || node instanceof SimpleName; - JavaSnippetContext AT_ROOT_LEVEL = (node) -> { - return (node instanceof TypeDeclaration) || (node instanceof SimpleName && node.getParent() != null && node.getParent() instanceof TypeDeclaration); + JavaSnippetContext AT_ROOT_LEVEL = (node, offset, prefix) -> { + if (node instanceof TypeDeclaration) return true; + + ASTNode nodeBeforePrefix = NodeFinder.perform(node.getRoot(), offset - (prefix.length() + 1), 0); + return nodeBeforePrefix != null && nodeBeforePrefix instanceof TypeDeclaration; }; - boolean appliesTo(ASTNode node); + boolean appliesTo(ASTNode node, int offset, CharSequence prefix); } 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 410f2a720..762e1a64a 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 @@ -54,29 +54,27 @@ public class JavaSnippetManager { } DocumentRegion query = PREFIX_FINDER.getPrefixRegion(doc, offset); - boolean isEndOfDocument = offset == doc.getLength() - 1; - - // check if the next character is a space or a new line - if (!isEndOfDocument) { - try { - char nextCharacter = doc.getChar(offset + 1); - if (!Character.isWhitespace(nextCharacter) && nextCharacter != '\n' && nextCharacter != '\r') { - return; - } - } catch (BadLocationException e) { - } - } + for (JavaSnippet javaSnippet : snippets) { - + + String filterText = null; + if (javaSnippet.getName().toLowerCase().startsWith(query.toString().toLowerCase())) { - javaSnippet.generateCompletion(snippetBuilderFactory, query, node, cu, javaSnippet.getName()) - .ifPresent((completion) -> completions.add(completion)); - - } else if (javaSnippet.getAdditionalTriggerPrefix().toLowerCase().startsWith(query.toString().toLowerCase())) { - javaSnippet.generateCompletion(snippetBuilderFactory, query, node, cu, javaSnippet.getAdditionalTriggerPrefix()) - .ifPresent((completion) -> completions.add(completion)); + filterText = javaSnippet.getName(); } + else if (javaSnippet.getAdditionalTriggerPrefix().toLowerCase().startsWith(query.toString().toLowerCase())) { + filterText = javaSnippet.getAdditionalTriggerPrefix(); + } + + if (filterText != null) { + JavaSnippetContext context = javaSnippet.getContext(); + if (context.appliesTo(node, offset, query)) { + ICompletionProposal proposal = javaSnippet.generateCompletion(snippetBuilderFactory, query, node, cu, filterText); + completions.add(proposal); + } + } + } } diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSnippetTests.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSnippetTests.java index 15e5f671b..cf9a91bf6 100644 --- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSnippetTests.java +++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/requestmapping/test/RequestMappingSnippetTests.java @@ -33,6 +33,7 @@ import org.springframework.ide.vscode.languageserver.testharness.Editor; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.web.bind.annotation.RequestParam; @ExtendWith(SpringExtension.class) @BootLanguageServerTest @@ -208,6 +209,26 @@ public class RequestMappingSnippetTests { } } + @Test + void testSnippetNotShowUpWithinMethodBody() throws Exception { + prepareCase(CONTROLLER_WITH_RANDOM_CODE_CLASSNAME, "return new GetSomeService();", "get<*>\n return new GetSomeService();"); + + List completions = editor.getCompletions(); + for (CompletionItem completionItem : completions) { + assertNotEquals("@GetMapping(..) {..}", completionItem.getLabel()); + } + } + + @Test + void testSnippetNotShowUpWithinMethodParamDeclaration() throws Exception { + prepareCase(CONTROLLER_WITH_RANDOM_CODE_CLASSNAME, "@RequestParam(value = \"name\"", "@Get<*> @RequestParam(value = \"name\""); + + List completions = editor.getCompletions(); + for (CompletionItem completionItem : completions) { + assertNotEquals("@GetMapping(..) {..}", completionItem.getLabel()); + } + } + private void prepareCase(String className, String prefix) throws Exception { prepareCase(className, "class " + className + " {", "class " + className + " {\n\n" + prefix); }