diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java index 55196f5d99..67d12c42b1 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/dom/DOMElement.java @@ -378,7 +378,23 @@ public boolean isEndTagClosed() { } /** - * If Element has a closing end tag eg: -> true , -> false + * Returns true if the given element is an end tag (eg: which is not + * closed) and false otherwise. + * + * @param tagName the end tag name. + * @return true if the given element is an end tag (eg: which is not + * closed) and false otherwise. + */ + public boolean isOnlyEndTag(String tagName) { + return isSameTag(tagName) && hasEndTag() && !hasStartTag(); + } + + /** + * Returns true if element has a closing end tag (eg: ) and false + * otherwise (eg: ). + * + * @return true if element has a closing end tag (eg: ) and false + * otherwise (eg: ). */ @Override public boolean isClosed() { 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 f88a924e9a..4b40a35701 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 @@ -15,11 +15,13 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.eclipse.lemminx.commons.BadLocationException; import org.eclipse.lemminx.dom.DOMDocument; import org.eclipse.lemminx.dom.DOMElement; +import org.eclipse.lemminx.dom.DOMNode; import org.eclipse.lemminx.extensions.contentmodel.model.CMAttributeDeclaration; import org.eclipse.lemminx.extensions.contentmodel.model.CMDocument; import org.eclipse.lemminx.extensions.contentmodel.model.CMElementDeclaration; @@ -219,11 +221,11 @@ private static void addTagName(NodeList list, Set tags, ICompletionReque } } - private static void addCompletionItem(CMElementDeclaration elementDeclaration, DOMElement element, + private static void addCompletionItem(CMElementDeclaration elementDeclaration, DOMElement parentElement, String defaultPrefix, boolean forceUseOfPrefix, ICompletionRequest request, ICompletionResponse response, XMLGenerator generator, Set tags) { String prefix = forceUseOfPrefix ? defaultPrefix - : (element != null ? element.getPrefix(elementDeclaration.getNamespace()) : null); + : (parentElement != null ? parentElement.getPrefix(elementDeclaration.getNamespace()) : null); String label = elementDeclaration.getName(prefix); if (tags != null) { if (tags.contains(label)) { @@ -238,12 +240,62 @@ private static void addCompletionItem(CMElementDeclaration elementDeclaration, D item.setKind(CompletionItemKind.Property); MarkupContent documentation = XMLGenerator.createMarkupContent(elementDeclaration, request); item.setDocumentation(documentation); - String xml = generator.generate(elementDeclaration, prefix); + String xml = generator.generate(elementDeclaration, prefix, + isGenerateEndTag(request.getNode(), request.getOffset(), label)); item.setTextEdit(new TextEdit(request.getReplaceRange(), xml)); item.setInsertTextFormat(InsertTextFormat.Snippet); response.addCompletionItem(item, true); } + private static boolean isGenerateEndTag(DOMNode node, int offset, String tagName) { + if (node == null) { + return true; + } + if (node.isText()) { + DOMNode next = node.getNextSibling(); + if (next == null || !next.isElement()) { + return true; + } + // emp| + DOMElement nextElement = (DOMElement) next; + if (nextElement.isOnlyEndTag(tagName)) { + return false; + } + return true; + } + if (!node.isElement()) { + return true; + } + DOMElement element = (DOMElement) node; + if (!element.isInStartTag(offset) && !element.isInEndTag(offset)) { + // completion inside element content + // | + // | + // | + return true; + } + if (element.getEnd() <= offset) { + // | + // | + return true; + } + if (element.isClosed() && element.isSameTag(tagName)) { + // + return false; + } + // element is not + List children = element.getChildren(); + for (DOMNode child : children) { + if (child.isElement()) { + DOMElement childElement = (DOMElement) child; + if (childElement.isOnlyEndTag(tagName)) { + return false; + } + } + } + return true; + } + @Override public void onAttributeName(boolean generateValue, ICompletionRequest request, ICompletionResponse response) throws Exception { @@ -356,8 +408,7 @@ public void onXMLContent(ICompletionRequest request, ICompletionResponse respons end = document.positionAt(endOffset); } int completionOffset = request.getOffset(); - String tokenStart = StringUtils.getWhitespaces(document.getText(), startOffset, - completionOffset); + String tokenStart = StringUtils.getWhitespaces(document.getText(), startOffset, completionOffset); Range fullRange = new Range(start, end); values.forEach(value -> { CompletionItem item = new CompletionItem(); 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 3e454932c0..f80031c324 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 @@ -65,10 +65,6 @@ public XMLGenerator(XMLFormattingOptions formattingOptions, boolean autoCloseTag this.canSupportSnippets = canSupportSnippets; } - public String generate(CMElementDeclaration elementDeclaration) { - return generate(elementDeclaration, null); - } - /** * Returns the XML generated from the given element declaration. * @@ -76,20 +72,21 @@ public String generate(CMElementDeclaration elementDeclaration) { * @param prefix * @return the XML generated from the given element declaration. */ - public String generate(CMElementDeclaration elementDeclaration, String prefix) { + public String generate(CMElementDeclaration elementDeclaration, String prefix, boolean generateEndTag) { XMLBuilder xml = new XMLBuilder(formattingOptions, whitespacesIndent, lineDelimiter); - generate(elementDeclaration, prefix, 0, 0, xml, new ArrayList()); + generate(elementDeclaration, prefix, generateEndTag, 0, 0, xml, new ArrayList()); if (canSupportSnippets) { xml.addContent(SnippetsBuilder.tabstops(0)); // "$0" } return xml.toString(); } - private int generate(CMElementDeclaration elementDeclaration, String prefix, int level, int snippetIndex, + private int generate(CMElementDeclaration elementDeclaration, String prefix, boolean generateEndTag, int level, int snippetIndex, XMLBuilder xml, List generatedElements) { if (generatedElements.contains(elementDeclaration)) { return snippetIndex; } + boolean autoCloseTags = this.autoCloseTags && generateEndTag; generatedElements.add(elementDeclaration); if (level > 0) { xml.linefeed(); @@ -106,7 +103,7 @@ private int generate(CMElementDeclaration elementDeclaration, String prefix, int if ((level > maxLevel)) { level++; for (CMElementDeclaration child : children) { - snippetIndex = generate(child, prefix, level, snippetIndex, xml, generatedElements); + snippetIndex = generate(child, prefix, true, level, snippetIndex, xml, generatedElements); } level--; xml.linefeed(); diff --git a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java index 33405e25e3..ec97504d6d 100644 --- a/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java +++ b/org.eclipse.lemminx/src/main/java/org/eclipse/lemminx/services/XMLCompletions.java @@ -495,6 +495,7 @@ private void collectOpenTagSuggestions(boolean hasOpenBracket, Range replaceRang LOGGER.log(Level.SEVERE, "While performing ICompletionParticipant#onTagOpen", e); } } + DOMNode currentElement = completionRequest.getNode(); DOMElement parentNode = completionRequest.getParentElement(); if (parentNode != null && !parentNode.getOwnerDocument().hasGrammar()) { // no grammar, collect similar tags from the parent node @@ -522,7 +523,7 @@ private void collectOpenTagSuggestions(boolean hasOpenBracket, Range replaceRang if (completionRequest.isCompletionSnippetsSupported()) { xml.append("$0"); } - if (completionRequest.isAutoCloseTags()) { + if (!currentElement.isClosed() && completionRequest.isAutoCloseTags()) { xml.append(""); } } 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 f25255aed7..11bc740820 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 @@ -221,7 +221,8 @@ public void noNamespaceSchemaLocationCompletion() throws BadLocationException { " <|"; // Completion only with Name XMLAssert.testCompletionFor(xml, null, "src/test/resources/Format.xml", 5 + 2 /* CDATA and Comments */, - c("OutOfBand", "false"), c("ViewSelectedBy", ""), + c("OutOfBand", "false"), + c("ViewSelectedBy", ""), c("End with ''", "/Configuration>"), c("End with ''", "/ViewDefinitions>"), c("End with ''", "/View>")); } @@ -1004,6 +1005,66 @@ public void tag() throws BadLocationException { } + @Test + public void generateOnlyStartElementOnText() throws BadLocationException { + // already exists, completion must generate only + + // completion on empty text + String xml = "\r\n" + // + "\r\n" + + // + "|"; + testCompletionSnippetSupporytFor(xml, "src/test/resources/choice.xml", 2, // + c("member", te(2, 0, 2, 0, "$1$0"), "member"), // + c("employee", te(2, 0, 2, 0, "$1$0"), "employee")); // <-- here only start employee is + // generated + + // completion on text + xml = "\r\n" + // + "\r\n" + + // + "em|"; + testCompletionSnippetSupporytFor(xml, "src/test/resources/choice.xml", 2, // + c("member", te(2, 0, 2, 2, "$1$0"), "member"), // + c("employee", te(2, 0, 2, 2, "$1$0"), "employee")); // <-- here only start employee is + // generated + } + + @Test + public void generateOnlyStartElementOnElement() throws BadLocationException { + // already exists, completion must generate only + + // completion on start tag + String xml = "\r\n" + // + "\r\n" + + // + "<|"; + testCompletionSnippetSupporytFor(xml, "src/test/resources/choice.xml", 2, // + c("member", te(2, 0, 2, 1, "$1$0"), "$1$0"), "\r\n" + // + "\r\n" + + // + ""; + testCompletionSnippetSupporytFor(xml, "src/test/resources/choice.xml", 2, // + c("member", te(2, 0, 2, 3, "$1$0"), "$1$0"), "\r\n" + // + "\r\n" + + // + ""; + testCompletionSnippetSupporytFor(xml, "src/test/resources/choice.xml", 2, // + c("member", te(2, 0, 2, 10, "$1$0"), "$1$0"), "