Skip to content

Commit

Permalink
Completion for elements considers modelGroup of a complex type.
Browse files Browse the repository at this point in the history
This includes handling for (choice, all, and sequence)

Fixes eclipse-lemminx#347

Signed-off-by: Nikolas <[email protected]>
  • Loading branch information
NikolasKomonen committed Apr 11, 2019
1 parent 50b1927 commit dd83d8b
Show file tree
Hide file tree
Showing 23 changed files with 571 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;

import org.eclipse.lsp4xml.utils.StringUtils;
import org.w3c.dom.DOMException;
Expand All @@ -39,6 +40,8 @@ public class DOMElement extends DOMNode implements org.w3c.dom.Element {
Integer endTagCloseOffset;// <root> </root |>
//DomElement.end = <root> </root>| , is always scanner.getTokenEnd()

List<DOMElement> childElements;

public DOMElement(int start, int end, DOMDocument ownerDocument) {
super(start, end, ownerDocument);
}
Expand Down Expand Up @@ -393,6 +396,58 @@ public boolean isEndTagClosed() {
return endTagCloseOffset != null;
}

public boolean hasChildElement(String name) {
if(name == null) {
return false;
}
return name.equals(getChildElement(name).getTagName());
}

public DOMElement getChildElement(String name) {
if(!hasChildNodes() || name == null || name.isEmpty()) {
return null;
}
List<DOMElement> children = getChildElements();
for (DOMElement child : children) {
if(child instanceof DOMElement) {
if(name.equals(child.getTagName())) {
return child;
}
}
}
return null;
}

public DOMElement getChildElementAfterOffset(int offset) {
List<DOMElement> children = getChildElements();
for (DOMElement child : children) {
if(child instanceof DOMElement) {
if(child.getStart() > offset) {
return child;
}
}
}
return null;
}

@Override
public void addChild(DOMNode child) {
if(child instanceof DOMElement) {
if (childElements == null) {
childElements = new ArrayList<DOMElement>();
}
childElements.add((DOMElement) child);
}
super.addChild(child);
}

public List<DOMElement> getChildElements() {
if (childElements == null) {
childElements = new ArrayList<DOMElement>();
}
return this.childElements;
}

@Override
/**
* If Element has a closing end tag eg: <a> </a> -> true , <a> </b> -> false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
*/
package org.eclipse.lsp4xml.extensions.contentmodel.model;

import java.util.Collection;
import java.util.List;

import org.eclipse.lsp4xml.dom.DOMElement;

Expand All @@ -20,7 +20,7 @@
*/
public interface CMDocument {

Collection<CMElementDeclaration> getElements();
List<CMElementDeclaration> getElements();

/**
* Returns the declared element which matches the given XML element and null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
package org.eclipse.lsp4xml.extensions.contentmodel.model;

import java.util.Collection;
import java.util.List;

/**
* Content model element which abstracts element declaration from a given
Expand Down Expand Up @@ -57,7 +58,7 @@ default String getName(String prefix) {
*
* @return the children declared element of this declared element.
*/
Collection<CMElementDeclaration> getElements();
List<CMElementDeclaration> getElements();

/**
* Returns the declared element which matches the given XML tag name / namespace
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,13 @@
*/
package org.eclipse.lsp4xml.extensions.contentmodel.participants;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.apache.xerces.xs.XSModel;
import org.apache.xerces.xs.XSModelGroup;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.InsertTextFormat;
Expand All @@ -20,13 +25,16 @@
import org.eclipse.lsp4xml.commons.BadLocationException;
import org.eclipse.lsp4xml.dom.DOMDocument;
import org.eclipse.lsp4xml.dom.DOMElement;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMAttributeDeclaration;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMDocument;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMElementDeclaration;
import org.eclipse.lsp4xml.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lsp4xml.extensions.contentmodel.utils.XMLGenerator;
import org.eclipse.lsp4xml.extensions.xsd.contentmodel.CMXSDElementDeclaration;
import org.eclipse.lsp4xml.services.AttributeCompletionItem;
import org.eclipse.lsp4xml.services.extensions.CompletionParticipantAdapter;
import org.eclipse.lsp4xml.services.extensions.CompletionSortTextHelper;
import org.eclipse.lsp4xml.services.extensions.ICompletionRequest;
import org.eclipse.lsp4xml.services.extensions.ICompletionResponse;
import org.eclipse.lsp4xml.settings.SharedSettings;
Expand All @@ -50,7 +58,7 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response)
// XML Schema is done with pattern and not with XML root element)
CMDocument cmDocument = contentModelManager.findCMDocument(document, null);
if (cmDocument != null) {
fillWithChildrenElementDeclaration(null, cmDocument.getElements(), null, false, request, response);
fillWithChildrenElementDeclaration(null, cmDocument.getElements(), XSModelGroup.COMPOSITOR_ALL, null, false, request, response);
}
return;
}
Expand All @@ -59,8 +67,9 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response)
CMElementDeclaration cmElement = contentModelManager.findCMElement(parentElement);
String defaultPrefix = null;
if (cmElement != null) {
response.addCompletionItem(null, true); //Set a fake item to prevent existing elements as completion items
defaultPrefix = parentElement.getPrefix();
fillWithChildrenElementDeclaration(parentElement, cmElement.getElements(), defaultPrefix, false,
fillWithChildrenElementDeclaration(parentElement, cmElement, defaultPrefix, false,
request, response);
}
if (parentElement.isDocumentElement()) {
Expand All @@ -73,7 +82,7 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response)
String namespaceURI = parentElement.getNamespaceURI(prefix);
CMDocument cmDocument = contentModelManager.findCMDocument(parentElement, namespaceURI);
if (cmDocument != null) {
fillWithChildrenElementDeclaration(parentElement, cmDocument.getElements(), prefix, true,
fillWithChildrenElementDeclaration(parentElement, cmDocument.getElements(), XSModelGroup.COMPOSITOR_ALL, prefix, true,
request, response);
}
}
Expand All @@ -83,33 +92,164 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response)
CMElementDeclaration cmInternalElement = contentModelManager.findInternalCMElement(parentElement);
if (cmInternalElement != null) {
defaultPrefix = parentElement.getPrefix();
fillWithChildrenElementDeclaration(parentElement, cmInternalElement.getElements(), defaultPrefix, false,
fillWithChildrenElementDeclaration(parentElement, cmInternalElement, defaultPrefix, false,
request, response);
}
} catch (CacheResourceDownloadingException e) {
// XML Schema, DTD is loading, ignore this error
}
}

private void fillWithChildrenElementDeclaration(DOMElement element, Collection<CMElementDeclaration> cmElements,
private void fillWithChildrenElementDeclaration(DOMElement element, CMElementDeclaration cmElement,
String p, boolean forceUseOfPrefix, ICompletionRequest request, ICompletionResponse response)
throws BadLocationException {
XMLGenerator generator = request.getXMLGenerator();
for (CMElementDeclaration child : cmElements) {
String prefix = forceUseOfPrefix ? p : (element != null ? element.getPrefix(child.getNamespace()) : null);
String label = child.getName(prefix);
CompletionItem item = new CompletionItem(label);
item.setFilterText(request.getFilterForStartTagName(label));
item.setKind(CompletionItemKind.Property);
String documentation = child.getDocumentation();
if (documentation != null) {
item.setDetail(documentation);
short modelGroup = XSModelGroup.COMPOSITOR_ALL;
if(cmElement instanceof CMXSDElementDeclaration) {
modelGroup = ((CMXSDElementDeclaration) cmElement).getModelGroup();
}
fillWithChildrenElementDeclaration(element, cmElement.getElements(), modelGroup, p, forceUseOfPrefix, request, response);
}
private void fillWithChildrenElementDeclaration(DOMElement element, List<CMElementDeclaration> cmElements, short modelGroup,
String p, boolean forceUseOfPrefix, ICompletionRequest request, ICompletionResponse response)
throws BadLocationException {

for (CMElementDeclaration cmElement : cmElements) {
finallyFillWithChildrenElementDeclaration(element, cmElement.getElements() , modelGroup, p, forceUseOfPrefix, request, response);
}


}

private void finallyFillWithChildrenElementDeclaration(DOMElement element, List<CMElementDeclaration> cmElements, short modelGroup,
String p, boolean forceUseOfPrefix, ICompletionRequest request, ICompletionResponse response)
throws BadLocationException {

List<DOMElement> domElements = null;
if(element != null) {
domElements = element.getChildElements();
}
if(cmElements == null || cmElements.isEmpty()) {
return;
}

CompletionSortTextHelper sort = new CompletionSortTextHelper(CompletionItemKind.Property);
int offset = request.getOffset();

switch(modelGroup) {
case XSModelGroup.COMPOSITOR_CHOICE:
case XSModelGroup.COMPOSITOR_ALL: {
List<CMElementDeclaration> choicesCMElements = new ArrayList<CMElementDeclaration>();
for (CMElementDeclaration childDeclaration : cmElements) {
boolean addDecl = true;
if(domElements != null) {
for (DOMElement childDom: domElements) {
if(childDeclaration.getName().equals(childDom.getTagName())) {
if(modelGroup == XSModelGroup.COMPOSITOR_CHOICE) { // element already exists
return;
}
break;
}
}
}
if(addDecl == true) {
if(modelGroup == XSModelGroup.COMPOSITOR_CHOICE) {
choicesCMElements.add(childDeclaration);
}
if(modelGroup == XSModelGroup.COMPOSITOR_ALL) {
createElementCompletionItem(element, childDeclaration, p, request, response, sort, forceUseOfPrefix);
}
}
}
if(modelGroup == XSModelGroup.COMPOSITOR_CHOICE) {
createElementCompletionItems(element, choicesCMElements, p, request, response, sort, forceUseOfPrefix);
return;
}
break;
}

case XSModelGroup.COMPOSITOR_SEQUENCE: {
CMElementDeclaration cmElement = getNextElementInSequence(element, cmElements, offset);
if(cmElement == null) {
break;
}
createElementCompletionItem(element, cmElement, p, request, response, sort, forceUseOfPrefix);
break;
}
}
}

private CMElementDeclaration getNextElementInSequence(DOMElement element, List<CMElementDeclaration> cmElements, int offset) {
CMElementDeclaration candidateDecl = null;
List<DOMElement> domElements = element != null ? element.getChildElements() : null;
if(domElements != null && !domElements.isEmpty()) {
int iDecl;
for(iDecl = 0; iDecl < cmElements.size(); iDecl++) {
CMElementDeclaration declElement = cmElements.get(iDecl);
int iDom;
for (iDom = 0; iDom < domElements.size(); iDom++) {
DOMElement domElement = domElements.get(iDom);
if(offset < domElement.getStart()) { // Went past the completion offset position
candidateDecl = cmElements.get(iDecl);
if(candidateDecl.getName().equals(domElement.getTagName())) { // The element after offset is already the candidateDecl
return null;
}
return candidateDecl;
}
if(domElement.getTagName().equals(declElement.getName())) {
break; // Check next cmElement since this already exists
}
}
if(iDom == domElements.size()) { // The next cmElements wasn't present
return cmElements.get(iDecl);
}
}
if(iDecl == cmElements.size()) { // All cmElements already exist
return null;
}
String xml = generator.generate(child, prefix);
item.setTextEdit(new TextEdit(request.getReplaceRange(), xml));
item.setInsertTextFormat(InsertTextFormat.Snippet);
response.addCompletionItem(item, true);
}
if(candidateDecl == null) { // At this point we know we are before the first child (if it even exists)
candidateDecl = cmElements.get(0);
}
return candidateDecl;
}

private void createElementCompletionItems(DOMElement element, List<CMElementDeclaration> children, String p,
ICompletionRequest request, ICompletionResponse response, CompletionSortTextHelper sort, boolean forceUseOfPrefix)
throws BadLocationException {
if(children != null && children.size() == 1) {
createElementCompletionItem(element, children.get(0), p, request, response, sort, forceUseOfPrefix);
return;
}
for (CMElementDeclaration child : children) {
if(child instanceof CMXSDElementDeclaration) {
finallyFillWithChildrenElementDeclaration(element, child.getElements(), ((CMXSDElementDeclaration) child).getModelGroup(), p, forceUseOfPrefix, request, response);
}
}
}

private void createElementCompletionItem(DOMElement element, CMElementDeclaration child, String p,
ICompletionRequest request, ICompletionResponse response, CompletionSortTextHelper sort, boolean forceUseOfPrefix)
throws BadLocationException {


String prefix = forceUseOfPrefix ? p : (element != null ? element.getPrefix(child.getNamespace()) : null);
String label = child.getName(prefix);
if(response.hasSeen(label)) {
return;
}
XMLGenerator generator = request.getXMLGenerator();
CompletionItem item = new CompletionItem(label);
item.setFilterText(request.getFilterForStartTagName(label));
item.setKind(CompletionItemKind.Property);
item.setSortText(sort.next());
String documentation = child.getDocumentation();
if (documentation != null) {
item.setDetail(documentation);
}
String xml = generator.generate(child, prefix);
item.setTextEdit(new TextEdit(request.getReplaceRange(), xml));
item.setInsertTextFormat(InsertTextFormat.Snippet);
response.addCompletionItem(item, true);
}

@Override
Expand Down Expand Up @@ -155,7 +295,7 @@ private void fillAttributesWithCMAttributeDeclarations(DOMElement parentElement,
if (documentation != null) {
item.setDetail(documentation);
}
response.addCompletionAttribute(item);
response.addCompletionItemAsSeen(item);
}
}
}
Expand Down Expand Up @@ -190,7 +330,7 @@ private void fillAttributeValuesWithCMAttributeDeclarations(CMElementDeclaration
CompletionItem item = new CompletionItem();
item.setLabel(value);
item.setKind(CompletionItemKind.Value);
response.addCompletionAttribute(item);
response.addCompletionItemAsSeen(item);
});
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public class CMDTDDocument extends XMLDTDLoader implements CMDocument {
private List<String> hierachies;

@Override
public Collection<CMElementDeclaration> getElements() {
public List<CMElementDeclaration> getElements() {
if (elements == null) {
elements = new ArrayList<>();
int index = grammar.getFirstElementDeclIndex();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public Collection<CMAttributeDeclaration> getAttributes() {
}

@Override
public Collection<CMElementDeclaration> getElements() {
public List<CMElementDeclaration> getElements() {
if (elements == null) {
elements = new ArrayList<>();
document.collectElementsDeclaration(getName(), elements);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void onXMLContent(ICompletionRequest request, ICompletionResponse respons
CompletionItem item = new CompletionItem();
item.setLabel(label);
String insertText = label;
item.setKind(CompletionItemKind.Property);
item.setKind(CompletionItemKind.Text);
item.setDocumentation(Either.forLeft(label));
item.setFilterText(insertText);
item.setTextEdit(new TextEdit(range, insertText));
Expand Down
Loading

0 comments on commit dd83d8b

Please sign in to comment.