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
)) {
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
62 ((PsiLanguageInjectionHost
)text2
).getInjectedPsi() != null
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()))
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) {
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())
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());
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
);
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
;