Skip to content

Commit

Permalink
Completion for xmlns:xsi and xmlns
Browse files Browse the repository at this point in the history
Fixes eclipse-lemminx#326, eclipse-lemminx#208

Signed-off-by: Nikolas <nikolaskomonen@gmail.com>
  • Loading branch information
NikolasKomonen committed Mar 15, 2019
1 parent 3988871 commit 97c78af
Show file tree
Hide file tree
Showing 14 changed files with 216 additions and 62 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ private void fillWithChildrenElementDeclaration(DOMElement element, Collection<C

@Override
public void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request,
ICompletionResponse response) throws Exception {
ICompletionResponse response, SharedSettings settings) throws Exception {
// otherwise, manage completion based on XML Schema, DTD.
DOMElement parentElement = request.getNode().isElement() ? (DOMElement) request.getNode() : null;
if (parentElement == null) {
Expand All @@ -127,19 +127,19 @@ public void onAttributeName(boolean generateValue, Range fullRange, ICompletionR
// Completion on attribute based on external grammar
CMElementDeclaration cmElement = contentModelManager.findCMElement(parentElement);
fillAttributesWithCMAttributeDeclarations(parentElement, fullRange, cmElement, canSupportSnippet,
generateValue, response);
generateValue, response, settings);
// Completion on attribute based on internal grammar
cmElement = contentModelManager.findInternalCMElement(parentElement);
fillAttributesWithCMAttributeDeclarations(parentElement, fullRange, cmElement, canSupportSnippet,
generateValue, response);
generateValue, response, settings);
} catch (CacheResourceDownloadingException e) {
// XML Schema, DTD is loading, ignore this error
}
}

private void fillAttributesWithCMAttributeDeclarations(DOMElement parentElement, Range fullRange,
CMElementDeclaration cmElement, boolean canSupportSnippet, boolean generateValue,
ICompletionResponse response) {
ICompletionResponse response, SharedSettings settings) {
if (cmElement == null) {
return;
}
Expand All @@ -151,7 +151,7 @@ private void fillAttributesWithCMAttributeDeclarations(DOMElement parentElement,
String attrName = cmAttribute.getName();
if (!parentElement.hasAttribute(attrName)) {
CompletionItem item = new AttributeCompletionItem(attrName, canSupportSnippet, fullRange, generateValue,
cmAttribute.getDefaultValue(), cmAttribute.getEnumerationValues());
cmAttribute.getDefaultValue(), cmAttribute.getEnumerationValues(), settings);
String documentation = cmAttribute.getDocumentation();
if (documentation != null) {
item.setDetail(documentation);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import org.eclipse.lsp4xml.commons.SnippetsBuilder;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMAttributeDeclaration;
import org.eclipse.lsp4xml.extensions.contentmodel.model.CMElementDeclaration;
import org.eclipse.lsp4xml.settings.SharedSettings;
import org.eclipse.lsp4xml.settings.XMLFormattingOptions;
import org.eclipse.lsp4xml.utils.XMLBuilder;

Expand Down Expand Up @@ -170,9 +171,23 @@ private int generate(Collection<CMAttributeDeclaration> attributes, int level, i
*/
public static String generateAttributeValue(String defaultValue, Collection<String> enumerationValues,
boolean canSupportSnippets, int snippetIndex, boolean withQuote) {
return generateAttributeValue(defaultValue, enumerationValues, canSupportSnippets, snippetIndex, withQuote, null);
}

/**
* Creates the string value for a CompletionItem TextEdit
*
* Can create an enumerated TextEdit if given a collection of values.
*/
public static String generateAttributeValue(String defaultValue, Collection<String> enumerationValues,
boolean canSupportSnippets, int snippetIndex, boolean withQuote, SharedSettings settings) {
StringBuilder value = new StringBuilder();
String quotation = "\"";
if (withQuote) {
value.append("=\"");
if(settings != null) {
quotation = settings.formattingSettings.getQuotationAsString();
}
value.append("=" + quotation);
}
if (!canSupportSnippets) {
if (defaultValue != null) {
Expand All @@ -191,7 +206,7 @@ public static String generateAttributeValue(String defaultValue, Collection<Stri
}
}
if (withQuote) {
value.append("\"");
value.append(quotation);
if (canSupportSnippets) {
SnippetsBuilder.tabstops(0, value); // "$0"
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.eclipse.lsp4xml.services.extensions.CompletionParticipantAdapter;
import org.eclipse.lsp4xml.services.extensions.ICompletionRequest;
import org.eclipse.lsp4xml.services.extensions.ICompletionResponse;
import org.eclipse.lsp4xml.settings.SharedSettings;

/**
* XSD completion for xs:
Expand All @@ -23,7 +24,7 @@ public class XSDCompletionParticipant extends CompletionParticipantAdapter {

@Override
public void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request,
ICompletionResponse response) throws Exception {
ICompletionResponse response, SharedSettings settings) throws Exception {
// TODO: manage compeltion for types declared in XML Schema xsd
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ public class XSICompletionParticipant extends CompletionParticipantAdapter {

@Override
public void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request,
ICompletionResponse response) throws Exception {
if (request.getXMLDocument().hasSchemaInstancePrefix()) {
ICompletionResponse response, SharedSettings settings) throws Exception {
//if (request.getXMLDocument().hasSchemaInstancePrefix()) {
XSISchemaModel.computeCompletionResponses(request, response, fullRange, request.getXMLDocument(),
generateValue);
}
generateValue, settings);

}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4xml.extensions.contentmodel.utils.XMLGenerator;
import org.eclipse.lsp4xml.settings.SharedSettings;

public class AttributeCompletionItem extends CompletionItem {

Expand All @@ -34,15 +35,15 @@ public class AttributeCompletionItem extends CompletionItem {
* @param enumerationValues the enumeration values of attribute.
*/
public AttributeCompletionItem(String attrName, boolean canSupportSnippets, Range fullRange, boolean generateValue,
String defaultValue, Collection<String> enumerationValues) {
String defaultValue, Collection<String> enumerationValues, SharedSettings settings) {
super.setLabel(attrName);
super.setKind(CompletionItemKind.Unit);
super.setFilterText(attrName);
StringBuilder attributeContent = new StringBuilder(attrName);
if (generateValue) {
// Generate attribute value content
String attributeValue = XMLGenerator.generateAttributeValue(defaultValue, enumerationValues,
canSupportSnippets, 1, true);
canSupportSnippets, 1, true, settings);
attributeContent.append(attributeValue);
}
super.setTextEdit(new TextEdit(fullRange, attributeContent.toString()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position,
case AttributeName:
if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) {
collectAttributeNameSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd(), completionRequest,
completionResponse);
completionResponse, settings);
return completionResponse;
}
completionRequest.setCurrentAttributeName(scanner.getTokenText());
Expand Down Expand Up @@ -136,7 +136,7 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position,
return completionResponse;
case WithinTag:
case AfterAttributeName:
collectAttributeNameSuggestions(scanner.getTokenEnd(), completionRequest, completionResponse);
collectAttributeNameSuggestions(scanner.getTokenEnd(), completionRequest, completionResponse, settings);
return completionResponse;
case BeforeAttributeValue:
collectAttributeValueSuggestions(scanner.getTokenEnd(), offset, completionRequest,
Expand Down Expand Up @@ -694,13 +694,13 @@ private void collectCharacterEntityProposals(ICompletionRequest request, IComple
}

private void collectAttributeNameSuggestions(int nameStart, CompletionRequest completionRequest,
CompletionResponse completionResponse) {
CompletionResponse completionResponse, SharedSettings settings) {
collectAttributeNameSuggestions(nameStart, completionRequest.getOffset(), completionRequest,
completionResponse);
completionResponse, settings);
}

private void collectAttributeNameSuggestions(int nameStart, int nameEnd, CompletionRequest completionRequest,
CompletionResponse completionResponse) {
CompletionResponse completionResponse, SharedSettings settings) {
int replaceEnd = completionRequest.getOffset();
String text = completionRequest.getXMLDocument().getText();
while (replaceEnd < nameEnd && text.charAt(replaceEnd) != '<') { // < is a valid attribute name character, but
Expand All @@ -713,7 +713,7 @@ private void collectAttributeNameSuggestions(int nameStart, int nameEnd, Complet
boolean generateValue = !isFollowedBy(text, nameEnd, ScannerState.AfterAttributeName,
TokenType.DelimiterAssign);
for (ICompletionParticipant participant : getCompletionParticipants()) {
participant.onAttributeName(generateValue, range, completionRequest, completionResponse);
participant.onAttributeName(generateValue, range, completionRequest, completionResponse, settings);
}
} catch (BadLocationException e) {
LOGGER.log(Level.SEVERE, "While performing Completions, getReplaceRange() was given a bad Offset location",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,14 @@
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.Position;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextEdit;
import org.eclipse.lsp4xml.commons.BadLocationException;
import org.eclipse.lsp4xml.dom.DOMAttr;
import org.eclipse.lsp4xml.dom.DOMDocument;
import org.eclipse.lsp4xml.dom.DOMElement;
import org.eclipse.lsp4xml.dom.DOMNode;
import org.eclipse.lsp4xml.extensions.contentmodel.utils.XMLGenerator;
import org.eclipse.lsp4xml.services.extensions.ICompletionRequest;
import org.eclipse.lsp4xml.services.extensions.ICompletionResponse;
import org.eclipse.lsp4xml.services.extensions.IHoverRequest;
Expand Down Expand Up @@ -64,8 +60,10 @@ public class XSISchemaModel {
" <!-- ... --> " + lineSeparator +
"</root> " + lineSeparator +
"```" ;
public static final String XSI_WEBSITE = "http://www.w3.org/2001/XMLSchema-instance";
public static final String XSI_DOC = "The namespace that defines important attributes such as `noNamespaceSchemaLocation` and `schemaLocation`.";
public static void computeCompletionResponses(ICompletionRequest request,
ICompletionResponse response, Range editRange, DOMDocument document, boolean generateValue) throws BadLocationException {
ICompletionResponse response, Range editRange, DOMDocument document, boolean generateValue, SharedSettings settings) throws BadLocationException {

DOMElement rootElement = document.getDocumentElement();
int offset = document.offsetAt(editRange.getStart());
Expand All @@ -74,8 +72,20 @@ public static void computeCompletionResponses(ICompletionRequest request,
if(rootElement.equals(nodeAtOffset)) {
inRootElement = true;
}

boolean isSnippetsSupported = request.getCompletionSettings().isCompletionSnippetsSupported();
if(inRootElement) {
if(!hasAttribute(nodeAtOffset, "xmlns") && !response.hasAttribute("xmlns")) {
createCompletionItem("xmlns", isSnippetsSupported, generateValue, editRange, null, null, null, response, settings);
}
if(document.hasSchemaInstancePrefix() == false) {
createCompletionItem("xmlns:xsi", isSnippetsSupported, generateValue, editRange, XSI_WEBSITE, null, XSI_DOC, response, settings);
return;// All the following completion cases dont exist, so return.
}


}

String actualPrefix = document.getSchemaInstancePrefix();
String name;
String documentation;
Expand All @@ -87,7 +97,7 @@ public static void computeCompletionResponses(ICompletionRequest request,
documentation = NIL_DOC;
name = actualPrefix + ":nil";
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, StringUtils.TRUE,
StringUtils.TRUE_FALSE_ARRAY, documentation, response);
StringUtils.TRUE_FALSE_ARRAY, documentation, response, settings);
}
//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.
Expand All @@ -96,26 +106,29 @@ public static void computeCompletionResponses(ICompletionRequest request,
if(!hasAttribute(nodeAtOffset, actualPrefix, "type")) {
documentation = TYPE_DOC;
name = actualPrefix + ":type";
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation, response);
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation, response, settings);
}
//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(inRootElement && !schemaLocationExists && !noNamespaceSchemaLocationExists) {
documentation = SCHEMA_LOCATION_DOC;
name = actualPrefix + ":schemaLocation";
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation, response);

documentation = NO_NAMESPACE_SCHEMA_LOCATION_DOC;
name = actualPrefix + ":noNamespaceSchemaLocation";
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation, response);

if(inRootElement) {
if(!schemaLocationExists && !noNamespaceSchemaLocationExists) {
//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·.
documentation = SCHEMA_LOCATION_DOC;
name = actualPrefix + ":schemaLocation";
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation, response, settings);

documentation = NO_NAMESPACE_SCHEMA_LOCATION_DOC;
name = actualPrefix + ":noNamespaceSchemaLocation";
createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation, response, settings);
}
}
}

private static void createCompletionItem(String attrName, boolean canSupportSnippet, boolean generateValue,
Range editRange, String defaultValue, Collection<String> enumerationValues, String documentation,
ICompletionResponse response) {
ICompletionResponse response, SharedSettings settings){
CompletionItem item = new AttributeCompletionItem(attrName, canSupportSnippet, editRange, generateValue,
defaultValue, enumerationValues);
defaultValue, enumerationValues, settings);
MarkupContent markup = new MarkupContent();
markup.setKind(MarkupKind.MARKDOWN);
markup.setValue(documentation);
Expand All @@ -129,12 +142,21 @@ public static void computeValueCompletionResponses(ICompletionRequest request,
int offset = document.offsetAt(editRange.getStart());
DOMElement nodeAtOffset = (DOMElement) document.findNodeAt(offset);


String actualPrefix = document.getSchemaInstancePrefix();
DOMAttr attrAtOffset = nodeAtOffset.findAttrAt(offset);
String attrName = attrAtOffset.getName();

// Value completion for 'nil' attribute
DOMAttr nilAttr = nodeAtOffset.getAttributeNode(actualPrefix, "nil");
if(nilAttr != null) {
createCompletionItemsForValues(StringUtils.TRUE_FALSE_ARRAY, editRange, document, response, settings);
if(attrName != null) {
if(attrName.equals(actualPrefix + ":nil")) { // Value completion for 'nil' attribute
createCompletionItemsForValues(StringUtils.TRUE_FALSE_ARRAY, editRange, document, response, settings);
}
else if(document.getDocumentElement().equals(nodeAtOffset)) { // if in the root element
if(attrName.equals("xmlns:xsi")) {
createSingleCompletionItemForValue(XSI_WEBSITE, editRange, document, response, settings);
}
}

}
}

Expand Down Expand Up @@ -178,6 +200,10 @@ private static boolean hasAttribute(DOMElement root, String prefix, String suffi

}

private static boolean hasAttribute(DOMElement root, String name) {
return hasAttribute(root, null, name);
}

public static Hover computeHoverResponse(DOMAttr attribute, IHoverRequest request) {

String name = attribute.getName();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public void onXMLContent(ICompletionRequest request, ICompletionResponse respons

@Override
public void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request,
ICompletionResponse response) throws Exception {
ICompletionResponse response, SharedSettings settings) throws Exception {
// Do nothing
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ public interface ICompletionParticipant {

void onXMLContent(ICompletionRequest request, ICompletionResponse response) throws Exception;

void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request, ICompletionResponse response)
throws Exception;
void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request, ICompletionResponse response,
SharedSettings settings) throws Exception;

void onAttributeValue(String valuePrefix, Range fullRange, boolean addQuotes, ICompletionRequest request,
ICompletionResponse response, SharedSettings settings) throws Exception;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,9 @@ public String getQuotations() {
/**
* Returns the actual quotation value as a String.
*
* Either a {@code '} or {@code "}
* Either a {@code '} or {@code "}.
*
* Defaults to {@code "}.
*/
public String getQuotationAsString() {
return XMLFormattingOptions.DOUBLE_QUOTES_VALUE.equals(getQuotations()) ? "\"" : "\'";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,17 @@ public static void testCompletionFor(XMLLanguageService xmlLanguageService, Stri
public static void testCompletionFor(XMLLanguageService xmlLanguageService, String value, String catalogPath,
Consumer<XMLLanguageService> customConfiguration, String fileURI, Integer expectedCount,
CompletionSettings completionSettings, CompletionItem... expectedItems) throws BadLocationException {
testCompletionFor(xmlLanguageService, value, catalogPath, customConfiguration, fileURI, expectedCount, completionSettings, new XMLFormattingOptions(4, true), expectedItems);
}

public static void testCompletionFor(XMLLanguageService xmlLanguageService, String value, String catalogPath,
Consumer<XMLLanguageService> customConfiguration, String fileURI, Integer expectedCount,
CompletionSettings completionSettings, XMLFormattingOptions formattingSettings, CompletionItem... expectedItems)
throws BadLocationException {
int offset = value.indexOf('|');
value = value.substring(0, offset) + value.substring(offset + 1);

TextDocument document = new TextDocument(value, fileURI != null ? fileURI : "test://test/test.html");
TextDocument document = new TextDocument(value, fileURI != null ? fileURI : "test://test/test.xml");
Position position = document.positionAt(offset);
DOMDocument htmlDoc = DOMParser.getInstance().parse(document, xmlLanguageService.getResolverExtensionManager());
xmlLanguageService.setDocumentProvider((uri) -> htmlDoc);
Expand All @@ -139,7 +146,7 @@ public static void testCompletionFor(XMLLanguageService xmlLanguageService, Stri
}

SharedSettings sharedSettings = new SharedSettings();
sharedSettings.setFormattingSettings(new XMLFormattingOptions(4, false));
sharedSettings.setFormattingSettings(formattingSettings);
sharedSettings.setCompletionSettings(completionSettings);

CompletionList list = xmlLanguageService.doComplete(htmlDoc, position, sharedSettings);
Expand Down
Loading

0 comments on commit 97c78af

Please sign in to comment.