From b4e78092db778700200eb857a1a61f3748dabf10 Mon Sep 17 00:00:00 2001 From: azerr Date: Thu, 20 Jun 2019 16:21:48 +0200 Subject: [PATCH] Add support for `textDocument/completion` for xs:element/@name / xs:extension/@base Fix #451 Signed-off-by: azerr --- .../java/org/eclipse/lsp4xml/dom/DOMAttr.java | 5 + .../org/eclipse/lsp4xml/dom/DOMDocument.java | 75 +++--- .../ContentModelCompletionParticipant.java | 40 ++-- .../contentmodel/utils/XMLGenerator.java | 6 +- .../FilePathCompletionParticipant.java | 15 +- .../prolog/PrologCompletionParticipant.java | 21 +- .../extensions/prolog/PrologModel.java | 41 ++-- .../lsp4xml/extensions/xsd/DataType.java | 129 +++++++++++ .../XSDCompletionParticipant.java | 88 ++++++- .../XSDDefinitionParticipant.java | 15 +- .../extensions/xsd/utils/XSDUtils.java | 145 +++++++++--- .../xsi/XSICompletionParticipant.java | 22 +- .../extensions/xsi/XSISchemaModel.java | 57 ++--- .../services/AttributeCompletionItem.java | 7 +- .../lsp4xml/services/CompletionRequest.java | 29 ++- .../lsp4xml/services/XMLCompletions.java | 214 +++++++++--------- .../CompletionParticipantAdapter.java | 11 +- .../extensions/ICompletionParticipant.java | 11 +- .../extensions/ICompletionRequest.java | 2 + .../settings/XMLFormattingOptions.java | 5 +- .../main/resources/schemas/xsd/datatypes.xml | 110 +++++++++ .../PrologCompletionExtensionsTest.java | 4 +- .../lsp4xml/extensions/xsd/DataTypeTest.java | 29 +++ .../xsd/XSDCompletionExtensionsTest.java | 98 +++++++- .../xsd/XSDDefinitionExtensionsTest.java | 73 +++++- .../xsi/XSICompletionExtensionsTest.java | 4 +- .../HTMLCompletionExtensionsTest.java | 14 +- 27 files changed, 932 insertions(+), 338 deletions(-) create mode 100644 org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/DataType.java create mode 100644 org.eclipse.lsp4xml/src/main/resources/schemas/xsd/datatypes.xml create mode 100644 org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/DataTypeTest.java diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMAttr.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMAttr.java index 9511935f4..fa6ae3d43 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMAttr.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMAttr.java @@ -60,6 +60,11 @@ public short getNodeType() { public DOMAttr getOwnerAttr() { return ownerAttr; } + + @Override + public DOMDocument getOwnerDocument() { + return ownerAttr.getOwnerDocument(); + } } public DOMAttr(String name, DOMNode ownerElement) { diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMDocument.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMDocument.java index 7f3fcb781..8f45b456e 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMDocument.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMDocument.java @@ -55,6 +55,7 @@ public class DOMDocument extends DOMNode implements Document { private boolean hasNamespaces; private Map externalSchemaLocation; private String schemaInstancePrefix; + private String schemaPrefix; private boolean hasExternalGrammar; private CancelChecker cancelChecker; @@ -64,11 +65,11 @@ public DOMDocument(TextDocument textDocument, URIResolverExtensionManager resolv this.resolverExtensionManager = resolverExtensionManager; resetGrammar(); } - + public void setCancelChecker(CancelChecker cancelChecker) { this.cancelChecker = cancelChecker; } - + public CancelChecker getCancelChecker() { return cancelChecker; } @@ -246,6 +247,7 @@ private synchronized void initializeReferencedSchema() { return; } schemaInstancePrefix = null; + schemaPrefix = null; // Search if document element root declares namespace with "xmlns". if (documentElement.hasAttributes()) { for (DOMAttr attr : documentElement.getAttributeNodes()) { @@ -254,19 +256,23 @@ private synchronized void initializeReferencedSchema() { if (attributeName.equals("xmlns") || attributeName.startsWith("xmlns:")) //$NON-NLS-1$ //$NON-NLS-2$ { hasNamespaces = true; - } - String attributeValue = documentElement.getAttribute(attributeName); - if (attributeValue != null && attributeValue.startsWith("http://www.w3.org/") //$NON-NLS-1$ - && attributeValue.endsWith("/XMLSchema-instance")) //$NON-NLS-1$ - { - schemaInstancePrefix = attributeName.equals("xmlns") ? "" : getUnprefixedName(attributeName); //$NON-NLS-1$ //$NON-NLS-2$ + String attributeValue = documentElement.getAttribute(attributeName); + if (attributeValue != null && attributeValue.startsWith("http://www.w3.org/")) { + if (attributeValue.endsWith("/XMLSchema-instance")) { + schemaInstancePrefix = attributeName.equals("xmlns") ? "" + : getUnprefixedName(attributeName); + } else if (attributeValue.endsWith("/XMLSchema")) { + schemaPrefix = attributeName.equals("xmlns") ? "" : getUnprefixedName(attributeName); + } + } } } } if (schemaInstancePrefix != null) { - // DOM document can declared xsi:noNamespaceSchemaLocation and xsi:schemaLocation both even it's not valid + // DOM document can declared xsi:noNamespaceSchemaLocation and + // xsi:schemaLocation both even it's not valid noNamespaceSchemaLocation = createNoNamespaceSchemaLocation(documentElement, schemaInstancePrefix); - schemaLocation = createSchemaLocation(documentElement, schemaInstancePrefix); + schemaLocation = createSchemaLocation(documentElement, schemaInstancePrefix); } } } @@ -788,21 +794,21 @@ public boolean isDTD() { } /** - * Returns true if 'offset' is within an internal DOCTYPE dtd. - * Else false. + * Returns true if 'offset' is within an internal DOCTYPE dtd. Else false. + * * @param offset - * @return + * @return */ public boolean isWithinInternalDTD(int offset) { DOMDocumentType doctype = this.getDoctype(); - if(doctype != null && doctype.internalSubset != null) { + if (doctype != null && doctype.internalSubset != null) { return offset > doctype.internalSubset.start && offset < doctype.internalSubset.end; } return false; } public Range getTrimmedRange(Range range) { - if(range != null) { + if (range != null) { return getTrimmedRange(range.getStart().getCharacter(), range.getEnd().getCharacter()); } return null; @@ -812,16 +818,16 @@ public Range getTrimmedRange(Range range) { public Range getTrimmedRange(int start, int end) { String text = getText(); char c = text.charAt(start); - while(Character.isWhitespace(c)) { + while (Character.isWhitespace(c)) { start++; c = text.charAt(start); } - if(start == end) { + if (start == end) { return null; } end--; c = text.charAt(end); - while(Character.isWhitespace(c)) { + while (Character.isWhitespace(c)) { end--; c = text.charAt(end); } @@ -847,36 +853,47 @@ public Collection findDTDAttrList(String elementName) { } /** - * Given a schema URI, this will return true if the given schemaURI - * matches the one defined in this DOMDocument(xml document). + * Given a schema URI, this will return true if the given schemaURI matches the + * one defined in this DOMDocument(xml document). * * It will check either xsi:schemaLocation or xsi:noNamespaceSchemaLocation. */ public boolean usesSchema(String xsdURI) { - String rootURI = URI.create(textDocument.getUri()).getPath(); //remove "file://" if exists - if(rootURI == null || xsdURI == null) { + String rootURI = URI.create(textDocument.getUri()).getPath(); // remove "file://" if exists + if (rootURI == null || xsdURI == null) { return false; } - Path rootPath = Paths.get(rootURI).getParent(); + Path rootPath = Paths.get(rootURI).getParent(); xsdURI = URI.create(xsdURI).getPath(); Path xsdPath = Paths.get(xsdURI); - if(schemaLocation != null) { - return schemaLocation.usesSchema(rootPath ,xsdPath); - } - else if (noNamespaceSchemaLocation != null) { + if (schemaLocation != null) { + return schemaLocation.usesSchema(rootPath, xsdPath); + } else if (noNamespaceSchemaLocation != null) { String noNamespaceURI = URI.create(noNamespaceSchemaLocation.getLocation()).getPath(); Path noNamespacePath = Paths.get(noNamespaceURI).normalize(); - if(!noNamespacePath.isAbsolute()) { + if (!noNamespacePath.isAbsolute()) { noNamespacePath = rootPath.resolve(noNamespacePath); } return xsdPath.equals(noNamespacePath); } return false; } - + + /** + * Returns the XML Schema prefix (ex : 'xs' for + * xmlns:xs="http://www.w3.org/2001/XMLSchema") + * + * @return the XML Schema prefix (ex : 'xs' for + * xmlns:xs="http://www.w3.org/2001/XMLSchema") + */ + public String getSchemaPrefix() { + initializeReferencedSchemaIfNeeded(); + return schemaPrefix; + } + private void checkCanceled() { if (cancelChecker != null) { cancelChecker.checkCanceled(); diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/ContentModelCompletionParticipant.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/ContentModelCompletionParticipant.java index d4c01cbb2..0041f9b2f 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/ContentModelCompletionParticipant.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/participants/ContentModelCompletionParticipant.java @@ -29,7 +29,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; +import org.eclipse.lsp4xml.settings.XMLFormattingOptions; import org.eclipse.lsp4xml.uriresolver.CacheResourceDownloadingException; /** @@ -43,7 +43,6 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response) try { DOMDocument document = request.getXMLDocument(); String schemaURI; - String fileURI; ContentModelManager contentModelManager = request.getComponent(ContentModelManager.class); DOMElement parentElement = request.getParentElement(); CMDocument cmDocument; @@ -54,18 +53,19 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response) cmDocument = contentModelManager.findCMDocument(document, null); if (cmDocument != null) { schemaURI = cmDocument.getURI(); - fillWithChildrenElementDeclaration(null, cmDocument.getElements(), null, false, request, response, schemaURI); + 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, @@ -100,8 +100,8 @@ public void onTagOpen(ICompletionRequest request, ICompletionResponse response) } private void fillWithChildrenElementDeclaration(DOMElement element, Collection cmElements, - String p, boolean forceUseOfPrefix, ICompletionRequest request, ICompletionResponse response, String schemaURI) - throws BadLocationException { + String p, boolean forceUseOfPrefix, ICompletionRequest request, ICompletionResponse response, + String schemaURI) throws BadLocationException { XMLGenerator generator = request.getXMLGenerator(); for (CMElementDeclaration child : cmElements) { String prefix = forceUseOfPrefix ? p : (element != null ? element.getPrefix(child.getNamespace()) : null); @@ -121,24 +121,26 @@ private void fillWithChildrenElementDeclaration(DOMElement element, Collection { CompletionItem item = new CompletionItem(); item.setLabel(value); + String insertText = request.getInsertAttrValue(value); + item.setLabel(value); item.setKind(CompletionItemKind.Value); - response.addCompletionAttribute(item); + item.setFilterText(insertText); + item.setTextEdit(new TextEdit(fullRange, insertText)); + response.addCompletionItem(item); + }); } } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/utils/XMLGenerator.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/utils/XMLGenerator.java index 32d3a513b..450c4c550 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/utils/XMLGenerator.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/contentmodel/utils/XMLGenerator.java @@ -180,12 +180,12 @@ public static String generateAttributeValue(String defaultValue, Collection enumerationValues, - boolean canSupportSnippets, int snippetIndex, boolean withQuote, SharedSettings settings) { + boolean canSupportSnippets, int snippetIndex, boolean withQuote, XMLFormattingOptions formattingSettings) { StringBuilder value = new StringBuilder(); String quotation = "\""; if (withQuote) { - if (settings != null) { - quotation = settings.formattingSettings.getQuotationAsString(); + if (formattingSettings != null) { + quotation = formattingSettings.getQuotationAsString(); } value.append("=" + quotation); } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/general/completion/FilePathCompletionParticipant.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/general/completion/FilePathCompletionParticipant.java index a11e6a048..2bc6bebb3 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/general/completion/FilePathCompletionParticipant.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/general/completion/FilePathCompletionParticipant.java @@ -31,7 +31,6 @@ 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; import org.eclipse.lsp4xml.utils.CompletionSortTextHelper; import org.eclipse.lsp4xml.utils.FilesUtils; import org.eclipse.lsp4xml.utils.StringUtils; @@ -44,15 +43,15 @@ public class FilePathCompletionParticipant extends CompletionParticipantAdapter public static final String FILE_SCHEME = "file"; @Override - public void onAttributeValue(String valuePrefix, Range fullRange, boolean addQuotes, ICompletionRequest request, - ICompletionResponse response, SharedSettings settings) throws Exception { + public void onAttributeValue(String valuePrefix, + ICompletionRequest request, ICompletionResponse response) throws Exception { DOMDocument xmlDocument = request.getXMLDocument(); String text = xmlDocument.getText(); - + Range fullRange = request.getReplaceRange(); + // Get full attribute value range - // + 1 since it includes the quotations - int documentStartOffset = xmlDocument.offsetAt(fullRange.getStart()) + 1; + int documentStartOffset = xmlDocument.offsetAt(fullRange.getStart()); String fullAttributeValue = valuePrefix; if (isEmpty(fullAttributeValue)) { @@ -234,8 +233,4 @@ private void createFilePathCompletionItem(File f, Range replaceRange, ICompletio response.addCompletionItem(item); } - - - - } \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/prolog/PrologCompletionParticipant.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/prolog/PrologCompletionParticipant.java index 678364fca..a532d7327 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/prolog/PrologCompletionParticipant.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/prolog/PrologCompletionParticipant.java @@ -11,28 +11,27 @@ package org.eclipse.lsp4xml.extensions.prolog; -import org.eclipse.lsp4j.Range; 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; /** * PrologCompletionParticipant */ public class PrologCompletionParticipant extends CompletionParticipantAdapter { - + @Override - public void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request, - ICompletionResponse response, SharedSettings settings) throws Exception { - PrologModel.computeAttributeNameCompletionResponses(request, response, fullRange, request.getXMLDocument(), - settings); + public void onAttributeName(boolean generateValue, ICompletionRequest request, ICompletionResponse response) + throws Exception { + PrologModel.computeAttributeNameCompletionResponses(request, response, request.getReplaceRange(), + request.getXMLDocument(), request.getFormattingSettings()); } @Override - public void onAttributeValue(String valuePrefix, Range fullRange, boolean addQuotes, ICompletionRequest request, - ICompletionResponse response, SharedSettings settings) throws Exception { - PrologModel.computeValueCompletionResponses(request, response, fullRange, request.getXMLDocument(), settings); + public void onAttributeValue(String valuePrefix, ICompletionRequest request, ICompletionResponse response) + throws Exception { + PrologModel.computeValueCompletionResponses(request, response, request.getReplaceRange(), + request.getXMLDocument(), request.getFormattingSettings()); } - + } \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/prolog/PrologModel.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/prolog/PrologModel.java index 14b3851de..6eab57545 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/prolog/PrologModel.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/prolog/PrologModel.java @@ -37,6 +37,7 @@ import org.eclipse.lsp4xml.services.extensions.ICompletionRequest; import org.eclipse.lsp4xml.services.extensions.ICompletionResponse; import org.eclipse.lsp4xml.settings.SharedSettings; +import org.eclipse.lsp4xml.settings.XMLFormattingOptions; import org.eclipse.lsp4xml.utils.StringUtils; /** @@ -119,9 +120,9 @@ public static void computePrologCompletionResponses(int tokenEndOffset, String t private static void createCompletionItem(String attrName, boolean canSupportSnippet, boolean generateValue, Range editRange, String defaultValue, Collection enumerationValues, String documentation, - ICompletionResponse response, SharedSettings settings){ + ICompletionResponse response, XMLFormattingOptions formattingsSettings){ CompletionItem item = new AttributeCompletionItem(attrName, canSupportSnippet, editRange, generateValue, - defaultValue, enumerationValues, settings); + defaultValue, enumerationValues, formattingsSettings); MarkupContent markup = new MarkupContent(); markup.setKind(MarkupKind.MARKDOWN); @@ -131,7 +132,7 @@ private static void createCompletionItem(String attrName, boolean canSupportSnip } public static void computeAttributeNameCompletionResponses(ICompletionRequest request, - ICompletionResponse response, Range editRange, DOMDocument document, SharedSettings settings) + ICompletionResponse response, Range editRange, DOMDocument document, XMLFormattingOptions formattingsSettings) throws BadLocationException { if (document.hasProlog() == false) { @@ -145,21 +146,21 @@ public static void computeAttributeNameCompletionResponses(ICompletionRequest re boolean isSnippetsSupported = request.getCompletionSettings().isCompletionSnippetsSupported(); if(!prolog.hasAttribute(VERSION_NAME)) { - createCompletionItem(VERSION_NAME, isSnippetsSupported, true, editRange, VERSION_1, VERSION_VALUES, null, response, settings); + createCompletionItem(VERSION_NAME, isSnippetsSupported, true, editRange, VERSION_1, VERSION_VALUES, null, response, formattingsSettings); } if(!prolog.hasAttribute(ENCODING_NAME)) { - createCompletionItem(ENCODING_NAME, isSnippetsSupported, true, editRange, UTF_8, ENCODING_VALUES, null, response, settings); + createCompletionItem(ENCODING_NAME, isSnippetsSupported, true, editRange, UTF_8, ENCODING_VALUES, null, response, formattingsSettings); } if(!prolog.hasAttribute(STANDALONE_NAME)) { - createCompletionItem(STANDALONE_NAME, isSnippetsSupported, true, editRange, YES, STANDALONE_VALUES, null, response, settings); + createCompletionItem(STANDALONE_NAME, isSnippetsSupported, true, editRange, YES, STANDALONE_VALUES, null, response, formattingsSettings); } } public static void computeValueCompletionResponses(ICompletionRequest request, - ICompletionResponse response, Range editRange, DOMDocument document, SharedSettings settings) throws BadLocationException { + ICompletionResponse response, Range editRange, DOMDocument document, XMLFormattingOptions formattingSettings) throws BadLocationException { if (document.hasProlog() == false) { return; @@ -172,40 +173,28 @@ public static void computeValueCompletionResponses(ICompletionRequest request, DOMAttr attr = prolog.findAttrAt(offset); if(VERSION_NAME.equals(attr.getName())) { // version - createCompletionItemsForValues(VERSION_VALUES, editRange, document, response, settings); + createCompletionItemsForValues(VERSION_VALUES, editRange, document, request, response); } else if(ENCODING_NAME.equals(attr.getName())) { // encoding - createCompletionItemsForValues(ENCODING_VALUES, editRange, document, response, settings); + createCompletionItemsForValues(ENCODING_VALUES, editRange, document, request, response); } else if(STANDALONE_NAME.equals(attr.getName())) { - createCompletionItemsForValues(STANDALONE_VALUES, editRange, document, response, settings); + createCompletionItemsForValues(STANDALONE_VALUES, editRange, document, request, response); } } - private static void createCompletionItemsForValues(Collection enumerationValues, Range editRange, DOMDocument document, ICompletionResponse response, SharedSettings settings) { - - // Figure out which quotation to use for filter text - String settingQuotation = settings.formattingSettings.getQuotationAsString(); - String currentQuotation = settingQuotation; - try { - int start = document.offsetAt(editRange.getStart()); - int end = document.offsetAt(editRange.getEnd()); - if(start != end) { - currentQuotation = String.valueOf(document.getText().charAt(start)); - } - } catch (BadLocationException e) { - } + private static void createCompletionItemsForValues(Collection enumerationValues, Range editRange, DOMDocument document, ICompletionRequest request, ICompletionResponse response) { int sortText = 1; CompletionItem item; for (String option : enumerationValues) { - String optionWithQuotes = settingQuotation + option + settingQuotation; + String insertText = request.getInsertAttrValue(option); item = new CompletionItem(); item.setLabel(option); - item.setFilterText(currentQuotation + option + currentQuotation); + item.setFilterText(insertText); item.setKind(CompletionItemKind.Enum); - item.setTextEdit(new TextEdit(editRange, optionWithQuotes)); + item.setTextEdit(new TextEdit(editRange, insertText)); item.setSortText(Integer.toString(sortText)); sortText++; response.addCompletionItem(item); diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/DataType.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/DataType.java new file mode 100644 index 000000000..a54e18fa5 --- /dev/null +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/DataType.java @@ -0,0 +1,129 @@ +package org.eclipse.lsp4xml.extensions.xsd; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.eclipse.lsp4xml.dom.DOMAttr; +import org.eclipse.lsp4xml.dom.DOMElement; +import org.eclipse.lsp4xml.extensions.xsd.utils.XSDUtils; +import org.eclipse.lsp4xml.utils.StringUtils; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +public class DataType { + + private static String lineSeparator = System.lineSeparator(); + + private static final Map dataTypes; + + static { + dataTypes = loadDataTypes(); + } + + public static DataType getDataType(String name) { + return dataTypes.get(name); + } + + public static Collection getDataTypes() { + return dataTypes.values(); + } + + private final String name; + + private final String url; + + private String documentation; + + public DataType(String name, String url) { + this.name = name; + this.url = url; + } + + public String getName() { + return name; + } + + public String getUrl() { + return url; + } + + public String getDocumentation() { + if (documentation == null) { + documentation = createDocumentation(); + } + return documentation; + } + + private String createDocumentation() { + StringBuilder doc = new StringBuilder(); + doc.append("**"); + doc.append(getName()); + doc.append("**"); + if (!StringUtils.isEmpty(url)) { + doc.append(lineSeparator); + doc.append("See [documentation]("); + doc.append(getUrl()); + doc.append(") for more informations."); + } + return doc.toString(); + } + + public static String getDocumentation(DOMAttr attr) { + StringBuilder doc = new StringBuilder(); + doc.append("**"); + doc.append(attr.getValue()); + doc.append("**"); + DOMElement element = attr.getOwnerElement(); + if (XSDUtils.isComplexType(element)) { + doc.append(lineSeparator); + doc.append(" - Type: `Complex Type` "); + } else if (XSDUtils.isSimpleType(element)) { + doc.append(lineSeparator); + doc.append(" - Type: `Simple Type` "); + } + return doc.toString(); + } + + private static Map loadDataTypes() { + try { + SAXParserFactory factory = SAXParserFactory.newInstance(); + SAXParser saxParser = factory.newSAXParser(); + DataTypeHandler handler = new DataTypeHandler(); + saxParser.parse(new InputSource(DataType.class.getResourceAsStream("/schemas/xsd/datatypes.xml")), handler); + return handler.getDataTypes(); + } catch (Exception e) { + return null; + } + } + + private static class DataTypeHandler extends DefaultHandler { + + private final Map dataTypes; + + public DataTypeHandler() { + dataTypes = new HashMap<>(); + } + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) + throws SAXException { + if ("datatype".contentEquals(qName)) { + DataType dataType = new DataType(attributes.getValue("name"), attributes.getValue("url")); + dataTypes.put(dataType.getName(), dataType); + } + super.startElement(uri, localName, qName, attributes); + } + + public Map getDataTypes() { + return dataTypes; + } + + } + +} diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDCompletionParticipant.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDCompletionParticipant.java index 8764b216d..6aac47b69 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDCompletionParticipant.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDCompletionParticipant.java @@ -10,21 +10,99 @@ */ package org.eclipse.lsp4xml.extensions.xsd.participants; +import org.eclipse.lsp4j.CompletionItem; +import org.eclipse.lsp4j.CompletionItemKind; +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.DOMNode; +import org.eclipse.lsp4xml.extensions.xsd.DataType; +import org.eclipse.lsp4xml.extensions.xsd.utils.XSDUtils; +import org.eclipse.lsp4xml.extensions.xsd.utils.XSDUtils.BindingType; 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; +import org.eclipse.lsp4xml.utils.DOMUtils; /** - * XSD completion for xs: + * XSD completion for + * + *
    + *
  • xs:/@type -> xs:complexType/@name
  • + *
  • xs:/@base -> xs:complexType/@name
  • + *
* */ public class XSDCompletionParticipant extends CompletionParticipantAdapter { @Override - public void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request, - ICompletionResponse response, SharedSettings settings) throws Exception { - // TODO: manage compeltion for types declared in XML Schema xsd + public void onAttributeValue(String valuePrefix, ICompletionRequest request, ICompletionResponse response) + throws Exception { + DOMNode node = request.getNode(); + DOMDocument document = node.getOwnerDocument(); + if (!DOMUtils.isXSD(document)) { + return; + } + Range fullRange = request.getReplaceRange(); + DOMAttr originAttr = node.findAttrAt(request.getOffset()); + BindingType bindingType = XSDUtils.getBindingType(originAttr); + if (bindingType != BindingType.NONE) { + // Completion on + // - @type (ex : xs:element/@type) + // - @base (ex : xs:extension/@base) + // bound to complextTypes/@name + XSDUtils.collectXSTypes(originAttr, bindingType, false, (targetNamespacePrefix, targetAttr) -> { + CompletionItem item = new CompletionItem(); + item.setDocumentation(new MarkupContent(MarkupKind.MARKDOWN, DataType.getDocumentation(targetAttr))); + String value = createComplexTypeValue(targetAttr, targetNamespacePrefix); + String insertText = request.getInsertAttrValue(value); + item.setLabel(value); + item.setKind(CompletionItemKind.Value); + item.setFilterText(insertText); + item.setTextEdit(new TextEdit(fullRange, insertText)); + response.addCompletionItem(item); + }); + if (bindingType.isSimple()) { + // Completion on @type (ex : xs:element/@type) bound to Built-in types (ex: + // xs:string) -> + // https://www.w3.org/TR/xmlschema11-2/#built-in-datatypes + String prefix = document.getSchemaPrefix(); + DataType.getDataTypes().forEach(dataType -> { + CompletionItem item = new CompletionItem(); + item.setDocumentation(new MarkupContent(MarkupKind.MARKDOWN, dataType.getDocumentation())); + String value = createDatatypeValue(dataType, prefix); + String insertText = request.getInsertAttrValue(value); + item.setLabel(value); + item.setKind(CompletionItemKind.Value); + item.setFilterText(insertText); + item.setTextEdit(new TextEdit(fullRange, insertText)); + response.addCompletionItem(item); + }); + } + } } + + private static String createComplexTypeValue(DOMAttr targetAttr, String targetNamespacePrefix) { + StringBuilder value = new StringBuilder(); + if (targetNamespacePrefix != null) { + value.append(targetNamespacePrefix); + value.append(":"); + } + value.append(targetAttr.getValue()); + return value.toString(); + } + + private static String createDatatypeValue(DataType dataType, String prefix) { + StringBuilder value = new StringBuilder(); + if (prefix != null) { + value.append(prefix); + value.append(":"); + } + value.append(dataType.getName()); + return value.toString(); + } + } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDDefinitionParticipant.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDDefinitionParticipant.java index c52bb2ba0..a2683ac8f 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDDefinitionParticipant.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/participants/XSDDefinitionParticipant.java @@ -18,16 +18,18 @@ import org.eclipse.lsp4xml.dom.DOMDocument; import org.eclipse.lsp4xml.dom.DOMNode; import org.eclipse.lsp4xml.extensions.xsd.utils.XSDUtils; +import org.eclipse.lsp4xml.extensions.xsd.utils.XSDUtils.BindingType; import org.eclipse.lsp4xml.services.extensions.AbstractDefinitionParticipant; import org.eclipse.lsp4xml.utils.DOMUtils; import org.eclipse.lsp4xml.utils.XMLPositionUtility; /** - * XSD definition which manages teh following definition: + * XSD definition which manages the following definition: * *
    - *
  • xs:element/@type -> xs:complexType/@name
  • * + *
  • xs:element/@type -> xs:complexType/@name
  • *
  • xs:extension/@base -> xs:complexType/@name
  • + *
  • xs:element/@ref -> xs:complexType/@name
  • *
* * @author Angelo ZERR @@ -45,10 +47,13 @@ protected void findDefinition(DOMNode node, Position position, int offset, List< CancelChecker cancelChecker) { // - xs:element/@type -> xs:complexType/@name // - xs:extension/@base -> xs:complexType/@name + // - xs:element/@ref -> xs:complexType/@name DOMAttr attr = node.findAttrAt(offset); - if (XSDUtils.isBoundToComplexTypes(attr)) { - XSDUtils.collectComplexTypes(attr, true, (targetNamespacePrefix, targetAttr) -> { - LocationLink location = XMLPositionUtility.createLocationLink(attr, targetAttr); + BindingType bindingType = XSDUtils.getBindingType(attr); + if (bindingType != BindingType.NONE) { + XSDUtils.collectXSTypes(attr, bindingType, true, (targetNamespacePrefix, targetAttr) -> { + LocationLink location = XMLPositionUtility.createLocationLink(attr.getNodeAttrValue(), + targetAttr.getNodeAttrValue()); locations.add(location); }); } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/utils/XSDUtils.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/utils/XSDUtils.java index 466a8f7a2..7979cf79d 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/utils/XSDUtils.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsd/utils/XSDUtils.java @@ -30,39 +30,94 @@ public class XSDUtils { /** - * Returns true if the given attribute is bound to complexType/@name attribute - * and false otherwise. + * Binding type of xs attribute. + * + */ + public enum BindingType { + + COMPLEX, SIMPLE, COMPLEX_AND_SIMPLE, NONE, REF, ELEMENT; + + public boolean isSimple() { + return BindingType.COMPLEX_AND_SIMPLE.equals(this) || BindingType.SIMPLE.equals(this); + } + + public boolean isComplex() { + return BindingType.COMPLEX_AND_SIMPLE.equals(this) || BindingType.COMPLEX.equals(this); + } + } + + /** + * Returns the binding type of the given attribute. * * @param attr the attribute - * @return true if the given attribute is bound to complexType/@name attribute - * and false otherwise. + * @return the binding type of the given attribute. */ - public static boolean isBoundToComplexTypes(DOMAttr attr) { - if (attr == null) { - return false; + public static BindingType getBindingType(DOMAttr attr) { + String name = attr.getName(); + if ("type".equals(name)) { + if ("attribute".equals(attr.getOwnerElement().getLocalName())) { + // - xs:complexType/@name - return true; + if ("base".equals(name)) { + // - bounded type is complex + return BindingType.COMPLEX; + } + if (parent.getLocalName().equals("simpleContent") || isSimpleType(parent)) { + // parent element is simpleContent or simpleType -> bounded type is simple + return BindingType.SIMPLE; + } + } + return BindingType.NONE; } - if ("base".equals(attr.getName()) && "extension".equals(attr.getOwnerElement().getLocalName())) { - // - xs:extension/@base -> xs:complexType/@name - return true; + if ("ref".equals(name)) { + // - collector) { + if (bindingType == BindingType.NONE) { + return; + } + DOMDocument document = originAttr.getOwnerDocument(); DOMElement documentElement = document != null ? document.getDocumentElement() : null; if (documentElement == null) { @@ -80,15 +135,18 @@ public static void collectComplexTypes(DOMAttr originAttr, boolean matchAttr, // http://camel.apache.org/schema/spring String targetNamespacePrefix = documentElement.getPrefix(targetNamespace); // -> tns - String complexTypeName = attrValue; - String prefix = null; - int index = attrValue.indexOf(":"); - if (index != -1) { - prefix = attrValue.substring(0, index); - if (!Objects.equal(prefix, targetNamespacePrefix)) { - return; + String matchAttrName = null; + if (matchAttr) { + matchAttrName = attrValue; + String prefix = null; + int index = attrValue.indexOf(":"); + if (index != -1) { + prefix = attrValue.substring(0, index); + if (!Objects.equal(prefix, targetNamespacePrefix)) { + return; + } + matchAttrName = attrValue.substring(index + 1, attrValue.length()); } - complexTypeName = attrValue.substring(index + 1, attrValue.length()); } // Loop for element complexType. @@ -96,12 +154,12 @@ public static void collectComplexTypes(DOMAttr originAttr, boolean matchAttr, for (int i = 0; i < children.getLength(); i++) { Node node = children.item(i); if (node.getNodeType() == Node.ELEMENT_NODE) { - Element element = (Element) node; - if ("complexType".equals(element.getLocalName())) { - // node is a complexType element - // get the attribute complexType/@name - DOMAttr targetAttr = (DOMAttr) element.getAttributeNode("name"); - if (!matchAttr || complexTypeName.equals(targetAttr.getValue())) { + Element targetElement = (Element) node; + if (canCollectElement(originAttr, targetElement, bindingType)) { + // node is a xs:complexType, xs:simpleType element, xsl:element, xs:group which + // matches the binding type of the originAttr + DOMAttr targetAttr = (DOMAttr) targetElement.getAttributeNode("name"); + if (targetAttr != null && (!matchAttr || matchAttrName.equals(targetAttr.getValue()))) { collector.accept(targetNamespacePrefix, targetAttr); } } @@ -109,4 +167,27 @@ public static void collectComplexTypes(DOMAttr originAttr, boolean matchAttr, } } + private static boolean canCollectElement(DOMAttr originAttr, Element targetElement, BindingType bindingType) { + if (isComplexType(targetElement)) { + return bindingType.isComplex(); + } else if (isSimpleType(targetElement)) { + return bindingType.isSimple(); + } else if (bindingType == BindingType.REF) { + // - xs:element/@name attributes if originAttr is xs:element/@ref + // - xs:group/@name attributes if originAttr is xs:group/@ref + return (originAttr.getOwnerElement().getLocalName().equals(targetElement.getLocalName())); + } else if (bindingType == BindingType.ELEMENT) { + return "element".equals(targetElement.getLocalName()); + } + return false; + } + + public static boolean isComplexType(Element element) { + return "complexType".equals(element.getLocalName()); + } + + public static boolean isSimpleType(Element element) { + return "simpleType".equals(element.getLocalName()); + } + } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsi/XSICompletionParticipant.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsi/XSICompletionParticipant.java index a8a530fbb..06ab0f0a5 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsi/XSICompletionParticipant.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsi/XSICompletionParticipant.java @@ -8,32 +8,28 @@ * Contributors: * Red Hat Inc. - initial API and implementation */ - package org.eclipse.lsp4xml.extensions.xsi; -import org.eclipse.lsp4j.Range; 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; /** * XSICompletionParticipant */ public class XSICompletionParticipant extends CompletionParticipantAdapter { - + @Override - public void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request, - ICompletionResponse response, SharedSettings settings) throws Exception { - XSISchemaModel.computeCompletionResponses(request, response, fullRange, request.getXMLDocument(), - generateValue, settings); - + public void onAttributeName(boolean generateValue, ICompletionRequest request, ICompletionResponse response) + throws Exception { + XSISchemaModel.computeCompletionResponses(request, response, request.getXMLDocument(), generateValue, + request.getFormattingSettings()); } @Override - public void onAttributeValue(String valuePrefix, Range fullRange, boolean addQuotes, ICompletionRequest request, - ICompletionResponse response, SharedSettings settings) throws Exception { - XSISchemaModel.computeValueCompletionResponses(request, response, fullRange, request.getXMLDocument(), settings); + public void onAttributeValue(String valuePrefix, ICompletionRequest request, ICompletionResponse response) + throws Exception { + XSISchemaModel.computeValueCompletionResponses(request, response, request.getXMLDocument()); } - + } \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsi/XSISchemaModel.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsi/XSISchemaModel.java index 328b60aca..aae4b10f8 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsi/XSISchemaModel.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/extensions/xsi/XSISchemaModel.java @@ -30,6 +30,7 @@ import org.eclipse.lsp4xml.services.extensions.ICompletionResponse; import org.eclipse.lsp4xml.services.extensions.IHoverRequest; import org.eclipse.lsp4xml.settings.SharedSettings; +import org.eclipse.lsp4xml.settings.XMLFormattingOptions; import org.eclipse.lsp4xml.utils.StringUtils; /** @@ -65,8 +66,8 @@ public class XSISchemaModel { 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, SharedSettings settings) throws BadLocationException { - + ICompletionResponse response, DOMDocument document, boolean generateValue, XMLFormattingOptions formattingSettings) throws BadLocationException { + Range editRange = request.getReplaceRange(); DOMElement rootElement = document.getDocumentElement(); int offset = document.offsetAt(editRange.getStart()); if(rootElement == null || offset <= rootElement.getStart() || offset >= rootElement.getEnd()) { @@ -88,10 +89,10 @@ public static void computeCompletionResponses(ICompletionRequest request, boolean isSnippetsSupported = request.getCompletionSettings().isCompletionSnippetsSupported(); if(inRootElement) { if(!hasAttribute(elementAtOffset, "xmlns") && !response.hasAttribute("xmlns")) { // "xmlns" completion - createCompletionItem("xmlns", isSnippetsSupported, generateValue, editRange, null, null, null, response, settings); + createCompletionItem("xmlns", isSnippetsSupported, generateValue, editRange, null, null, null, response, formattingSettings); } if(document.hasSchemaInstancePrefix() == false) { // "xmlns:xsi" completion - createCompletionItem("xmlns:xsi", isSnippetsSupported, generateValue, editRange, XSI_WEBSITE, null, XSI_DOC, response, settings); + createCompletionItem("xmlns:xsi", isSnippetsSupported, generateValue, editRange, XSI_WEBSITE, null, XSI_DOC, response, formattingSettings); return;// All the following completion cases dont exist, so return. } } @@ -111,7 +112,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, settings); + StringUtils.TRUE_FALSE_ARRAY, documentation, response, formattingSettings); } //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. @@ -120,7 +121,7 @@ public static void computeCompletionResponses(ICompletionRequest request, if(!hasAttribute(elementAtOffset, actualPrefix, "type")) { documentation = TYPE_DOC; name = actualPrefix + ":type"; - createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation, response, settings); + createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation, response, formattingSettings); } if(inRootElement) { @@ -129,21 +130,21 @@ public static void computeCompletionResponses(ICompletionRequest request, //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); + createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation, response, formattingSettings); } if(!noNamespaceSchemaLocationExists) { documentation = NO_NAMESPACE_SCHEMA_LOCATION_DOC; name = actualPrefix + ":noNamespaceSchemaLocation"; - createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation, response, settings); + createCompletionItem(name, isSnippetsSupported, generateValue, editRange, null, null, documentation, response, formattingSettings); } } } private static void createCompletionItem(String attrName, boolean canSupportSnippet, boolean generateValue, Range editRange, String defaultValue, Collection enumerationValues, String documentation, - ICompletionResponse response, SharedSettings settings){ + ICompletionResponse response, XMLFormattingOptions formattingSettings){ CompletionItem item = new AttributeCompletionItem(attrName, canSupportSnippet, editRange, generateValue, - defaultValue, enumerationValues, settings); + defaultValue, enumerationValues, formattingSettings); MarkupContent markup = new MarkupContent(); markup.setKind(MarkupKind.MARKDOWN); markup.setValue(StringUtils.getDefaultString(documentation)); @@ -152,10 +153,10 @@ private static void createCompletionItem(String attrName, boolean canSupportSnip } public static void computeValueCompletionResponses(ICompletionRequest request, - ICompletionResponse response, Range editRange, DOMDocument document, SharedSettings settings) throws BadLocationException { - + ICompletionResponse response, DOMDocument document) throws BadLocationException { + Range editRange = request.getReplaceRange(); int offset = document.offsetAt(editRange.getStart()); - DOMNode nodeAtOffset = document.findNodeAt(offset); + DOMNode nodeAtOffset = request.getNode(); String actualPrefix = document.getSchemaInstancePrefix(); DOMAttr attrAtOffset = nodeAtOffset.findAttrAt(offset); @@ -163,42 +164,30 @@ public static void computeValueCompletionResponses(ICompletionRequest request, if(attrName != null) { if(actualPrefix != null && attrName.equals(actualPrefix + ":nil")) { // Value completion for 'nil' attribute - createCompletionItemsForValues(StringUtils.TRUE_FALSE_ARRAY, editRange, document, response, settings); + createCompletionItemsForValues(StringUtils.TRUE_FALSE_ARRAY, document, request, response); } else if(document.getDocumentElement() != null && document.getDocumentElement().equals(nodeAtOffset)) { // if in the root element if(attrName.equals("xmlns:xsi")) { - createSingleCompletionItemForValue(XSI_WEBSITE, editRange, document, response, settings); + createSingleCompletionItemForValue(XSI_WEBSITE, document, request, response); } } } } - private static void createSingleCompletionItemForValue(String value, Range editRange, DOMDocument document, ICompletionResponse response, SharedSettings settings) { - createCompletionItemsForValues(Arrays.asList(value), editRange, document, response, settings); + private static void createSingleCompletionItemForValue(String value, DOMDocument document, ICompletionRequest request, ICompletionResponse response) { + createCompletionItemsForValues(Arrays.asList(value), document, request, response); } - private static void createCompletionItemsForValues(Collection enumerationValues, Range editRange, DOMDocument document, ICompletionResponse response, SharedSettings settings) { - - // Figure out which quotation to use for filter text - String settingQuotation = settings.formattingSettings.getQuotationAsString(); - String currentQuotation = settingQuotation; - try { - int start = document.offsetAt(editRange.getStart()); - int end = document.offsetAt(editRange.getEnd()); - if(start != end) { - currentQuotation = String.valueOf(document.getText().charAt(start)); - } - } catch (BadLocationException e) { - } - + private static void createCompletionItemsForValues(Collection enumerationValues, DOMDocument document, ICompletionRequest request, ICompletionResponse response) { + Range editRange = request.getReplaceRange(); CompletionItem item; for (String option : enumerationValues) { - String optionWithQuotes = settingQuotation + option + settingQuotation; item = new CompletionItem(); + String insertText = request.getInsertAttrValue(option); item.setLabel(option); - item.setFilterText(currentQuotation + option + currentQuotation); + item.setFilterText(insertText); item.setKind(CompletionItemKind.Enum); - item.setTextEdit(new TextEdit(editRange, optionWithQuotes)); + item.setTextEdit(new TextEdit(editRange, insertText)); response.addCompletionItem(item); } } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/AttributeCompletionItem.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/AttributeCompletionItem.java index 3a55bd929..6216ee387 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/AttributeCompletionItem.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/AttributeCompletionItem.java @@ -18,7 +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; +import org.eclipse.lsp4xml.settings.XMLFormattingOptions; public class AttributeCompletionItem extends CompletionItem { @@ -33,9 +33,10 @@ public class AttributeCompletionItem extends CompletionItem { * otherwise. * @param defaultValue the default value of attribute. * @param enumerationValues the enumeration values of attribute. + * @param formattingSettings formatting settings */ public AttributeCompletionItem(String attrName, boolean canSupportSnippets, Range fullRange, boolean generateValue, - String defaultValue, Collection enumerationValues, SharedSettings settings) { + String defaultValue, Collection enumerationValues, XMLFormattingOptions formattingSettings) { super.setLabel(attrName); super.setKind(CompletionItemKind.Unit); super.setFilterText(attrName); @@ -43,7 +44,7 @@ public AttributeCompletionItem(String attrName, boolean canSupportSnippets, Rang if (generateValue) { // Generate attribute value content String attributeValue = XMLGenerator.generateAttributeValue(defaultValue, enumerationValues, - canSupportSnippets, 1, true, settings); + canSupportSnippets, 1, true, formattingSettings); attributeContent.append(attributeValue); } super.setTextEdit(new TextEdit(fullRange, attributeContent.toString())); diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/CompletionRequest.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/CompletionRequest.java index 3954e1291..8b338464d 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/CompletionRequest.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/CompletionRequest.java @@ -21,6 +21,7 @@ import org.eclipse.lsp4xml.services.extensions.XMLExtensionsRegistry; import org.eclipse.lsp4xml.settings.SharedSettings; import org.eclipse.lsp4xml.settings.XMLFormattingOptions; +import org.eclipse.lsp4xml.utils.StringUtils; /** * Completion request implementation. @@ -40,8 +41,10 @@ class CompletionRequest extends AbstractPositionRequest implements ICompletionRe private boolean hasOpenBracket; - public CompletionRequest(DOMDocument xmlDocument, Position position, SharedSettings settings, XMLExtensionsRegistry extensionsRegistry) - throws BadLocationException { + private boolean addQuotes; + + public CompletionRequest(DOMDocument xmlDocument, Position position, SharedSettings settings, + XMLExtensionsRegistry extensionsRegistry) throws BadLocationException { super(xmlDocument, position); this.formattingSettings = settings.formattingSettings; this.completionSettings = settings.completionSettings; @@ -97,4 +100,26 @@ public void setHasOpenBracket(boolean hasOpenBracket) { public T getComponent(Class clazz) { return extensionsRegistry.getComponent(clazz); } + + public void setAddQuotes(boolean addQuotes) { + this.addQuotes = addQuotes; + } + + public boolean isAddQuotes() { + return addQuotes; + } + + @Override + public String getInsertAttrValue(String value) { + if (!addQuotes) { + return value; + } + String quotation = getQuotation(); + return quotation + value + quotation; + } + + private String getQuotation() { + String quotation = formattingSettings != null ? formattingSettings.getQuotationAsString() : null; + return StringUtils.isEmpty(quotation) ? XMLFormattingOptions.DEFAULT_QUOTATION : quotation; + } } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/XMLCompletions.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/XMLCompletions.java index 606c3b05e..944771cfd 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/XMLCompletions.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/XMLCompletions.java @@ -64,8 +64,8 @@ public XMLCompletions(XMLExtensionsRegistry extensionsRegistry) { this.extensionsRegistry = extensionsRegistry; } - public CompletionList doComplete(DOMDocument xmlDocument, Position position, - SharedSettings settings, CancelChecker cancelChecker) { + public CompletionList doComplete(DOMDocument xmlDocument, Position position, SharedSettings settings, + CancelChecker cancelChecker) { CompletionResponse completionResponse = new CompletionResponse(); CompletionRequest completionRequest = null; try { @@ -111,22 +111,22 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, case AttributeName: if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) { collectAttributeNameSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd(), completionRequest, - completionResponse, settings); + completionResponse); return completionResponse; } completionRequest.setCurrentAttributeName(scanner.getTokenText()); break; case DelimiterAssign: if (scanner.getTokenEnd() == offset) { - //int endPos = scanNextForEndPos(offset, scanner, TokenType.AttributeValue); - collectAttributeValueSuggestions(offset, offset, completionRequest, completionResponse, settings); + // int endPos = scanNextForEndPos(offset, scanner, TokenType.AttributeValue); + collectAttributeValueSuggestions(offset, offset, completionRequest, completionResponse); return completionResponse; } break; case AttributeValue: if (scanner.getTokenOffset() <= offset && offset <= scanner.getTokenEnd()) { collectAttributeValueSuggestions(scanner.getTokenOffset(), scanner.getTokenEnd(), completionRequest, - completionResponse, settings); + completionResponse); return completionResponse; } break; @@ -140,11 +140,11 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, return completionResponse; case WithinTag: case AfterAttributeName: - collectAttributeNameSuggestions(scanner.getTokenEnd(), completionRequest, completionResponse, settings); + collectAttributeNameSuggestions(scanner.getTokenEnd(), completionRequest, completionResponse); return completionResponse; case BeforeAttributeValue: collectAttributeValueSuggestions(scanner.getTokenEnd(), offset, completionRequest, - completionResponse, settings); + completionResponse); return completionResponse; case AfterOpeningEndTag: collectCloseTagSuggestions(scanner.getTokenOffset() - 1, false, offset, completionRequest, @@ -192,7 +192,9 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, break; case StartTagSelfClose: if (offset <= scanner.getTokenEnd()) { - if (currentTag != null && currentTag.length() > 0 && xmlDocument.getText().charAt(offset - 1) == '>') { // if the actual character typed was '>' + if (currentTag != null && currentTag.length() > 0 + && xmlDocument.getText().charAt(offset - 1) == '>') { // if the actual character typed was + // '>' collectInsideContent(completionRequest, completionResponse); return completionResponse; } @@ -207,7 +209,8 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, } break; case Content: - if(completionRequest.getXMLDocument().isDTD() || completionRequest.getXMLDocument().isWithinInternalDTD(offset)) { + if (completionRequest.getXMLDocument().isDTD() + || completionRequest.getXMLDocument().isWithinInternalDTD(offset)) { if (scanner.getTokenOffset() <= offset) { collectInsideDTDContent(completionRequest, completionResponse, true); return completionResponse; @@ -223,7 +226,8 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, try { boolean isFirstNode = xmlDocument.positionAt(scanner.getTokenOffset()).getLine() == 0; if (isFirstNode && offset <= scanner.getTokenEnd()) { - collectPrologSuggestion(scanner.getTokenEnd(), "", completionRequest, completionResponse, settings); + collectPrologSuggestion(scanner.getTokenEnd(), "", completionRequest, completionResponse, + settings); return completionResponse; } } catch (BadLocationException e) { @@ -237,8 +241,8 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, if (isFirstNode && offset <= scanner.getTokenEnd()) { String substringXML = "xml".substring(0, scanner.getTokenText().length()); if (scanner.getTokenText().equals(substringXML)) { - PrologModel.computePrologCompletionResponses(scanner.getTokenEnd(), scanner.getTokenText(), completionRequest, - completionResponse, true, settings); + PrologModel.computePrologCompletionResponses(scanner.getTokenEnd(), scanner.getTokenText(), + completionRequest, completionResponse, true, settings); return completionResponse; } } @@ -308,13 +312,13 @@ private static boolean isInsideDTDContent(DOMNode node, DOMDocument xmlDocument) } public boolean isBalanced(DOMNode node) { - if(node.isClosed() == false) { + if (node.isClosed() == false) { return false; } String name = node.getNodeName(); DOMNode parent = node.getParentElement(); - while(parent != null && name.equals(parent.getNodeName())) { - if(parent.isClosed() == false) { + while (parent != null && name.equals(parent.getNodeName())) { + if (parent.isClosed() == false) { return false; } parent = parent.getParentElement(); @@ -326,7 +330,7 @@ public AutoCloseTagResponse doTagComplete(DOMDocument xmlDocument, Position posi int offset; try { offset = xmlDocument.offsetAt(position); - if(offset - 2 < 0) { //There is not enough content for autoClose + if (offset - 2 < 0) { // There is not enough content for autoClose return null; } } catch (BadLocationException e) { @@ -341,20 +345,16 @@ public AutoCloseTagResponse doTagComplete(DOMDocument xmlDocument, Position posi String snippet = null; if (c == '>') { // Case: | DOMNode node = xmlDocument.findNodeBefore(offset); - if(!(node instanceof DOMElement)) { + if (!(node instanceof DOMElement)) { return null; } DOMElement element = ((DOMElement) node); - if (node != null - && node.isElement() - && !element.isSelfClosed() - && element.getTagName() != null - && !isEmptyElement(((DOMElement) node).getTagName()) - && node.getStart() < offset - && (!element.hasEndTag() || - (element.getTagName().equals(node.getParentNode().getNodeName()) && !isBalanced(node)))) { + if (node != null && node.isElement() && !element.isSelfClosed() && element.getTagName() != null + && !isEmptyElement(((DOMElement) node).getTagName()) && node.getStart() < offset + && (!element.hasEndTag() || (element.getTagName().equals(node.getParentNode().getNodeName()) + && !isBalanced(node)))) { snippet = "$0"; - + } } else if (cBefore == '<' && c == '/') { // Case: attrList = element1.getAttributeNodes(); - if(attrList != null) { + if (attrList != null) { DOMAttr lastAttr = attrList.get(attrList.size() - 1); - if(slashOffset < lastAttr.getEnd()) { //slash in attribute value + if (slashOffset < lastAttr.getEnd()) { // slash in attribute value return null; } } String text = xmlDocument.getText(); - boolean closeBracketAfterSlash = offset < text.length() ? text.charAt(offset) == '>' : false; // After the slash is a close bracket - + boolean closeBracketAfterSlash = offset < text.length() ? text.charAt(offset) == '>' : false; // After + // the + // slash + // is + // a + // close + // bracket + // Case: ' after slash - if(element1.isStartTagClosed()) { // tag has closing '>', but slash is in incorrect area (not directly before the '>') + if (closeBracketAfterSlash == false) { // no '>' after slash + if (element1.isStartTagClosed()) { // tag has closing '>', but slash is in incorrect area (not + // directly before the '>') return null; } snippet = ">$0"; - if(element1.hasEndTag()) { // Case: + if (element1.hasEndTag()) { // Case: try { end = xmlDocument.positionAt(element1.getEnd()); } catch (BadLocationException e) { return null; } } - } - else { + } else { DOMNode nextSibling = node.getNextSibling(); - //If there is text in between the tags it will skip this - if(nextSibling != null && nextSibling.isElement()){ // Case: + // If there is text in between the tags it will skip this + if (nextSibling != null && nextSibling.isElement()) { // Case: DOMElement element2 = (DOMElement) nextSibling; - if(!element2.hasStartTag() && node.getNodeName().equals(element2.getNodeName())) { + if (!element2.hasStartTag() && node.getNodeName().equals(element2.getNodeName())) { try { snippet = ">$0"; end = xmlDocument.positionAt(element2.getEnd()); @@ -409,33 +415,32 @@ public AutoCloseTagResponse doTagComplete(DOMDocument xmlDocument, Position posi return null; } } - } - else if(nextSibling == null) { // Case: + } else if (nextSibling == null) { // Case: DOMElement parentElement = node.getParentElement(); - if(parentElement != null && node.getNodeName().equals(parentElement.getTagName())) { + if (parentElement != null && node.getNodeName().equals(parentElement.getTagName())) { DOMNode nodeAfterParent = parentElement.getNextSibling(); - if(nodeAfterParent != null && nodeAfterParent.isElement()) { + if (nodeAfterParent != null && nodeAfterParent.isElement()) { DOMElement elementAfterParent = (DOMElement) nodeAfterParent; - if(parentElement.getTagName().equals(elementAfterParent.getTagName()) - && !elementAfterParent.hasStartTag()) { + if (parentElement.getTagName().equals(elementAfterParent.getTagName()) + && !elementAfterParent.hasStartTag()) { try { snippet = ">$0"; end = xmlDocument.positionAt(parentElement.getEnd()); } catch (BadLocationException e) { return null; - } + } } } } } } - if(snippet != null && end != null) { + if (snippet != null && end != null) { return new AutoCloseTagResponse(snippet, new Range(position, end)); } } } } - if(snippet == null) { + if (snippet == null) { return null; } return new AutoCloseTagResponse(snippet); @@ -675,26 +680,32 @@ private void collectCharacterEntityProposals(ICompletionRequest request, IComple } private void collectAttributeNameSuggestions(int nameStart, CompletionRequest completionRequest, - CompletionResponse completionResponse, SharedSettings settings) { + CompletionResponse completionResponse) { collectAttributeNameSuggestions(nameStart, completionRequest.getOffset(), completionRequest, - completionResponse, settings); + completionResponse); } private void collectAttributeNameSuggestions(int nameStart, int nameEnd, CompletionRequest completionRequest, - CompletionResponse completionResponse, SharedSettings settings) { + CompletionResponse completionResponse) { int replaceEnd = completionRequest.getOffset(); String text = completionRequest.getXMLDocument().getText(); - while (replaceEnd < nameEnd && text.charAt(replaceEnd) != '<' && text.charAt(replaceEnd) != '?') { // < is a valid attribute name character, but - // we rather assume the attribute name ends. - // See #23236. + while (replaceEnd < nameEnd && text.charAt(replaceEnd) != '<' && text.charAt(replaceEnd) != '?') { // < is a + // valid + // attribute + // name + // character, + // but + // we rather assume the attribute name ends. + // See #23236. replaceEnd++; } try { - Range range = getReplaceRange(nameStart, replaceEnd, completionRequest); + Range replaceRange = getReplaceRange(nameStart, replaceEnd, completionRequest); + completionRequest.setReplaceRange(replaceRange); boolean generateValue = !isFollowedBy(text, nameEnd, ScannerState.AfterAttributeName, TokenType.DelimiterAssign); for (ICompletionParticipant participant : getCompletionParticipants()) { - participant.onAttributeName(generateValue, range, completionRequest, completionResponse, settings); + participant.onAttributeName(generateValue, completionRequest, completionResponse); } } catch (BadLocationException e) { LOGGER.log(Level.SEVERE, "While performing Completions, getReplaceRange() was given a bad Offset location", @@ -705,13 +716,12 @@ private void collectAttributeNameSuggestions(int nameStart, int nameEnd, Complet } private void collectAttributeValueSuggestions(int valueStart, int valueEnd, CompletionRequest completionRequest, - CompletionResponse completionResponse, SharedSettings settings) { - Range range = null; + CompletionResponse completionResponse) { boolean addQuotes = false; String valuePrefix; int offset = completionRequest.getOffset(); String text = completionRequest.getXMLDocument().getText(); - + // Adjusts range to handle if quotations for the value exist if (offset > valueStart && offset <= valueEnd && StringUtils.isQuote(text.charAt(valueStart))) { // inside quoted attribute @@ -721,25 +731,13 @@ private void collectAttributeValueSuggestions(int valueStart, int valueEnd, Comp if (valueEnd > valueStart && text.charAt(valueEnd - 1) == text.charAt(valueStart)) { valueContentEnd--; } - int wsBefore = getWordStart(text, offset, valueContentStart); - int wsAfter = getWordEnd(text, offset, valueContentEnd); - try { - range = getReplaceRange(wsBefore, wsAfter, completionRequest); - } catch (BadLocationException e) { - LOGGER.log(Level.SEVERE, - "While performing Completions, getReplaceRange() was given a bad Offset location", e); - } valuePrefix = offset >= valueContentStart && offset <= valueContentEnd ? text.substring(valueContentStart, offset) : ""; + valueStart = valueContentStart; + valueEnd = valueContentEnd; addQuotes = false; } else { - try { - range = getReplaceRange(valueStart, valueEnd, completionRequest); - } catch (BadLocationException e) { - LOGGER.log(Level.SEVERE, - "While performing Completions, getReplaceRange() was given a bad Offset location", e); - } valuePrefix = text.substring(valueStart, offset); addQuotes = true; } @@ -747,10 +745,12 @@ private void collectAttributeValueSuggestions(int valueStart, int valueEnd, Comp Collection completionParticipants = getCompletionParticipants(); if (completionParticipants.size() > 0) { try { - Range fullRange = getReplaceRange(valueStart, valueEnd, completionRequest); + Range replaceRange = getReplaceRange(valueStart, valueEnd, completionRequest); + completionRequest.setReplaceRange(replaceRange); + completionRequest.setAddQuotes(addQuotes); for (ICompletionParticipant participant : completionParticipants) { - participant.onAttributeValue(valuePrefix, fullRange, addQuotes, completionRequest, - completionResponse, settings); + participant.onAttributeValue(valuePrefix, completionRequest, + completionResponse); } } catch (BadLocationException e) { LOGGER.log(Level.SEVERE, @@ -762,8 +762,9 @@ private void collectAttributeValueSuggestions(int valueStart, int valueEnd, Comp } private void collectInsideDTDContent(CompletionRequest request, CompletionResponse response) { - collectInsideDTDContent(request,response, false); + collectInsideDTDContent(request, response, false); } + private void collectInsideDTDContent(CompletionRequest request, CompletionResponse response, boolean isContent) { // Insert DTD Element Declaration // see https://www.w3.org/TR/REC-xml/#dt-eldecl @@ -779,23 +780,24 @@ private void collectInsideDTDContent(CompletionRequest request, CompletionRespon DOMDocument document = request.getXMLDocument(); DOMNode node = document.findNodeAt(startOffset); try { - if(node.isDoctype()) { + if (node.isDoctype()) { editRange = getReplaceRange(startOffset, startOffset, request); } else { - if(isContent) { + if (isContent) { editRange = document.getTrimmedRange(node.getStart(), node.getEnd()); - } - if(editRange == null) { + } + if (editRange == null) { editRange = getReplaceRange(node.getStart(), node.getEnd(), request); } } } catch (BadLocationException e) { LOGGER.log(Level.SEVERE, "While performing getReplaceRange for DTD completion.", e); } - String textEdit = isSnippetsSupported ? "" : ""; + String textEdit = isSnippetsSupported ? "" + : ""; elementDecl.setTextEdit(new TextEdit(editRange, textEdit)); elementDecl.setDocumentation(""); - + response.addCompletionItem(elementDecl); // Insert DTD AttrList Declaration @@ -806,12 +808,12 @@ private void collectInsideDTDContent(CompletionRequest request, CompletionRespon attrListDecl.setFilterText("" - : ""; + + textEdit = isSnippetsSupported ? "" + : ""; attrListDecl.setTextEdit(new TextEdit(editRange, textEdit)); attrListDecl.setDocumentation(""); - + response.addCompletionItem(attrListDecl); // Insert Internal DTD Entity Declaration @@ -822,11 +824,12 @@ private void collectInsideDTDContent(CompletionRequest request, CompletionRespon internalEntity.setFilterText("" : ""; + + textEdit = isSnippetsSupported ? "" + : ""; internalEntity.setTextEdit(new TextEdit(editRange, textEdit)); internalEntity.setDocumentation(""); - + response.addCompletionItem(internalEntity); // Insert External DTD Entity Declaration @@ -837,12 +840,12 @@ private void collectInsideDTDContent(CompletionRequest request, CompletionRespon externalEntity.setFilterText("" : ""; - externalEntity - .setTextEdit(new TextEdit(editRange, textEdit)); + + textEdit = isSnippetsSupported ? "" + : ""; + externalEntity.setTextEdit(new TextEdit(editRange, textEdit)); externalEntity.setDocumentation(""); - + response.addCompletionItem(externalEntity); } @@ -865,7 +868,6 @@ private Collection getCompletionParticipants() { return extensionsRegistry.getCompletionParticipants(); } - private static boolean isFollowedBy(String s, int offset, ScannerState intialState, TokenType expectedToken) { return getOffsetFollowedBy(s, offset, intialState, expectedToken) != -1; } @@ -889,20 +891,6 @@ public static int getOffsetFollowedBy(String s, int offset, ScannerState intialS return (token == expectedToken) ? scanner.getTokenOffset() : -1; } - private static int getWordStart(String s, int offset, int limit) { - while (offset > limit && !isWhitespace(s.charAt(offset - 1))) { - offset--; - } - return offset; - } - - private static int getWordEnd(String s, int offset, int limit) { - while (offset < limit && !isWhitespace(s.charAt(offset))) { - offset++; - } - return offset; - } - public static Range getReplaceRange(int replaceStart, int replaceEnd, ICompletionRequest context) throws BadLocationException { int offset = context.getOffset(); diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/CompletionParticipantAdapter.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/CompletionParticipantAdapter.java index 12a2028de..0a57f0530 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/CompletionParticipantAdapter.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/CompletionParticipantAdapter.java @@ -10,9 +10,6 @@ */ package org.eclipse.lsp4xml.services.extensions; -import org.eclipse.lsp4j.Range; -import org.eclipse.lsp4xml.settings.SharedSettings; - /** * Completion participant adapter. * @@ -31,14 +28,14 @@ public void onXMLContent(ICompletionRequest request, ICompletionResponse respons } @Override - public void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request, - ICompletionResponse response, SharedSettings settings) throws Exception { + public void onAttributeName(boolean generateValue, ICompletionRequest request, ICompletionResponse response) + throws Exception { // Do nothing } @Override - public void onAttributeValue(String valuePrefix, Range fullRange, boolean addQuotes, ICompletionRequest request, - ICompletionResponse response, SharedSettings settings) throws Exception { + public void onAttributeValue(String valuePrefix, ICompletionRequest request, ICompletionResponse response) + throws Exception { // Do nothing } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/ICompletionParticipant.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/ICompletionParticipant.java index 393d73525..4f31b8d39 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/ICompletionParticipant.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/ICompletionParticipant.java @@ -10,9 +10,6 @@ */ package org.eclipse.lsp4xml.services.extensions; -import org.eclipse.lsp4j.Range; -import org.eclipse.lsp4xml.settings.SharedSettings; - /** * Completion participant API. * @@ -23,10 +20,10 @@ public interface ICompletionParticipant { void onXMLContent(ICompletionRequest request, ICompletionResponse response) throws Exception; - void onAttributeName(boolean generateValue, Range fullRange, ICompletionRequest request, ICompletionResponse response, - SharedSettings settings) throws Exception; + void onAttributeName(boolean generateValue, ICompletionRequest request, ICompletionResponse response) + throws Exception; - void onAttributeValue(String valuePrefix, Range fullRange, boolean addQuotes, ICompletionRequest request, - ICompletionResponse response, SharedSettings settings) throws Exception; + void onAttributeValue(String valuePrefix, ICompletionRequest request, ICompletionResponse response) + throws Exception; } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/ICompletionRequest.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/ICompletionRequest.java index bb698aaa9..29419eae6 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/ICompletionRequest.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/services/extensions/ICompletionRequest.java @@ -30,4 +30,6 @@ public interface ICompletionRequest extends IPositionRequest { XMLGenerator getXMLGenerator() throws BadLocationException; String getFilterForStartTagName(String tagName); + + String getInsertAttrValue(String value); } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/settings/XMLFormattingOptions.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/settings/XMLFormattingOptions.java index 53a3905ed..f6d4953b4 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/settings/XMLFormattingOptions.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/settings/XMLFormattingOptions.java @@ -21,6 +21,9 @@ * All defaults should be set here to eventually be overridden if needed. */ public class XMLFormattingOptions extends FormattingOptions { + + public static final String DEFAULT_QUOTATION = "\""; + // All possible keys private static final String SPLIT_ATTRIBUTES = "splitAttributes"; private static final String JOIN_CDATA_LINES = "joinCDATALines"; @@ -210,7 +213,7 @@ public String getQuotations() { * Defaults to {@code "}. */ public String getQuotationAsString() { - return XMLFormattingOptions.DOUBLE_QUOTES_VALUE.equals(getQuotations()) ? "\"" : "\'"; + return XMLFormattingOptions.DOUBLE_QUOTES_VALUE.equals(getQuotations()) ? DEFAULT_QUOTATION : "\'"; } /** diff --git a/org.eclipse.lsp4xml/src/main/resources/schemas/xsd/datatypes.xml b/org.eclipse.lsp4xml/src/main/resources/schemas/xsd/datatypes.xml new file mode 100644 index 000000000..dad1311b1 --- /dev/null +++ b/org.eclipse.lsp4xml/src/main/resources/schemas/xsd/datatypes.xml @@ -0,0 +1,110 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/prolog/PrologCompletionExtensionsTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/prolog/PrologCompletionExtensionsTest.java index 08675fd28..f513fc58a 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/prolog/PrologCompletionExtensionsTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/prolog/PrologCompletionExtensionsTest.java @@ -118,8 +118,8 @@ public void completionEncodingExists() throws BadLocationException { String xml = "\r\n" + // ""; testCompletionFor(xml, - c(PrologModel.WINDOWS_1251, te(0, 32, 0, 37, "\"" + PrologModel.WINDOWS_1251 + "\""), "\"" + PrologModel.WINDOWS_1251 + "\""), - c(PrologModel.WINDOWS_1252, te(0, 32, 0, 37, "\"" + PrologModel.WINDOWS_1252 + "\""), "\"" + PrologModel.WINDOWS_1252 + "\"")); + c(PrologModel.WINDOWS_1251, te(0, 33, 0, 36, PrologModel.WINDOWS_1251), PrologModel.WINDOWS_1251), + c(PrologModel.WINDOWS_1252, te(0, 33, 0, 36, PrologModel.WINDOWS_1252), PrologModel.WINDOWS_1252)); } @Test diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/DataTypeTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/DataTypeTest.java new file mode 100644 index 000000000..4ec119961 --- /dev/null +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/DataTypeTest.java @@ -0,0 +1,29 @@ +/******************************************************************************* +* Copyright (c) 2019 Red Hat Inc. and others. +* All rights reserved. This program and the accompanying materials +* which accompanies this distribution, and is available at +* http://www.eclipse.org/legal/epl-v20.html +* +* Contributors: +* Red Hat Inc. - initial API and implementation +*******************************************************************************/ +package org.eclipse.lsp4xml.extensions.xsd; + +import org.junit.Assert; +import org.junit.Test; + +/** + * XSD Data type tests + * + * @author Angelo ZERR + * + */ +public class DataTypeTest { + + @Test + public void xsString() { + DataType string = DataType.getDataType("string"); + Assert.assertNotNull(string); + Assert.assertEquals(string.getName(), "string"); + } +} diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSDCompletionExtensionsTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSDCompletionExtensionsTest.java index c2558d0cc..a6b901ca3 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSDCompletionExtensionsTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSDCompletionExtensionsTest.java @@ -42,8 +42,11 @@ public void completionWithSourceDetail() throws BadLocationException { + " \r\n" + // "|"; - testCompletionFor(xml, c("xs:annotation", te(2, 0, 2, 0, ""), "xs:annotation", "Source: XMLSchema.xsd"), - c("xs:attribute", te(2, 0, 2, 0, ""), "xs:attribute", "Source: XMLSchema.xsd")); + testCompletionFor(xml, + c("xs:annotation", te(2, 0, 2, 0, ""), "xs:annotation", + "Source: XMLSchema.xsd"), + c("xs:attribute", te(2, 0, 2, 0, ""), "xs:attribute", + "Source: XMLSchema.xsd")); } @Test @@ -53,10 +56,99 @@ public void completionWithSourceDescriptionAndDetail() throws BadLocationExcepti + " 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, ""), ""), "")); } + @Test + public void completionOnElementType() throws BadLocationException { + // completion on | xs:element/@type -> xs:complexType/@name, xs:simpleType/@name + String xml = "\r\n" + // + "\r\n" + + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + testCompletionFor(xml, c("xs:aComplexType", te(2, 30, 2, 30, "xs:aComplexType"), "xs:aComplexType"), + c("xs:aSimpleType", te(2, 30, 2, 30, "xs:aSimpleType"), "xs:aSimpleType"), + c("xs:string", te(2, 30, 2, 30, "xs:string"), "xs:string")); + } + + @Test + public void completionOnAttributeType() throws BadLocationException { + // completion on | xs:attribute/@type -> xs:simpleType/@name + String xml = "\r\n" + // + "\r\n" + + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + testCompletionFor(xml, c("xs:aSimpleType", te(2, 33, 2, 33, "xs:aSimpleType"), "xs:aSimpleType"), + c("xs:string", te(2, 33, 2, 33, "xs:string"), "xs:string")); + } + + @Test + public void completionOnElementRef() throws BadLocationException { + // completion on | xs:element/@ref -> xs:element/@name + String xml = "\r\n" + // + "\r\n" + + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // here completion shows xs:element, xs:attribute, + // xs:notation + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + XMLAssert.testCompletionFor(xml, 3, c("xs:element", te(8, 20, 8, 20, "xs:element"), "xs:element"), + c("xs:attribute", te(8, 20, 8, 20, "xs:attribute"), "xs:attribute"), + c("xs:notation", te(8, 20, 8, 20, "xs:notation"), "xs:notation")); + } + + @Test + public void completionOnGroupRef() throws BadLocationException { + // completion on | xs:groupt/@ref -> xs:group/@name + String xml = "\r\n" + // + "\r\n" + + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // here completion shows xs:schemaTop, xs:redefinable + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + XMLAssert.testCompletionFor(xml, 2, c("xs:redefinable", te(5, 18, 5, 18, "xs:redefinable"), "xs:redefinable"), + c("xs:schemaTop", te(5, 18, 5, 18, "xs:schemaTop"), "xs:schemaTop")); + } + private void testCompletionFor(String xml, CompletionItem... expectedItems) throws BadLocationException { XMLAssert.testCompletionFor(xml, null, expectedItems); } diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSDDefinitionExtensionsTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSDDefinitionExtensionsTest.java index b7234b61e..965c12cc5 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSDDefinitionExtensionsTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsd/XSDDefinitionExtensionsTest.java @@ -20,12 +20,13 @@ /** * XSD definition tests. * + * @author Angelo ZERR */ public class XSDDefinitionExtensionsTest { @Test - public void elementTypeDefinition() throws BadLocationException { - // completion on | + public void definitionOnElementType() throws BadLocationException { + // definition on | String xml = "\r\n" + // "\r\n" + // @@ -38,12 +39,12 @@ public void elementTypeDefinition() throws BadLocationException { " \r\n" + // " \r\n" + // ""; - testDefinitionFor(xml, l("test.xsd", r(3, 29, 3, 50), r(5, 17, 5, 38))); + testDefinitionFor(xml, l("test.xsd", r(3, 34, 3, 50), r(5, 22, 5, 38))); } @Test - public void extensionBaseDefinition() throws BadLocationException { - // completion on | + public void definitionOnExtensionBase() throws BadLocationException { + // definition on | String xml = "\r\n" + // "\r\n" + // @@ -62,11 +63,11 @@ public void extensionBaseDefinition() throws BadLocationException { " \r\n" + // " \r\n" + // ""; - testDefinitionFor(xml, l("test.xsd", r(9, 17, 9, 34), r(3, 17, 3, 34))); + testDefinitionFor(xml, l("test.xsd", r(9, 22, 9, 34), r(3, 22, 3, 34))); } @Test - public void targetDefinition() throws BadLocationException { + public void definitionWithTargetNamespace() throws BadLocationException { // completion on | String xml = "\r\n" + "\r\n" @@ -82,7 +83,63 @@ public void targetDefinition() throws BadLocationException { " \r\n" + // " \r\n" + // ""; - testDefinitionFor(xml, l("test.xsd", r(3, 30, 3, 60), r(6, 17, 6, 43))); + testDefinitionFor(xml, l("test.xsd", r(3, 35, 3, 60), r(6, 22, 6, 43))); + } + + @Test + public void definitionOnElementRef() throws BadLocationException { + // definition on | xs:element/@ref -> xs:element/@name + String xml = "\r\n" + // + "\r\n" + + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // here definition go to the + // elmeent/@name='notation' + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + testDefinitionFor(xml, l("test.xsd", r(8, 19, 8, 32), r(18, 18, 18, 28))); + } + + @Test + public void definitionOnGroupRef() throws BadLocationException { + // definition on | xs:groupt/@ref -> xs:group/@name + String xml = "\r\n" + // + "\r\n" + + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // here definition go to the + // group/@name='redefinable' + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + " \r\n" + // + ""; + testDefinitionFor(xml, l("test.xsd", r(5, 17, 5, 33), r(12, 16, 12, 29))); } private static void testDefinitionFor(String xml, LocationLink... expectedItems) throws BadLocationException { diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsi/XSICompletionExtensionsTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsi/XSICompletionExtensionsTest.java index c4fcc7220..b1b30f6dd 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsi/XSICompletionExtensionsTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/extensions/xsi/XSICompletionExtensionsTest.java @@ -54,8 +54,8 @@ public void completion2() throws BadLocationException { String xml = "\r\n" + // ""; testCompletionFor(xml, - c("true", te(1, 71, 1, 73, "\"true\""), "\"true\""), - c("false", te(1, 71, 1, 73, "\"false\""), "\"false\"")); + c("true", te(1, 72, 1, 72, "true"), "true"), + c("false", te(1, 72, 1, 72, "false"), "false")); } @Test diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/services/extensions/HTMLCompletionExtensionsTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/services/extensions/HTMLCompletionExtensionsTest.java index a8ded0a3b..fb35dad20 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/services/extensions/HTMLCompletionExtensionsTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/services/extensions/HTMLCompletionExtensionsTest.java @@ -151,13 +151,14 @@ public void onTagOpen(ICompletionRequest completionRequest, ICompletionResponse } @Override - public void onAttributeName(boolean generateValue, Range replaceRange, ICompletionRequest completionRequest, - ICompletionResponse completionResponse, SharedSettings settings) { + public void onAttributeName(boolean generateValue, ICompletionRequest completionRequest, + ICompletionResponse completionResponse) { String tag = completionRequest.getCurrentTag(); HTMLTag htmlTag = HTMLTag.getHTMLTag(tag); if (htmlTag != null) { String[] attributes = htmlTag.getAttributes(); if (attributes != null) { + Range replaceRange = completionRequest.getReplaceRange(); for (String attribute : attributes) { int index = attribute.indexOf(":"); if (index != -1) { @@ -178,8 +179,8 @@ public void onAttributeName(boolean generateValue, Range replaceRange, ICompleti } @Override - public void onAttributeValue(String valuePrefix, Range fullRange, boolean addQuotes, - ICompletionRequest completionRequest, ICompletionResponse completionResponse, SharedSettings settings) { + public void onAttributeValue(String valuePrefix, + ICompletionRequest completionRequest, ICompletionResponse completionResponse) { String tag = completionRequest.getCurrentTag(); String attributeName = completionRequest.getCurrentAttributeName(); HTMLTag htmlTag = HTMLTag.getHTMLTag(tag); @@ -195,10 +196,11 @@ public void onAttributeValue(String valuePrefix, Range fullRange, boolean addQuo attrType = attribute.substring(index + 1, attribute.length()); } if (attrType != null && attributeName.equals(attrName)) { + Range fullRange = completionRequest.getReplaceRange(); String[] values = HTMLTag.getAttributeValues(attrType); for (String value : values) { - String insertText = addQuotes ? '"' + value + '"' : value; - + String insertText = completionRequest.getInsertAttrValue(value); + CompletionItem item = new CompletionItem(); item.setLabel(value); item.setFilterText(insertText);