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.
17 package org
.jetbrains
.plugins
.groovy
.lang
.editor
.actions
.moveUpDown
;
19 import com
.intellij
.openapi
.diagnostic
.Logger
;
20 import com
.intellij
.openapi
.editor
.Document
;
21 import com
.intellij
.openapi
.editor
.Editor
;
22 import com
.intellij
.openapi
.editor
.LogicalPosition
;
23 import com
.intellij
.openapi
.util
.Pair
;
24 import com
.intellij
.openapi
.util
.TextRange
;
25 import com
.intellij
.psi
.PsiComment
;
26 import com
.intellij
.psi
.PsiDocumentManager
;
27 import com
.intellij
.psi
.PsiElement
;
28 import com
.intellij
.psi
.PsiFile
;
29 import com
.intellij
.psi
.impl
.PsiDocumentManagerImpl
;
30 import com
.intellij
.psi
.impl
.source
.tree
.LeafPsiElement
;
31 import com
.intellij
.psi
.util
.PsiTreeUtil
;
32 import org
.jetbrains
.annotations
.Nullable
;
33 import org
.jetbrains
.plugins
.groovy
.lang
.lexer
.GroovyTokenTypes
;
34 import org
.jetbrains
.plugins
.groovy
.lang
.psi
.GroovyFile
;
35 import org
.jetbrains
.plugins
.groovy
.lang
.psi
.api
.statements
.GrBlockStatement
;
36 import org
.jetbrains
.plugins
.groovy
.lang
.psi
.api
.statements
.GrForStatement
;
37 import org
.jetbrains
.plugins
.groovy
.lang
.psi
.api
.statements
.GrIfStatement
;
38 import org
.jetbrains
.plugins
.groovy
.lang
.psi
.api
.statements
.GrWhileStatement
;
39 import org
.jetbrains
.plugins
.groovy
.lang
.psi
.api
.statements
.blocks
.GrClosableBlock
;
40 import org
.jetbrains
.plugins
.groovy
.lang
.psi
.api
.statements
.blocks
.GrCodeBlock
;
41 import org
.jetbrains
.plugins
.groovy
.lang
.psi
.api
.statements
.blocks
.GrOpenBlock
;
42 import org
.jetbrains
.plugins
.groovy
.lang
.psi
.api
.statements
.typedef
.GrTypeDefinition
;
43 import org
.jetbrains
.plugins
.groovy
.lang
.psi
.api
.statements
.typedef
.GrTypeDefinitionBody
;
44 import org
.jetbrains
.plugins
.groovy
.lang
.psi
.api
.statements
.typedef
.members
.GrMembersDeclaration
;
45 import org
.jetbrains
.plugins
.groovy
.lang
.psi
.api
.statements
.typedef
.members
.GrMethod
;
46 import org
.jetbrains
.plugins
.groovy
.lang
.psi
.api
.toplevel
.GrTopStatement
;
47 import org
.jetbrains
.plugins
.groovy
.lang
.psi
.impl
.statements
.expressions
.literals
.GrLiteralImpl
;
48 import org
.jetbrains
.plugins
.groovy
.refactoring
.GroovyRefactoringUtil
;
53 public class StatementMover
extends LineMover
{
55 private static final Logger LOG
= Logger
.getInstance("#org.jetbrains.plugins.groovy.lang.editor.actions.moveUpDown.StatementMover");
57 public StatementMover(boolean down
) {
61 protected boolean checkAvailable(Editor editor
, PsiFile file
) {
62 final boolean available
= super.checkAvailable(editor
, file
);
63 if (!available
) return false;
64 LineRange range
= toMove
;
66 if (editor
== null) return false;
67 final Document document
= editor
.getDocument();
69 range
= expandLineRangeToCoverPsiElements(range
, editor
, file
);
70 if (range
== null) return false;
71 final int startOffset
= editor
.logicalPositionToOffset(new LogicalPosition(range
.startLine
, 0));
72 final int endOffset
= editor
.logicalPositionToOffset(new LogicalPosition(range
.endLine
, 0));
73 final PsiElement
[] statements
= GroovyRefactoringUtil
.findStatementsInRange(file
, startOffset
, endOffset
, false);
74 if (statements
.length
== 0) return false;
75 for (final PsiElement statement
: statements
) {
76 if (statement
instanceof GrMembersDeclaration
) {
77 final GrMembersDeclaration declaration
= (GrMembersDeclaration
)statement
;
78 if (declaration
.getMembers().length
> 0) {
85 range
= toMove
= new LineRange(statements
[0], statements
[statements
.length
- 1], document
);
87 updateComplementaryRange();
89 final PsiElement commonParent
= PsiTreeUtil
.findCommonParent(range
.firstElement
, range
.lastElement
);
91 // cannot move in/outside method/class/comment
92 Class
<?
extends PsiElement
>[] classes
= new Class
[]{GrMethod
.class, GrTypeDefinition
.class, PsiComment
.class, GroovyFile
.class};
93 PsiElement guard
= PsiTreeUtil
.getParentOfType(commonParent
, classes
);
94 if (!calcInsertOffset(file
, editor
, range
)) return false;
95 int insertOffset
= isDown ?
getLineStartSafeOffset(document
, toMove2
.endLine
) - 1 : document
.getLineStartOffset(toMove2
.startLine
);
97 PsiElement newGuard
= file
.getViewProvider().findElementAt(insertOffset
);
98 newGuard
= PsiTreeUtil
.getParentOfType(newGuard
, classes
);
99 if (newGuard
!= null && newGuard
!= guard
&& isInside(insertOffset
, newGuard
) != PsiTreeUtil
.isAncestor(newGuard
, range
.lastElement
, false)) {
100 if (!PsiTreeUtil
.isAncestor(guard
, newGuard
, false)) {
104 PsiElement candidate
= PsiTreeUtil
.getParentOfType(newGuard
, classes
);
105 if (candidate
== null || candidate
== guard
|| !PsiTreeUtil
.isAncestor(guard
, candidate
, false)) {
108 newGuard
= candidate
;
110 toMove2
= new LineRange(newGuard
, newGuard
, document
);
117 private static LineRange
expandLineRangeToCoverPsiElements(final LineRange range
, Editor editor
, final PsiFile file
) {
118 Pair
<PsiElement
, PsiElement
> psiRange
= getElementRange(editor
, file
, range
);
119 if (psiRange
== null) return null;
120 final PsiElement parent
= PsiTreeUtil
.findCommonParent(psiRange
.getFirst(), psiRange
.getSecond());
121 if (parent
instanceof LeafPsiElement
&& parent
.getParent() instanceof GrLiteralImpl
) {
122 return null; //multiline GString
125 Pair
<PsiElement
, PsiElement
> elementRange
= getElementRange(parent
, psiRange
.getFirst(), psiRange
.getSecond());
126 if (elementRange
== null) return null;
127 int endOffset
= elementRange
.getSecond().getTextRange().getEndOffset();
128 Document document
= editor
.getDocument();
129 if (endOffset
> document
.getTextLength()) {
130 LOG
.assertTrue(!PsiDocumentManager
.getInstance(file
.getProject()).isUncommited(document
));
131 LOG
.assertTrue(PsiDocumentManagerImpl
.checkConsistency(file
, document
));
134 if (endOffset
== document
.getTextLength()) {
135 endLine
= document
.getLineCount();
137 endLine
= editor
.offsetToLogicalPosition(endOffset
).line
+ 1;
138 endLine
= Math
.min(endLine
, document
.getLineCount());
140 int startLine
= Math
.min(range
.startLine
, editor
.offsetToLogicalPosition(elementRange
.getFirst().getTextOffset()).line
);
141 endLine
= Math
.max(endLine
, range
.endLine
);
142 return new LineRange(startLine
, endLine
);
145 private static boolean isInside(final int offset
, final PsiElement guard
) {
146 if (guard
== null) return false;
148 TextRange inside
= null;
149 if (guard
instanceof GrMethod
) {
150 GrOpenBlock block
= ((GrMethod
) guard
).getBlock();
151 if (block
!= null) inside
= block
.getTextRange();
152 } else if (guard
instanceof GrClosableBlock
) {
153 inside
= guard
.getTextRange();
154 } else if (guard
instanceof GrTypeDefinition
) {
155 GrTypeDefinitionBody body
= ((GrTypeDefinition
) guard
).getBody();
157 final PsiElement lBrace
= body
.getLBrace();
158 if (lBrace
!= null) {
159 inside
= new TextRange(lBrace
.getTextOffset(), body
.getTextRange().getEndOffset());
164 return inside
!= null && inside
.contains(offset
);
167 private boolean calcInsertOffset(PsiFile file
, final Editor editor
, LineRange range
) {
168 int line
= isDown ? range
.endLine
+ 1 : range
.startLine
- 1;
169 int startLine
= isDown ? range
.endLine
: range
.startLine
- 1;
170 if (line
< 0 || startLine
< 0) return false;
173 final int offset
= editor
.logicalPositionToOffset(new LogicalPosition(line
, 0));
174 if (offset
== file
.getTextLength()) {
178 PsiElement element
= firstNonWhiteElement(offset
, file
, true, true);
180 while (element
!= null && !(element
instanceof PsiFile
)) {
181 if (!element
.getTextRange().grown(-1).shiftRight(1).contains(offset
)) {
182 boolean found
= false;
183 if ((element
instanceof GrTopStatement
|| element
instanceof PsiComment
)
184 && statementCanBePlacedAlong(element
)) {
186 } else if (element
.getNode() != null &&
187 element
.getNode().getElementType() == GroovyTokenTypes
.mRCURLY
) {
188 // before code block closing brace
194 if (startLine
> endLine
) {
201 toMove2
= isDown ?
new LineRange(startLine
, endLine
) : new LineRange(startLine
, endLine
+ 1);
205 element
= element
.getParent();
207 line
+= isDown ?
1 : -1;
208 if (line
== 0 || line
>= editor
.getDocument().getLineCount()) {
214 private static boolean statementCanBePlacedAlong(final PsiElement element
) {
215 if (element
instanceof GrBlockStatement
) return false;
216 final PsiElement parent
= element
.getParent();
217 if (parent
instanceof GrCodeBlock
) return true;
218 if (parent
instanceof GroovyFile
) return true;
219 if (parent
instanceof GrIfStatement
&&
220 (element
== ((GrIfStatement
) parent
).getThenBranch() || element
== ((GrIfStatement
) parent
).getElseBranch())) {
223 if (parent
instanceof GrWhileStatement
&& element
== ((GrWhileStatement
) parent
).getBody()) {
226 if (parent
instanceof GrForStatement
&& element
== ((GrForStatement
) parent
).getBody()) {
229 // know nothing about that