update copyright
[fedora-idea.git] / java / java-impl / src / com / intellij / codeInsight / editorActions / moveUpDown / StatementMover.java
blob5775503ab08328e132ba9bc7779451db7dce2c31
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.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;
41 @Override
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) {
50 try {
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();
66 if (down) {
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()));
72 else {
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) {
80 LOG.error(e);
84 @Override
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;
93 info.toMove = range;
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)) {
102 info.toMove2 = null;
103 return true;
105 return true;
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;
112 while (true) {
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)) {
122 found = true;
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
131 found = true;
133 if (found) {
134 statementToSurroundWithCodeBlock = elementToSurround;
135 info.toMove = range;
136 int endLine = line;
137 if (startLine > endLine) {
138 int tmp = endLine;
139 endLine = startLine;
140 startLine = tmp;
143 info.toMove2 = down ? new LineRange(startLine, endLine) : new LineRange(startLine, endLine+1);
144 return true;
147 element = element.getParent();
149 line += down ? 1 : -1;
150 if (line == 0 || line >= editor.getDocument().getLineCount()) {
151 return false;
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())) {
168 return true;
170 if (parent instanceof PsiWhileStatement && element == ((PsiWhileStatement)parent).getBody()) {
171 return true;
173 if (parent instanceof PsiDoWhileStatement && element == ((PsiDoWhileStatement)parent).getBody()) {
174 return true;
176 // know nothing about that
177 return false;
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;
187 do {
188 guard = PsiTreeUtil.getParentOfType(guard, PsiMethod.class, PsiClassInitializer.class, PsiClass.class, PsiComment.class);
190 while (guard instanceof PsiAnonymousClass);
192 PsiElement brace = itIsTheClosingCurlyBraceWeAreMoving(file, editor);
193 if (brace != null) {
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;
205 do {
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;
220 return false;
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));
246 int endLine;
247 if (endOffset == document.getTextLength()) {
248 endLine = document.getLineCount();
250 else {
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('}'));