first experiments towards spring-specific content-assist for spring annotations, starting with scope

This commit is contained in:
Martin Lippert
2017-01-25 20:57:34 +01:00
parent ef42e7962c
commit 5b01042206
6 changed files with 275 additions and 13 deletions

View File

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

View File

@@ -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<ICompletionProposal> getCompletions(IDocument document, int offset) throws Exception {
List<ICompletionProposal> completions = new ArrayList<>();
System.out.println("create parser");
ASTParser parser = ASTParser.newParser(AST.JLS8);
Map<String, String> 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<ICompletionProposal> 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<ICompletionProposal> 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<Path> classpathEntries = classpath.getClasspathEntries();
return classpathEntries.map(path -> path.toAbsolutePath().toString()).toArray(String[]::new);
}
}

View File

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

View File

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

View File

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

View File

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