From 9003b5fd0babe481b86810ef2d7457aa9144cde8 Mon Sep 17 00:00:00 2001 From: azerr Date: Thu, 9 Apr 2020 17:50:56 +0200 Subject: [PATCH] xsd:enumeration autocomplete don't work for text node Fixes https://github.com/redhat-developer/vscode-xml/issues/218 Signed-off-by: azerr --- .../lemminx/commons/SnippetsBuilder.java | 6 +++ .../model/CMElementDeclaration.java | 8 ++++ .../ContentModelCompletionParticipant.java | 45 +++++++++++++++++- .../contentmodel/utils/XMLGenerator.java | 32 ++++++++++++- .../contentmodel/CMDTDElementDeclaration.java | 5 ++ .../xsd/contentmodel/CMXSDDocument.java | 2 +- .../contentmodel/CMXSDElementDeclaration.java | 17 +++++-- .../eclipse/lemminx/utils/StringUtils.java | 35 +++++++++----- .../XMLSchemaCompletionExtensionsTest.java | 47 +++++++++++++++++++ 9 files changed, 177 insertions(+), 20 deletions(-) diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/SnippetsBuilder.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/SnippetsBuilder.java index dcef5e042c..cfa458b19a 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/SnippetsBuilder.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/commons/SnippetsBuilder.java @@ -45,6 +45,12 @@ public static void placeholders(int index, String text, StringBuilder snippets) snippets.append("}"); } + public static String choice(int index, Collection values) { + StringBuilder snippets = new StringBuilder(); + choice(index, values, snippets); + return snippets.toString(); + } + /** * * @param index diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/model/CMElementDeclaration.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/model/CMElementDeclaration.java index bd9e789280..3fef55e488 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/model/CMElementDeclaration.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/model/CMElementDeclaration.java @@ -121,6 +121,14 @@ default String getName(String prefix) { */ Collection getEnumerationValues(); + /** + * Returns the documentation for the given enumeration value and null otherwise. + * + * @param value the enumeration value. + * @return the documentation for the given enumeration value and null otherwise. + */ + String getValueDocumentation(String value); + /** * Returns the owner document URI where the element is declared. * diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java index 5125a546e9..54f4f347d0 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/participants/ContentModelCompletionParticipant.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 Angelo ZERR + * Copyright (c) 2018-2020 Angelo ZERR * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -13,6 +13,7 @@ package org.eclipse.lemminx.extensions.contentmodel.participants; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; import java.util.Set; @@ -35,6 +36,7 @@ import org.eclipse.lsp4j.CompletionItemKind; import org.eclipse.lsp4j.InsertTextFormat; import org.eclipse.lsp4j.MarkupContent; +import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.eclipse.lsp4j.TextEdit; import org.w3c.dom.Document; @@ -334,4 +336,45 @@ private void fillAttributeValuesWithCMAttributeDeclarations(CMElementDeclaration } } + @Override + public void onXMLContent(ICompletionRequest request, ICompletionResponse response) throws Exception { + try { + ContentModelManager contentModelManager = request.getComponent(ContentModelManager.class); + DOMElement parentElement = request.getParentElement(); + if (parentElement != null) { + CMElementDeclaration elementDeclaration = contentModelManager.findCMElement(parentElement); + Collection values = elementDeclaration != null ? elementDeclaration.getEnumerationValues() + : Collections.emptyList(); + if (!values.isEmpty()) { + DOMDocument document = parentElement.getOwnerDocument(); + int startOffset = parentElement.getStartTagCloseOffset() + 1; + Position start = parentElement.getOwnerDocument().positionAt(startOffset); + Position end = request.getPosition(); + int endOffset = parentElement.getEndTagOpenOffset(); + if (endOffset > 0) { + end = document.positionAt(endOffset); + } + int completionOffset = request.getOffset(); + String tokenStart = StringUtils.getWhitespaces(document.getText(), startOffset, + completionOffset); + Range fullRange = new Range(start, end); + values.forEach(value -> { + CompletionItem item = new CompletionItem(); + item.setLabel(value); + String insertText = value; // request.getInsertAttrValue(value); + item.setLabel(value); + item.setKind(CompletionItemKind.Value); + item.setFilterText(tokenStart + insertText); + item.setTextEdit(new TextEdit(fullRange, insertText)); + MarkupContent documentation = XMLGenerator.createMarkupContent(elementDeclaration, value, + request); + item.setDocumentation(documentation); + response.addCompletionItem(item); + }); + } + } + } catch (CacheResourceDownloadingException e) { + // XML Schema, DTD is loading, ignore this error + } + } } \ No newline at end of file diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/utils/XMLGenerator.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/utils/XMLGenerator.java index 4376108fc9..e8ca68ada2 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/utils/XMLGenerator.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/contentmodel/utils/XMLGenerator.java @@ -1,5 +1,5 @@ /** - * Copyright (c) 2018 Angelo ZERR + * Copyright (c) 2018-2020 Angelo ZERR * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v2.0 * which accompanies this distribution, and is available at @@ -21,8 +21,8 @@ import org.eclipse.lemminx.extensions.contentmodel.model.CMElementDeclaration; import org.eclipse.lemminx.settings.XMLFormattingOptions; import org.eclipse.lemminx.utils.MarkupContentFactory; -import org.eclipse.lemminx.utils.XMLBuilder; import org.eclipse.lemminx.utils.MarkupContentFactory.IMarkupKindSupport; +import org.eclipse.lemminx.utils.XMLBuilder; import org.eclipse.lsp4j.MarkupContent; import org.eclipse.lsp4j.MarkupKind; @@ -124,6 +124,15 @@ private int generate(CMElementDeclaration elementDeclaration, String prefix, int xml.selfCloseElement(); } else { xml.closeStartElement(); + Collection values = elementDeclaration.getEnumerationValues(); + if (!values.isEmpty()) { + if (canSupportSnippets) { + snippetIndex++; + xml.addContent(SnippetsBuilder.choice(snippetIndex, values)); + } else { + xml.addContent(values.iterator().next()); + } + } if (canSupportSnippets) { snippetIndex++; xml.addContent(SnippetsBuilder.tabstops(snippetIndex)); @@ -323,4 +332,23 @@ public static MarkupContent createMarkupContent(CMAttributeDeclaration cmAttribu } return null; } + + /** + * Returns a markup content for element text documentation and null otherwise. + * + * @param cmElement element declaration. + * @param textContent the text content. + * @param support markup kind support. + * + * @return a markup content for element text documentation and null otherwise. + */ + public static MarkupContent createMarkupContent(CMElementDeclaration cmElement, String textContent, + IMarkupKindSupport support) { + String documentation = XMLGenerator.generateDocumentation(cmElement.getValueDocumentation(textContent), + cmElement.getDocumentURI(), support.canSupportMarkupKind(MarkupKind.MARKDOWN)); + if (documentation != null) { + return MarkupContentFactory.createMarkupContent(documentation, MarkupKind.MARKDOWN, support); + } + return null; + } } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/contentmodel/CMDTDElementDeclaration.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/contentmodel/CMDTDElementDeclaration.java index 19b31d3dad..9beaa61047 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/contentmodel/CMDTDElementDeclaration.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/dtd/contentmodel/CMDTDElementDeclaration.java @@ -131,6 +131,11 @@ public Collection getEnumerationValues() { return null; } + @Override + public String getValueDocumentation(String textContent) { + return null; + } + public int getIndex() { return index; } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDDocument.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDDocument.java index 61893b7fa5..fdc1a68e4e 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDDocument.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDDocument.java @@ -454,7 +454,7 @@ private static int getComplexTypeOffset(XSComplexTypeDefinition complexType, Sch // - fCTLocators array of locator // - fComplexTypeDecls array of XSComplexTypeDecl - // As it's not an API, we must use Java Reflection to get thoses 2 arrays + // As it's not an API, we must use Java Reflection to get those 2 arrays Field f = SchemaGrammar.class.getDeclaredField("fCTLocators"); f.setAccessible(true); SimpleLocator[] fCTLocators = (SimpleLocator[]) f.get(grammar); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDElementDeclaration.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDElementDeclaration.java index a0171f6d2d..2c9863201c 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDElementDeclaration.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/extensions/xsd/contentmodel/CMXSDElementDeclaration.java @@ -401,12 +401,24 @@ public boolean isEmpty() { @Override public Collection getEnumerationValues() { XSTypeDefinition typeDefinition = elementDeclaration.getTypeDefinition(); - if (typeDefinition != null && typeDefinition.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) { - return CMXSDDocument.getEnumerationValues((XSSimpleTypeDefinition) typeDefinition); + if (typeDefinition != null) { + XSSimpleTypeDefinition simpleDefinition = null; + if (typeDefinition.getTypeCategory() == XSTypeDefinition.SIMPLE_TYPE) { + simpleDefinition = (XSSimpleTypeDefinition) typeDefinition; + } else if (typeDefinition.getTypeCategory() == XSTypeDefinition.COMPLEX_TYPE) { + simpleDefinition = ((XSComplexTypeDefinition) typeDefinition).getSimpleType(); + } + return CMXSDDocument.getEnumerationValues(simpleDefinition); } return Collections.emptyList(); } + @Override + public String getValueDocumentation(String value) { + // FIXME: implement xsd:enumeration for Text node. + return null; + } + XSElementDeclaration getElementDeclaration() { return elementDeclaration; } @@ -416,5 +428,4 @@ public String getDocumentURI() { SchemaGrammar schemaGrammar = document.getOwnerSchemaGrammar(elementDeclaration); return CMXSDDocument.getSchemaURI(schemaGrammar); } - } diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java index 691b5eaf07..2e67ddedc4 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/utils/StringUtils.java @@ -99,10 +99,21 @@ public static String normalizeSpace(String str) { * @return the start whitespaces of the given line text. */ public static String getStartWhitespaces(String lineText) { + return getWhitespaces(lineText, 0, lineText.length()); + } + + /** + * Returns the whitespaces from the given range start/end of the given text. + * + * @param start the range start + * @param end the range end + * @param text the text + * @return the whitespaces from the given range start/end of the given text. + */ + public static String getWhitespaces(String text, int start, int end) { StringBuilder whitespaces = new StringBuilder(); - char[] chars = lineText.toCharArray(); - for (int i = 0; i < chars.length; i++) { - char c = chars[i]; + for (int i = start; i < end; i++) { + char c = text.charAt(i); if (Character.isWhitespace(c)) { whitespaces.append(c); } else { @@ -266,11 +277,10 @@ public static int getOffsetAfterWhitespace(String text, int endOffset) { } /** - * Returns the number of consecutive whitespace characters in front - * of text + * Returns the number of consecutive whitespace characters in front of text + * * @param text String of interest - * @return the number of consecutive whitespace characters in front - * of text + * @return the number of consecutive whitespace characters in front of text */ public static int getFrontWhitespaceLength(String text) { @@ -287,14 +297,13 @@ public static int getFrontWhitespaceLength(String text) { } /** - * Returns the number of consecutive whitespace characters from - * the end of text + * Returns the number of consecutive whitespace characters from the end of text + * * @param text String of interest - * @return the number of consecutive whitespace characters from - * the end of text + * @return the number of consecutive whitespace characters from the end of text */ public static int getTrailingWhitespaceLength(String text) { - + if (StringUtils.isWhitespace(text)) { return text.length(); } @@ -397,7 +406,7 @@ public static int findExprBeforeAt(String text, String expr, int offset) { } public static String getString(Object obj) { - if(obj != null) { + if (obj != null) { return obj.toString(); } return null; diff --git a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaCompletionExtensionsTest.java b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaCompletionExtensionsTest.java index 5569de6fe6..35dd9fd70a 100644 --- a/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaCompletionExtensionsTest.java +++ b/org.eclipse.lemminx/src/test/java/org/eclipse/lemminx/extensions/contentmodel/XMLSchemaCompletionExtensionsTest.java @@ -274,6 +274,40 @@ public void completionOnAttributeValue2() throws BadLocationException { XMLAssert.testCompletionFor(xml, null, "src/test/resources/invoice.xml", null, c("credit", "credit"), c("debit", "debit"), c("cash", "cash")); } + + @Test + public void completionOnTextWithEnumeration() throws BadLocationException { + String xml = "\r\n" + + // + " \r\n" + // + " \r\n" + // + " |\r\n" + // + " "; + // Completion on skills Text node + // - without snippet + XMLAssert.testCompletionFor(xml, null, "src/test/resources/team.xml", null, c("skill", "Java")); + // - with snippet + testCompletionSnippetSupporytFor(xml, "src/test/resources/team.xml", null, c("skill", "${1|Java,Node,XML|}$2$0")); + + xml = "\r\n" + + // + " \r\n" + // + " \r\n" + // + " |\r\n" + // + " "; + // Completion on skill Text node + XMLAssert.testCompletionFor(xml, null, "src/test/resources/team.xml", null, c("Java", "Java"), c("Node", "Node"), c("XML", "XML")); + + xml = "\r\n" + + // + " \r\n" + // + " \r\n" + // + " |\r\n" + // + " "; + // Completion on skill Text node + XMLAssert.testCompletionFor(xml, null, "src/test/resources/team.xml", null, c("Java", "Java"), c("Node", "Node"), c("XML", "XML")); + + } @Test public void schemaLocationWithElementAndAttributeCompletion() throws BadLocationException { @@ -1019,4 +1053,17 @@ private void testCompletionMarkdownSupporytFor(String xml, CompletionItem... exp XMLAssert.testCompletionFor(new XMLLanguageService(), xml, "src/test/resources/catalogs/catalog.xml", null, null, null, completionSettings, expectedItems); } + + private void testCompletionSnippetSupporytFor(String xml, String fileURI, Integer expectedCount, + CompletionItem... expectedItems) + throws BadLocationException { + XMLCompletionSettings completionSettings = new XMLCompletionSettings(); + CompletionCapabilities completionCapabilities = new CompletionCapabilities(); + CompletionItemCapabilities completionItem = new CompletionItemCapabilities(true); + completionItem.setDocumentationFormat(Arrays.asList(MarkupKind.MARKDOWN)); + completionCapabilities.setCompletionItem(completionItem); + completionSettings.setCapabilities(completionCapabilities); + XMLAssert.testCompletionFor(new XMLLanguageService(), xml, null, null, + fileURI, null, completionSettings, expectedItems); + } } \ No newline at end of file