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 4d0cc4c13..6b4289f02 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 @@ -55,7 +55,7 @@ public class BootJavaCompletionEngineConfigurer { if (LspClient.currentClient() != LspClient.Client.ECLIPSE) { JavaSnippetContext webControllerContext = new CompositeJavaSnippetContext( - JavaSnippetContext.AT_ROOT_LEVEL, +// JavaSnippetContext.AT_ROOT_LEVEL, new AnnotatedTypeDeclarationContext(Annotations.CONTROLLER)); snippetManager.add( 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 80d965cbe..410f2a720 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 @@ -21,6 +21,7 @@ import org.eclipse.jdt.core.dom.CompilationUnit; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; import org.springframework.ide.vscode.commons.languageserver.util.PrefixFinder; import org.springframework.ide.vscode.commons.languageserver.util.SnippetBuilder; +import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.DocumentRegion; import org.springframework.ide.vscode.commons.util.text.IDocument; @@ -43,7 +44,6 @@ public class JavaSnippetManager { public void add(JavaSnippet javaSnippet) { snippets.add(javaSnippet); - } public void getCompletions(IDocument doc, int offset, ASTNode node, CompilationUnit cu, @@ -54,6 +54,18 @@ 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) { 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 0e820ce84..15e5f671b 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 @@ -11,6 +11,7 @@ package org.springframework.ide.vscode.boot.java.requestmapping.test; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotEquals; import java.io.InputStream; import java.util.List; @@ -22,6 +23,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Import; import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest; import org.springframework.ide.vscode.boot.bootiful.HoverTestConf; @@ -38,6 +40,8 @@ import org.springframework.test.context.junit.jupiter.SpringExtension; public class RequestMappingSnippetTests { private static final String CONTROLLER_CLASSNAME = "SampleController"; + private static final String CONTROLLER_WITH_EXISTING_CODE_CLASSNAME = "SampleControllerWithExistingCode"; + private static final String CONTROLLER_WITH_RANDOM_CODE_CLASSNAME = "SampleControllerWithRandomCode"; private static final String NON_CONTROLLER_CLASSNAME = "SampleNonController"; @Autowired private BootLanguageServerHarness harness; @@ -51,7 +55,7 @@ public class RequestMappingSnippetTests { } @Test - void testAnnotationMarkerOnkyPrefix() throws Exception { + void testAnnotationMarkerOnlyPrefix() throws Exception { prepareCase(CONTROLLER_CLASSNAME, "@<*>"); List completions = editor.getCompletions(); @@ -73,6 +77,17 @@ public class RequestMappingSnippetTests { assertEquals("@RequestMapping(..) {..}", completions.get(3).getFilterText()); } + @Test + void testAnnotationMarkerOnlyPrefixWithExistingCode() throws Exception { + prepareCase(CONTROLLER_WITH_EXISTING_CODE_CLASSNAME, "// <*>", "@G<*>"); + + List completions = editor.getCompletions(); + assertEquals(1, completions.size()); + + assertEquals("@GetMapping(..) {..}", completions.get(0).getLabel()); + assertEquals("@GetMapping(..) {..}", completions.get(0).getFilterText()); + } + @Test void testSimpleGetPrefix() throws Exception { prepareCase(CONTROLLER_CLASSNAME, "Get<*>"); @@ -162,12 +177,46 @@ public class RequestMappingSnippetTests { List completions = editor.getCompletions(); assertEquals(0, completions.size()); } + + @Test + void testSnippetNotShowUpForPrefixInMethodName() throws Exception { + prepareCase(CONTROLLER_WITH_RANDOM_CODE_CLASSNAME, "getSomethingMethod", "get<*>SomethingMethod"); + + List completions = editor.getCompletions(); + for (CompletionItem completionItem : completions) { + assertNotEquals("@GetMapping(..) {..}", completionItem.getLabel()); + } + } + + @Test + void testSnippetNotShowUpForPrefixInAnnotationPropertyValue() throws Exception { + prepareCase(CONTROLLER_WITH_RANDOM_CODE_CLASSNAME, "@Value(\"${getValueProperty}\")", "@Value(\"${get<*>ValueProperty}\")"); + + List completions = editor.getCompletions(); + for (CompletionItem completionItem : completions) { + assertNotEquals("@GetMapping(..) {..}", completionItem.getLabel()); + } + } + + @Test + void testSnippetNotShowUpForPrefixInMethodReturnType() throws Exception { + prepareCase(CONTROLLER_WITH_RANDOM_CODE_CLASSNAME, "public GetSomeService getSomethingMethod", "public Get<*>SomeService getSomethingMethod"); + + 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); + } - private void prepareCase(String className, String prefix) throws Exception { + private void prepareCase(String className, String replace, String replaceWith) throws Exception { InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-request-mapping-completions/src/main/java/example/" + className + ".java"); String content = IOUtils.toString(resource); - content = content.replace("class " + className + " {", "class " + className + " {\n\n" + prefix); + content = content.replace(replace, replaceWith); editor = new Editor(harness, content, LanguageId.JAVA); } diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/java/example/GetSomeService.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/java/example/GetSomeService.java new file mode 100644 index 000000000..911da53ea --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/java/example/GetSomeService.java @@ -0,0 +1,4 @@ +package example; + +public class GetSomeService { +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/java/example/SampleControllerWithExistingCode.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/java/example/SampleControllerWithExistingCode.java new file mode 100644 index 000000000..d54bc47aa --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/java/example/SampleControllerWithExistingCode.java @@ -0,0 +1,26 @@ +package example; + +import java.util.concurrent.atomic.AtomicLong; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class SampleControllerWithExistingCode { + + private static final String template = "Hello, %s!"; + + @GetMapping("/greeting1") + public String greeting1(@RequestParam(value = "name", defaultValue = "World") String name) { + return String.format(template, name); + } + + // <*> + + @GetMapping("/greeting2") + public String greeting2(@RequestParam(value = "name", defaultValue = "World") String name) { + return String.format(template, name); + } + +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/java/example/SampleControllerWithRandomCode.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/java/example/SampleControllerWithRandomCode.java new file mode 100644 index 000000000..b5dafea81 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/java/example/SampleControllerWithRandomCode.java @@ -0,0 +1,24 @@ +package example; + +import java.util.concurrent.atomic.AtomicLong; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import org.springframework.beans.factory.annotation.Value; + +@RestController +public class SampleControllerWithRandomCode { + + private static final String template = "Hello, %s!"; + + @Value("${getValueProperty}") + private String someValue; + + @GetMapping("/greeting1") + public GetSomeService getSomethingMethod(@RequestParam(value = "name", defaultValue = "World") String name) { + return new GetSomeService(); + } + +}