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
.codeInsight
.CodeInsightUtil
;
19 import com
.intellij
.codeInsight
.CodeInsightUtilBase
;
20 import com
.intellij
.lang
.StdLanguages
;
21 import com
.intellij
.openapi
.diagnostic
.Logger
;
22 import com
.intellij
.openapi
.editor
.Document
;
23 import com
.intellij
.openapi
.editor
.Editor
;
24 import com
.intellij
.openapi
.editor
.LogicalPosition
;
25 import com
.intellij
.openapi
.editor
.RangeMarker
;
26 import com
.intellij
.openapi
.util
.Pair
;
27 import com
.intellij
.openapi
.util
.TextRange
;
28 import com
.intellij
.psi
.*;
29 import com
.intellij
.psi
.impl
.PsiDocumentManagerImpl
;
30 import com
.intellij
.psi
.impl
.source
.jsp
.jspJava
.JspClassLevelDeclarationStatement
;
31 import com
.intellij
.psi
.impl
.source
.jsp
.jspJava
.JspTemplateStatement
;
32 import com
.intellij
.psi
.util
.PsiTreeUtil
;
33 import com
.intellij
.util
.IncorrectOperationException
;
34 import org
.jetbrains
.annotations
.NotNull
;
36 class StatementMover
extends LineMover
{
37 private static final Logger LOG
= Logger
.getInstance("#com.intellij.openapi.editor.actions.moveUpDown.StatementMover");
39 private PsiElement statementToSurroundWithCodeBlock
;
42 public void beforeMove(@NotNull final Editor editor
, @NotNull final MoveInfo info
, final boolean down
) {
43 super.beforeMove(editor
, info
, down
);
44 if (statementToSurroundWithCodeBlock
!= null) {
45 surroundWithCodeBlock(info
, down
);
49 private void surroundWithCodeBlock(@NotNull final MoveInfo info
, final boolean down
) {
51 final Document document
= PsiDocumentManager
.getInstance(statementToSurroundWithCodeBlock
.getProject()).getDocument(statementToSurroundWithCodeBlock
.getContainingFile());
52 int startOffset
= document
.getLineStartOffset(info
.toMove
.startLine
);
53 int endOffset
= getLineStartSafeOffset(document
, info
.toMove
.endLine
);
54 if (document
.getText().charAt(endOffset
-1) == '\n') endOffset
--;
55 final RangeMarker lineRangeMarker
= document
.createRangeMarker(startOffset
, endOffset
);
57 final PsiElementFactory factory
= JavaPsiFacade
.getInstance(statementToSurroundWithCodeBlock
.getProject()).getElementFactory();
58 PsiCodeBlock codeBlock
= factory
.createCodeBlock();
59 codeBlock
.add(statementToSurroundWithCodeBlock
);
60 final PsiBlockStatement blockStatement
= (PsiBlockStatement
)factory
.createStatementFromText("{}", statementToSurroundWithCodeBlock
);
61 blockStatement
.getCodeBlock().replace(codeBlock
);
62 PsiBlockStatement newStatement
= (PsiBlockStatement
)statementToSurroundWithCodeBlock
.replace(blockStatement
);
63 newStatement
= CodeInsightUtilBase
.forcePsiPostprocessAndRestoreElement(newStatement
);
64 info
.toMove
= new LineRange(document
.getLineNumber(lineRangeMarker
.getStartOffset()), document
.getLineNumber(lineRangeMarker
.getEndOffset())+1);
65 PsiCodeBlock newCodeBlock
= newStatement
.getCodeBlock();
67 PsiElement blockChild
= firstNonWhiteElement(newCodeBlock
.getFirstBodyElement(), true);
68 if (blockChild
== null) blockChild
= newCodeBlock
.getRBrace();
69 info
.toMove2
= new LineRange(info
.toMove2
.startLine
, //document.getLineNumber(newCodeBlock.getParent().getTextRange().getStartOffset()),
70 document
.getLineNumber(blockChild
.getTextRange().getStartOffset()));
73 int start
= document
.getLineNumber(newCodeBlock
.getRBrace().getTextRange().getStartOffset());
74 int end
= info
.toMove
.startLine
;
75 if (start
> end
) end
= start
;
76 info
.toMove2
= new LineRange(start
, end
);
79 catch (IncorrectOperationException e
) {
85 public boolean checkAvailable(@NotNull final Editor editor
, @NotNull final PsiFile file
, @NotNull final MoveInfo info
, final boolean down
) {
86 //if (!(file instanceof PsiJavaFile)) return false;
87 final boolean available
= super.checkAvailable(editor
, file
, info
, down
);
88 if (!available
) return false;
89 LineRange range
= info
.toMove
;
91 range
= expandLineRangeToCoverPsiElements(range
, editor
, file
);
92 if (range
== null) return false;
94 final int startOffset
= editor
.logicalPositionToOffset(new LogicalPosition(range
.startLine
, 0));
95 final int endOffset
= editor
.logicalPositionToOffset(new LogicalPosition(range
.endLine
, 0));
96 final PsiElement
[] statements
= CodeInsightUtil
.findStatementsInRange(file
, startOffset
, endOffset
);
97 if (statements
.length
== 0) return false;
98 range
.firstElement
= statements
[0];
99 range
.lastElement
= statements
[statements
.length
-1];
101 if (!checkMovingInsideOutside(file
, editor
, range
, info
, down
)) {
108 private boolean calcInsertOffset(PsiFile file
, final Editor editor
, LineRange range
, @NotNull final MoveInfo info
, final boolean down
) {
109 int line
= down ? range
.endLine
+1 : range
.startLine
- 1;
110 int startLine
= down ? range
.endLine
: range
.startLine
- 1;
111 if (line
< 0 || startLine
< 0) return false;
113 final int offset
= editor
.logicalPositionToOffset(new LogicalPosition(line
, 0));
114 PsiElement element
= firstNonWhiteElement(offset
, file
, true);
116 while (element
!= null && !(element
instanceof PsiFile
)) {
117 if (!element
.getTextRange().grown(-1).shiftRight(1).contains(offset
)) {
118 PsiElement elementToSurround
= null;
119 boolean found
= false;
120 if ((element
instanceof PsiStatement
|| element
instanceof PsiComment
)
121 && statementCanBePlacedAlong(element
)) {
123 if (!(element
.getParent() instanceof PsiCodeBlock
)) {
124 elementToSurround
= element
;
127 else if (element
instanceof PsiJavaToken
128 && ((PsiJavaToken
)element
).getTokenType() == JavaTokenType
.RBRACE
129 && element
.getParent() instanceof PsiCodeBlock
) {
130 // before code block closing brace
134 statementToSurroundWithCodeBlock
= elementToSurround
;
137 if (startLine
> endLine
) {
143 info
.toMove2
= down ?
new LineRange(startLine
, endLine
) : new LineRange(startLine
, endLine
+1);
147 element
= element
.getParent();
149 line
+= down ?
1 : -1;
150 if (line
== 0 || line
>= editor
.getDocument().getLineCount()) {
156 private static boolean statementCanBePlacedAlong(final PsiElement element
) {
157 if (element
instanceof JspTemplateStatement
) {
158 PsiElement neighbour
= element
.getPrevSibling();
159 // we can place statement inside scriptlet only
160 return neighbour
!= null && !(neighbour
instanceof JspTemplateStatement
);
162 if (element
instanceof PsiBlockStatement
) return false;
163 final PsiElement parent
= element
.getParent();
164 if (parent
instanceof JspClassLevelDeclarationStatement
) return false;
165 if (parent
instanceof PsiCodeBlock
) return true;
166 if (parent
instanceof PsiIfStatement
&&
167 (element
== ((PsiIfStatement
)parent
).getThenBranch() || element
== ((PsiIfStatement
)parent
).getElseBranch())) {
170 if (parent
instanceof PsiWhileStatement
&& element
== ((PsiWhileStatement
)parent
).getBody()) {
173 if (parent
instanceof PsiDoWhileStatement
&& element
== ((PsiDoWhileStatement
)parent
).getBody()) {
176 // know nothing about that
180 private boolean checkMovingInsideOutside(PsiFile file
, final Editor editor
, LineRange range
, @NotNull final MoveInfo info
, final boolean down
) {
181 final int offset
= editor
.getCaretModel().getOffset();
183 PsiElement elementAtOffset
= file
.getViewProvider().findElementAt(offset
, StdLanguages
.JAVA
);
184 if (elementAtOffset
== null) return false;
186 PsiElement guard
= elementAtOffset
;
188 guard
= PsiTreeUtil
.getParentOfType(guard
, PsiMethod
.class, PsiClassInitializer
.class, PsiClass
.class, PsiComment
.class);
190 while (guard
instanceof PsiAnonymousClass
);
192 PsiElement brace
= itIsTheClosingCurlyBraceWeAreMoving(file
, editor
);
194 int line
= editor
.getDocument().getLineNumber(offset
);
195 final LineRange toMove
= new LineRange(line
, line
+ 1);
196 toMove
.firstElement
= toMove
.lastElement
= brace
;
197 info
.toMove
= toMove
;
200 // cannot move in/outside method/class/initializer/comment
201 if (!calcInsertOffset(file
, editor
, info
.toMove
, info
, down
)) return false;
202 int insertOffset
= down ?
getLineStartSafeOffset(editor
.getDocument(), info
.toMove2
.endLine
) : editor
.getDocument().getLineStartOffset(info
.toMove2
.startLine
);
203 PsiElement elementAtInsertOffset
= file
.getViewProvider().findElementAt(insertOffset
, StdLanguages
.JAVA
);
204 PsiElement newGuard
= elementAtInsertOffset
;
206 newGuard
= PsiTreeUtil
.getParentOfType(newGuard
, PsiMethod
.class, PsiClassInitializer
.class, PsiClass
.class, PsiComment
.class);
208 while (newGuard
instanceof PsiAnonymousClass
);
210 if (brace
!= null && PsiTreeUtil
.getParentOfType(brace
, PsiCodeBlock
.class, false) !=
211 PsiTreeUtil
.getParentOfType(elementAtInsertOffset
, PsiCodeBlock
.class, false)) {
212 info
.indentSource
= true;
214 if (newGuard
== guard
&& isInside(insertOffset
, newGuard
) == isInside(offset
, guard
)) return true;
216 // moving in/out nested class is OK
217 if (guard
instanceof PsiClass
&& guard
.getParent() instanceof PsiClass
) return true;
218 if (newGuard
instanceof PsiClass
&& newGuard
.getParent() instanceof PsiClass
) return true;
223 private static boolean isInside(final int offset
, final PsiElement guard
) {
224 if (guard
== null) return false;
225 TextRange inside
= guard
instanceof PsiMethod
226 ?
((PsiMethod
)guard
).getBody().getTextRange()
227 : guard
instanceof PsiClassInitializer
228 ?
((PsiClassInitializer
)guard
).getBody().getTextRange()
229 : guard
instanceof PsiClass ?
new TextRange(((PsiClass
)guard
).getLBrace().getTextOffset(),
230 ((PsiClass
)guard
).getRBrace().getTextOffset()) : guard
.getTextRange();
231 return inside
!= null && inside
.contains(offset
);
234 private static LineRange
expandLineRangeToCoverPsiElements(final LineRange range
, Editor editor
, final PsiFile file
) {
235 Pair
<PsiElement
, PsiElement
> psiRange
= getElementRange(editor
, file
, range
);
236 if (psiRange
== null) return null;
237 final PsiElement parent
= PsiTreeUtil
.findCommonParent(psiRange
.getFirst(), psiRange
.getSecond());
238 Pair
<PsiElement
, PsiElement
> elementRange
= getElementRange(parent
, psiRange
.getFirst(), psiRange
.getSecond());
239 if (elementRange
== null) return null;
240 int endOffset
= elementRange
.getSecond().getTextRange().getEndOffset();
241 Document document
= editor
.getDocument();
242 if (endOffset
> document
.getTextLength()) {
243 LOG
.assertTrue(!PsiDocumentManager
.getInstance(file
.getProject()).isUncommited(document
));
244 LOG
.assertTrue(PsiDocumentManagerImpl
.checkConsistency(file
, document
));
247 if (endOffset
== document
.getTextLength()) {
248 endLine
= document
.getLineCount();
251 endLine
= editor
.offsetToLogicalPosition(endOffset
).line
+1;
252 endLine
= Math
.min(endLine
, document
.getLineCount());
254 int startLine
= Math
.min(range
.startLine
, editor
.offsetToLogicalPosition(elementRange
.getFirst().getTextOffset()).line
);
255 endLine
= Math
.max(endLine
, range
.endLine
);
256 return new LineRange(startLine
, endLine
);
259 private static PsiElement
itIsTheClosingCurlyBraceWeAreMoving(final PsiFile file
, final Editor editor
) {
260 LineRange range
= getLineRangeFromSelection(editor
);
261 if (range
.endLine
- range
.startLine
!= 1) return null;
262 int offset
= editor
.getCaretModel().getOffset();
263 Document document
= editor
.getDocument();
264 int line
= document
.getLineNumber(offset
);
265 int lineStartOffset
= document
.getLineStartOffset(line
);
266 String lineText
= document
.getText().substring(lineStartOffset
, document
.getLineEndOffset(line
));
267 if (!lineText
.trim().equals("}")) return null;
269 return file
.findElementAt(lineStartOffset
+ lineText
.indexOf('}'));