Skip to content

Commit

Permalink
Hover show documentation for XSI
Browse files Browse the repository at this point in the history
Fixes eclipse-lemminx#164

Signed-off-by: Nikolas Komonen <[email protected]>
  • Loading branch information
NikolasKomonen committed Nov 29, 2018
1 parent 921459b commit 64812dc
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ private void fillWithChildrenElementDeclaration(DOMElement element, Collection<C
public void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request,
ICompletionResponse response) throws Exception {
if(request.getXMLDocument().hasSchemaInstancePrefix()) {
computeXSIAttributes(fullRange, request, response);
computeXSIAttributes(fullRange, request, response, generateValue);
}
// otherwise, manage completion based on XML Schema, DTD.
DOMElement parentElement = request.getNode().isElement() ? (DOMElement) request.getNode() : null;
Expand Down Expand Up @@ -198,12 +198,12 @@ public void onAttributeValue(String valuePrefix, Range fullRange, boolean addQuo
* @param response
* @throws BadLocationException
*/
private void computeXSIAttributes(Range editRange, ICompletionRequest request, ICompletionResponse response) throws BadLocationException {
private void computeXSIAttributes(Range editRange, ICompletionRequest request, ICompletionResponse response, boolean generateValue) throws BadLocationException {
DOMDocument document = request.getXMLDocument();
DOMElement rootElement = document.getDocumentElement();
int offset = document.offsetAt(editRange.getStart());
if(rootElement.equals(document.findNodeAt(offset))) {
XSISchemaModel.computeCompletionResponses(request, response, editRange, document);
XSISchemaModel.computeCompletionResponses(request, response, editRange, document, generateValue);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMAttributeDeclaration;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMElementDeclaration;
import org.eclipse.lsp4xml.extensions.contentmodel.model.ContentModelManager;
import org.eclipse.lsp4xml.services.XSISchemaModel;
import org.eclipse.lsp4xml.services.extensions.HoverParticipantAdapter;
import org.eclipse.lsp4xml.services.extensions.IHoverRequest;
import org.eclipse.lsp4xml.uriresolver.CacheResourceDownloadingException;
Expand Down Expand Up @@ -54,6 +55,10 @@ public Hover onAttributeName(IHoverRequest hoverRequest) throws Exception {
try {
ContentModelManager contentModelManager = hoverRequest.getComponent(ContentModelManager.class);
DOMAttr attribute = (DOMAttr) hoverRequest.getNode();
Hover temp = XSISchemaModel.computeHoverResponse(attribute, hoverRequest);
if(temp != null) {
return temp;
}
CMElementDeclaration cmElement = contentModelManager.findCMElement(attribute.getOwnerElement());
if (cmElement != null) {
String attributeName = attribute.getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,53 @@
*/
package org.eclipse.lsp4xml.services;

import org.apache.xml.serialize.LineSeparator;
import org.eclipse.lsp4j.CompletionItem;
import org.eclipse.lsp4j.CompletionItemKind;
import org.eclipse.lsp4j.Hover;
import org.eclipse.lsp4j.InsertTextFormat;
import org.eclipse.lsp4j.MarkupContent;
import org.eclipse.lsp4j.MarkupKind;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4xml.dom.DOMAttr;
import org.eclipse.lsp4xml.dom.DOMDocument;
import org.eclipse.lsp4xml.dom.DOMElement;
import org.eclipse.lsp4xml.services.extensions.ICompletionRequest;
import org.eclipse.lsp4xml.services.extensions.ICompletionResponse;
import org.eclipse.lsp4xml.services.extensions.IHoverRequest;

/**
* This class holds values that represent the XSI xsd.
* Can be seen at https://www.w3.org/2001/XMLSchema-instance
*/
public class XSISchemaModel {

private static String lineSeparator = System.lineSeparator();
public static final String TYPE_DOC = "Specifies the type of an element. This attribute labels an element as a particular type, even though there might not be an element declaration in the schema binding that element to the type.";
public static final String NIL_DOC = "Indicates if an element should contain content. Valid values are true/false";
public static final String SCHEMA_LOCATION_DOC =
"The xsi:schemaLocation attribute can be used in an XML document " +
"to reference an XML Schema document that has a target namespace. " + lineSeparator +
"```xml " + lineSeparator +
"<ns:root "+ lineSeparator +
" xmlns:ns=\"http://example.com/ns\" " + lineSeparator +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" " + lineSeparator +
" xsi:schemaLocation=\"http://example.com/ns example.xsd\">" + lineSeparator +
" <!-- ... --> " + lineSeparator +
"</ns:root> " + lineSeparator +
"```" ;
public static final String NO_NAMESPACE_SCHEMA_LOCATION_DOC=
"The xsi:noNamespaceSchemaLocation attribute can be used in an XML document " + lineSeparator +
"to reference an XML Schema document that does not have a target namespace. " + lineSeparator +
"```xml " + lineSeparator +
"<root " + lineSeparator +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "+ lineSeparator +
" xsi:noNamespaceSchemaLocation=\"example.xsd\">" + lineSeparator +
" <!-- ... --> " + lineSeparator +
"</root> " + lineSeparator +
"```" ;
public static void computeCompletionResponses(ICompletionRequest request,
ICompletionResponse response, Range editRange, DOMDocument document) {
ICompletionResponse response, Range editRange, DOMDocument document, boolean generateValue) {
String snippet = "";
boolean isSnippetsSupported = request.getCompletionSettings().isCompletionSnippetsSupported();
if(isSnippetsSupported) {
Expand All @@ -43,55 +70,42 @@ public static void computeCompletionResponses(ICompletionRequest request,
boolean noNamespaceSchemaLocationExists = document.hasNoNamespaceSchemaLocation();
//Indicates that no values are allowed inside an XML element
if(!attributeAlreadyExists(root, actualPrefix, "nil")) {
documentation = "Indicates if an element should contain content. Valid values are true/false";
documentation = NIL_DOC;
name = actualPrefix + ":nil";
String nilSnippet = isSnippetsSupported ? "${0:true}" : "true";
createCompletionItem(name, isSnippetsSupported, nilSnippet, editRange, response, documentation);
createCompletionItem(name, isSnippetsSupported, nilSnippet, editRange, response, documentation, generateValue);
}
//Signals that an element should be accepted as ·valid· when it has no content despite
//a content type which does not require or even necessarily allow empty content.
//An element may be ·valid· without content if it has the attribute xsi:nil with
//the value true.
if(!attributeAlreadyExists(root, actualPrefix, "type")) {
documentation = "Specifies the type of an element. This attribute labels an element as a particular type, even though there might not be an element declaration in the schema binding that element to the type.";
documentation = TYPE_DOC;
name = actualPrefix + ":type";
createCompletionItem(name, isSnippetsSupported, snippet, editRange, response, documentation);
createCompletionItem(name, isSnippetsSupported, snippet, editRange, response, documentation, generateValue);
}
//The xsi:schemaLocation and xsi:noNamespaceSchemaLocation attributes can be used in a document
//to provide hints as to the physical location of schema documents which may be used for ·assessment·.
if(!schemaLocationExists && !noNamespaceSchemaLocationExists) {
documentation = "The xsi:schemaLocation attribute can be used in an XML document " +
"to reference an XML Schema document that has a target namespace.\r\n " +
"```xml \r\n" +
"<ns:root xmlns:ns=\"http://example.com/ns\"" +
" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"" +
" xsi:schemaLocation=\"http://example.com/ns example-ns.xsd\">\r\n " +
" <!-- ... -->\r\n " +
"</ns:root>\r\n " +
"```" ;
documentation = NO_NAMESPACE_SCHEMA_LOCATION_DOC;
name = actualPrefix + ":schemaLocation";
createCompletionItem(name, isSnippetsSupported, snippet, editRange, response, documentation);
createCompletionItem(name, isSnippetsSupported, snippet, editRange, response, documentation, generateValue);

documentation = "The xsi:noNamespaceSchemaLocation attribute can be used in an XML document " +
"to reference an XML Schema document that does not have a target namespace.\r\n " +
"```xml \r\n" +
"\r\n<root xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" "+
" xsi:noNamespaceSchemaLocation=\"example.xsd\"> \r\n" +
" <!-- ... --> \r\n" +
"</root> \r\n" +
"```" ;
documentation = SCHEMA_LOCATION_DOC;
name = actualPrefix + ":noNamespaceSchemaLocation";
createCompletionItem(name, isSnippetsSupported, snippet, editRange, response, documentation); }
createCompletionItem(name, isSnippetsSupported, snippet, editRange, response, documentation, generateValue);
}
}

private static void createCompletionItem(String text, boolean isSnippetsSupported,
String defaultAndSnippet, Range editRange, ICompletionResponse response, String documentation) {
String defaultAndSnippet, Range editRange, ICompletionResponse response, String documentation, boolean generateValue) {
CompletionItem item = new CompletionItem();
item.setLabel(text);
item.setKind(CompletionItemKind.Value);
item.setFilterText(text);
item.setInsertTextFormat(isSnippetsSupported ? InsertTextFormat.Snippet : InsertTextFormat.PlainText);
item.setTextEdit(new TextEdit(editRange, text + "=\"" + defaultAndSnippet + "\""));
text = generateValue ? text + "=\"" + defaultAndSnippet + "\"" : text + "$0";
item.setTextEdit(new TextEdit(editRange, text));
MarkupContent markup = new MarkupContent();
markup.setKind(MarkupKind.MARKDOWN);
markup.setValue(documentation);
Expand All @@ -102,4 +116,40 @@ private static void createCompletionItem(String text, boolean isSnippetsSupporte
private static boolean attributeAlreadyExists(DOMElement root, String actualPrefix, String suffix) {
return root.getAttributeNode(actualPrefix + ":" + suffix) != null;
}

public static Hover computeHoverResponse(DOMAttr attribute, IHoverRequest request) {
DOMDocument document = request.getXMLDocument();
DOMElement root = document.getDocumentElement();
if(root != null) {
if(!root.equals(document.findNodeAt(attribute.getStart()))) {
return null;
}
}

String name = attribute.getName();
if(!name.startsWith(request.getXMLDocument().getSchemaInstancePrefix() + ":")) {
return null;
}
String doc = null;
if(name.endsWith(":schemaLocation")) {
doc = SCHEMA_LOCATION_DOC;
}
else if(name.endsWith(":noNamespaceSchemaLocation")) {
doc = NO_NAMESPACE_SCHEMA_LOCATION_DOC;
}
else if(name.endsWith(":nil")) {
doc = NIL_DOC;
}
else if(name.endsWith(":type")) {
doc = TYPE_DOC;
}
else {
return null;
}

MarkupContent content = new MarkupContent();
content.setKind(MarkupKind.MARKDOWN);
content.setValue(doc);
return new Hover(content);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import org.eclipse.lsp4xml.XMLAssert;
import org.eclipse.lsp4xml.commons.BadLocationException;
import org.eclipse.lsp4xml.services.XMLLanguageService;
import org.eclipse.lsp4xml.services.XSISchemaModel;
import org.junit.Test;

/**
Expand Down Expand Up @@ -40,20 +41,89 @@ public void testAttributeNameHover() throws BadLocationException {
assertHover(xml,
"The fully qualified name of the bean's class, except if it serves only as a parent definition for child bean definitions. ",
null);

};

@Test
public void testTagHoverFromXSType() throws BadLocationException {
// web.xml servlet, servlet-name declares their xs:annotation not in the element
// declaration but in type (servletType),
// this test check that
// this test checks that
String xml = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n" + //
"<invoi|ce xmlns=\"http://invoice\" xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"\r\n"
+ " xsi:schemaLocation=\"http://invoice xsd/invoice.xsd \">\r\n";
XMLAssert.assertHover(new XMLLanguageService(), xml, null, "src/test/resources/invoice.xml",
"An invoice type...", null);
};

@Test
public void testTagHoverForSchemaLocation() 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:schema|Location=\"http://invoice xsd/invoice.xsd \">\r\n";

XMLAssert.assertHover(new XMLLanguageService(), xml, null, "src/test/resources/invoice.xml",
XSISchemaModel.SCHEMA_LOCATION_DOC, null);
};

@Test
public void testTagHoverForNoNamespaceSchemaLocation() 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:noNamespace|SchemaLocation=\"http://invoice xsd/invoice.xsd \">\r\n";

XMLAssert.assertHover(new XMLLanguageService(), xml, null, "src/test/resources/invoice.xml",
XSISchemaModel.NO_NAMESPACE_SCHEMA_LOCATION_DOC, null);
};

@Test
public void testTagHoverForXSInil() 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:n|il=\"http://invoice xsd/invoice.xsd \">\r\n";

XMLAssert.assertHover(new XMLLanguageService(), xml, null, "src/test/resources/invoice.xml",
XSISchemaModel.NIL_DOC, null);
};

@Test
public void testTagHoverForXSIType() 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:ty|pe=\"http://invoice xsd/invoice.xsd \">\r\n";

XMLAssert.assertHover(new XMLLanguageService(), xml, null, "src/test/resources/invoice.xml",
XSISchemaModel.TYPE_DOC, null);
};

@Test
public void testTagHoverForXSIType2() 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"
+ " x|si:type=\"http://invoice xsd/invoice.xsd \">\r\n";

XMLAssert.assertHover(new XMLLanguageService(), xml, null, "src/test/resources/invoice.xml",
XSISchemaModel.TYPE_DOC, null);
};

@Test
public void testTagHoverForXSIBadPrefix() 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"
+ " BAD:t|ype=\"http://invoice xsd/invoice.xsd \">\r\n";

XMLAssert.assertHover(new XMLLanguageService(), xml, null, "src/test/resources/invoice.xml",
null, null);
};

@Test
public void testTagHoverForXSINotRoot() 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"
+ ">\r\n" +
"<a xsi:ty|pe=\"\"></a>";

XMLAssert.assertHover(new XMLLanguageService(), xml, null, "src/test/resources/invoice.xml",
null, null);
};

private static void assertHover(String value, String expectedHoverLabel, Integer expectedHoverOffset)
Expand Down

0 comments on commit 64812dc

Please sign in to comment.