private boolean completeEndTag(Project project, Editor editor, PsiFile psiFile) {
    final PsiElement atCaret = getStatementAtCaret(editor, psiFile);
    XmlTag tagAtCaret = PsiTreeUtil.getParentOfType(atCaret, XmlTag.class);
    if (tagAtCaret == null) {
      return false;
    }
    try {
      final ASTNode emptyTagEnd = XmlChildRole.EMPTY_TAG_END_FINDER.findChild(tagAtCaret.getNode());
      final ASTNode endTagEnd = XmlChildRole.START_TAG_END_FINDER.findChild(tagAtCaret.getNode());
      final Document doc = editor.getDocument();
      if (emptyTagEnd == null && endTagEnd == null) {
        int insertionOffset = tagAtCaret.getTextRange().getEndOffset();
        int caretAt = editor.getCaretModel().getOffset();
        final CharSequence text = doc.getCharsSequence();
        final int probableCommaOffset = CharArrayUtil.shiftForward(text, insertionOffset, " \t");
        final PsiElement sibling = tagAtCaret.getNextSibling();
        int caretTo = caretAt;
        char ch;

        if (caretAt < probableCommaOffset) {
          final XmlAttribute xmlAttribute = PsiTreeUtil.getParentOfType(atCaret, XmlAttribute.class, false, XmlTag.class);

          CharSequence tagNameText = null;
          if (xmlAttribute != null) {
            final ASTNode node = tagAtCaret.getNode();
            if (node != null) {
              final ASTNode tagName = XmlChildRole.START_TAG_NAME_FINDER.findChild(node);
              if (tagName != null) {
                tagNameText = tagName.getText();
              }
            }

            final XmlAttributeValue valueElement = xmlAttribute.getValueElement();
            final TextRange textRange = xmlAttribute.getTextRange();
            caretAt = valueElement == null
                      ? textRange.getStartOffset()
                      : getClosingQuote(xmlAttribute).length() == 0 ? textRange.getEndOffset() : caretAt;
          }

          if (tagNameText == null) {
            int start = tagAtCaret.getTextRange().getStartOffset() + 1;
            if (start > caretAt) {
              return false;
            }
            tagNameText = text.subSequence(start, caretAt);
          }

          final PsiElement element = psiFile.findElementAt(probableCommaOffset);
          final XmlTag tag = PsiTreeUtil.getParentOfType(element, XmlTag.class);
          boolean shouldInsertClosingTag = shouldAfterWrapTextWithTag(caretAt, probableCommaOffset) || shouldInsertClosingTag(xmlAttribute, tagAtCaret);
          final CharSequence text2insert = getClosingPart(xmlAttribute, tagAtCaret, !shouldInsertClosingTag);

          if (tag != null && tag.getTextRange().getStartOffset() == probableCommaOffset) {
            doc.insertString(caretAt, text2insert);
            if (shouldInsertClosingTag) {
              doc.insertString(tag.getTextRange().getEndOffset() + text2insert.length(), "</" + tagAtCaret.getName() + ">");
            }

            caretTo = tag.getTextRange().getEndOffset() + text2insert.length();
          }
          else {
            doc.insertString(caretAt, text2insert);
            if (shouldInsertClosingTag) {
              doc.insertString(probableCommaOffset + text2insert.length(), "</" + tagNameText + ">");
            }

            caretTo = probableCommaOffset + text2insert.length();
          }
        }
        else if (sibling instanceof XmlTag && sibling.getTextRange().getStartOffset() == caretAt) {
          final XmlAttribute xmlAttribute = PsiTreeUtil.getParentOfType(atCaret, XmlAttribute.class, false, XmlTag.class);
          final CharSequence text2insert = getClosingPart(xmlAttribute, tagAtCaret, false);

          doc.insertString(caretAt, text2insert);
          if (shouldInsertClosingTag(xmlAttribute, tagAtCaret)) {
            doc.insertString(sibling.getTextRange().getEndOffset() + text2insert.length(), "</" + tagAtCaret.getName() + ">");
          }

          caretTo = sibling.getTextRange().getEndOffset() + text2insert.length();
        }
        else if (probableCommaOffset >= text.length() || ((ch = text.charAt(probableCommaOffset)) != '/' && ch != '>')) {
          final XmlAttribute xmlAttribute = PsiTreeUtil.getParentOfType(atCaret, XmlAttribute.class, false, XmlTag.class);
          final String text2insert = getClosingPart(xmlAttribute, tagAtCaret, true);

          doc.insertString(insertionOffset, text2insert);
          caretTo = insertionOffset + text2insert.indexOf('>') + 1;
        }

        commitChanges(project, editor, psiFile, caretTo, null);
      }
      else {
        final XmlTag unclosedTag = findClosestUnclosedTag(tagAtCaret);
        if (unclosedTag == null) {
          return false;
        }

        final String closingTagString = "</" + unclosedTag.getName() + ">";

        final XmlTag parentTag = unclosedTag.getParentTag();
        final ASTNode parentEndTagNode = parentTag != null ? XmlChildRole.CLOSING_TAG_START_FINDER.findChild(parentTag.getNode()) : null;
        final int offset = parentEndTagNode != null
                           ? parentEndTagNode.getTextRange().getStartOffset()
                           : unclosedTag.getTextRange().getEndOffset();

        doc.insertString(offset, closingTagString);
        commitChanges(project, editor, psiFile, offset, parentTag != null ? parentTag : unclosedTag);
      }
      return true;
    }
    catch (IncorrectOperationException e) {
      LOG.error(e);
    }
    return false;
  }
private boolean completeEndTag(Project project, Editor editor, PsiFile psiFile)