IDEA-51514 Move statement to move lines in multiline Groovy string literals
[fedora-idea.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / lang / editor / actions / moveUpDown / StatementMover.java
blobeb8655355ef654328ee8f4eabf6aab5fbffec83c
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.
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;
50 /**
51 * @author ilyas
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) {
58 super(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) {
79 return false;
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)) {
101 return false;
103 while (true) {
104 PsiElement candidate = PsiTreeUtil.getParentOfType(newGuard, classes);
105 if (candidate == null || candidate == guard || !PsiTreeUtil.isAncestor(guard, candidate, false)) {
106 break;
108 newGuard = candidate;
110 toMove2 = new LineRange(newGuard, newGuard, document);
113 return true;
116 @Nullable
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));
133 int endLine;
134 if (endOffset == document.getTextLength()) {
135 endLine = document.getLineCount();
136 } else {
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();
156 if (body != null) {
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;
172 while (true) {
173 final int offset = editor.logicalPositionToOffset(new LogicalPosition(line, 0));
174 if (offset == file.getTextLength()) {
175 return true;
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)) {
185 found = true;
186 } else if (element.getNode() != null &&
187 element.getNode().getElementType() == GroovyTokenTypes.mRCURLY) {
188 // before code block closing brace
189 found = true;
191 if (found) {
192 toMove = range;
193 int endLine = line;
194 if (startLine > endLine) {
195 int tmp = endLine;
196 endLine = startLine;
197 startLine = tmp;
199 // startLine--;
200 // endLine--;
201 toMove2 = isDown ? new LineRange(startLine, endLine) : new LineRange(startLine, endLine + 1);
202 return true;
205 element = element.getParent();
207 line += isDown ? 1 : -1;
208 if (line == 0 || line >= editor.getDocument().getLineCount()) {
209 return false;
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())) {
221 return true;
223 if (parent instanceof GrWhileStatement && element == ((GrWhileStatement) parent).getBody()) {
224 return true;
226 if (parent instanceof GrForStatement && element == ((GrForStatement) parent).getBody()) {
227 return true;
229 // know nothing about that
230 return false;