From 5b010422066786dc1fc3e944d0b580261e93b56b Mon Sep 17 00:00:00 2001 From: Martin Lippert Date: Wed, 25 Jan 2017 20:57:34 +0100 Subject: [PATCH] first experiments towards spring-specific content-assist for spring annotations, starting with scope --- .../boot/java/BootJavaLanguageServer.java | 2 +- .../completions/BootJavaCompletionEngine.java | 77 +++++++++++++++--- .../completions/BootJavaReconcileEngine.java | 4 +- .../completions/ScopeCompletionProcessor.java | 69 ++++++++++++++++ .../java/completions/ScopeNameCompletion.java | 56 +++++++++++++ .../ScopeNameCompletionProposal.java | 80 +++++++++++++++++++ 6 files changed, 275 insertions(+), 13 deletions(-) create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ScopeCompletionProcessor.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ScopeNameCompletion.java create mode 100644 vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ScopeNameCompletionProposal.java diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java index 1aadcdd72..59e1abd98 100644 --- a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java @@ -52,7 +52,7 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { validateWith(doc, reconcileEngine); }); - ICompletionEngine bootCompletionEngine = new BootJavaCompletionEngine(); + ICompletionEngine bootCompletionEngine = new BootJavaCompletionEngine(javaProjectFinder); completionEngine = new VscodeCompletionEngineAdapter(this, bootCompletionEngine); completionEngine.setMaxCompletionsNumber(100); documents.onCompletion(completionEngine::getCompletions); diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/BootJavaCompletionEngine.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/BootJavaCompletionEngine.java index 77d71387a..f151264fc 100644 --- a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/BootJavaCompletionEngine.java +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/BootJavaCompletionEngine.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2016-2017 Pivotal, Inc. + * Copyright (c) 2017 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 @@ -8,32 +8,47 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ - package org.springframework.ide.vscode.boot.java.completions; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.dom.AST; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.ASTParser; +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.springframework.ide.vscode.commons.java.IClasspath; +import org.springframework.ide.vscode.commons.java.IJavaProject; 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.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.util.text.IDocument; +/** + * @author Martin Lippert + */ public class BootJavaCompletionEngine implements ICompletionEngine { + + private static final String SPRING_SCOPE = "org.springframework.context.annotation.Scope"; + + private JavaProjectFinder projectFinder; + + public BootJavaCompletionEngine(JavaProjectFinder projectFinder) { + this.projectFinder = projectFinder; + } @Override public Collection getCompletions(IDocument document, int offset) throws Exception { List completions = new ArrayList<>(); - System.out.println("create parser"); - ASTParser parser = ASTParser.newParser(AST.JLS8); Map options = JavaCore.getOptions(); JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options); @@ -41,22 +56,62 @@ public class BootJavaCompletionEngine implements ICompletionEngine { parser.setKind(ASTParser.K_COMPILATION_UNIT); parser.setStatementsRecovery(true); parser.setBindingsRecovery(true); + parser.setResolveBindings(true); + + String[] classpathEntries = getClasspathEntries(document); + String[] sourceEntries = new String[] {}; + parser.setEnvironment(classpathEntries, sourceEntries, null, true); - System.out.println("set source"); - + String docURI = document.getUri(); + String unitName = docURI.substring(docURI.lastIndexOf("/")); + parser.setUnitName(unitName); parser.setSource(document.get(0, document.getLength()).toCharArray()); - System.out.println("start parse"); - CompilationUnit cu = (CompilationUnit) parser.createAST(null); - - System.out.println("end parse"); - ASTNode node = NodeFinder.perform(cu, offset, 0); + + if (node != null) { + collectCompletionsForAnnotations(node, completions, offset, document); + } System.out.println("AST node found: " + node.getClass().getName()); return completions; } + private void collectCompletionsForAnnotations(ASTNode node, List completions, int offset, IDocument doc) { + Annotation annotation = null; + ASTNode exactNode = node; + + while (node != null && !(node instanceof Annotation)) { + node = node.getParent(); + } + + if (node != null) { + annotation = (Annotation) node; + ITypeBinding type = annotation.resolveTypeBinding(); + if (type != null) { + String qualifiedName = type.getQualifiedName(); + if (qualifiedName != null && qualifiedName.startsWith("org.springframework")) { + collectCompletionsForSpringAnnotation(exactNode, annotation, type, completions, offset, doc); + } + } + } + } + + private void collectCompletionsForSpringAnnotation(ASTNode node, Annotation annotation, ITypeBinding type, + List completions, int offset, IDocument doc) { + + if (type.getQualifiedName().equals(SPRING_SCOPE)) { + new ScopeCompletionProcessor().collectCompletionsForScopeAnnotation(node, annotation, type, completions, offset, doc); + } + } + + private String[] getClasspathEntries(IDocument doc) throws Exception { + IJavaProject project = this.projectFinder.find(doc); + IClasspath classpath = project.getClasspath(); + Stream classpathEntries = classpath.getClasspathEntries(); + return classpathEntries.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new); + } + } diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/BootJavaReconcileEngine.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/BootJavaReconcileEngine.java index 5a6b9e48e..5b8a39217 100644 --- a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/BootJavaReconcileEngine.java +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/BootJavaReconcileEngine.java @@ -8,13 +8,15 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ - package org.springframework.ide.vscode.boot.java.completions; import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector; import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine; import org.springframework.ide.vscode.commons.util.text.IDocument; +/** + * @author Martin Lippert + */ public class BootJavaReconcileEngine implements IReconcileEngine { @Override diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ScopeCompletionProcessor.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ScopeCompletionProcessor.java new file mode 100644 index 000000000..350e94605 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ScopeCompletionProcessor.java @@ -0,0 +1,69 @@ +/******************************************************************************* + * Copyright (c) 2017 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.completions; + +import java.util.List; + +import org.eclipse.jdt.core.dom.ASTNode; +import org.eclipse.jdt.core.dom.Annotation; +import org.eclipse.jdt.core.dom.ITypeBinding; +import org.eclipse.jdt.core.dom.MemberValuePair; +import org.eclipse.jdt.core.dom.SimpleName; +import org.eclipse.jdt.core.dom.StringLiteral; +import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; +import org.springframework.ide.vscode.commons.util.text.IDocument; + +/** + * @author Martin Lippert + */ +public class ScopeCompletionProcessor { + + public void collectCompletionsForScopeAnnotation(ASTNode node, Annotation annotation, ITypeBinding type, + List completions, int offset, IDocument doc) { + + try { + if (node instanceof SimpleName && node.getParent() instanceof MemberValuePair) { + MemberValuePair memberPair = (MemberValuePair) node.getParent(); + + // case: @Scope(value=<*>) + if (memberPair.getName() != null && memberPair.getValue().toString().equals("$missing$")) { + for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { + ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, offset, offset, ""); + completions.add(proposal); + } + } + } + // case: @Scope(<*>) + else if (node == annotation && doc.get(offset - 1, 2).endsWith("()")) { + for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { + ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, offset, offset, ""); + completions.add(proposal); + } + } + else if (node instanceof StringLiteral && node.getParent() instanceof Annotation) { + // case: @Scope(value="") + if (node.toString().startsWith("\"") && node.toString().endsWith("\"")) { + String prefix = doc.get(node.getStartPosition(), offset - node.getStartPosition()); + for (ScopeNameCompletion completion : ScopeNameCompletionProposal.COMPLETIONS) { + if (completion.getValue().startsWith(prefix)) { + ICompletionProposal proposal = new ScopeNameCompletionProposal(completion, doc, node.getStartPosition(), node.getStartPosition() + node.getLength(), prefix); + completions.add(proposal); + } + } + } + } + } + catch (Exception e) { + e.printStackTrace(); + } + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ScopeNameCompletion.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ScopeNameCompletion.java new file mode 100644 index 000000000..35ab00b9f --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ScopeNameCompletion.java @@ -0,0 +1,56 @@ +/******************************************************************************* + * Copyright (c) 2017 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.completions; + +import org.eclipse.lsp4j.CompletionItemKind; +import org.springframework.ide.vscode.commons.util.Renderable; + +/** + * @author Martin Lippert + */ +public class ScopeNameCompletion { + + private final String label; + private final String detail; + private final Renderable documentation; + private final CompletionItemKind kind; + private final String value; + + public ScopeNameCompletion(String value, String label, String detail, Renderable documentation, CompletionItemKind kind) { + super(); + this.value = value; + this.label = label; + this.detail = detail; + this.documentation = documentation; + this.kind = kind; + } + + public String getLabel() { + return label; + } + + public String getDetail() { + return detail; + } + + public CompletionItemKind getKind() { + return kind; + } + + public Renderable getDocumentation() { + return documentation; + } + + public String getValue() { + return this.value; + } + +} diff --git a/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ScopeNameCompletionProposal.java b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ScopeNameCompletionProposal.java new file mode 100644 index 000000000..3f439ea76 --- /dev/null +++ b/vscode-extensions/vscode-boot-java/src/main/java/org/springframework/ide/vscode/boot/java/completions/ScopeNameCompletionProposal.java @@ -0,0 +1,80 @@ +/******************************************************************************* + * Copyright (c) 2017 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 + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.java.completions; + +import org.eclipse.lsp4j.CompletionItemKind; +import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; +import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal; +import org.springframework.ide.vscode.commons.util.Renderable; +import org.springframework.ide.vscode.commons.util.text.IDocument; + +/** + * @author Martin Lippert + */ +public class ScopeNameCompletionProposal implements ICompletionProposal { + + public static final ScopeNameCompletion[] COMPLETIONS = new ScopeNameCompletion[] { + new ScopeNameCompletion("\"prototype\"", "prototype", "prototype scope", null, CompletionItemKind.Value), + new ScopeNameCompletion("\"singleton\"", "singleton", "singleton scope (default)", null, CompletionItemKind.Value), + new ScopeNameCompletion("\"request\"", "request", "request scope", null, CompletionItemKind.Value), + new ScopeNameCompletion("\"session\"", "session", "session scope", null, CompletionItemKind.Value), + new ScopeNameCompletion("\"globalSession\"", "globalSession", "globalSession scope", null, CompletionItemKind.Value), + new ScopeNameCompletion("\"application\"", "application", "application scope", null, CompletionItemKind.Value), + new ScopeNameCompletion("\"websocket\"", "websocket", "websocket scope", null, CompletionItemKind.Value) + }; + + private final IDocument doc; + private final int startOffset; + private final int endOffset; + private final ScopeNameCompletion completion; + private final String prefix; + + public ScopeNameCompletionProposal(ScopeNameCompletion completion, IDocument doc, int startOffset, int endOffset, String prefix) { + this.completion = completion; + this.doc = doc; + this.startOffset = startOffset; + this.endOffset = endOffset; + this.prefix = prefix; + } + + @Override + public ICompletionProposal deemphasize() { + return null; + } + + @Override + public String getLabel() { + return completion.getLabel(); + } + + @Override + public CompletionItemKind getKind() { + return completion.getKind(); + } + + @Override + public DocumentEdits getTextEdit() { + DocumentEdits edits = new DocumentEdits(doc); + edits.replace(startOffset + prefix.length(), endOffset, completion.getValue().substring(prefix.length())); + return edits; + } + + @Override + public String getDetail() { + return completion.getDetail(); + } + + @Override + public Renderable getDocumentation() { + return completion.getDocumentation(); + } + +}