public boolean checkAvailable( @NotNull final Editor editor, @NotNull final PsiFile file, @NotNull final MoveInfo info, final boolean down) { if (!(file instanceof XmlFile)) { return false; } boolean available = super.checkAvailable(editor, file, info, down); if (!available) 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); PsiElement movedEndElement = file.findElementAt(movedLineEnd); if (movedEndElement instanceof PsiWhiteSpace) movedEndElement = PsiTreeUtil.prevLeaf(movedEndElement); PsiElement movedStartElement = file.findElementAt(movedLineStart); if (movedStartElement instanceof PsiWhiteSpace) movedStartElement = PsiTreeUtil.nextLeaf(movedStartElement); if (movedEndElement == null || movedStartElement == null) return false; final PsiNamedElement namedParentAtEnd = PsiTreeUtil.getParentOfType(movedEndElement, PsiNamedElement.class); final PsiNamedElement namedParentAtStart = PsiTreeUtil.getParentOfType(movedStartElement, PsiNamedElement.class); final XmlText text = PsiTreeUtil.getParentOfType(movedStartElement, XmlText.class); final XmlText text2 = PsiTreeUtil.getParentOfType(movedEndElement, XmlText.class); // Let's do not care about injections for this mover if ((text != null && InjectedLanguageUtil.getInjectedPsiFiles(text) != null) || (text2 != null && InjectedLanguageUtil.getInjectedPsiFiles(text2) != null)) { return false; } XmlTag nearestTag = PsiTreeUtil.getParentOfType(movedStartElement, XmlTag.class); if (nearestTag != null && ("script".equals(nearestTag.getLocalName()) || (nearestTag instanceof HtmlTag && "script".equalsIgnoreCase(nearestTag.getLocalName())))) { return false; } PsiNamedElement movedParent = null; if (namedParentAtEnd == namedParentAtStart) movedParent = namedParentAtEnd; else if (namedParentAtEnd instanceof XmlAttribute && namedParentAtStart instanceof XmlTag && namedParentAtEnd.getParent() == namedParentAtStart) { movedParent = namedParentAtStart; } else if (namedParentAtStart instanceof XmlAttribute && namedParentAtEnd instanceof XmlTag && namedParentAtStart.getParent() == namedParentAtEnd) { movedParent = namedParentAtEnd; } if (movedParent == null) { return false; } final TextRange textRange = movedParent.getTextRange(); if (movedParent instanceof XmlTag) { final XmlTag tag = (XmlTag) movedParent; final TextRange valueRange = tag.getValue().getTextRange(); final int valueStart = valueRange.getStartOffset(); if (movedLineStart < valueStart && valueStart + 1 < document.getTextLength()) { movedLineStart = updateMovedRegionEnd(document, movedLineStart, valueStart + 1, info, down); } if (movedLineStart < valueStart) { movedLineStart = updatedMovedRegionStart( document, movedLineStart, tag.getTextRange().getStartOffset(), info, down); } } else if (movedParent instanceof XmlAttribute) { final int endOffset = textRange.getEndOffset() + 1; if (endOffset < document.getTextLength()) movedLineStart = updateMovedRegionEnd(document, movedLineStart, endOffset, info, down); movedLineStart = updatedMovedRegionStart(document, movedLineStart, textRange.getStartOffset(), info, down); } final TextRange moveDestinationRange = new TextRange( document.getLineStartOffset(info.toMove2.startLine), document.getLineStartOffset(info.toMove2.endLine)); if (movedParent instanceof XmlAttribute) { final XmlTag parent = ((XmlAttribute) movedParent).getParent(); if (parent != null) { final TextRange valueRange = parent.getValue().getTextRange(); // Do not move attributes out of tags if ((down && moveDestinationRange.getEndOffset() >= valueRange.getStartOffset()) || (!down && moveDestinationRange.getStartOffset() <= parent.getTextRange().getStartOffset())) { info.toMove2 = null; } } } if (down) { PsiElement updatedElement = file.findElementAt(moveDestinationRange.getEndOffset()); if (updatedElement instanceof PsiWhiteSpace) updatedElement = PsiTreeUtil.prevLeaf(updatedElement); if (updatedElement != null) { final PsiNamedElement namedParent = PsiTreeUtil.getParentOfType(updatedElement, movedParent.getClass()); if (namedParent instanceof XmlTag) { final XmlTag tag = (XmlTag) namedParent; final int offset = tag.isEmpty() ? tag.getTextRange().getStartOffset() : tag.getValue().getTextRange().getStartOffset(); updatedMovedIntoEnd(document, info, offset); } else if (namedParent instanceof XmlAttribute) { updatedMovedIntoEnd(document, info, namedParent.getTextRange().getEndOffset()); } } } else { PsiElement updatedElement = file.findElementAt(moveDestinationRange.getStartOffset()); if (updatedElement instanceof PsiWhiteSpace) updatedElement = PsiTreeUtil.nextLeaf(updatedElement); if (updatedElement != null) { final PsiNamedElement namedParent = PsiTreeUtil.getParentOfType(updatedElement, movedParent.getClass()); if (namedParent instanceof XmlTag) { final XmlTag tag = (XmlTag) namedParent; final TextRange tagValueRange = tag.getValue().getTextRange(); // We need to update destination range to jump over tag start final XmlTag[] subtags = tag.getSubTags(); if ((tagValueRange.contains(movedLineStart) && subtags.length > 0 && subtags[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); } } else if (namedParent instanceof XmlAttribute) { final int line = document.getLineNumber(namedParent.getTextRange().getStartOffset()); final LineRange toMove2 = info.toMove2; info.toMove2 = new LineRange(Math.min(line, toMove2.startLine), toMove2.endLine); } } } return true; }