diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java index e826a74c3..a7dee3f84 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/completion/VscodeCompletionEngineAdapter.java @@ -276,6 +276,7 @@ public class VscodeCompletionEngineAdapter implements VscodeCompletionEngine { item.setKind(completion.getKind()); item.setSortText(sortkeys.next()); item.setFilterText(completion.getFilterText()); + item.setInsertTextMode(InsertTextMode.AsIs); if (completion.isDeprecated()) { item.setDeprecated(completion.isDeprecated()); } 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 b36884156..4f3d86624 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 @@ -47,35 +47,39 @@ public class BootJavaCompletionEngineConfigurer { // TODO: REMOVE this check once STS3 is no longer supported if (LspClient.currentClient() != LspClient.Client.ECLIPSE) { snippetManager.add( - new JavaSnippet("RequestMapping method", JavaSnippetContext.BOOT_MEMBERS, CompletionItemKind.Method, + new JavaSnippet("@RequestMapping(..) {..}", JavaSnippetContext.AT_ROOT_LEVEL, 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" + "@RequestMapping(\"${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, + + " return new ${SomeData}(${cursor});\n" + "}\n", + "RequestMapping")); + snippetManager.add( + new JavaSnippet("@GetMapping(..) {..}", JavaSnippetContext.AT_ROOT_LEVEL, CompletionItemKind.Method, ImmutableList.of("org.springframework.web.bind.annotation.GetMapping", "org.springframework.web.bind.annotation.RequestParam"), - "@GetMapping(value=\"${path}\")\n" + "@GetMapping(\"${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 ${SomeData}(${cursor});\n" + "}\n", + "GetMapping")); + snippetManager.add( + new JavaSnippet("@PostMapping(..) {..}", JavaSnippetContext.AT_ROOT_LEVEL, CompletionItemKind.Method, + ImmutableList.of("org.springframework.web.bind.annotation.PostMapping", + "org.springframework.web.bind.annotation.RequestBody"), + "@PostMapping(\"${path}\")\n" + + "public ${SomeEnityData} ${postMethodName}(@RequestBody ${SomeEnityData} ${entity}) {\n" + + " //TODO: process POST request\n" + " ${cursor}\n" + " return ${entity};\n" + "}\n", + "PostMapping")); + snippetManager.add( + new JavaSnippet("@PutMapping(..) {..}", JavaSnippetContext.AT_ROOT_LEVEL, CompletionItemKind.Method, + ImmutableList.of("org.springframework.web.bind.annotation.PutMapping", + "org.springframework.web.bind.annotation.RequestBody", + "org.springframework.web.bind.annotation.PathVariable"), + "@PutMapping(\"${path}/{${id}}\")\n" + + "public ${SomeEnityData} ${putMethodName}(@PathVariable ${pvt:String} ${id}, @RequestBody ${SomeEnityData} ${entity}) {\n" + + " //TODO: process PUT request\n" + " ${cursor}\n" + " return ${entity};\n" + "}", + "PutMapping")); } return snippetManager; @@ -97,4 +101,5 @@ public class BootJavaCompletionEngineConfigurer { return new BootJavaCompletionEngine(cuCache, providers, snippetManager); } + } 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 3d4335d7f..8a6496247 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2023 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 @@ -26,32 +26,33 @@ public class JavaSnippet { private JavaSnippetContext context; private String name; - private String template; - private List imports; private CompletionItemKind kind; + private String additionalTriggerPrefix; public JavaSnippet(String name, JavaSnippetContext context, CompletionItemKind kind, List imports, - String template) { + String template, String additionalTriggerPrefix) { super(); this.context = context; this.name = name; this.template = template; this.imports = imports; this.kind = kind; + this.additionalTriggerPrefix = additionalTriggerPrefix; } public Optional generateCompletion(Supplier snippetBuilderFactory, - DocumentRegion query, ASTNode node, CompilationUnit cu) { + DocumentRegion query, ASTNode node, CompilationUnit cu, String filterText) { if (context.appliesTo(node)) { return Optional.of( new JavaSnippetCompletion(snippetBuilderFactory, query, cu, - this + this, + filterText ) ); } @@ -75,4 +76,8 @@ public class JavaSnippet { return kind; } + public String getAdditionalTriggerPrefix() { + return additionalTriggerPrefix; + } + } diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java index affcdf578..b5f78fc3a 100644 --- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java +++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/snippets/JavaSnippetCompletion.java @@ -23,18 +23,20 @@ import org.springframework.ide.vscode.commons.util.Renderable; import org.springframework.ide.vscode.commons.util.Renderables; import org.springframework.ide.vscode.commons.util.text.DocumentRegion; -public class JavaSnippetCompletion implements ICompletionProposal{ +public class JavaSnippetCompletion implements ICompletionProposal { - private DocumentRegion query; - private JavaSnippet javaSnippet; - private Supplier snippetBuilderFactory; - private CompilationUnit cu; + private final DocumentRegion query; + private final JavaSnippet javaSnippet; + private final Supplier snippetBuilderFactory; + private final CompilationUnit cu; + private final String filterText; - public JavaSnippetCompletion(Supplier snippetBuilderFactory, DocumentRegion query, CompilationUnit cu, JavaSnippet javaSnippet) { + public JavaSnippetCompletion(Supplier snippetBuilderFactory, DocumentRegion query, CompilationUnit cu, JavaSnippet javaSnippet, String filterText) { this.snippetBuilderFactory = snippetBuilderFactory; this.query = query; this.cu = cu; this.javaSnippet = javaSnippet; + this.filterText = filterText; } @Override @@ -66,4 +68,9 @@ public class JavaSnippetCompletion implements ICompletionProposal{ public Optional> getAdditionalEdit() { return javaSnippet.getImports().map(imports -> () -> ASTUtils.getImportsEdit(cu, imports, query.getDocument()).orElse(null)); } + + @Override + public String getFilterText() { + return this.filterText; + } } 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 ee3a471da..a023f90a2 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 @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2017 Pivotal, Inc. + * Copyright (c) 2017, 2023 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 @@ -11,10 +11,16 @@ package org.springframework.ide.vscode.boot.java.snippets; import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.SimpleName; import org.eclipse.jdt.core.dom.TypeDeclaration; public interface JavaSnippetContext { - JavaSnippetContext BOOT_MEMBERS = (node) -> node instanceof TypeDeclaration; + JavaSnippetContext BOOT_MEMBERS = (node) -> 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); + }; + boolean appliesTo(ASTNode node); } 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 64d1d6c2f..80d965cbe 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, 2018 Pivotal, Inc. + * Copyright (c) 2017, 2023 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 @@ -21,7 +21,6 @@ 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.FuzzyMatcher; import org.springframework.ide.vscode.commons.util.text.DocumentRegion; import org.springframework.ide.vscode.commons.util.text.IDocument; @@ -34,7 +33,7 @@ public class JavaSnippetManager { @Override protected boolean isPrefixChar(char c) { - return Character.isJavaIdentifierPart(c); + return Character.isJavaIdentifierPart(c) || c == '@'; } }; @@ -47,19 +46,23 @@ public class JavaSnippetManager { } - public void getCompletions(IDocument doc, int offset, ASTNode node, CompilationUnit cu, Collection completions) { + public void getCompletions(IDocument doc, int offset, ASTNode node, CompilationUnit cu, + Collection completions) { // check if current offset is within the range of possible compiler problems if (Arrays.stream(cu.getProblems()).anyMatch(p -> p.getSourceStart() <= offset && offset <= p.getSourceEnd())) { return; } - + DocumentRegion query = PREFIX_FINDER.getPrefixRegion(doc, offset); for (JavaSnippet javaSnippet : snippets) { - if (FuzzyMatcher.matchScore(query.toString(), javaSnippet.getName()) != 0 - || FuzzyMatcher.matchScore(query.toString(), javaSnippet.getTemplate()) != 0) { - javaSnippet.generateCompletion(snippetBuilderFactory, query, node, cu) + 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)); } } 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 45a37c618..fb8e40bab 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 @@ -17,6 +17,8 @@ import org.apache.commons.io.IOUtils; import static org.junit.jupiter.api.Assertions.assertEquals; import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionList; +import org.eclipse.lsp4j.InsertTextMode; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -41,153 +43,125 @@ public class RequestMappingSnippetTests { @BeforeEach public void setup() throws Exception { - IJavaProject testProject = ProjectsHarness.INSTANCE.mavenProject("test-request-mapping-live-hover"); + IJavaProject testProject = ProjectsHarness.INSTANCE.mavenProject("test-request-mapping-completions"); harness.useProject(testProject); harness.intialize(null); } + + @Test + void testAnnotationMarkerOnkyPrefix() throws Exception { + prepareCase("@<*>"); + + List completions = editor.getCompletions(); + assertEquals(4, completions.size()); + + assertEquals("@GetMapping(..) {..}", completions.get(0).getLabel()); + assertEquals("@PostMapping(..) {..}", completions.get(1).getLabel()); + assertEquals("@PutMapping(..) {..}", completions.get(2).getLabel()); + assertEquals("@RequestMapping(..) {..}", completions.get(3).getLabel()); + + assertEquals(InsertTextMode.AsIs, completions.get(0).getInsertTextMode()); + assertEquals(InsertTextMode.AsIs, completions.get(1).getInsertTextMode()); + assertEquals(InsertTextMode.AsIs, completions.get(2).getInsertTextMode()); + assertEquals(InsertTextMode.AsIs, completions.get(3).getInsertTextMode()); + + assertEquals("@GetMapping(..) {..}", completions.get(0).getFilterText()); + assertEquals("@PostMapping(..) {..}", completions.get(1).getFilterText()); + assertEquals("@PutMapping(..) {..}", completions.get(2).getFilterText()); + assertEquals("@RequestMapping(..) {..}", completions.get(3).getFilterText()); + } @Test - void getMapping() throws Exception { + void testSimpleGetPrefix() throws Exception { prepareCase("Get<*>"); - assertSnippets("package example;\n" - + "\n" - + "import org.springframework.stereotype.Controller;\n" - + "import org.springframework.web.bind.annotation.DeleteMapping;\n" - + "import org.springframework.web.bind.annotation.GetMapping;\n" - + "import org.springframework.web.bind.annotation.PathVariable;\n" - + "import org.springframework.web.bind.annotation.PostMapping;\n" - + "import org.springframework.web.bind.annotation.PutMapping;\n" - + "import org.springframework.web.bind.annotation.RequestBody;\n" - + "import org.springframework.web.bind.annotation.RequestMapping;\n" - + "import org.springframework.web.bind.annotation.ResponseBody;\n" - + "import org.springframework.web.bind.annotation.RequestParam;\n" - + "\n" - + "\n" - + "/** Boot Java - Test Completion */\n" - + "@Controller\n" - + "public class RestApi {\n" - + "\n" - + "@GetMapping(value=\"${1:path}\")\n" - + "public ${2:SomeData} ${3:getMethodName}(@RequestParam ${4:String} ${5:param}) {\n" - + " return new ${2:SomeData}($0);\n" - + "}\n" - + "<*>\n" - + "\n" - + "\n" - + " @RequestMapping(\"/hello\")\n" - + " @ResponseBody\n" - + " public String hello() {\n" - + " return \"Hello there!\";\n" - + " }\n" - + " \n" - + " \n" - + " @RequestMapping(\"/goodbye\")\n" - + " @ResponseBody\n" - + " public String goodbye() {\n" - + " return \"Good bye\";\n" - + " }\n" - + "\n" - + " @GetMapping(\"/person/{name}\")\n" - + " public String getMapping(@PathVariable String name) {\n" - + " return \"Hello \" + name;\n" - + " }\n" - + "\n" - + " @DeleteMapping(\"/delete/{id}\")\n" - + " public String removeMe(@PathVariable int id) {\n" - + " System.out.println(\"You are removed: \" + id);\n" - + " return \"Done\";\n" - + " }\n" - + "\n" - + " @PostMapping(\"/postHello\")\n" - + " public String postMethod(@RequestBody String name) {\n" - + " System.out.println(\"Posted hello: \" + name);\n" - + " return name;\n" - + " }\n" - + "\n" - + " @PutMapping(\"/put/{id}\")\n" - + " public String putMethod(@PathVariable int id, @RequestBody String name) {\n" - + " System.out.println(\"Added \" + name + \" with ID: \" + id);\n" - + " return name;\n" - + " }\n" - + "}\n" - + "", - - "package example;\n" - + "\n" - + "import org.springframework.stereotype.Controller;\n" - + "import org.springframework.web.bind.annotation.DeleteMapping;\n" - + "import org.springframework.web.bind.annotation.GetMapping;\n" - + "import org.springframework.web.bind.annotation.PathVariable;\n" - + "import org.springframework.web.bind.annotation.PostMapping;\n" - + "import org.springframework.web.bind.annotation.PutMapping;\n" - + "import org.springframework.web.bind.annotation.RequestBody;\n" - + "import org.springframework.web.bind.annotation.RequestMapping;\n" - + "import org.springframework.web.bind.annotation.ResponseBody;\n" - + "import org.springframework.web.bind.annotation.RequestMethod;\n" - + "import org.springframework.web.bind.annotation.RequestParam;\n" - + "\n" - + "\n" - + "/** Boot Java - Test Completion */\n" - + "@Controller\n" - + "public class RestApi {\n" - + "\n" - + "@RequestMapping(value=\"${1:path}\", method=RequestMethod.${2:GET})\n" - + "public ${3:SomeData} ${4:requestMethodName}(@RequestParam ${5:String} ${6:param}) {\n" - + " return new ${3:SomeData}($0);\n" - + "}\n" - + "<*>\n" - + "\n" - + "\n" - + " @RequestMapping(\"/hello\")\n" - + " @ResponseBody\n" - + " public String hello() {\n" - + " return \"Hello there!\";\n" - + " }\n" - + " \n" - + " \n" - + " @RequestMapping(\"/goodbye\")\n" - + " @ResponseBody\n" - + " public String goodbye() {\n" - + " return \"Good bye\";\n" - + " }\n" - + "\n" - + " @GetMapping(\"/person/{name}\")\n" - + " public String getMapping(@PathVariable String name) {\n" - + " return \"Hello \" + name;\n" - + " }\n" - + "\n" - + " @DeleteMapping(\"/delete/{id}\")\n" - + " public String removeMe(@PathVariable int id) {\n" - + " System.out.println(\"You are removed: \" + id);\n" - + " return \"Done\";\n" - + " }\n" - + "\n" - + " @PostMapping(\"/postHello\")\n" - + " public String postMethod(@RequestBody String name) {\n" - + " System.out.println(\"Posted hello: \" + name);\n" - + " return name;\n" - + " }\n" - + "\n" - + " @PutMapping(\"/put/{id}\")\n" - + " public String putMethod(@PathVariable int id, @RequestBody String name) {\n" - + " System.out.println(\"Added \" + name + \" with ID: \" + id);\n" - + " return name;\n" - + " }\n" - + "}\n" - + ""); + + List completions = editor.getCompletions(); + assertEquals(1, completions.size()); + + assertEquals("@GetMapping(..) {..}", completions.get(0).getLabel()); + assertEquals("GetMapping", completions.get(0).getFilterText()); + } + + @Test + void testSimplePostPrefix() throws Exception { + prepareCase("Post<*>"); + + List completions = editor.getCompletions(); + assertEquals(1, completions.size()); + + assertEquals("@PostMapping(..) {..}", completions.get(0).getLabel()); + assertEquals("PostMapping", completions.get(0).getFilterText()); + } + + @Test + void testAnnotationAndTextPrefixForGet() throws Exception { + prepareCase("@G<*>"); + + List completions = editor.getCompletions(); + assertEquals(1, completions.size()); + + assertEquals("@GetMapping(..) {..}", completions.get(0).getLabel()); + assertEquals("@GetMapping(..) {..}", completions.get(0).getFilterText()); + } + + @Test + void testAnnotationAndTextPrefixForGet2() throws Exception { + prepareCase("@Get<*>"); + + List completions = editor.getCompletions(); + assertEquals(1, completions.size()); + + assertEquals("@GetMapping(..) {..}", completions.get(0).getLabel()); + assertEquals("@GetMapping(..) {..}", completions.get(0).getFilterText()); + } + + @Test + void testAnnotationAndTextPrefixForGet3() throws Exception { + prepareCase("@GetM<*>"); + + List completions = editor.getCompletions(); + assertEquals(1, completions.size()); + + assertEquals("@GetMapping(..) {..}", completions.get(0).getLabel()); + assertEquals("@GetMapping(..) {..}", completions.get(0).getFilterText()); + } + + @Test + void testSnippetExtractionIntoCode() throws Exception { + prepareCase("Get<*>"); + + List completions = editor.getCompletions(); + assertEquals(1, completions.size()); + + assertSnippets(completions, "package example;\n" + + "\n" + + "import org.springframework.stereotype.Controller;\n" + + "import org.springframework.web.bind.annotation.GetMapping;\n" + + "import org.springframework.web.bind.annotation.RequestParam;\n" + + "\n" + + "\n" + + "@Controller\n" + + "public class SampleController {\n" + + "\n" + + "@GetMapping(\"${1:path}\")\n" + + "public ${2:SomeData} ${3:getMethodName}(@RequestParam ${4:String} ${5:param}) {\n" + + " return new ${2:SomeData}($0);\n" + + "}\n" + + "<*>\n" + + "\n" + + "}\n" + + ""); } private void prepareCase(String prefix) throws Exception { - InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-request-mapping-live-hover/src/main/java/example/RestApi.java"); + InputStream resource = this.getClass().getResourceAsStream("/test-projects/test-request-mapping-completions/src/main/java/example/SampleController.java"); String content = IOUtils.toString(resource); - content = content.replace("class RestApi {", "class RestApi {\n\n" + prefix); + content = content.replace("class SampleController {", "class SampleController {\n\n" + prefix); editor = new Editor(harness, content, LanguageId.JAVA); } - private void assertSnippets(String... expected) throws Exception { - List completions = editor.getCompletions(); - assertEquals(expected.length, completions.size()); + private void assertSnippets(List completions, String... expected) throws Exception { for (int i = 0; i < expected.length; i++) { Editor clonedEditor = editor.clone(); clonedEditor.apply(completions.get(i)); diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/.gitignore b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/.gitignore new file mode 100644 index 000000000..e8d89416a --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/.gitignore @@ -0,0 +1,7 @@ +.classpath +.gradle/ +.project +.settings/ +bin/ +build/ +target/ \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/.mvn/wrapper/maven-wrapper.jar b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 000000000..5fd4d5023 Binary files /dev/null and b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/.mvn/wrapper/maven-wrapper.jar differ diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/.mvn/wrapper/maven-wrapper.properties b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 000000000..c954cec91 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.9/apache-maven-3.3.9-bin.zip diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/mvnw b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/mvnw new file mode 100755 index 000000000..02217b1ec --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/mvnw @@ -0,0 +1,233 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# Optional ENV vars +# ----------------- +# M2_HOME - location of maven2's installed home dir +# MAVEN_OPTS - parameters passed to the Java VM when running Maven +# e.g. to debug Maven itself, use +# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # + # Look for the Apple JDKs first to preserve the existing behaviour, and then look + # for the new JDKs provided by Oracle. + # + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Frameworks/JavaVM.framework/Versions/CurrentJDK/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L /System/Library/Java/JavaVirtualMachines/CurrentJDK ] ; then + # + # Apple JDKs + # + export JAVA_HOME=/System/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -L "/Library/Java/JavaVirtualMachines/CurrentJDK" ] ; then + # + # Oracle JDKs + # + export JAVA_HOME=/Library/Java/JavaVirtualMachines/CurrentJDK/Contents/Home + fi + + if [ -z "$JAVA_HOME" ] && [ -x "/usr/libexec/java_home" ]; then + # + # Apple JDKs + # + export JAVA_HOME=`/usr/libexec/java_home` + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + JAVA_HOME=`java-config --jre-home` + fi +fi + +if [ -z "$M2_HOME" ] ; then + ## resolve links - $0 may be a link to maven's home + PRG="$0" + + # need this for relative symlinks + while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG="`dirname "$PRG"`/$link" + fi + done + + saveddir=`pwd` + + M2_HOME=`dirname "$PRG"`/.. + + # make it fully qualified + M2_HOME=`cd "$M2_HOME" && pwd` + + cd "$saveddir" + # echo Using m2 at $M2_HOME +fi + +# For Cygwin, ensure paths are in UNIX format before anything is touched +if $cygwin ; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --unix "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, ensure paths are in UNIX format before anything is touched +if $mingw ; then + [ -n "$M2_HOME" ] && + M2_HOME="`(cd "$M2_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && + JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; then + javaExecutable="`which javac`" + if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + # readlink(1) is not available as standard on Solaris 10. + readLink=`which readlink` + if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +if [ -z "$JAVA_HOME" ] ; then + echo "Warning: JAVA_HOME environment variable is not set." +fi + +CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher + +# For Cygwin, switch paths to Windows format before running java +if $cygwin; then + [ -n "$M2_HOME" ] && + M2_HOME=`cygpath --path --windows "$M2_HOME"` + [ -n "$JAVA_HOME" ] && + JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` +fi + +# traverses directory structure from process work directory to filesystem root +# first directory with .mvn subdirectory is considered project base directory +find_maven_basedir() { + local basedir=$(pwd) + local wdir=$(pwd) + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + wdir=$(cd "$wdir/.."; pwd) + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-$(find_maven_basedir)} +MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" + +# Provide a "standardized" way to retrieve the CLI args that will +# work with both Windows and non-Windows executions. +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +export MAVEN_CMD_LINE_ARGS + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} "$@" diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/mvnw.cmd b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/mvnw.cmd new file mode 100644 index 000000000..4b98b78c4 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/mvnw.cmd @@ -0,0 +1,145 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM https://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@REM +@REM Required ENV vars: +@REM JAVA_HOME - location of a JDK home dir +@REM +@REM Optional ENV vars +@REM M2_HOME - location of maven2's installed home dir +@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands +@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +set MAVEN_CMD_LINE_ARGS=%* + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" + +set WRAPPER_JAR="".\.mvn\wrapper\maven-wrapper.jar"" +set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CMD_LINE_ARGS% +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% \ No newline at end of file diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/pom.xml b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/pom.xml new file mode 100644 index 000000000..0bdd818c8 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + + org.springframework + test-request-mapping-completions + 0.1.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.1.0 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + 17 + + + diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/java/example/SampleController.java b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/java/example/SampleController.java new file mode 100644 index 000000000..c38197ba2 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/java/example/SampleController.java @@ -0,0 +1,8 @@ +package example; + +import org.springframework.stereotype.Controller; + +@Controller +public class SampleController { + +} diff --git a/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/resources/application.properties b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/resources/application.properties new file mode 100644 index 000000000..2ff7531e4 --- /dev/null +++ b/headless-services/spring-boot-language-server/src/test/resources/test-projects/test-request-mapping-completions/src/main/resources/application.properties @@ -0,0 +1,3 @@ +server.port: 9000 +management.port: 9001 +management.address: 127.0.0.1 \ No newline at end of file