boolean (final @NotNull Editor editor, final @NotNull PsiFile file, final @NotNull MoveInfo info, final boolean down) { if (!super.checkAvailable(editor, file, info, down)) return false; // updated moved range end to cover multiline tag start final Document document = editor.getDocument(); int movedLineStart = document.getLineStartOffset(info.toMove.startLine); final int movedLineEnd = document.getLineEndOffset(info.toMove.endLine - 1); XmlElement xmlElementAtStart = getSourceElement(file, movedLineStart, true); XmlElement xmlElementAtEnd = getSourceElement(file, movedLineEnd, false); if (xmlElementAtStart == null || xmlElementAtEnd == null) return false; if (checkInjections(xmlElementAtStart, xmlElementAtEnd)) return false; XmlElement movedParent = null; if (xmlElementAtEnd == xmlElementAtStart) movedParent = xmlElementAtEnd; else if (xmlElementAtEnd instanceof XmlAttribute && xmlElementAtStart instanceof XmlTag && xmlElementAtEnd.getParent() == xmlElementAtStart) { movedParent = xmlElementAtStart; } else if (xmlElementAtStart instanceof XmlAttribute && xmlElementAtEnd instanceof XmlTag && xmlElementAtStart.getParent() == xmlElementAtEnd) { movedParent = xmlElementAtEnd; } if (movedParent == null) { return false; } final TextRange textRange = movedParent.getTextRange(); if (movedParent instanceof XmlTag) { final XmlTag tag = (XmlTag)movedParent; PsiElement parent = tag.getParent(); if (!(parent instanceof XmlTag) && PsiTreeUtil.getChildrenOfType(parent, XmlTag.class).length < 2) { // the only top-level tag return info.prohibitMove(); } final TextRange valueRange = getTagContentRange(tag); final int valueStart = valueRange.getStartOffset(); if (HtmlUtil.isHtmlTag(tag) && (HtmlUtil.isScriptTag(tag) || HtmlUtil.STYLE_TAG_NAME.equals(tag.getName()))) { info.toMove = new LineRange(tag); int nextLine = down ? info.toMove.endLine : info.toMove.startLine - 1; info.toMove2 = new LineRange(nextLine, nextLine + 1); } if (movedLineStart < valueStart && valueStart + 1 < document.getTextLength()) { movedLineStart = updateMovedRegionEnd(document, movedLineStart, valueStart + 1, info, down); } if (movedLineStart < valueStart) { movedLineStart = updateMovedRegionStart(document, movedLineStart, tag.getTextRange().getStartOffset(), info, down); } } else if (movedParent instanceof XmlTagChild || movedParent instanceof XmlAttribute) { final int endOffset = textRange.getEndOffset() + 1; if (endOffset < document.getTextLength()) movedLineStart = updateMovedRegionEnd(document, movedLineStart, endOffset, info, down); movedLineStart = updateMovedRegionStart(document, movedLineStart, textRange.getStartOffset(), info, down); } final TextRange moveDestinationRange = new UnfairTextRange( document.getLineStartOffset(info.toMove2.startLine), document.getLineEndOffset(info.toMove2.endLine - 1) ); if (movedParent instanceof XmlAttribute) { final XmlTag parent = ((XmlAttribute)movedParent).getParent(); if (parent != null) { final TextRange valueRange = getTagContentRange(parent); // Do not move attributes out of tags if ( (down && moveDestinationRange.getEndOffset() >= valueRange.getStartOffset()) || (!down && moveDestinationRange.getStartOffset() <= parent.getTextRange().getStartOffset()) ) { return info.prohibitMove(); } } } if (down) { final XmlElement targetParent = getDestinationElement(file, movedParent, moveDestinationRange.getEndOffset(), false); if (targetParent != null) { if (movedParent instanceof XmlTagChild && targetParent instanceof XmlTag tag) { if (targetParent == movedParent) return false; if (movedParent instanceof XmlTag && moveTags(info, (XmlTag)movedParent, (XmlTag)targetParent, true)) return true; final int offset = tag.isEmpty() ? tag.getTextRange().getStartOffset() : getTagContentRange(tag).getStartOffset(); updatedMovedIntoEnd(document, info, offset); if (tag.isEmpty()) { info.toMove2 = new LineRange(targetParent); } } else if ((movedParent instanceof XmlTagChild && targetParent instanceof XmlTagChild) || targetParent instanceof XmlAttribute) { updatedMovedIntoEnd(document, info, targetParent.getTextRange().getEndOffset()); } } } else { final XmlElement targetParent = getDestinationElement(file, movedParent, moveDestinationRange.getStartOffset(), true); if (targetParent != null) { if (movedParent instanceof XmlTagChild && targetParent instanceof XmlTag tag) { final TextRange tagValueRange = getTagContentRange(tag); // We need to update destination range to jump over tag start final XmlTag[] subtags = tag.getSubTags(); XmlTagChild[] children = tag.getValue().getChildren(); if ((tagValueRange.contains(movedLineStart) && (subtags.length > 0 && subtags[0] == movedParent || children.length > 0 && children[0] == movedParent)) || ( tagValueRange.getLength() == 0 && tag.getTextRange().intersects(moveDestinationRange)) ) { final int line = document.getLineNumber(tag.getTextRange().getStartOffset()); final LineRange toMove2 = info.toMove2; info.toMove2 = new LineRange(Math.min(line, toMove2.startLine), toMove2.endLine); } if (targetParent == movedParent) return false; if (movedParent instanceof XmlTag && moveTags(info, (XmlTag)movedParent, (XmlTag)targetParent, false)) return true; } else if ((movedParent instanceof XmlTagChild && targetParent instanceof XmlTagChild) || targetParent instanceof XmlAttribute) { final int line = document.getLineNumber(targetParent.getTextRange().getStartOffset()); final LineRange toMove2 = info.toMove2; info.toMove2 = new LineRange(Math.min(line, toMove2.startLine), toMove2.endLine); } } } if (movedParent instanceof XmlTagChild) { // it's quite simple after all... info.toMove = new LineRange(movedParent); } return true; }