| /* |
| * Copyright 2000-2009 JetBrains s.r.o. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.intellij.codeInsight.editorActions.moveUpDown; |
| |
| import com.intellij.codeInsight.CodeInsightUtil; |
| import com.intellij.codeInsight.CodeInsightUtilCore; |
| import com.intellij.lang.StdLanguages; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.LogicalPosition; |
| import com.intellij.openapi.editor.RangeMarker; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.psi.*; |
| import com.intellij.psi.impl.PsiDocumentManagerImpl; |
| import com.intellij.psi.impl.source.jsp.jspJava.JspClassLevelDeclarationStatement; |
| import com.intellij.psi.impl.source.jsp.jspJava.JspTemplateStatement; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.util.IncorrectOperationException; |
| import org.jetbrains.annotations.NotNull; |
| |
| class StatementMover extends LineMover { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.editor.actions.moveUpDown.StatementMover"); |
| |
| private PsiElement statementToSurroundWithCodeBlock; |
| |
| @Override |
| public void beforeMove(@NotNull final Editor editor, @NotNull final MoveInfo info, final boolean down) { |
| super.beforeMove(editor, info, down); |
| if (statementToSurroundWithCodeBlock != null) { |
| surroundWithCodeBlock(info, down); |
| } |
| } |
| |
| private void surroundWithCodeBlock(@NotNull final MoveInfo info, final boolean down) { |
| try { |
| final Document document = PsiDocumentManager.getInstance(statementToSurroundWithCodeBlock.getProject()).getDocument(statementToSurroundWithCodeBlock.getContainingFile()); |
| int startOffset = document.getLineStartOffset(info.toMove.startLine); |
| int endOffset = getLineStartSafeOffset(document, info.toMove.endLine); |
| if (document.getText().charAt(endOffset-1) == '\n') endOffset--; |
| final RangeMarker lineRangeMarker = document.createRangeMarker(startOffset, endOffset); |
| |
| final PsiElementFactory factory = JavaPsiFacade.getInstance(statementToSurroundWithCodeBlock.getProject()).getElementFactory(); |
| PsiCodeBlock codeBlock = factory.createCodeBlock(); |
| codeBlock.add(statementToSurroundWithCodeBlock); |
| final PsiBlockStatement blockStatement = (PsiBlockStatement)factory.createStatementFromText("{}", statementToSurroundWithCodeBlock); |
| blockStatement.getCodeBlock().replace(codeBlock); |
| PsiBlockStatement newStatement = (PsiBlockStatement)statementToSurroundWithCodeBlock.replace(blockStatement); |
| newStatement = CodeInsightUtilCore.forcePsiPostprocessAndRestoreElement(newStatement); |
| info.toMove = new LineRange(document.getLineNumber(lineRangeMarker.getStartOffset()), document.getLineNumber(lineRangeMarker.getEndOffset())+1); |
| PsiCodeBlock newCodeBlock = newStatement.getCodeBlock(); |
| if (down) { |
| PsiElement blockChild = firstNonWhiteElement(newCodeBlock.getFirstBodyElement(), true); |
| if (blockChild == null) blockChild = newCodeBlock.getRBrace(); |
| info.toMove2 = new LineRange(info.toMove2.startLine, //document.getLineNumber(newCodeBlock.getParent().getTextRange().getStartOffset()), |
| document.getLineNumber(blockChild.getTextRange().getStartOffset())); |
| } |
| else { |
| int start = document.getLineNumber(newCodeBlock.getRBrace().getTextRange().getStartOffset()); |
| int end = info.toMove.startLine; |
| if (start > end) end = start; |
| info.toMove2 = new LineRange(start, end); |
| } |
| } |
| catch (IncorrectOperationException e) { |
| LOG.error(e); |
| } |
| } |
| |
| @Override |
| public boolean checkAvailable(@NotNull final Editor editor, @NotNull final PsiFile file, @NotNull final MoveInfo info, final boolean down) { |
| //if (!(file instanceof PsiJavaFile)) return false; |
| final boolean available = super.checkAvailable(editor, file, info, down); |
| if (!available) return false; |
| LineRange range = info.toMove; |
| |
| range = expandLineRangeToCoverPsiElements(range, editor, file); |
| if (range == null) return false; |
| info.toMove = range; |
| final int startOffset = editor.logicalPositionToOffset(new LogicalPosition(range.startLine, 0)); |
| final int endOffset = editor.logicalPositionToOffset(new LogicalPosition(range.endLine, 0)); |
| final PsiElement[] statements = CodeInsightUtil.findStatementsInRange(file, startOffset, endOffset); |
| if (statements.length == 0) return false; |
| range.firstElement = statements[0]; |
| range.lastElement = statements[statements.length-1]; |
| |
| if (!checkMovingInsideOutside(file, editor, range, info, down)) { |
| info.toMove2 = null; |
| return true; |
| } |
| return true; |
| } |
| |
| private int getDestLineForAnon(PsiFile file, Editor editor, LineRange range, MoveInfo info, boolean down) { |
| int destLine = down ? range.endLine+1 : range.startLine - 1; |
| if (!(range.firstElement instanceof PsiStatement)) { |
| return destLine; |
| } |
| PsiElement sibling = |
| StatementUpDownMover.firstNonWhiteElement(down ? range.firstElement.getNextSibling() : range.firstElement.getPrevSibling(), down); |
| PsiElement toMove = sibling; |
| if (!(sibling instanceof PsiStatement)) { |
| return destLine; |
| } |
| if (sibling instanceof PsiDeclarationStatement) { |
| PsiElement[] elements = ((PsiDeclarationStatement)sibling).getDeclaredElements(); |
| if (elements.length == 0) return destLine; |
| sibling = down ? elements[elements.length - 1] : elements[0]; |
| } |
| if (sibling instanceof PsiVariable) { |
| sibling = ((PsiVariable)sibling).getInitializer(); |
| } |
| if (sibling instanceof PsiExpressionStatement) { |
| sibling = ((PsiExpressionStatement)sibling).getExpression(); |
| } |
| if (sibling instanceof PsiNewExpression) { |
| sibling = ((PsiNewExpression)sibling).getAnonymousClass(); |
| } |
| if (!(sibling instanceof PsiClass)) return destLine; |
| destLine = editor.getDocument().getLineNumber(down ? toMove.getTextRange().getEndOffset() : toMove.getTextRange().getStartOffset()); |
| |
| return destLine; |
| } |
| private boolean calcInsertOffset(@NotNull PsiFile file, @NotNull Editor editor, @NotNull LineRange range, @NotNull final MoveInfo info, final boolean down) { |
| int destLine = getDestLineForAnon(file, editor, range, info, down); |
| |
| int startLine = down ? range.endLine : range.startLine - 1; |
| if (destLine < 0 || startLine < 0) return false; |
| while (true) { |
| final int offset = editor.logicalPositionToOffset(new LogicalPosition(destLine, 0)); |
| PsiElement element = firstNonWhiteElement(offset, file, true); |
| |
| while (element != null && !(element instanceof PsiFile)) { |
| if (!element.getTextRange().grown(-1).shiftRight(1).contains(offset)) { |
| PsiElement elementToSurround = null; |
| boolean found = false; |
| if ((element instanceof PsiStatement || element instanceof PsiComment) |
| && statementCanBePlacedAlong(element)) { |
| found = true; |
| if (!(element.getParent() instanceof PsiCodeBlock)) { |
| elementToSurround = element; |
| } |
| } |
| else if (element instanceof PsiJavaToken |
| && ((PsiJavaToken)element).getTokenType() == JavaTokenType.RBRACE |
| && element.getParent() instanceof PsiCodeBlock) { |
| // before code block closing brace |
| found = true; |
| } |
| if (found) { |
| statementToSurroundWithCodeBlock = elementToSurround; |
| info.toMove = range; |
| int endLine = destLine; |
| if (startLine > endLine) { |
| int tmp = endLine; |
| endLine = startLine; |
| startLine = tmp; |
| } |
| |
| info.toMove2 = down ? new LineRange(startLine, endLine) : new LineRange(startLine, endLine+1); |
| return true; |
| } |
| } |
| element = element.getParent(); |
| } |
| destLine += down ? 1 : -1; |
| if (destLine == 0 || destLine >= editor.getDocument().getLineCount()) { |
| return false; |
| } |
| } |
| } |
| |
| private static boolean statementCanBePlacedAlong(final PsiElement element) { |
| if (element instanceof JspTemplateStatement) { |
| PsiElement neighbour = element.getPrevSibling(); |
| // we can place statement inside scriptlet only |
| return neighbour != null && !(neighbour instanceof JspTemplateStatement); |
| } |
| if (element instanceof PsiBlockStatement) return false; |
| final PsiElement parent = element.getParent(); |
| if (parent instanceof JspClassLevelDeclarationStatement) return false; |
| if (parent instanceof PsiCodeBlock) return true; |
| if (parent instanceof PsiIfStatement && |
| (element == ((PsiIfStatement)parent).getThenBranch() || element == ((PsiIfStatement)parent).getElseBranch())) { |
| return true; |
| } |
| if (parent instanceof PsiWhileStatement && element == ((PsiWhileStatement)parent).getBody()) { |
| return true; |
| } |
| if (parent instanceof PsiDoWhileStatement && element == ((PsiDoWhileStatement)parent).getBody()) { |
| return true; |
| } |
| // know nothing about that |
| return false; |
| } |
| |
| private boolean checkMovingInsideOutside(PsiFile file, final Editor editor, LineRange range, @NotNull final MoveInfo info, final boolean down) { |
| final int offset = editor.getCaretModel().getOffset(); |
| |
| PsiElement elementAtOffset = file.getViewProvider().findElementAt(offset, StdLanguages.JAVA); |
| if (elementAtOffset == null) return false; |
| |
| PsiElement guard = elementAtOffset; |
| do { |
| guard = PsiTreeUtil.getParentOfType(guard, PsiMethod.class, PsiClassInitializer.class, PsiClass.class, PsiComment.class); |
| } |
| while (guard instanceof PsiAnonymousClass); |
| |
| PsiElement brace = itIsTheClosingCurlyBraceWeAreMoving(file, editor); |
| if (brace != null) { |
| int line = editor.getDocument().getLineNumber(offset); |
| final LineRange toMove = new LineRange(line, line + 1); |
| toMove.firstElement = toMove.lastElement = brace; |
| info.toMove = toMove; |
| } |
| |
| // cannot move in/outside method/class/initializer/comment |
| if (!calcInsertOffset(file, editor, info.toMove, info, down)) return false; |
| int insertOffset = down ? getLineStartSafeOffset(editor.getDocument(), info.toMove2.endLine) : editor.getDocument().getLineStartOffset(info.toMove2.startLine); |
| PsiElement elementAtInsertOffset = file.getViewProvider().findElementAt(insertOffset, StdLanguages.JAVA); |
| PsiElement newGuard = elementAtInsertOffset; |
| do { |
| newGuard = PsiTreeUtil.getParentOfType(newGuard, PsiMethod.class, PsiClassInitializer.class, PsiClass.class, PsiComment.class); |
| } |
| while (newGuard instanceof PsiAnonymousClass); |
| |
| if (brace != null && PsiTreeUtil.getParentOfType(brace, PsiCodeBlock.class, false) != |
| PsiTreeUtil.getParentOfType(elementAtInsertOffset, PsiCodeBlock.class, false)) { |
| info.indentSource = true; |
| } |
| if (newGuard == guard && isInside(insertOffset, newGuard) == isInside(offset, guard)) return true; |
| |
| // moving in/out nested class is OK |
| if (guard instanceof PsiClass && guard.getParent() instanceof PsiClass) return true; |
| if (newGuard instanceof PsiClass && newGuard.getParent() instanceof PsiClass) return true; |
| |
| return false; |
| } |
| |
| private static boolean isInside(final int offset, final PsiElement guard) { |
| if (guard == null) return false; |
| TextRange inside = guard instanceof PsiMethod |
| ? ((PsiMethod)guard).getBody().getTextRange() |
| : guard instanceof PsiClassInitializer |
| ? ((PsiClassInitializer)guard).getBody().getTextRange() |
| : guard instanceof PsiClass ? new TextRange(((PsiClass)guard).getLBrace().getTextOffset(), |
| ((PsiClass)guard).getRBrace().getTextOffset()) : guard.getTextRange(); |
| return inside != null && inside.contains(offset); |
| } |
| |
| private static LineRange expandLineRangeToCoverPsiElements(final LineRange range, Editor editor, final PsiFile file) { |
| Pair<PsiElement, PsiElement> psiRange = getElementRange(editor, file, range); |
| if (psiRange == null) return null; |
| final PsiElement parent = PsiTreeUtil.findCommonParent(psiRange.getFirst(), psiRange.getSecond()); |
| Pair<PsiElement, PsiElement> elementRange = getElementRange(parent, psiRange.getFirst(), psiRange.getSecond()); |
| if (elementRange == null) return null; |
| int endOffset = elementRange.getSecond().getTextRange().getEndOffset(); |
| Document document = editor.getDocument(); |
| if (endOffset > document.getTextLength()) { |
| LOG.assertTrue(!PsiDocumentManager.getInstance(file.getProject()).isUncommited(document)); |
| LOG.assertTrue(PsiDocumentManagerImpl.checkConsistency(file, document)); |
| } |
| int endLine; |
| if (endOffset == document.getTextLength()) { |
| endLine = document.getLineCount(); |
| } |
| else { |
| endLine = editor.offsetToLogicalPosition(endOffset).line+1; |
| endLine = Math.min(endLine, document.getLineCount()); |
| } |
| int startLine = Math.min(range.startLine, editor.offsetToLogicalPosition(elementRange.getFirst().getTextOffset()).line); |
| endLine = Math.max(endLine, range.endLine); |
| return new LineRange(startLine, endLine); |
| } |
| |
| private static PsiElement itIsTheClosingCurlyBraceWeAreMoving(final PsiFile file, final Editor editor) { |
| LineRange range = getLineRangeFromSelection(editor); |
| if (range.endLine - range.startLine != 1) return null; |
| int offset = editor.getCaretModel().getOffset(); |
| Document document = editor.getDocument(); |
| int line = document.getLineNumber(offset); |
| int lineStartOffset = document.getLineStartOffset(line); |
| String lineText = document.getText().substring(lineStartOffset, document.getLineEndOffset(line)); |
| if (!lineText.trim().equals("}")) return null; |
| |
| return file.findElementAt(lineStartOffset + lineText.indexOf('}')); |
| } |
| } |
| |