Skip to content

Commit

Permalink
Indicate source of element for Completion.
Browse files Browse the repository at this point in the history
Fixes eclipse-lemminx#210

Signed-off-by: Nikolas Komonen <nikolaskomonen@gmail.com>
  • Loading branch information
NikolasKomonen committed May 29, 2019
1 parent 1097abe commit 2e17fde
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,4 +32,6 @@ public interface CMDocument {
*/
CMElementDeclaration findCMElement(DOMElement element, String namespace);

String getURI();

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
*/
package org.eclipse.lsp4xml.extensions.contentmodel.participants;

import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection;

import org.eclipse.lsp4j.CompletionItem;
Expand All @@ -31,6 +33,7 @@
import org.eclipse.lsp4xml.services.extensions.ICompletionResponse;
import org.eclipse.lsp4xml.settings.SharedSettings;
import org.eclipse.lsp4xml.uriresolver.CacheResourceDownloadingException;
import org.eclipse.lsp4xml.utils.StringUtils;

/**
* Extension to support XML completion based on content model (XML Schema
Expand All @@ -42,26 +45,34 @@ public class ContentModelCompletionParticipant extends CompletionParticipantAdap
public void onTagOpen(ICompletionRequest request, ICompletionResponse response) throws Exception {
try {
DOMDocument document = request.getXMLDocument();
String schemaURI;
String fileURI;
ContentModelManager contentModelManager = request.getComponent(ContentModelManager.class);
DOMElement parentElement = request.getParentElement();
CMDocument cmDocument;
if (parentElement == null) {
// XML is empty, in case of XML file associations, a XMl Schema/DTD can be bound
// check if it's root element (in the case of XML file associations, the link to
// XML Schema is done with pattern and not with XML root element)
CMDocument cmDocument = contentModelManager.findCMDocument(document, null);
cmDocument = contentModelManager.findCMDocument(document, null);
if (cmDocument != null) {
fillWithChildrenElementDeclaration(null, cmDocument.getElements(), null, false, request, response);
schemaURI = cmDocument.getURI();
fillWithChildrenElementDeclaration(null, cmDocument.getElements(), null, false, request, response, schemaURI);
}
return;
}
// Try to retrieve XML Schema/DTD element declaration for the parent element
// where completion was triggered.
cmDocument = contentModelManager.findCMDocument(parentElement, parentElement.getNamespaceURI());

schemaURI = cmDocument != null ? cmDocument.getURI() : null;
CMElementDeclaration cmElement = contentModelManager.findCMElement(parentElement);
String defaultPrefix = null;

if (cmElement != null) {
defaultPrefix = parentElement.getPrefix();
fillWithChildrenElementDeclaration(parentElement, cmElement.getElements(), defaultPrefix, false,
request, response);
request, response, schemaURI);
}
if (parentElement.isDocumentElement()) {
// root document element
Expand All @@ -71,10 +82,10 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response)
continue;
}
String namespaceURI = parentElement.getNamespaceURI(prefix);
CMDocument cmDocument = contentModelManager.findCMDocument(parentElement, namespaceURI);
cmDocument = contentModelManager.findCMDocument(parentElement, namespaceURI);
if (cmDocument != null) {
fillWithChildrenElementDeclaration(parentElement, cmDocument.getElements(), prefix, true,
request, response);
request, response, cmDocument.getURI());
}
}
}
Expand All @@ -84,15 +95,15 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response)
if (cmInternalElement != null) {
defaultPrefix = parentElement.getPrefix();
fillWithChildrenElementDeclaration(parentElement, cmInternalElement.getElements(), defaultPrefix, false,
request, response);
request, response, schemaURI);
}
} catch (CacheResourceDownloadingException e) {
// XML Schema, DTD is loading, ignore this error
}
}

private void fillWithChildrenElementDeclaration(DOMElement element, Collection<CMElementDeclaration> cmElements,
String p, boolean forceUseOfPrefix, ICompletionRequest request, ICompletionResponse response)
String p, boolean forceUseOfPrefix, ICompletionRequest request, ICompletionResponse response, String schemaURI)
throws BadLocationException {
XMLGenerator generator = request.getXMLGenerator();
for (CMElementDeclaration child : cmElements) {
Expand All @@ -101,8 +112,26 @@ private void fillWithChildrenElementDeclaration(DOMElement element, Collection<C
CompletionItem item = new CompletionItem(label);
item.setFilterText(request.getFilterForStartTagName(label));
item.setKind(CompletionItemKind.Property);
String documentation = child.getDocumentation();
if (documentation != null) {
StringBuilder sb = new StringBuilder();
String documentation;
String tempDoc = child.getDocumentation();
boolean tempDocHasContent = !StringUtils.isEmpty(tempDoc);
if(tempDocHasContent) {
sb.append(tempDoc);
}

if(schemaURI != null) {
if(tempDocHasContent) {
String lineSeparator = System.getProperty("line.separator");
sb.append(lineSeparator);
sb.append(lineSeparator);
}
Path schemaPath = Paths.get(schemaURI);
sb.append("Source: ");
sb.append(schemaPath.getFileName().toString());
}
documentation = sb.toString();
if (documentation != null && !documentation.isEmpty()) {
item.setDetail(documentation);
}
String xml = generator.generate(child, prefix);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ public String getSystemId(DOMDocument xmlDocument, String namespaceURI) {
@Override
public CMDocument createCMDocument(String key) {
try {
CMDTDDocument document = new CMDTDDocument();
CMDTDDocument document = new CMDTDDocument(key);
document.setEntityResolver(resolverExtensionManager);
Grammar grammar = document.loadGrammar(new XMLInputSource(null, key, null));
if (grammar != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,18 @@
*/
public class CMDTDDocument extends XMLDTDLoader implements CMDocument {

private Map<String, Set<String>> hierachiesMap;
private Map<String, Set<String>> hierarchiesMap;
private List<CMElementDeclaration> elements;
private DTDGrammar grammar;
private Set<String> hierarchies;
private String uri;


public CMDTDDocument() {}

public CMDTDDocument(String uri) {
this.uri = uri;
}

@Override
public Collection<CMElementDeclaration> getElements() {
Expand All @@ -60,6 +68,15 @@ public Collection<CMElementDeclaration> getElements() {
return elements;
}

@Override
/**
* Returns the URI of this document, is none was provided this
* returns null.
*/
public String getURI() {
return uri;
}

@Override
public CMElementDeclaration findCMElement(DOMElement element, String namespace) {
List<DOMElement> paths = new ArrayList<>();
Expand Down Expand Up @@ -93,11 +110,11 @@ private CMElementDeclaration findElementDeclaration(String tag, String namespace

@Override
public void startContentModel(String elementName, Augmentations augs) throws XNIException {
if (hierachiesMap == null) {
hierachiesMap = new HashMap<>();
if (hierarchiesMap == null) {
hierarchiesMap = new HashMap<>();
}
hierarchies = new LinkedHashSet<String>();
hierachiesMap.put(elementName, hierarchies);
hierarchiesMap.put(elementName, hierarchies);
super.startContentModel(elementName, augs);
}

Expand Down Expand Up @@ -136,10 +153,10 @@ public void loadInternalDTD(String internalSubset, String baseSystemId, String s
}

void collectElementsDeclaration(String elementName, List<CMElementDeclaration> elements) {
if (hierachiesMap == null) {
if (hierarchiesMap == null) {
return;
}
Set<String> children = hierachiesMap.get(elementName);
Set<String> children = hierarchiesMap.get(elementName);
if (children == null) {
return;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public CMDocument createCMDocument(String key) {
XSModel model = getLoader().loadURI(key);
if (model != null) {
// XML Schema can be loaded
return new CMXSDDocument(model);
return new CMXSDDocument(model, key);
}
return null;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,11 +43,23 @@ public class CMXSDDocument implements CMDocument {

private Collection<CMElementDeclaration> elements;

private String uri;

public CMXSDDocument(XSModel model) {
this.model = model;
this.elementMappings = new HashMap<>();
}

public CMXSDDocument(XSModel model, String uri) {
this(model);
this.uri = uri;
}

@Override
public String getURI() {
return uri;
}

@Override
public Collection<CMElementDeclaration> getElements() {
if (elements == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,19 @@ private static void assertCompletion(CompletionList completions, CompletionItem
Assert.assertEquals(expected.getFilterText(), match.getFilterText());
}

if(expected.getDetail() != null) {
Assert.assertEquals(expected.getDetail(), match.getDetail());
}

}

public static CompletionItem c(String label, TextEdit textEdit, String filterText, String detail) {
CompletionItem item = new CompletionItem();
item.setLabel(label);
item.setFilterText(filterText);
item.setTextEdit(textEdit);
item.setDetail(detail);
return item;
}

public static CompletionItem c(String label, TextEdit textEdit, String filterText) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,18 @@ public void completionWithChoiceAttribute() throws BadLocationException {
testCompletionFor(xml, c("prefer", te(5, 11, 5, 11, "prefer=\"${1|system,public|}\"$0"), "prefer"));
}

@Test
public void testCompletionDetailWithSource() throws BadLocationException {
// completion on <|
String xml = "<?xml version=\"1.0\"?>\r\n" + //
" <!DOCTYPE catalog\r\n" + //
" PUBLIC \"-//OASIS//DTD Entity Resolution XML Catalog V1.0//EN\"\r\n" + //
" \"http://www.oasis-open.org/committees/entity/release/1.0/catalog.dtd\">\r\n" + //
"\r\n" + //
" <|";
testCompletionFor(xml, c("catalog", te(5, 2, 5, 3, "<catalog>$1</catalog>$0"), "<catalog", "Source: catalog.dtd"));
}

@Test
public void externalDTDCompletionElement() throws BadLocationException {
// completion on <|
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,28 @@ public void completion() throws BadLocationException {
c("xs:attribute", te(2, 0, 2, 0, "<xs:attribute name=\"\"></xs:attribute>"), "xs:attribute"));
}

@Test
public void completionWithSourceDetail() throws BadLocationException {
// completion on |
String xml = "<?xml version=\"1.1\"?>\r\n"
+ "<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" elementFormDefault=\"qualified\" attributeFormDefault=\"unqualified\"> \r\n"
+ //
"|";
testCompletionFor(xml, c("xs:annotation", te(2, 0, 2, 0, "<xs:annotation></xs:annotation>"), "xs:annotation", "Source: XMLSchema.xsd"),
c("xs:attribute", te(2, 0, 2, 0, "<xs:attribute name=\"\"></xs:attribute>"), "xs:attribute", "Source: XMLSchema.xsd"));
}

@Test
public void completionWithSourceDescriptionAndDetail() throws BadLocationException {
String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" + //
"<invoice xmlns=\"http://invoice\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n"
+ " xsi:schemaLocation=\"http://invoice xsd/invoice.xsd \">\r\n" + //
" <|";
String lineSeparator = System.getProperty("line.separator");
XMLAssert.testCompletionFor(xml, null, "src/test/resources/invoice.xml", null, c("date", te(3, 2, 3, 3, "<date></date>"), "<date", "Date Description" + lineSeparator + lineSeparator + "Source: invoice.xsd"),
c("number", "<number></number>"));
}

private void testCompletionFor(String xml, CompletionItem... expectedItems) throws BadLocationException {
XMLAssert.testCompletionFor(xml, null, expectedItems);
}
Expand Down
10 changes: 7 additions & 3 deletions org.eclipse.lsp4xml/src/test/resources/xsd/invoice.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,13 @@
<xsd:documentation>An invoice type...</xsd:documentation>
</xsd:annotation>
<xsd:sequence>
<xsd:element name="date" type="xsd:date" nillable="true"></xsd:element>
<xsd:element name="number" type="xsd:int"></xsd:element>
<xsd:element name="products" type="productsType"></xsd:element>
<xsd:element name="date" type="xsd:date" nillable="true">
<xsd:annotation>
<xsd:documentation>Date Description</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element name="number" type="xsd:int"></xsd:element>
<xsd:element name="products" type="productsType"></xsd:element>
<xsd:element name="payments" type="paymentsType"></xsd:element>
</xsd:sequence>
</xsd:complexType>
Expand Down

0 comments on commit 2e17fde

Please sign in to comment.