diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMParser.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMParser.java index 4465fac6b..d688c5aa0 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMParser.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/DOMParser.java @@ -135,13 +135,13 @@ public DOMDocument parse(TextDocument document, URIResolverExtensionManager reso case EndTag: // end tag (ex: ) - String closeTag = scanner.getTokenText().toLowerCase(); + String closeTag = scanner.getTokenText(); DOMNode current = curr; /** eg: will set a,b,c end position to the start of | */ - while (!(curr.isElement() && ((DOMElement) curr).isSameTag(closeTag)) && curr.parent != null) { + while (!(curr.isElement() && ((DOMElement) curr).isSameTag(closeTag.toLowerCase())) && curr.parent != null) { curr.end = endTagOpenOffset; curr = curr.parent; } diff --git a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/parser/XMLScanner.java b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/parser/XMLScanner.java index 6af405655..39a4a1ac3 100644 --- a/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/parser/XMLScanner.java +++ b/org.eclipse.lsp4xml/src/main/java/org/eclipse/lsp4xml/dom/parser/XMLScanner.java @@ -263,7 +263,7 @@ TokenType internalScan() { } return finishToken(offset, TokenType.Unknown); - case WithinTag: + case WithinTag: { if (stream.skipWhitespace()) { return finishToken(offset, TokenType.Whitespace); } @@ -272,18 +272,26 @@ TokenType internalScan() { return finishToken(offset, TokenType.PrologEnd); } - lastAttributeName = nextAttributeName(); - if (lastAttributeName.length() > 0) { - state = ScannerState.AfterAttributeName; - return finishToken(offset, TokenType.AttributeName); - } - + lastAttributeName = nextAttributeName(); + if (lastAttributeName.length() > 0) { + state = ScannerState.AfterAttributeName; + return finishToken(offset, TokenType.AttributeName); + } + if (stream.advanceIfChar(_FSL)) { // / - state = ScannerState.WithinContent; + state = ScannerState.WithinTag; if(stream.advanceIfChar(_RAN)) { // > + state = ScannerState.WithinContent; return finishToken(offset, TokenType.StartTagSelfClose); } + return finishToken(offset, TokenType.Unknown); } + int c = stream.peekChar(); + if (c == _DQO || c == _SIQ) { // " || ' + state = ScannerState.BeforeAttributeValue; + return internalScan(); + } + if (stream.advanceIfChar(_RAN)) { // > state = ScannerState.WithinContent; return finishToken(offset, TokenType.StartTagClose); @@ -294,7 +302,7 @@ TokenType internalScan() { return internalScan(); } return finishToken(offset, TokenType.Unknown); - + } case AfterAttributeName: if (stream.skipWhitespace()) { return finishToken(offset, TokenType.Whitespace); 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 8db21acbd..614da2356 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 @@ -14,6 +14,7 @@ import java.util.Collection; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; @@ -30,6 +31,7 @@ import org.eclipse.lsp4xml.commons.BadLocationException; import org.eclipse.lsp4xml.commons.TextDocument; import org.eclipse.lsp4xml.customservice.AutoCloseTagResponse; +import org.eclipse.lsp4xml.dom.DOMAttr; import org.eclipse.lsp4xml.dom.DOMDocument; import org.eclipse.lsp4xml.dom.DOMElement; import org.eclipse.lsp4xml.dom.DOMNode; @@ -189,7 +191,7 @@ public CompletionList doComplete(DOMDocument xmlDocument, Position position, break; case StartTagSelfClose: if (offset <= scanner.getTokenEnd()) { - if (currentTag != null && currentTag.length() > 0) { + if (currentTag != null && currentTag.length() > 0 && xmlDocument.getText().charAt(offset - 1) == '>') { // if the actual character typed was '>' collectInsideContent(completionRequest, completionResponse); return completionResponse; } @@ -365,13 +367,25 @@ public AutoCloseTagResponse doTagComplete(DOMDocument xmlDocument, Position posi DOMNode node = xmlDocument.findNodeBefore(offset); if(node.isElement() && node.getNodeName() != null) { DOMElement element1 = (DOMElement) node; + Integer slashOffset = element1.endsWith('/', offset); Position end = null; - if(slashOffset != null) { //The typed characted was '/' - Integer closeBracket = element1.isNextChar('>', offset); // After the slash is a close bracket + if(!element1.isInEndTag(offset) && slashOffset != null) { //The typed characted was '/' + List attrList = element1.getAttributeNodes(); + if(attrList != null) { + DOMAttr lastAttr = attrList.get(attrList.size() - 1); + 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 - // Case: ' after slash + // Case: ' after slash + if(element1.getStartTagCloseOffset() != null) { // tag has closing '>', but slash is in incorrect area (not directly before the '>') + return null; + } snippet = ">$0"; if(element1.hasEndTag()) { // Case: try { @@ -383,6 +397,7 @@ public AutoCloseTagResponse doTagComplete(DOMDocument xmlDocument, Position posi } else { DOMNode nextSibling = node.getNextSibling(); + //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())) { diff --git a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/services/XMLCompletionTest.java b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/services/XMLCompletionTest.java index 9783e663b..01ffb1b20 100644 --- a/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/services/XMLCompletionTest.java +++ b/org.eclipse.lsp4xml/src/test/java/org/eclipse/lsp4xml/services/XMLCompletionTest.java @@ -15,6 +15,7 @@ import static org.eclipse.lsp4xml.XMLAssert.testCompletionFor; import static org.eclipse.lsp4xml.XMLAssert.testTagCompletion; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; import java.util.Arrays; @@ -146,6 +147,19 @@ public void testAutoCloseTagCompletionWithRange() { assertAutoCloseEndTagCompletionWithRange("", ">$0", new Range(new Position(0, 3), new Position(0,8))); assertAutoCloseEndTagCompletionWithRange("", ">$0", new Range(new Position(0, 3), new Position(0,8))); assertAutoCloseEndTagCompletionWithRange(" ", ">$0", new Range(new Position(0, 7), new Position(0,13))); + assertAutoCloseEndTagCompletionWithRange("", ">$0", new Range(new Position(0, 13), new Position(0,18))); + assertAutoCloseEndTagCompletionWithRange("", ">$0", new Range(new Position(0, 16), new Position(0,21))); + assertAutoCloseEndTagCompletionWithRange("", ">$0", new Range(new Position(0, 4), new Position(0,10))); + } + + @Test + public void testAutoCloseTagCompletionWithSlashAtBadLocations() { + assertAutoCloseEndTagCompletionWithRange("", null, null); + assertAutoCloseEndTagCompletionWithRange(" ", null, null); + assertAutoCloseEndTagCompletionWithRange(" ", null, null); + assertAutoCloseEndTagCompletionWithRange(" ", null, null); + assertAutoCloseEndTagCompletionWithRange(" ", null, null); + } @Test @@ -154,6 +168,7 @@ public void testAutoCloseEnabledDisabled() throws BadLocationException { testCompletionFor("", true, c("div", "
")); testCompletionFor("
", false, c("div", "
")); testCompletionFor(" ", true, c("div", "
")); + assertAutoCloseEndTagCompletionWithRange("
Text", null, null); } @Test @@ -209,6 +224,11 @@ public void assertAutoCloseEndTagCompletionWithRange(String xmlText, String expe fail("Couldn't get position at offset"); } AutoCloseTagResponse response = languageService.doTagComplete(xmlDocument, position); + if(response == null) { + assertNull(expectedTextEdit); + assertNull(range); + return; + } String completionList = response.snippet; assertEquals(expectedTextEdit, completionList); assertEquals(range, response.range);