GH-758: improved verification of whether to show mapping method snippets or not at a certain position in the code

This commit is contained in:
Martin Lippert
2023-11-27 14:15:50 +01:00
parent 9c32fc3001
commit 885977ee7d
5 changed files with 62 additions and 44 deletions

View File

@@ -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;
}
}

View File

@@ -43,21 +43,14 @@ public class JavaSnippet {
this.additionalTriggerPrefix = additionalTriggerPrefix;
}
public Optional<ICompletionProposal> generateCompletion(Supplier<SnippetBuilder> snippetBuilderFactory,
public ICompletionProposal generateCompletion(Supplier<SnippetBuilder> 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;
}
}

View File

@@ -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);
}

View File

@@ -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);
}
}
}
}

View File

@@ -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<CompletionItem> 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<CompletionItem> 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);
}