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("").append(tag).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"), "