update copyright
[fedora-idea.git] / xml / impl / src / com / intellij / codeInsight / editorActions / moveUpDown / XmlMover.java
blobbca3660aa4b6af0a2da58ce85e1c750ca97527b9
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.codeInsight.editorActions.moveUpDown;
18 import com.intellij.openapi.editor.Document;
19 import com.intellij.openapi.editor.Editor;
20 import com.intellij.openapi.util.TextRange;
21 import com.intellij.psi.*;
22 import com.intellij.psi.html.HtmlTag;
23 import com.intellij.psi.util.PsiTreeUtil;
24 import com.intellij.psi.xml.XmlAttribute;
25 import com.intellij.psi.xml.XmlFile;
26 import com.intellij.psi.xml.XmlTag;
27 import com.intellij.psi.xml.XmlText;
28 import org.jetbrains.annotations.NotNull;
30 class XmlMover extends LineMover {
31 //private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.actions.moveUpDown.XmlMover");
33 public boolean checkAvailable(@NotNull final Editor editor, @NotNull final PsiFile file, @NotNull final MoveInfo info, final boolean down) {
34 if (!(file instanceof XmlFile)) {
35 return false;
37 boolean available = super.checkAvailable(editor, file, info, down);
38 if (!available) return false;
40 // updated moved range end to cover multiline tag start
41 final Document document = editor.getDocument();
42 int movedLineStart = document.getLineStartOffset(info.toMove.startLine);
43 final int movedLineEnd = document.getLineEndOffset(info.toMove.endLine - 1);
45 PsiElement movedEndElement = file.findElementAt(movedLineEnd);
46 if (movedEndElement instanceof PsiWhiteSpace) movedEndElement = PsiTreeUtil.prevLeaf(movedEndElement);
47 PsiElement movedStartElement = file.findElementAt(movedLineStart);
48 if (movedStartElement instanceof PsiWhiteSpace) movedStartElement = PsiTreeUtil.nextLeaf(movedStartElement);
50 if (movedEndElement == null || movedStartElement == null) return false;
51 final PsiNamedElement namedParentAtEnd = PsiTreeUtil.getParentOfType(movedEndElement, PsiNamedElement.class);
52 final PsiNamedElement namedParentAtStart = PsiTreeUtil.getParentOfType(movedStartElement, PsiNamedElement.class);
54 final XmlText text = PsiTreeUtil.getParentOfType(movedStartElement, XmlText.class);
55 final XmlText text2 = PsiTreeUtil.getParentOfType(movedEndElement, XmlText.class);
57 // Let's do not care about injections for this mover
58 if ( ( text != null &&
59 ((PsiLanguageInjectionHost)text).getInjectedPsi() != null
60 ) ||
61 ( text2 != null &&
62 ((PsiLanguageInjectionHost)text2).getInjectedPsi() != null
64 ) {
65 return false;
68 XmlTag nearestTag = PsiTreeUtil.getParentOfType(movedStartElement, XmlTag.class);
69 if (nearestTag != null &&
70 ( "script".equals(nearestTag.getLocalName()) ||
71 (nearestTag instanceof HtmlTag && "script".equalsIgnoreCase(nearestTag.getLocalName()))
73 ) {
74 return false;
77 PsiNamedElement movedParent = null;
79 if (namedParentAtEnd == namedParentAtStart) movedParent = namedParentAtEnd;
80 else if (namedParentAtEnd instanceof XmlAttribute && namedParentAtStart instanceof XmlTag && namedParentAtEnd.getParent() == namedParentAtStart) {
81 movedParent = namedParentAtStart;
82 } else if (namedParentAtStart instanceof XmlAttribute && namedParentAtEnd instanceof XmlTag && namedParentAtStart.getParent() == namedParentAtEnd) {
83 movedParent = namedParentAtEnd;
86 if (movedParent == null) {
87 return false;
90 final TextRange textRange = movedParent.getTextRange();
92 if (movedParent instanceof XmlTag) {
93 final XmlTag tag = (XmlTag)movedParent;
94 final TextRange valueRange = tag.getValue().getTextRange();
95 final int valueStart = valueRange.getStartOffset();
97 if (movedLineStart < valueStart && valueStart + 1 < document.getTextLength()) {
98 movedLineStart = updateMovedRegionEnd(document, movedLineStart, valueStart + 1, info, down);
100 if (movedLineStart < valueStart) {
101 movedLineStart = updatedMovedRegionStart(document, movedLineStart, tag.getTextRange().getStartOffset(), info, down);
103 } else if (movedParent instanceof XmlAttribute) {
104 final int endOffset = textRange.getEndOffset() + 1;
105 if (endOffset < document.getTextLength()) movedLineStart = updateMovedRegionEnd(document, movedLineStart, endOffset, info, down);
106 movedLineStart = updatedMovedRegionStart(document, movedLineStart, textRange.getStartOffset(), info, down);
109 final TextRange moveDestinationRange = new TextRange(
110 document.getLineStartOffset(info.toMove2.startLine),
111 document.getLineStartOffset(info.toMove2.endLine)
114 if (movedParent instanceof XmlAttribute) {
115 final XmlTag parent = ((XmlAttribute)movedParent).getParent();
117 if (parent != null) {
118 final TextRange valueRange = parent.getValue().getTextRange();
120 // Do not move attributes out of tags
121 if ( (down && moveDestinationRange.getEndOffset() >= valueRange.getStartOffset()) ||
122 (!down && moveDestinationRange.getStartOffset() <= parent.getTextRange().getStartOffset())
124 info.toMove2 = null;
129 if (down) {
130 PsiElement updatedElement = file.findElementAt(moveDestinationRange.getEndOffset());
131 if (updatedElement instanceof PsiWhiteSpace) updatedElement = PsiTreeUtil.prevLeaf(updatedElement);
133 if (updatedElement != null) {
134 final PsiNamedElement namedParent = PsiTreeUtil.getParentOfType(updatedElement, movedParent.getClass());
136 if (namedParent instanceof XmlTag) {
137 final XmlTag tag = (XmlTag)namedParent;
138 final int offset = tag.isEmpty() ? tag.getTextRange().getStartOffset() : tag.getValue().getTextRange().getStartOffset();
139 updatedMovedIntoEnd(document, info, offset);
140 } else if (namedParent instanceof XmlAttribute) {
141 updatedMovedIntoEnd(document, info, namedParent.getTextRange().getEndOffset());
144 } else {
145 PsiElement updatedElement = file.findElementAt(moveDestinationRange.getStartOffset());
146 if (updatedElement instanceof PsiWhiteSpace) updatedElement = PsiTreeUtil.nextLeaf(updatedElement);
148 if (updatedElement != null) {
149 final PsiNamedElement namedParent = PsiTreeUtil.getParentOfType(updatedElement, movedParent.getClass());
151 if (namedParent instanceof XmlTag) {
152 final XmlTag tag = (XmlTag)namedParent;
153 final TextRange tagValueRange = tag.getValue().getTextRange();
155 // We need to update destination range to jump over tag start
156 final XmlTag[] subtags = tag.getSubTags();
157 if ((tagValueRange.contains(movedLineStart) && subtags.length > 0 && subtags[0] == movedParent) ||
158 ( tagValueRange.getLength() == 0 && tag.getTextRange().intersects(moveDestinationRange))
160 final int line = document.getLineNumber(tag.getTextRange().getStartOffset());
161 final LineRange toMove2 = info.toMove2;
162 info.toMove2 = new LineRange(Math.min(line, toMove2.startLine), toMove2.endLine);
164 } else if (namedParent instanceof XmlAttribute) {
165 final int line = document.getLineNumber(namedParent.getTextRange().getStartOffset());
166 final LineRange toMove2 = info.toMove2;
167 info.toMove2 = new LineRange(Math.min(line, toMove2.startLine), toMove2.endLine);
172 return true;
175 private void updatedMovedIntoEnd(final Document document, @NotNull final MoveInfo info, final int offset) {
176 if (offset + 1 < document.getTextLength()) {
177 final int line = document.getLineNumber(offset + 1);
178 final LineRange toMove2 = info.toMove2;
179 info.toMove2 = new LineRange(toMove2.startLine, Math.min(Math.max(line, toMove2.endLine), document.getLineCount() - 1));
183 private int updatedMovedRegionStart(final Document document, int movedLineStart, final int offset, @NotNull final MoveInfo info, final boolean down) {
184 final int line = document.getLineNumber(offset);
185 final LineRange toMove = info.toMove;
186 int delta = toMove.startLine - line;
187 info.toMove = new LineRange(Math.min(line, toMove.startLine), toMove.endLine);
189 // update moved range
190 if (delta > 0 && !down) {
191 final LineRange toMove2 = info.toMove2;
192 info.toMove2 = new LineRange(toMove2.startLine - delta, toMove2.endLine - delta);
193 movedLineStart = document.getLineStartOffset(toMove.startLine);
195 return movedLineStart;
198 private int updateMovedRegionEnd(final Document document, int movedLineStart, final int valueStart, @NotNull final MoveInfo info, final boolean down) {
199 final int line = document.getLineNumber(valueStart);
200 final LineRange toMove = info.toMove;
201 int delta = line - toMove.endLine;
202 info.toMove = new LineRange(toMove.startLine, Math.max(line, toMove.endLine));
204 // update moved range
205 if (delta > 0 && down) {
206 final LineRange toMove2 = info.toMove2;
207 info.toMove2 = new LineRange(toMove2.startLine + delta, Math.min(toMove2.endLine + delta, document.getLineCount() - 1));
208 movedLineStart = document.getLineStartOffset(toMove.startLine);
210 return movedLineStart;