RangeMarker (RangeMarker leader, PsiElement element, Document document) { int offset = leader.getStartOffset(); if (offset != leader.getEndOffset() || element == null || offset == 0) return null; XmlTag tag = tryCast(element.getParent(), XmlTag.class); CharSequence contents = document.getCharsSequence(); if (tag != null && tag.getName().isEmpty()) { // Support JSX empty tags PsiElement startTag = tag.getFirstChild(); PsiElement endTag = tag.getLastChild(); if (endTag instanceof XmlToken && endTag.getNode().getElementType() == XmlTokenType.XML_TAG_END) { endTag = PsiTreeUtil.skipWhitespacesBackward(endTag); } if (startTag != endTag && startTag instanceof LeafPsiElement && endTag instanceof LeafPsiElement) { int startTagOffset = startTag.getNode().getStartOffset(); int endTagOffset = endTag.getNode().getStartOffset(); if (startTagOffset + 1 == offset) { return contents.charAt(endTagOffset) == '<' && contents.charAt(endTagOffset + 1) == '/' ? document.createRangeMarker(endTagOffset + 2, endTagOffset + 2, true) : null; } else if (endTagOffset + 2 == offset) { return contents.charAt(startTagOffset) == '<' ? document.createRangeMarker(startTagOffset + 1, startTagOffset + 1, true) : null; } } return null; } TextRange range = doIfNotNull(doIfNotNull(element.getParent(), PsiElement::getParent), PsiElement::getTextRange); if (range == null) return null; int length = contents.length(); char prev = contents.charAt(offset - 1); if (prev == '<') { int nextCharPos = StringUtil.skipWhitespaceForward(contents, offset); if (nextCharPos >= 0 && nextCharPos < length && contents.charAt(nextCharPos) == '>') { // We look for the next opening tag and start to search for the closing empty tag after the end of the next tag's pair int nextTagStart = StringUtil.indexOf(contents, '<', nextCharPos, range.getEndOffset()); int nextTagFirstChar = contents.charAt(StringUtil.skipWhitespaceForward(contents, nextTagStart + 1)); int searchStart = nextCharPos; if (nextTagFirstChar != '/' && nextTagFirstChar != '>') { PsiFile file = element.getContainingFile(); TextRange nextTagsPair = findSupportRange(findNameElement(InjectedLanguageUtilBase.findElementAtNoCommit(file, nextTagStart))); if (nextTagsPair != null && nextTagsPair.getEndOffset() > nextCharPos) { searchStart = nextTagsPair.getEndOffset(); } } int endTagStart = StringUtil.indexOf(contents, '<', searchStart, range.getEndOffset()); int endTagEnd = endTagStart > 0 && endTagStart < length - 1 && contents.charAt(endTagStart + 1) == '/' ? StringUtil.skipWhitespaceForward(contents, endTagStart + 2) : -1; if (endTagEnd > 0 && endTagEnd < length && contents.charAt(endTagEnd) == '>') { // Sync with it return document.createRangeMarker(endTagStart + 2, endTagStart + 2, true); } else { // Otherwise create a self pointing sync to avoid code destruction return document.createRangeMarker(offset, offset, true); } } } else if (prev == '/' && offset >= 2 && contents.charAt(offset - 2) == '<') { int nextCharPos = StringUtil.skipWhitespaceForward(contents, offset); if (nextCharPos >= 0 && nextCharPos < length && contents.charAt(nextCharPos) == '>') { // Try to find matching empty opening tag int startTagEnd = StringUtil.lastIndexOf(contents, '>', range.getStartOffset(), offset - 2); int startTagStart = startTagEnd > 0 ? StringUtil.skipWhitespaceBackward(contents, startTagEnd) - 1 : -1; if (startTagStart > 0 && startTagStart < length && contents.charAt(startTagStart) == '<') { return document.createRangeMarker(startTagStart + 1, startTagStart + 1, true); } else { // Otherwise create a self pointing sync to avoid code destruction return document.createRangeMarker(offset, offset, true); } } } return null; }