[groovy] allow to select 'this' reference for 'introduce X' refactoring
[idea/community.git] / plugins / groovy / src / org / jetbrains / plugins / groovy / refactoring / introduce / GrIntroduceHandlerBase.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
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
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16 package org.jetbrains.plugins.groovy.refactoring.introduce;
17
18 import com.intellij.codeInsight.highlighting.HighlightManager;
19 import com.intellij.diagnostic.LogMessageEx;
20 import com.intellij.lang.LanguageRefactoringSupport;
21 import com.intellij.lang.refactoring.RefactoringSupportProvider;
22 import com.intellij.openapi.actionSystem.DataContext;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.command.CommandProcessor;
25 import com.intellij.openapi.diagnostic.Logger;
26 import com.intellij.openapi.editor.Document;
27 import com.intellij.openapi.editor.Editor;
28 import com.intellij.openapi.editor.RangeMarker;
29 import com.intellij.openapi.editor.SelectionModel;
30 import com.intellij.openapi.editor.colors.EditorColors;
31 import com.intellij.openapi.editor.colors.EditorColorsManager;
32 import com.intellij.openapi.editor.markup.RangeHighlighter;
33 import com.intellij.openapi.editor.markup.TextAttributes;
34 import com.intellij.openapi.project.Project;
35 import com.intellij.openapi.util.*;
36 import com.intellij.openapi.wm.WindowManager;
37 import com.intellij.psi.*;
38 import com.intellij.psi.codeStyle.CodeStyleManager;
39 import com.intellij.psi.search.LocalSearchScope;
40 import com.intellij.psi.search.searches.ReferencesSearch;
41 import com.intellij.psi.util.PsiTreeUtil;
42 import com.intellij.refactoring.IntroduceTargetChooser;
43 import com.intellij.refactoring.RefactoringActionHandler;
44 import com.intellij.refactoring.RefactoringBundle;
45 import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer;
46 import com.intellij.refactoring.introduce.inplace.OccurrencesChooser;
47 import com.intellij.refactoring.rename.inplace.InplaceRefactoring;
48 import com.intellij.refactoring.util.CommonRefactoringUtil;
49 import com.intellij.util.Function;
50 import com.intellij.util.IncorrectOperationException;
51 import com.intellij.util.containers.ContainerUtil;
52 import org.jetbrains.annotations.NotNull;
53 import org.jetbrains.annotations.Nullable;
54 import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils;
55 import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase;
56 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
57 import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
58 import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
59 import org.jetbrains.plugins.groovy.lang.psi.api.statements.*;
60 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
61 import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock;
62 import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrCaseLabel;
63 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
64 import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrStringInjection;
65 import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
66 import org.jetbrains.plugins.groovy.lang.psi.api.util.GrDeclarationHolder;
67 import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
68 import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
69 import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass;
70 import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil;
71 import org.jetbrains.plugins.groovy.refactoring.GrRefactoringError;
72 import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle;
73 import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringUtil;
74 import org.jetbrains.plugins.groovy.refactoring.NameValidator;
75
76 import java.util.*;
77
78 /**
79  * Created by Max Medvedev on 10/29/13
80  */
81 public abstract class GrIntroduceHandlerBase<Settings extends GrIntroduceSettings, Scope extends PsiElement> implements RefactoringActionHandler {
82   private static final Logger LOG = Logger.getInstance(GrIntroduceHandlerBase.class);
83
84   public static final Function<GrExpression, String> GR_EXPRESSION_RENDERER = expr -> expr.getText();
85
86   public static GrExpression insertExplicitCastIfNeeded(GrVariable variable, GrExpression initializer) {
87     PsiType ltype = findLValueType(initializer);
88     PsiType rtype = initializer.getType();
89
90     GrExpression rawExpr = (GrExpression)PsiUtil.skipParentheses(initializer, false);
91
92     if (ltype == null || TypesUtil.isAssignableWithoutConversions(ltype, rtype, initializer) || !TypesUtil.isAssignable(ltype, rtype, initializer)) {
93       return rawExpr;
94     }
95     else { // implicit coercion should be replaced with explicit cast
96       GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(variable.getProject());
97       GrSafeCastExpression cast =
98         (GrSafeCastExpression)factory.createExpressionFromText("a as B");
99       cast.getOperand().replaceWithExpression(rawExpr, false);
100       cast.getCastTypeElement().replace(factory.createTypeElement(ltype));
101       return cast;
102     }
103   }
104
105   @Nullable
106   private static PsiType findLValueType(GrExpression initializer) {
107     if (initializer.getParent() instanceof GrAssignmentExpression && ((GrAssignmentExpression)initializer.getParent()).getRValue() == initializer) {
108       return ((GrAssignmentExpression)initializer.getParent()).getLValue().getNominalType();
109     }
110     else if (initializer.getParent() instanceof GrVariable) {
111       return ((GrVariable)initializer.getParent()).getDeclaredType();
112     }
113     else {
114       return null;
115     }
116   }
117
118   @NotNull
119   public static GrStatement getAnchor(@NotNull PsiElement[] occurrences, @NotNull PsiElement scope) {
120     PsiElement parent = PsiTreeUtil.findCommonParent(occurrences);
121     PsiElement container = getEnclosingContainer(parent);
122     assert container != null;
123     PsiElement anchor = findAnchor(occurrences, container);
124
125     assertStatement(anchor, scope);
126     return (GrStatement)anchor;
127   }
128
129   @Nullable
130   public static PsiElement getEnclosingContainer(PsiElement place) {
131     PsiElement parent = place;
132     while (true) {
133       if (parent == null) {
134         return null;
135       }
136       if (parent instanceof GrDeclarationHolder && !(parent instanceof GrClosableBlock && parent.getParent() instanceof GrStringInjection)) {
137         return parent;
138       }
139       if (parent instanceof GrLoopStatement) {
140         return parent;
141       }
142
143       parent = parent.getParent();
144     }
145   }
146
147   @NotNull
148   protected abstract String getRefactoringName();
149
150   @NotNull
151   protected abstract String getHelpID();
152
153   @NotNull
154   protected abstract Scope[] findPossibleScopes(GrExpression expression, GrVariable variable, StringPartInfo stringPart, Editor editor);
155
156   protected abstract void checkExpression(@NotNull GrExpression selectedExpr) throws GrRefactoringError;
157
158   protected abstract void checkVariable(@NotNull GrVariable variable) throws GrRefactoringError;
159
160   protected abstract void checkStringLiteral(@NotNull StringPartInfo info) throws GrRefactoringError;
161
162   protected abstract void checkOccurrences(@NotNull PsiElement[] occurrences);
163
164   @NotNull
165   protected abstract GrIntroduceDialog<Settings> getDialog(@NotNull GrIntroduceContext context);
166
167   @Nullable
168   public abstract GrVariable runRefactoring(@NotNull GrIntroduceContext context, @NotNull Settings settings);
169
170   protected abstract GrAbstractInplaceIntroducer<Settings> getIntroducer(@NotNull GrIntroduceContext context,
171                                                                          @NotNull OccurrencesChooser.ReplaceChoice choice);
172
173   public static Map<OccurrencesChooser.ReplaceChoice, List<Object>> fillChoice(GrIntroduceContext context) {
174     HashMap<OccurrencesChooser.ReplaceChoice, List<Object>> map = ContainerUtil.newLinkedHashMap();
175
176     if (context.getExpression() != null) {
177       map.put(OccurrencesChooser.ReplaceChoice.NO, Collections.<Object>singletonList(context.getExpression()));
178     }
179     else if (context.getStringPart() != null) {
180       map.put(OccurrencesChooser.ReplaceChoice.NO, Collections.<Object>singletonList(context.getStringPart()));
181       return map;
182     }
183     else if (context.getVar() != null) {
184       map.put(OccurrencesChooser.ReplaceChoice.ALL, Collections.<Object>singletonList(context.getVar()));
185       return map;
186     }
187
188     PsiElement[] occurrences = context.getOccurrences();
189     if (occurrences.length > 1) {
190       map.put(OccurrencesChooser.ReplaceChoice.ALL, Arrays.<Object>asList(occurrences));
191     }
192     return map;
193   }
194
195   @NotNull
196   public static List<GrExpression> collectExpressions(final PsiFile file, final Editor editor, final int offset, boolean acceptVoidCalls) {
197     int correctedOffset = correctOffset(editor, offset);
198     final PsiElement elementAtCaret = file.findElementAt(correctedOffset);
199     return collectExpressions(elementAtCaret, acceptVoidCalls);
200   }
201
202   @NotNull
203   public static List<GrExpression> collectExpressions(PsiElement elementAtCaret, boolean acceptVoidCalls) {
204     final List<GrExpression> expressions = new ArrayList<>();
205
206     for (GrExpression expression = PsiTreeUtil.getParentOfType(elementAtCaret, GrExpression.class);
207          expression != null;
208          expression = PsiTreeUtil.getParentOfType(expression, GrExpression.class)) {
209       if (expressions.contains(expression)) continue;
210       if (expression instanceof GrParenthesizedExpression && !expressions.contains(((GrParenthesizedExpression)expression).getOperand())) {
211         expressions.add(((GrParenthesizedExpression)expression).getOperand());
212       }
213       if (expressionIsIncorrect(expression, acceptVoidCalls)) continue;
214
215       expressions.add(expression);
216     }
217     return expressions;
218   }
219
220   public static boolean expressionIsIncorrect(@Nullable GrExpression expression, boolean acceptVoidCalls) {
221     if (expression instanceof GrParenthesizedExpression) return true;
222     if (PsiUtil.isSuperReference(expression)) return true;
223     if (expression instanceof GrAssignmentExpression) return true;
224     if (expression instanceof GrReferenceExpression && expression.getParent() instanceof GrCall) {
225       final GroovyResolveResult resolveResult = ((GrReferenceExpression)expression).advancedResolve();
226       final PsiElement resolved = resolveResult.getElement();
227       return resolved instanceof PsiMethod && !resolveResult.isInvokedOnProperty() || resolved instanceof PsiClass;
228     }
229     if (expression instanceof GrReferenceExpression && expression.getParent() instanceof GrReferenceExpression) {
230       return !PsiUtil.isThisReference(expression) && ((GrReferenceExpression)expression).resolve() instanceof PsiClass;
231     }
232     if (expression instanceof GrClosableBlock && expression.getParent() instanceof GrStringInjection) return true;
233     if (!acceptVoidCalls && expression instanceof GrMethodCall && PsiType.VOID.equals(expression.getType())) return true;
234
235     return false;
236   }
237
238   public static int correctOffset(Editor editor, int offset) {
239     Document document = editor.getDocument();
240     CharSequence text = document.getCharsSequence();
241     int correctedOffset = offset;
242     int textLength = document.getTextLength();
243     if (offset >= textLength) {
244       correctedOffset = textLength - 1;
245     }
246     else if (!Character.isJavaIdentifierPart(text.charAt(offset))) {
247       correctedOffset--;
248     }
249
250     if (correctedOffset < 0) {
251       correctedOffset = offset;
252     }
253     else {
254       char c = text.charAt(correctedOffset);
255       if (c == ';' && correctedOffset != 0) {//initially caret on the end of line
256         correctedOffset--;
257       }
258       else if (!Character.isJavaIdentifierPart(c) && c != ')' && c != ']' && c != '}' && c != '\'' && c != '"' && c != '/') {
259         correctedOffset = offset;
260       }
261     }
262     return correctedOffset;
263   }
264
265   @Nullable
266   public static GrVariable findVariableAtCaret(final PsiFile file, final Editor editor, final int offset) {
267     final int correctOffset = correctOffset(editor, offset);
268     final PsiElement elementAtCaret = file.findElementAt(correctOffset);
269     final GrVariable variable = PsiTreeUtil.getParentOfType(elementAtCaret, GrVariable.class);
270     if (variable != null && variable.getNameIdentifierGroovy().getTextRange().contains(correctOffset)) return variable;
271     return null;
272   }
273
274   @Override
275   public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file, @Nullable final DataContext dataContext) {
276     final SelectionModel selectionModel = editor.getSelectionModel();
277     if (!selectionModel.hasSelection()) {
278       final int offset = editor.getCaretModel().getOffset();
279
280       final List<GrExpression> expressions = collectExpressions(file, editor, offset, false);
281       if (expressions.isEmpty()) {
282         updateSelectionForVariable(editor, file, selectionModel, offset);
283       }
284       else if (expressions.size() == 1 || ApplicationManager.getApplication().isUnitTestMode()) {
285         final TextRange textRange = expressions.get(0).getTextRange();
286         selectionModel.setSelection(textRange.getStartOffset(), textRange.getEndOffset());
287       }
288       else {
289         IntroduceTargetChooser.showChooser(editor, expressions, new Pass<GrExpression>() {
290           @Override
291           public void pass(final GrExpression selectedValue) {
292             invoke(project, editor, file, selectedValue.getTextRange().getStartOffset(), selectedValue.getTextRange().getEndOffset());
293           }
294         }, GR_EXPRESSION_RENDERER);
295         return;
296       }
297     }
298     invoke(project, editor, file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd());
299   }
300
301   public static void updateSelectionForVariable(Editor editor, PsiFile file, SelectionModel selectionModel, int offset) {
302     final GrVariable variable = findVariableAtCaret(file, editor, offset);
303     if (variable == null || variable instanceof GrField || variable instanceof GrParameter) {
304       selectionModel.selectLineAtCaret();
305     }
306     else {
307       final TextRange textRange = variable.getTextRange();
308       selectionModel.setSelection(textRange.getStartOffset(), textRange.getEndOffset());
309     }
310   }
311
312   @Override
313   public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
314     // Does nothing
315   }
316
317   public void getContextAndInvoke(@NotNull final Project project,
318                                   @NotNull final Editor editor,
319                                   @Nullable final GrExpression expression,
320                                   @Nullable final GrVariable variable,
321                                   @Nullable final StringPartInfo stringPart) {
322     final Scope[] scopes = findPossibleScopes(expression, variable, stringPart, editor);
323
324     Pass<Scope> callback = new Pass<Scope>() {
325       @Override
326       public void pass(Scope scope) {
327         GrIntroduceContext context = getContext(project, editor, expression, variable, stringPart, scope);
328         invokeImpl(project, context, editor);
329       }
330     };
331
332     if (scopes.length == 0) {
333       CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle
334         .getCannotRefactorMessage(getRefactoringName() + "is not available in current scope"),
335                                           getRefactoringName(), getHelpID());
336     }
337     else if (scopes.length == 1) {
338       callback.pass(scopes[0]);
339     }
340     else {
341       showScopeChooser(scopes, callback, editor);
342     }
343   }
344
345   protected void extractStringPart(final Ref<GrIntroduceContext> ref) {
346     CommandProcessor.getInstance().executeCommand(ref.get().getProject(), () -> ApplicationManager.getApplication().runWriteAction(() -> {
347       GrIntroduceContext context = ref.get();
348
349       StringPartInfo stringPart = context.getStringPart();
350       assert stringPart != null;
351
352       GrExpression expression = stringPart.replaceLiteralWithConcatenation(null);
353
354       ref.set(new GrIntroduceContextImpl(context.getProject(), context.getEditor(), expression, null, null, new PsiElement[]{expression}, context.getScope()));
355     }), getRefactoringName(), getRefactoringName());
356   }
357
358   protected void addBraces(@NotNull final GrStatement anchor, @NotNull final Ref<GrIntroduceContext> contextRef) {
359     CommandProcessor.getInstance().executeCommand(contextRef.get().getProject(), () -> ApplicationManager.getApplication().runWriteAction(() -> {
360       GrIntroduceContext context = contextRef.get();
361       SmartPointerManager pointManager = SmartPointerManager.getInstance(context.getProject());
362       SmartPsiElementPointer<GrExpression> expressionRef = context.getExpression() != null ? pointManager.createSmartPsiElementPointer(context.getExpression()) : null;
363       SmartPsiElementPointer<GrVariable> varRef = context.getVar() != null ? pointManager.createSmartPsiElementPointer(context.getVar()) : null;
364
365       SmartPsiElementPointer[] occurrencesRefs = new SmartPsiElementPointer[context.getOccurrences().length];
366       PsiElement[] occurrences = context.getOccurrences();
367       for (int i = 0; i < occurrences.length; i++) {
368         occurrencesRefs[i] = pointManager.createSmartPsiElementPointer(occurrences[i]);
369       }
370
371
372       PsiFile file = anchor.getContainingFile();
373       SmartPsiFileRange anchorPointer = pointManager.createSmartPsiFileRangePointer(file, anchor.getTextRange());
374
375       Document document = context.getEditor().getDocument();
376       CharSequence sequence = document.getCharsSequence();
377
378       TextRange range = anchor.getTextRange();
379
380       int end = range.getEndOffset();
381       document.insertString(end, "\n}");
382
383       int start = range.getStartOffset();
384       while (start > 0 && Character.isWhitespace(sequence.charAt(start - 1))) {
385         start--;
386       }
387       document.insertString(start, "{");
388
389       PsiDocumentManager.getInstance(context.getProject()).commitDocument(document);
390
391       Segment anchorSegment = anchorPointer.getRange();
392       PsiElement restoredAnchor = PsiImplUtil
393         .findElementInRange(file, anchorSegment.getStartOffset(), anchorSegment.getEndOffset(), PsiElement.class);
394       GrCodeBlock block = (GrCodeBlock)restoredAnchor.getParent();
395       CodeStyleManager.getInstance(context.getProject()).reformat(block.getRBrace());
396       CodeStyleManager.getInstance(context.getProject()).reformat(block.getLBrace());
397
398       for (int i = 0; i < occurrencesRefs.length; i++) {
399         occurrences[i] = occurrencesRefs[i].getElement();
400       }
401
402       contextRef.set(new GrIntroduceContextImpl(context.getProject(), context.getEditor(),
403                                                 expressionRef != null ? expressionRef.getElement() : null,
404                                                 varRef != null ? varRef.getElement() : null,
405                                                 null, occurrences, context.getScope()));
406     }), getRefactoringName(), getRefactoringName());
407   }
408
409   @NotNull
410   protected static GrStatement findAnchor(@NotNull final GrIntroduceContext context, final boolean replaceAll) {
411     return ApplicationManager.getApplication().runReadAction(new Computable<GrStatement>() {
412       @Override
413       public GrStatement compute() {
414         PsiElement[] occurrences = replaceAll ? context.getOccurrences() : new GrExpression[]{context.getExpression()};
415         return getAnchor(occurrences, context.getScope());
416       }
417     });
418   }
419
420   protected abstract void showScopeChooser(Scope[] scopes, Pass<Scope> callback, Editor editor);
421
422   public GrIntroduceContext getContext(@NotNull Project project,
423                                        @NotNull Editor editor,
424                                        @Nullable GrExpression expression,
425                                        @Nullable GrVariable variable,
426                                        @Nullable StringPartInfo stringPart,
427                                        @NotNull PsiElement scope) {
428     if (variable != null) {
429       final PsiElement[] occurrences = collectVariableUsages(variable, scope);
430       return new GrIntroduceContextImpl(project, editor, null, variable, stringPart, occurrences, scope);
431     }
432     else if (expression != null ) {
433       final PsiElement[] occurrences = findOccurrences(expression, scope);
434       return new GrIntroduceContextImpl(project, editor, expression, variable, stringPart, occurrences, scope);
435     }
436     else {
437       assert stringPart != null;
438       return new GrIntroduceContextImpl(project, editor, expression, variable, stringPart, new PsiElement[]{stringPart.getLiteral()}, scope);
439     }
440   }
441
442   public static PsiElement[] collectVariableUsages(GrVariable variable, PsiElement scope) {
443     final List<PsiElement> list = Collections.synchronizedList(new ArrayList<PsiElement>());
444     if (scope instanceof GroovyScriptClass) {
445       scope = scope.getContainingFile();
446     }
447     ReferencesSearch.search(variable, new LocalSearchScope(scope)).forEach(psiReference -> {
448       final PsiElement element = psiReference.getElement();
449       if (element != null) {
450         list.add(element);
451       }
452       return true;
453     });
454     return list.toArray(new PsiElement[list.size()]);
455   }
456
457   private boolean invokeImpl(final Project project, final GrIntroduceContext context, final Editor editor) {
458     try {
459       if (!CommonRefactoringUtil.checkReadOnlyStatus(project, context.getOccurrences())) {
460         return false;
461       }
462       checkOccurrences(context.getOccurrences());
463
464
465       if (isInplace(context.getEditor(), context.getPlace())) {
466         Map<OccurrencesChooser.ReplaceChoice, List<Object>> occurrencesMap = getOccurrenceOptions(context);
467         new IntroduceOccurrencesChooser(editor).showChooser(new Pass<OccurrencesChooser.ReplaceChoice>() {
468           @Override
469           public void pass(final OccurrencesChooser.ReplaceChoice choice) {
470             getIntroducer(context, choice).startInplaceIntroduceTemplate();
471           }
472         }, occurrencesMap);
473       }
474       else {
475         final Settings settings = showDialog(context);
476         if (settings == null) return false;
477
478         CommandProcessor.getInstance().executeCommand(context.getProject(), () -> ApplicationManager.getApplication().runWriteAction(() -> {
479           runRefactoring(context, settings);
480         }), getRefactoringName(), null);
481       }
482
483       return true;
484     }
485     catch (GrRefactoringError e) {
486       CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle.getCannotRefactorMessage(e.getMessage()), getRefactoringName(), getHelpID());
487       return false;
488     }
489   }
490
491   @NotNull
492   protected Map<OccurrencesChooser.ReplaceChoice, List<Object>> getOccurrenceOptions(@NotNull GrIntroduceContext context) {
493     return fillChoice(context);
494   }
495
496   @NotNull
497   protected PsiElement[] findOccurrences(@NotNull GrExpression expression, @NotNull PsiElement scope) {
498     final PsiElement[] occurrences = GroovyRefactoringUtil.getExpressionOccurrences(PsiUtil.skipParentheses(expression, false), scope);
499     if (occurrences == null || occurrences.length == 0) {
500       throw new GrRefactoringError(GroovyRefactoringBundle.message("no.occurrences.found"));
501     }
502     return occurrences;
503   }
504
505   private void invoke(@NotNull final Project project,
506                       @NotNull final Editor editor,
507                       @NotNull PsiFile file,
508                       int startOffset,
509                       int endOffset) throws GrRefactoringError {
510     try {
511       PsiDocumentManager.getInstance(project).commitAllDocuments();
512       if (!(file instanceof GroovyFileBase)) {
513         throw new GrRefactoringError(GroovyRefactoringBundle.message("only.in.groovy.files"));
514       }
515       if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) {
516         throw new GrRefactoringError(RefactoringBundle.message("readonly.occurences.found"));
517       }
518
519       GrExpression selectedExpr = findExpression(file, startOffset, endOffset);
520       final GrVariable variable = findVariable(file, startOffset, endOffset);
521       final StringPartInfo stringPart = StringPartInfo.findStringPart(file, startOffset, endOffset);
522       if (variable != null) {
523         checkVariable(variable);
524       }
525       else if (selectedExpr != null) {
526         checkExpression(selectedExpr);
527       }
528       else if (stringPart != null) {
529         checkStringLiteral(stringPart);
530       }
531       else {
532         throw new GrRefactoringError(null);
533       }
534
535       getContextAndInvoke(project, editor, selectedExpr, variable, stringPart);
536     }
537     catch (GrRefactoringError e) {
538       CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle.getCannotRefactorMessage(e.getMessage()), getRefactoringName(), getHelpID());
539     }
540   }
541
542   public static RangeMarker createRange(Document document, StringPartInfo part) {
543     if (part == null) {
544       return null;
545     }
546     TextRange range = part.getRange().shiftRight(part.getLiteral().getTextRange().getStartOffset());
547     return document.createRangeMarker(range.getStartOffset(), range.getEndOffset(), true);
548
549   }
550
551   @Nullable
552   public static RangeMarker createRange(@NotNull Document document, @Nullable PsiElement expression) {
553     if (expression == null) {
554       return null;
555     }
556     TextRange range = expression.getTextRange();
557     return document.createRangeMarker(range.getStartOffset(), range.getEndOffset(), false);
558   }
559
560
561   public static boolean isInplace(@NotNull Editor editor, @NotNull PsiElement place) {
562     final RefactoringSupportProvider supportProvider = LanguageRefactoringSupport.INSTANCE.forLanguage(place.getLanguage());
563     return supportProvider != null &&
564            (editor.getUserData(InplaceRefactoring.INTRODUCE_RESTART) == null || !editor.getUserData(InplaceRefactoring.INTRODUCE_RESTART)) &&
565            editor.getUserData(AbstractInplaceIntroducer.ACTIVE_INTRODUCE) == null &&
566            editor.getSettings().isVariableInplaceRenameEnabled() &&
567            supportProvider.isInplaceIntroduceAvailable(place, place) &&
568            !ApplicationManager.getApplication().isUnitTestMode();
569   }
570
571   @Nullable
572   public static GrVariable findVariable(@NotNull PsiFile file, int startOffset, int endOffset) {
573     GrVariable var = PsiImplUtil.findElementInRange(file, startOffset, endOffset, GrVariable.class);
574     if (var == null) {
575       final GrVariableDeclaration variableDeclaration =
576         PsiImplUtil.findElementInRange(file, startOffset, endOffset, GrVariableDeclaration.class);
577       if (variableDeclaration == null) return null;
578       final GrVariable[] variables = variableDeclaration.getVariables();
579       if (variables.length == 1) {
580         var = variables[0];
581       }
582     }
583     if (var instanceof GrParameter || var instanceof GrField) {
584       return null;
585     }
586     return var;
587   }
588
589   @Nullable
590   public static GrVariable findVariable(@NotNull GrStatement statement) {
591     if (!(statement instanceof GrVariableDeclaration)) return null;
592     final GrVariableDeclaration variableDeclaration = (GrVariableDeclaration)statement;
593     final GrVariable[] variables = variableDeclaration.getVariables();
594
595     GrVariable var = null;
596     if (variables.length == 1) {
597       var = variables[0];
598     }
599     if (var instanceof GrParameter || var instanceof GrField) {
600       return null;
601     }
602     return var;
603   }
604
605
606   @Nullable
607   public static GrExpression findExpression(PsiFile file, int startOffset, int endOffset) {
608     GrExpression selectedExpr = PsiImplUtil.findElementInRange(file, startOffset, endOffset, GrExpression.class);
609     return findExpression(selectedExpr);
610   }
611
612   @Nullable
613   public static GrExpression findExpression(GrStatement selectedExpr) {
614     if (!(selectedExpr instanceof GrExpression)) return null;
615
616     GrExpression selected = (GrExpression)selectedExpr;
617     while (selected instanceof GrParenthesizedExpression) selected = ((GrParenthesizedExpression)selected).getOperand();
618
619     return selected;
620   }
621
622   @Nullable
623   private Settings showDialog(@NotNull GrIntroduceContext context) {
624
625     // Add occurrences highlighting
626     ArrayList<RangeHighlighter> highlighters = new ArrayList<>();
627     HighlightManager highlightManager = null;
628     if (context.getEditor() != null) {
629       highlightManager = HighlightManager.getInstance(context.getProject());
630       EditorColorsManager colorsManager = EditorColorsManager.getInstance();
631       TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES);
632       if (context.getOccurrences().length > 1) {
633         highlightManager.addOccurrenceHighlights(context.getEditor(), context.getOccurrences(), attributes, true, highlighters);
634       }
635     }
636
637     GrIntroduceDialog<Settings> dialog = getDialog(context);
638
639     dialog.show();
640     if (dialog.isOK()) {
641       if (context.getEditor() != null) {
642         for (RangeHighlighter highlighter : highlighters) {
643           highlightManager.removeSegmentHighlighter(context.getEditor(), highlighter);
644         }
645       }
646       return dialog.getSettings();
647     }
648     else {
649       if (context.getOccurrences().length > 1) {
650         WindowManager.getInstance().getStatusBar(context.getProject())
651           .setInfo(GroovyRefactoringBundle.message("press.escape.to.remove.the.highlighting"));
652       }
653     }
654     return null;
655   }
656
657   @Nullable
658   public static PsiElement findAnchor(@NotNull PsiElement[] occurrences,
659                                       @NotNull PsiElement container) {
660     if (occurrences.length == 0) return null;
661
662     PsiElement candidate;
663     if (occurrences.length == 1) {
664       candidate = findContainingStatement(occurrences[0]);
665     }
666     else {
667       candidate = occurrences[0];
668       while (candidate != null && candidate.getParent() != container) {
669         candidate = candidate.getParent();
670       }
671     }
672
673     final GrStringInjection injection = PsiTreeUtil.getParentOfType(candidate, GrStringInjection.class);
674     if (injection != null && !injection.getText().contains("\n")) {
675       candidate = findContainingStatement(injection);
676     }
677
678     if (candidate == null) return null;
679
680     if ((container instanceof GrWhileStatement) &&
681         candidate.equals(((GrWhileStatement)container).getCondition())) {
682       return container;
683     }
684     if ((container instanceof GrIfStatement) &&
685         candidate.equals(((GrIfStatement)container).getCondition())) {
686       return container;
687     }
688     if ((container instanceof GrForStatement) &&
689         candidate.equals(((GrForStatement)container).getClause())) {
690       return container;
691     }
692
693     while (candidate instanceof GrIfStatement &&
694            candidate.getParent() instanceof GrIfStatement &&
695            ((GrIfStatement)candidate.getParent()).getElseBranch() == candidate) {
696       candidate = candidate.getParent();
697     }
698     return candidate;
699   }
700
701   public static void assertStatement(@Nullable PsiElement anchor, @NotNull PsiElement scope) {
702     if (!(anchor instanceof GrStatement)) {
703       LogMessageEx.error(LOG, "cannot find anchor for variable", scope.getText());
704     }
705   }
706
707   @Nullable
708   private static PsiElement findContainingStatement(@Nullable PsiElement candidate) {
709     while (candidate != null && (candidate.getParent() instanceof GrLabeledStatement || !(PsiUtil.isExpressionStatement(candidate)))) {
710       candidate = candidate.getParent();
711       if (candidate instanceof GrCaseLabel) candidate = candidate.getParent();
712     }
713     return candidate;
714   }
715
716   public static void deleteLocalVar(GrVariable var) {
717     final PsiElement parent = var.getParent();
718     if (((GrVariableDeclaration)parent).getVariables().length == 1) {
719       parent.delete();
720     }
721     else {
722       GrExpression initializer = var.getInitializerGroovy();
723       if (initializer != null) initializer.delete(); //don't special check for tuple, but this line is for the tuple case
724       var.delete();
725     }
726   }
727
728   @Nullable
729   public static GrVariable resolveLocalVar(@NotNull GrIntroduceContext context) {
730     final GrVariable var = context.getVar();
731     if (var != null) {
732       return var;
733     }
734
735     return resolveLocalVar(context.getExpression());
736   }
737
738   @Nullable
739   public static GrVariable resolveLocalVar(@Nullable GrExpression expression) {
740     if (expression instanceof GrReferenceExpression) {
741       final GrReferenceExpression ref = (GrReferenceExpression)expression;
742
743       final PsiElement resolved = ref.resolve();
744       if (PsiUtil.isLocalVariable(resolved)) {
745         return (GrVariable)resolved;
746       }
747       return null;
748     }
749
750     return null;
751   }
752
753   public static boolean hasLhs(@NotNull final PsiElement[] occurrences) {
754     for (PsiElement element : occurrences) {
755       if (element instanceof GrReferenceExpression) {
756         if (PsiUtil.isLValue((GroovyPsiElement)element)) return true;
757         if (ControlFlowUtils.isIncOrDecOperand((GrReferenceExpression)element)) return true;
758       }
759     }
760     return false;
761   }
762
763   @NotNull
764   public static PsiElement getCurrentPlace(@Nullable GrExpression expr,
765                                            @Nullable GrVariable var,
766                                            @Nullable StringPartInfo stringPartInfo) {
767     if (var != null) return var;
768     if (expr != null) return expr;
769     if (stringPartInfo != null) return stringPartInfo.getLiteral();
770
771     throw new IncorrectOperationException();
772   }
773
774   public interface Validator extends NameValidator {
775     boolean isOK(GrIntroduceDialog dialog);
776   }
777 }