initial implementation towards a compilation-unit-based content-assist for types and packages

This commit is contained in:
Martin Lippert
2019-05-02 09:15:10 +02:00
parent 652f5dcbcd
commit 5abc6f9df0
12 changed files with 512 additions and 66 deletions

View File

@@ -30,7 +30,7 @@ import org.springframework.ide.vscode.boot.xml.completions.TypeCompletionProposa
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.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
@@ -51,12 +51,12 @@ public class SpringXMLCompletionEngine implements ICompletionEngine {
private final BootJavaConfig config;
public SpringXMLCompletionEngine(SpringXMLLanguageServerComponents springXMLLanguageServerComponents,
JavaProjectFinder projectFinder, SpringSymbolIndex symbolIndex, SimpleTextDocumentService simpleTextDocumentService, BootJavaConfig config) {
SimpleLanguageServer server, JavaProjectFinder projectFinder, SpringSymbolIndex symbolIndex, BootJavaConfig config) {
this.config = config;
this.completionProviders = new HashMap<>();
this.completionProviders.put(new XMLCompletionProviderKey(BEANS_NAMESPACE, null, BEAN_ELEMENT, CLASS_ATTRIBUTE), new TypeCompletionProposalProvider(projectFinder, simpleTextDocumentService, true));
this.completionProviders.put(new XMLCompletionProviderKey(BEANS_NAMESPACE, null, BEAN_ELEMENT, CLASS_ATTRIBUTE), new TypeCompletionProposalProvider(server, projectFinder, true, true, false, false));
this.completionProviders.put(new XMLCompletionProviderKey(BEANS_NAMESPACE, BEAN_ELEMENT, PROPERTY_ELEMENT, NAME_ATTRIBUTE), new PropertyNameCompletionProposalProvider(projectFinder));
this.completionProviders.put(new XMLCompletionProviderKey(BEANS_NAMESPACE, BEAN_ELEMENT, PROPERTY_ELEMENT, REF_ATTRIBUTE), new BeanRefCompletionProposalProvider(projectFinder, symbolIndex));
}

View File

@@ -55,7 +55,7 @@ public class SpringXMLLanguageServerComponents implements LanguageServerComponen
server.doOnInitialized(this::initialized);
server.onShutdown(this::shutdown);
this.completionEngine = new SpringXMLCompletionEngine(this, projectFinder, symbolIndex, server.getTextDocumentService(), config);
this.completionEngine = new SpringXMLCompletionEngine(this, server, projectFinder, symbolIndex, config);
}
@Override

View File

@@ -12,9 +12,10 @@ package org.springframework.ide.vscode.boot.xml.completions;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4xml.dom.DOMAttr;
@@ -22,33 +23,35 @@ import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.dom.parser.Scanner;
import org.springframework.ide.vscode.boot.xml.XMLCompletionProvider;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.IType;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.protocol.java.JavaCodeCompleteData;
import org.springframework.ide.vscode.commons.protocol.java.JavaCodeCompleteParams;
import org.springframework.ide.vscode.commons.util.Renderable;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
import reactor.core.publisher.Flux;
import reactor.util.function.Tuple2;
/**
* @author Martin Lippert
*/
public class TypeCompletionProposalProvider implements XMLCompletionProvider {
private final JavaProjectFinder projectFinder;
private final boolean classesOnly;
private final Set<String> typeSearchAlreadyInitializedProjects = Collections.synchronizedSet(new HashSet<>());
private final SimpleLanguageServer server;
private final boolean packagesAllowed;
private final boolean classesAllowed;
private final boolean interfacesAllowed;
private final boolean enumsAllowed;
public TypeCompletionProposalProvider(JavaProjectFinder projectFinder, SimpleTextDocumentService simpleTextDocumentService, boolean classesOnly) {
public TypeCompletionProposalProvider(SimpleLanguageServer server, JavaProjectFinder projectFinder,
boolean packagesAllowed, boolean classesAllowed, boolean interfacesAllowed, boolean enumsAllowed) {
this.server = server;
this.projectFinder = projectFinder;
this.classesOnly = classesOnly;
simpleTextDocumentService.onDidOpen(doc -> {
initializeTypeSearch(doc);
});
this.packagesAllowed = packagesAllowed;
this.classesAllowed = classesAllowed;
this.interfacesAllowed = interfacesAllowed;
this.enumsAllowed = enumsAllowed;
}
@Override
@@ -69,68 +72,81 @@ public class TypeCompletionProposalProvider implements XMLCompletionProvider {
}
// Flux<Tuple2<IType, Double>> types = project.getIndex().fuzzySearchTypes(prefix, true, true);
Flux<Tuple2<IType, Double>> types = project.getIndex().camelcaseSearchTypes(prefix, true, true);
// Flux<Tuple2<IType, Double>> types = project.getIndex().camelcaseSearchTypes(prefix, true, true);
JavaCodeCompleteParams params = new JavaCodeCompleteParams(project.getLocationUri().toString(), prefix, true, true);
CompletableFuture<List<JavaCodeCompleteData>> completions = server.getClient().javaCodeComplete(params);
final String prefixStr = prefix;
return types
.filter(result -> result.getT1() != null && result.getT1().getElementName() != null && result.getT1().getElementName().length() > 0)
.filter(result -> classesOnly ? result.getT1().isClass() : true)
.map(t -> createProposal(t, doc, offset, prefixStr))
.collectList().block();
final String finalPrefix = prefix;
try {
return completions.get().stream()
.filter(proposal -> {
if (proposal.isClassProposal()) return classesAllowed;
if (proposal.isInterfaceProposal()) return interfacesAllowed;
if (proposal.isEnumProposal()) return enumsAllowed;
if (proposal.isPackageProposal()) return packagesAllowed;
return false;
})
.map(proposal -> createProposal(proposal, doc, finalPrefix, offset))
.filter(proposal -> proposal != null)
.collect(Collectors.toList());
}
catch (Exception e) {
// TODO: logging
}
};
return Collections.emptyList();
}
private ICompletionProposal createProposal(Tuple2<IType, Double> t, TextDocument doc, int offset, String prefix) {
IType type = t.getT1();
private ICompletionProposal createProposal(JavaCodeCompleteData proposal, TextDocument doc, String prefix, int offset) {
if (proposal.isPackageProposal()) {
return createPackageProposal(proposal, doc, prefix, offset);
}
else if (proposal.isClassProposal() || proposal.isInterfaceProposal() || proposal.isEnumProposal()) {
return createTypeProposal(proposal, doc, prefix, offset);
}
else {
return null;
}
}
private TypeCompletionProposal createPackageProposal(JavaCodeCompleteData proposal, TextDocument doc, String prefix, int offset) {
String label = proposal.getFullyQualifiedName();
CompletionItemKind kind = CompletionItemKind.Module;
DocumentEdits edits = new DocumentEdits(doc, false);
edits.replace(offset - prefix.length(), offset, proposal.getFullyQualifiedName());
Renderable renderable = null;
return new TypeCompletionProposal(label, kind, edits, proposal.getFullyQualifiedName(), renderable, proposal.getRelevance());
}
private TypeCompletionProposal createTypeProposal(JavaCodeCompleteData proposal, TextDocument doc, String prefix, int offset) {
String label = proposal.getFullyQualifiedName();
String label = type.getFullyQualifiedName();
int splitIndex = Math.max(label.lastIndexOf("."), label.lastIndexOf("$"));
if (splitIndex > 0) {
label = label.substring(splitIndex + 1) + " - " + label.substring(0, splitIndex);
}
CompletionItemKind kind;
if (type.isClass()) {
kind = CompletionItemKind.Class;
}
else if (type.isInterface()) {
CompletionItemKind kind = CompletionItemKind.Class;
if (proposal.isInterfaceProposal()) {
kind = CompletionItemKind.Interface;
}
else if (type.isEnum()) {
else if (proposal.isEnumProposal()) {
kind = CompletionItemKind.Enum;
}
else {
kind = CompletionItemKind.Property;
}
DocumentEdits edits = new DocumentEdits(doc, false);
edits.replace(offset - prefix.length(), offset, type.getFullyQualifiedName());
edits.replace(offset - prefix.length(), offset, proposal.getFullyQualifiedName());
Renderable renderable = null;
return new TypeCompletionProposal(label, kind, edits, type.getFullyQualifiedName(), renderable, t.getT2());
return new TypeCompletionProposal(label, kind, edits, proposal.getFullyQualifiedName(), renderable, proposal.getRelevance());
}
/**
* trigger initial type search as soon as the doc opens to avoid search timeouts when doing type proposals for the first time
*/
private void initializeTypeSearch(TextDocument doc) {
Optional<IJavaProject> foundProject = this.projectFinder.find(doc.getId());
if (foundProject.isPresent()) {
IJavaProject project = foundProject.get();
String projectName = project.getElementName();
if (!this.typeSearchAlreadyInitializedProjects.contains(projectName)) {
this.typeSearchAlreadyInitializedProjects.add(projectName);
Flux<Tuple2<IType, Double>> types = project.getIndex().camelcaseSearchTypes("", true, true);
types.count();
}
}
}
}