first steps towards auto completion for bean class attribute in spring XML config files

This commit is contained in:
Martin Lippert
2019-04-08 16:05:38 +02:00
parent 327746cdcd
commit 1281abbf92
6 changed files with 322 additions and 6 deletions

View File

@@ -12,9 +12,19 @@ package org.springframework.ide.vscode.boot.xml;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.lsp4xml.dom.DOMAttr;
import org.eclipse.lsp4xml.dom.DOMDocument;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.dom.DOMParser;
import org.eclipse.lsp4xml.dom.parser.Scanner;
import org.eclipse.lsp4xml.dom.parser.TokenType;
import org.eclipse.lsp4xml.dom.parser.XMLScanner;
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.TextDocument;
/**
@@ -22,13 +32,54 @@ import org.springframework.ide.vscode.commons.util.text.TextDocument;
*/
public class SpringXMLCompletionEngine implements ICompletionEngine {
public SpringXMLCompletionEngine(SpringXMLLanguageServerComponents springXMLLanguageServerComponents) {
private static final String BEANS_NAMESPACE = "http://www.springframework.org/schema/beans";
private static final String BEAN_ELEMENT = "bean";
private static final String BEAN_CLASS_ATTRIBUTE = "class";
private final Map<XMLCompletionProviderKey, XMLCompletionProvider> completionProviders;
public SpringXMLCompletionEngine(SpringXMLLanguageServerComponents springXMLLanguageServerComponents, JavaProjectFinder projectFinder) {
this.completionProviders = new HashMap<>();
this.completionProviders.put(new XMLCompletionProviderKey(BEANS_NAMESPACE, BEAN_ELEMENT, BEAN_CLASS_ATTRIBUTE), new TypeCompletionProposalProvider(projectFinder));
}
@Override
public Collection<ICompletionProposal> getCompletions(TextDocument document, int offset) throws Exception {
System.out.println(" SUPER COOL SPRING XML COMPLETION PROPOSALS COMING SOON... STAY TUNED...");
public Collection<ICompletionProposal> getCompletions(TextDocument doc, int offset) throws Exception {
String content = doc.get();
DOMParser parser = DOMParser.getInstance();
DOMDocument dom = parser.parse(content, "", null);
DOMNode node = dom.findNodeBefore(offset);
if (node != null) {
String namespace = node.getNamespaceURI();
Scanner scanner = XMLScanner.createScanner(content, node.getStart(), false);
TokenType token = scanner.scan();
while (token != TokenType.EOS && scanner.getTokenOffset() <= offset) {
switch (token) {
case AttributeValue:
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
DOMAttr attributeAt = dom.findAttrAt(offset);
if (attributeAt != null) {
XMLCompletionProviderKey key = new XMLCompletionProviderKey(namespace, node.getNodeName(), attributeAt.getNodeName());
XMLCompletionProvider completionProvider = this.completionProviders.get(key);
if (completionProvider != null) {
return completionProvider.getCompletions(doc, namespace, node, attributeAt, scanner, offset);
}
}
}
break;
default:
break;
}
token = scanner.scan();
}
}
return Collections.emptyList();
}

View File

@@ -17,10 +17,9 @@ import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.app.BootLanguageServerParams;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine;
import org.springframework.ide.vscode.commons.languageserver.composable.LanguageServerComponents;
import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
import org.springframework.ide.vscode.commons.languageserver.util.HoverHandler;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService;
import org.springframework.ide.vscode.commons.languageserver.util.SimpleWorkspaceService;
import org.springframework.ide.vscode.commons.util.text.LanguageId;
import com.google.common.collect.ImmutableSet;
@@ -36,12 +35,14 @@ public class SpringXMLLanguageServerComponents implements LanguageServerComponen
private final SimpleLanguageServer server;
private final BootLanguageServerParams serverParams;
private final JavaProjectFinder projectFinder;
public SpringXMLLanguageServerComponents(
SimpleLanguageServer server,
BootLanguageServerParams serverParams) {
this.server = server;
this.serverParams = serverParams;
this.projectFinder = serverParams.projectFinder;
server.doOnInitialized(this::initialized);
server.onShutdown(this::shutdown);
@@ -54,7 +55,7 @@ public class SpringXMLLanguageServerComponents implements LanguageServerComponen
@Override
public ICompletionEngine getCompletionEngine() {
return new SpringXMLCompletionEngine(this);
return new SpringXMLCompletionEngine(this, projectFinder);
}
@Override

View File

@@ -0,0 +1,63 @@
/*******************************************************************************
* Copyright (c) 2019 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.xml;
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;
/**
* @author Martin Lippert
*/
public class TypeCompletionProposal implements ICompletionProposal {
private final String label;
private final CompletionItemKind kind;
private final DocumentEdits edits;
private final String detail;
private final Renderable documentation;
public TypeCompletionProposal(String label, CompletionItemKind kind, DocumentEdits edits, String detail, Renderable documentation) {
super();
this.label = label;
this.kind = kind;
this.edits = edits;
this.detail = detail;
this.documentation = documentation;
}
@Override
public String getLabel() {
return this.label;
}
@Override
public CompletionItemKind getKind() {
return this.kind;
}
@Override
public DocumentEdits getTextEdit() {
return this.edits;
}
@Override
public String getDetail() {
return this.detail;
}
@Override
public Renderable getDocumentation() {
return this.documentation;
}
}

View File

@@ -0,0 +1,94 @@
/*******************************************************************************
* Copyright (c) 2019 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.xml;
import java.util.Collection;
import java.util.Optional;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4xml.dom.DOMAttr;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.dom.parser.Scanner;
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.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;
public TypeCompletionProposalProvider(JavaProjectFinder projectFinder) {
this.projectFinder = projectFinder;
}
@Override
public Collection<ICompletionProposal> getCompletions(TextDocument doc, String namespace, DOMNode node, DOMAttr attributeAt,
Scanner scanner, int offset) {
int tokenOffset = scanner.getTokenOffset();
int tokenEnd = scanner.getTokenEnd();
String tokenText = scanner.getTokenText();
Optional<IJavaProject> foundProject = this.projectFinder.find(doc.getId());
if (foundProject.isPresent()) {
IJavaProject project = foundProject.get();
String prefix = tokenText.substring(0, offset - tokenOffset);
if (prefix.startsWith("\"")) {
prefix = prefix.substring(1);
}
Flux<Tuple2<IType, Double>> types = project.getIndex().fuzzySearchTypes(prefix, true, true);
return types.collectSortedList((o1, o2) -> o2.getT2().compareTo(o1.getT2()))
.flatMapIterable(l -> l)
.filter(result -> result.getT1() != null && result.getT1().getElementName() != null && result.getT1().getElementName().length() > 0)
.map(t -> createProposal(t, doc, offset, tokenOffset, tokenEnd)).collectList().block();
};
return null;
}
private ICompletionProposal createProposal(Tuple2<IType, Double> t, TextDocument doc, int offset, int tokenStart, int tokenEnd) {
IType type = t.getT1();
String label = type.getElementName();
CompletionItemKind kind;
if (type.isClass()) {
kind = CompletionItemKind.Class;
}
else if (type.isInterface()) {
kind = CompletionItemKind.Interface;
}
else {
kind = CompletionItemKind.Value;
}
DocumentEdits edits = new DocumentEdits(doc);
edits.replace(tokenStart, tokenEnd, "\"" + type.getFullyQualifiedName() + "\"");
Renderable renderable = null;
return new TypeCompletionProposal(label, kind, edits, type.getFullyQualifiedName(), renderable);
}
}

View File

@@ -0,0 +1,29 @@
/*******************************************************************************
* Copyright (c) 2019 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.xml;
import java.util.Collection;
import org.eclipse.lsp4xml.dom.DOMAttr;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.dom.parser.Scanner;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.util.text.TextDocument;
/**
* @author Martin Lippert
*/
public interface XMLCompletionProvider {
Collection<ICompletionProposal> getCompletions(TextDocument doc, String namespace, DOMNode node,
DOMAttr attributeAt, Scanner scanner, int offset);
}

View File

@@ -0,0 +1,78 @@
/*******************************************************************************
* Copyright (c) 2019 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
* https://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Pivotal, Inc. - initial API and implementation
*******************************************************************************/
package org.springframework.ide.vscode.boot.xml;
/**
* @author Martin Lippert
*/
public class XMLCompletionProviderKey {
private final String namespaceURI;
private final String elementName;
private final String attributeName;
public XMLCompletionProviderKey(String namespaceURI, String elementName, String attributeName) {
super();
this.namespaceURI = namespaceURI;
this.elementName = elementName;
this.attributeName = attributeName;
}
public String getNamespaceURI() {
return namespaceURI;
}
public String getElementName() {
return elementName;
}
public String getAttributeName() {
return attributeName;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((attributeName == null) ? 0 : attributeName.hashCode());
result = prime * result + ((elementName == null) ? 0 : elementName.hashCode());
result = prime * result + ((namespaceURI == null) ? 0 : namespaceURI.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
XMLCompletionProviderKey other = (XMLCompletionProviderKey) obj;
if (attributeName == null) {
if (other.attributeName != null)
return false;
} else if (!attributeName.equals(other.attributeName))
return false;
if (elementName == null) {
if (other.elementName != null)
return false;
} else if (!elementName.equals(other.elementName))
return false;
if (namespaceURI == null) {
if (other.namespaceURI != null)
return false;
} else if (!namespaceURI.equals(other.namespaceURI))
return false;
return true;
}
}