| /* |
| * Copyright 2000-2014 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 org.jetbrains.plugins.groovy.refactoring.introduce; |
| |
| import com.intellij.codeInsight.highlighting.HighlightManager; |
| import com.intellij.diagnostic.LogMessageEx; |
| import com.intellij.lang.LanguageRefactoringSupport; |
| import com.intellij.lang.refactoring.RefactoringSupportProvider; |
| import com.intellij.openapi.actionSystem.DataContext; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.command.CommandProcessor; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.RangeMarker; |
| import com.intellij.openapi.editor.SelectionModel; |
| import com.intellij.openapi.editor.colors.EditorColors; |
| import com.intellij.openapi.editor.colors.EditorColorsManager; |
| import com.intellij.openapi.editor.markup.RangeHighlighter; |
| import com.intellij.openapi.editor.markup.TextAttributes; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.*; |
| import com.intellij.openapi.wm.WindowManager; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.CodeStyleManager; |
| import com.intellij.psi.search.LocalSearchScope; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.psi.util.TypeConversionUtil; |
| import com.intellij.refactoring.IntroduceTargetChooser; |
| import com.intellij.refactoring.RefactoringActionHandler; |
| import com.intellij.refactoring.RefactoringBundle; |
| import com.intellij.refactoring.introduce.inplace.AbstractInplaceIntroducer; |
| import com.intellij.refactoring.introduce.inplace.OccurrencesChooser; |
| import com.intellij.refactoring.rename.inplace.InplaceRefactoring; |
| import com.intellij.refactoring.util.CommonRefactoringUtil; |
| import com.intellij.util.Function; |
| import com.intellij.util.IncorrectOperationException; |
| import com.intellij.util.Processor; |
| import com.intellij.util.containers.ContainerUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.plugins.groovy.codeInspection.utils.ControlFlowUtils; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyFileBase; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement; |
| import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory; |
| import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.*; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrCodeBlock; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrCaseLabel; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrStringInjection; |
| import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter; |
| import org.jetbrains.plugins.groovy.lang.psi.api.util.GrDeclarationHolder; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil; |
| import org.jetbrains.plugins.groovy.lang.psi.impl.synthetic.GroovyScriptClass; |
| import org.jetbrains.plugins.groovy.lang.psi.util.PsiUtil; |
| import org.jetbrains.plugins.groovy.refactoring.GrRefactoringError; |
| import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringBundle; |
| import org.jetbrains.plugins.groovy.refactoring.GroovyRefactoringUtil; |
| import org.jetbrains.plugins.groovy.refactoring.NameValidator; |
| |
| import java.util.*; |
| |
| /** |
| * Created by Max Medvedev on 10/29/13 |
| */ |
| public abstract class GrIntroduceHandlerBase<Settings extends GrIntroduceSettings, Scope extends PsiElement> implements RefactoringActionHandler { |
| private static final Logger LOG = Logger.getInstance(GrIntroduceHandlerBase.class); |
| |
| public static final Function<GrExpression, String> GR_EXPRESSION_RENDERER = new Function<GrExpression, String>() { |
| @Override |
| public String fun(@NotNull GrExpression expr) { |
| return expr.getText(); |
| } |
| }; |
| |
| public static GrExpression insertExplicitCastIfNeeded(GrVariable variable, GrExpression initializer) { |
| PsiType ltype = findLValueType(initializer); |
| PsiType rtype = initializer.getType(); |
| |
| GrExpression rawExpr = (GrExpression)PsiUtil.skipParentheses(initializer, false); |
| |
| if (ltype == null || TypesUtil.isAssignableWithoutConversions(ltype, rtype, initializer) || !TypesUtil.isAssignable(ltype, rtype, initializer)) { |
| return rawExpr; |
| } |
| else { // implicit coercion should be replaced with explicit cast |
| GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(variable.getProject()); |
| GrSafeCastExpression cast = |
| (GrSafeCastExpression)factory.createExpressionFromText("a as B"); |
| cast.getOperand().replaceWithExpression(rawExpr, false); |
| cast.getCastTypeElement().replace(factory.createTypeElement(ltype)); |
| return cast; |
| } |
| } |
| |
| @Nullable |
| private static PsiType findLValueType(GrExpression initializer) { |
| if (initializer.getParent() instanceof GrAssignmentExpression && ((GrAssignmentExpression)initializer.getParent()).getRValue() == initializer) { |
| return ((GrAssignmentExpression)initializer.getParent()).getLValue().getNominalType(); |
| } |
| else if (initializer.getParent() instanceof GrVariable) { |
| return ((GrVariable)initializer.getParent()).getDeclaredType(); |
| } |
| else { |
| return null; |
| } |
| } |
| |
| @NotNull |
| public static GrStatement getAnchor(@NotNull PsiElement[] occurrences, @NotNull PsiElement scope) { |
| PsiElement parent = PsiTreeUtil.findCommonParent(occurrences); |
| PsiElement container = getEnclosingContainer(parent); |
| assert container != null; |
| PsiElement anchor = findAnchor(occurrences, container); |
| |
| assertStatement(anchor, scope); |
| return (GrStatement)anchor; |
| } |
| |
| @Nullable |
| public static PsiElement getEnclosingContainer(PsiElement place) { |
| PsiElement parent = place; |
| while (true) { |
| if (parent == null) { |
| return null; |
| } |
| if (parent instanceof GrDeclarationHolder && !(parent instanceof GrClosableBlock && parent.getParent() instanceof GrStringInjection)) { |
| return parent; |
| } |
| if (parent instanceof GrLoopStatement) { |
| return parent; |
| } |
| |
| parent = parent.getParent(); |
| } |
| } |
| |
| @NotNull |
| protected abstract String getRefactoringName(); |
| |
| @NotNull |
| protected abstract String getHelpID(); |
| |
| @NotNull |
| protected abstract Scope[] findPossibleScopes(GrExpression expression, GrVariable variable, StringPartInfo stringPart, Editor editor); |
| |
| protected abstract void checkExpression(@NotNull GrExpression selectedExpr) throws GrRefactoringError; |
| |
| protected abstract void checkVariable(@NotNull GrVariable variable) throws GrRefactoringError; |
| |
| protected abstract void checkStringLiteral(@NotNull StringPartInfo info) throws GrRefactoringError; |
| |
| protected abstract void checkOccurrences(@NotNull PsiElement[] occurrences); |
| |
| @NotNull |
| protected abstract GrIntroduceDialog<Settings> getDialog(@NotNull GrIntroduceContext context); |
| |
| @Nullable |
| public abstract GrVariable runRefactoring(@NotNull GrIntroduceContext context, @NotNull Settings settings); |
| |
| protected abstract GrAbstractInplaceIntroducer<Settings> getIntroducer(@NotNull GrIntroduceContext context, |
| OccurrencesChooser.ReplaceChoice choice); |
| |
| public static Map<OccurrencesChooser.ReplaceChoice, List<Object>> fillChoice(GrIntroduceContext context) { |
| HashMap<OccurrencesChooser.ReplaceChoice, List<Object>> map = ContainerUtil.newLinkedHashMap(); |
| |
| if (context.getExpression() != null) { |
| map.put(OccurrencesChooser.ReplaceChoice.NO, Collections.<Object>singletonList(context.getExpression())); |
| } |
| else if (context.getStringPart() != null) { |
| map.put(OccurrencesChooser.ReplaceChoice.NO, Collections.<Object>singletonList(context.getStringPart())); |
| return map; |
| } |
| else if (context.getVar() != null) { |
| map.put(OccurrencesChooser.ReplaceChoice.ALL, Collections.<Object>singletonList(context.getVar())); |
| return map; |
| } |
| |
| PsiElement[] occurrences = context.getOccurrences(); |
| if (occurrences.length > 1) { |
| map.put(OccurrencesChooser.ReplaceChoice.ALL, Arrays.<Object>asList(occurrences)); |
| } |
| return map; |
| } |
| |
| @NotNull |
| public static List<GrExpression> collectExpressions(final PsiFile file, final Editor editor, final int offset, boolean acceptVoidCalls) { |
| int correctedOffset = correctOffset(editor, offset); |
| final PsiElement elementAtCaret = file.findElementAt(correctedOffset); |
| final List<GrExpression> expressions = new ArrayList<GrExpression>(); |
| |
| for (GrExpression expression = PsiTreeUtil.getParentOfType(elementAtCaret, GrExpression.class); |
| expression != null; |
| expression = PsiTreeUtil.getParentOfType(expression, GrExpression.class)) { |
| if (expressions.contains(expression)) continue; |
| if (expression instanceof GrParenthesizedExpression && !expressions.contains(((GrParenthesizedExpression)expression).getOperand())) { |
| expressions.add(((GrParenthesizedExpression)expression).getOperand()); |
| } |
| if (expressionIsIncorrect(expression, acceptVoidCalls)) continue; |
| |
| expressions.add(expression); |
| } |
| return expressions; |
| } |
| |
| public static boolean expressionIsIncorrect(@Nullable GrExpression expression, boolean acceptVoidCalls) { |
| if (expression instanceof GrParenthesizedExpression) return true; |
| if (PsiUtil.isSuperReference(expression)) return true; |
| if (expression instanceof GrAssignmentExpression) return true; |
| if (expression instanceof GrReferenceExpression && expression.getParent() instanceof GrCall) { |
| final GroovyResolveResult resolveResult = ((GrReferenceExpression)expression).advancedResolve(); |
| final PsiElement resolved = resolveResult.getElement(); |
| return resolved instanceof PsiMethod && !resolveResult.isInvokedOnProperty() || resolved instanceof PsiClass; |
| } |
| |
| if (expression instanceof GrClosableBlock && expression.getParent() instanceof GrStringInjection) return true; |
| if (!acceptVoidCalls && expression instanceof GrMethodCall && PsiType.VOID == expression.getType()) return true; |
| |
| return false; |
| } |
| |
| public static int correctOffset(Editor editor, int offset) { |
| Document document = editor.getDocument(); |
| CharSequence text = document.getCharsSequence(); |
| int correctedOffset = offset; |
| int textLength = document.getTextLength(); |
| if (offset >= textLength) { |
| correctedOffset = textLength - 1; |
| } |
| else if (!Character.isJavaIdentifierPart(text.charAt(offset))) { |
| correctedOffset--; |
| } |
| |
| if (correctedOffset < 0) { |
| correctedOffset = offset; |
| } |
| else { |
| char c = text.charAt(correctedOffset); |
| if (c == ';' && correctedOffset != 0) {//initially caret on the end of line |
| correctedOffset--; |
| } |
| else if (!Character.isJavaIdentifierPart(c) && c != ')' && c != ']' && c != '}' && c != '\'' && c != '"' && c != '/') { |
| correctedOffset = offset; |
| } |
| } |
| return correctedOffset; |
| } |
| |
| @Nullable |
| public static GrVariable findVariableAtCaret(final PsiFile file, final Editor editor, final int offset) { |
| final int correctOffset = correctOffset(editor, offset); |
| final PsiElement elementAtCaret = file.findElementAt(correctOffset); |
| final GrVariable variable = PsiTreeUtil.getParentOfType(elementAtCaret, GrVariable.class); |
| if (variable != null && variable.getNameIdentifierGroovy().getTextRange().contains(correctOffset)) return variable; |
| return null; |
| } |
| |
| @Override |
| public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file, @Nullable final DataContext dataContext) { |
| final SelectionModel selectionModel = editor.getSelectionModel(); |
| if (!selectionModel.hasSelection()) { |
| final int offset = editor.getCaretModel().getOffset(); |
| |
| final List<GrExpression> expressions = collectExpressions(file, editor, offset, false); |
| if (expressions.isEmpty()) { |
| updateSelectionForVariable(editor, file, selectionModel, offset); |
| } |
| else if (expressions.size() == 1) { |
| final TextRange textRange = expressions.get(0).getTextRange(); |
| selectionModel.setSelection(textRange.getStartOffset(), textRange.getEndOffset()); |
| } |
| else { |
| IntroduceTargetChooser.showChooser(editor, expressions, new Pass<GrExpression>() { |
| @Override |
| public void pass(final GrExpression selectedValue) { |
| invoke(project, editor, file, selectedValue.getTextRange().getStartOffset(), selectedValue.getTextRange().getEndOffset()); |
| } |
| }, GR_EXPRESSION_RENDERER); |
| return; |
| } |
| } |
| invoke(project, editor, file, selectionModel.getSelectionStart(), selectionModel.getSelectionEnd()); |
| } |
| |
| public static void updateSelectionForVariable(Editor editor, PsiFile file, SelectionModel selectionModel, int offset) { |
| final GrVariable variable = findVariableAtCaret(file, editor, offset); |
| if (variable == null || variable instanceof GrField || variable instanceof GrParameter) { |
| selectionModel.selectLineAtCaret(); |
| } |
| else { |
| final TextRange textRange = variable.getTextRange(); |
| selectionModel.setSelection(textRange.getStartOffset(), textRange.getEndOffset()); |
| } |
| } |
| |
| @Override |
| public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) { |
| // Does nothing |
| } |
| |
| public void getContextAndInvoke(@NotNull final Project project, |
| @NotNull final Editor editor, |
| @Nullable final GrExpression expression, |
| @Nullable final GrVariable variable, |
| @Nullable final StringPartInfo stringPart) { |
| final Scope[] scopes = findPossibleScopes(expression, variable, stringPart, editor); |
| |
| Pass<Scope> callback = new Pass<Scope>() { |
| @Override |
| public void pass(Scope scope) { |
| GrIntroduceContext context = getContext(project, editor, expression, variable, stringPart, scope); |
| invokeImpl(project, context, editor); |
| } |
| }; |
| |
| if (scopes.length == 0) { |
| CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle |
| .getCannotRefactorMessage(getRefactoringName() + "is not available in current scope"), |
| getRefactoringName(), getHelpID()); |
| } |
| else if (scopes.length == 1) { |
| callback.pass(scopes[0]); |
| } |
| else { |
| showScopeChooser(scopes, callback, editor); |
| } |
| } |
| |
| protected void extractStringPart(final Ref<GrIntroduceContext> ref) { |
| CommandProcessor.getInstance().executeCommand(ref.get().getProject(), new Runnable() { |
| @Override |
| public void run() { |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| GrIntroduceContext context = ref.get(); |
| |
| StringPartInfo stringPart = context.getStringPart(); |
| assert stringPart != null; |
| |
| GrExpression expression = stringPart.replaceLiteralWithConcatenation(null); |
| |
| ref.set(new GrIntroduceContextImpl(context.getProject(), context.getEditor(), expression, null, null, new PsiElement[]{expression}, context.getScope())); |
| } |
| }); |
| } |
| }, getRefactoringName(), getRefactoringName()); |
| } |
| |
| protected void addBraces(@NotNull final GrStatement anchor, @NotNull final Ref<GrIntroduceContext> contextRef) { |
| CommandProcessor.getInstance().executeCommand(contextRef.get().getProject(), new Runnable() { |
| @Override |
| public void run() { |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| GrIntroduceContext context = contextRef.get(); |
| SmartPointerManager pointManager = SmartPointerManager.getInstance(context.getProject()); |
| SmartPsiElementPointer<GrExpression> expressionRef = context.getExpression() != null ? pointManager.createSmartPsiElementPointer(context.getExpression()) : null; |
| SmartPsiElementPointer<GrVariable> varRef = context.getVar() != null ? pointManager.createSmartPsiElementPointer(context.getVar()) : null; |
| |
| SmartPsiElementPointer[] occurrencesRefs = new SmartPsiElementPointer[context.getOccurrences().length]; |
| PsiElement[] occurrences = context.getOccurrences(); |
| for (int i = 0; i < occurrences.length; i++) { |
| occurrencesRefs[i] = pointManager.createSmartPsiElementPointer(occurrences[i]); |
| } |
| |
| |
| PsiFile file = anchor.getContainingFile(); |
| SmartPsiFileRange anchorPointer = pointManager.createSmartPsiFileRangePointer(file, anchor.getTextRange()); |
| |
| Document document = context.getEditor().getDocument(); |
| CharSequence sequence = document.getCharsSequence(); |
| |
| TextRange range = anchor.getTextRange(); |
| |
| int end = range.getEndOffset(); |
| document.insertString(end, "\n}"); |
| |
| int start = range.getStartOffset(); |
| while (start > 0 && Character.isWhitespace(sequence.charAt(start - 1))) { |
| start--; |
| } |
| document.insertString(start, "{"); |
| |
| PsiDocumentManager.getInstance(context.getProject()).commitDocument(document); |
| |
| Segment anchorSegment = anchorPointer.getRange(); |
| PsiElement restoredAnchor = PsiImplUtil |
| .findElementInRange(file, anchorSegment.getStartOffset(), anchorSegment.getEndOffset(), PsiElement.class); |
| GrCodeBlock block = (GrCodeBlock)restoredAnchor.getParent(); |
| CodeStyleManager.getInstance(context.getProject()).reformat(block.getRBrace()); |
| CodeStyleManager.getInstance(context.getProject()).reformat(block.getLBrace()); |
| |
| for (int i = 0; i < occurrencesRefs.length; i++) { |
| occurrences[i] = occurrencesRefs[i].getElement(); |
| } |
| |
| contextRef.set(new GrIntroduceContextImpl(context.getProject(), context.getEditor(), |
| expressionRef != null ? expressionRef.getElement() : null, |
| varRef != null ? varRef.getElement() : null, |
| null, occurrences, context.getScope())); |
| } |
| }); |
| } |
| }, getRefactoringName(), getRefactoringName()); |
| } |
| |
| @NotNull |
| protected static GrStatement findAnchor(@NotNull final GrIntroduceContext context, final boolean replaceAll) { |
| return ApplicationManager.getApplication().runReadAction(new Computable<GrStatement>() { |
| @Override |
| public GrStatement compute() { |
| PsiElement[] occurrences = replaceAll ? context.getOccurrences() : new GrExpression[]{context.getExpression()}; |
| return getAnchor(occurrences, context.getScope()); |
| } |
| }); |
| } |
| |
| protected abstract void showScopeChooser(Scope[] scopes, Pass<Scope> callback, Editor editor); |
| |
| public GrIntroduceContext getContext(@NotNull Project project, |
| @NotNull Editor editor, |
| @Nullable GrExpression expression, |
| @Nullable GrVariable variable, |
| @Nullable StringPartInfo stringPart, |
| @NotNull PsiElement scope) { |
| if (variable != null) { |
| final PsiElement[] occurrences = collectVariableUsages(variable, scope); |
| return new GrIntroduceContextImpl(project, editor, null, variable, stringPart, occurrences, scope); |
| } |
| else if (expression != null ) { |
| final PsiElement[] occurrences = findOccurrences(expression, scope); |
| return new GrIntroduceContextImpl(project, editor, expression, variable, stringPart, occurrences, scope); |
| } |
| else { |
| assert stringPart != null; |
| return new GrIntroduceContextImpl(project, editor, expression, variable, stringPart, new PsiElement[]{stringPart.getLiteral()}, scope); |
| } |
| } |
| |
| public static PsiElement[] collectVariableUsages(GrVariable variable, PsiElement scope) { |
| final List<PsiElement> list = Collections.synchronizedList(new ArrayList<PsiElement>()); |
| if (scope instanceof GroovyScriptClass) { |
| scope = scope.getContainingFile(); |
| } |
| ReferencesSearch.search(variable, new LocalSearchScope(scope)).forEach(new Processor<PsiReference>() { |
| @Override |
| public boolean process(PsiReference psiReference) { |
| final PsiElement element = psiReference.getElement(); |
| if (element != null) { |
| list.add(element); |
| } |
| return true; |
| } |
| }); |
| return list.toArray(new PsiElement[list.size()]); |
| } |
| |
| private boolean invokeImpl(final Project project, final GrIntroduceContext context, final Editor editor) { |
| try { |
| if (!CommonRefactoringUtil.checkReadOnlyStatus(project, context.getOccurrences())) { |
| return false; |
| } |
| checkOccurrences(context.getOccurrences()); |
| |
| |
| if (isInplace(context.getEditor(), context.getPlace())) { |
| Map<OccurrencesChooser.ReplaceChoice, List<Object>> occurrencesMap = getOccurrenceOptions(context); |
| new IntroduceOccurrencesChooser(editor).showChooser(new Pass<OccurrencesChooser.ReplaceChoice>() { |
| @Override |
| public void pass(final OccurrencesChooser.ReplaceChoice choice) { |
| getIntroducer(context, choice).startInplaceIntroduceTemplate(); |
| } |
| }, occurrencesMap); |
| } |
| else { |
| final Settings settings = showDialog(context); |
| if (settings == null) return false; |
| |
| CommandProcessor.getInstance().executeCommand(context.getProject(), new Runnable() { |
| @Override |
| public void run() { |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| @Override |
| public void run() { |
| runRefactoring(context, settings); |
| } |
| }); |
| } |
| }, getRefactoringName(), null); |
| } |
| |
| return true; |
| } |
| catch (GrRefactoringError e) { |
| CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle.getCannotRefactorMessage(e.getMessage()), getRefactoringName(), getHelpID()); |
| return false; |
| } |
| } |
| |
| @NotNull |
| protected Map<OccurrencesChooser.ReplaceChoice, List<Object>> getOccurrenceOptions(@NotNull GrIntroduceContext context) { |
| return fillChoice(context); |
| } |
| |
| @NotNull |
| protected PsiElement[] findOccurrences(@NotNull GrExpression expression, @NotNull PsiElement scope) { |
| final PsiElement[] occurrences = GroovyRefactoringUtil.getExpressionOccurrences(PsiUtil.skipParentheses(expression, false), scope); |
| if (occurrences == null || occurrences.length == 0) { |
| throw new GrRefactoringError(GroovyRefactoringBundle.message("no.occurrences.found")); |
| } |
| return occurrences; |
| } |
| |
| private void invoke(@NotNull final Project project, |
| @NotNull final Editor editor, |
| @NotNull PsiFile file, |
| int startOffset, |
| int endOffset) throws GrRefactoringError { |
| try { |
| PsiDocumentManager.getInstance(project).commitAllDocuments(); |
| if (!(file instanceof GroovyFileBase)) { |
| throw new GrRefactoringError(GroovyRefactoringBundle.message("only.in.groovy.files")); |
| } |
| if (!CommonRefactoringUtil.checkReadOnlyStatus(project, file)) { |
| throw new GrRefactoringError(RefactoringBundle.message("readonly.occurences.found")); |
| } |
| |
| GrExpression selectedExpr = findExpression(file, startOffset, endOffset); |
| final GrVariable variable = findVariable(file, startOffset, endOffset); |
| final StringPartInfo stringPart = StringPartInfo.findStringPart(file, startOffset, endOffset); |
| if (variable != null) { |
| checkVariable(variable); |
| } |
| else if (selectedExpr != null) { |
| checkExpression(selectedExpr); |
| } |
| else if (stringPart != null) { |
| checkStringLiteral(stringPart); |
| } |
| else { |
| throw new GrRefactoringError(null); |
| } |
| |
| getContextAndInvoke(project, editor, selectedExpr, variable, stringPart); |
| } |
| catch (GrRefactoringError e) { |
| CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle.getCannotRefactorMessage(e.getMessage()), getRefactoringName(), getHelpID()); |
| } |
| } |
| |
| public static RangeMarker createRange(Document document, StringPartInfo part) { |
| if (part == null) { |
| return null; |
| } |
| TextRange range = part.getRange().shiftRight(part.getLiteral().getTextRange().getStartOffset()); |
| return document.createRangeMarker(range.getStartOffset(), range.getEndOffset(), true); |
| |
| } |
| |
| @Nullable |
| public static RangeMarker createRange(@NotNull Document document, @Nullable PsiElement expression) { |
| if (expression == null) { |
| return null; |
| } |
| TextRange range = expression.getTextRange(); |
| return document.createRangeMarker(range.getStartOffset(), range.getEndOffset(), false); |
| } |
| |
| |
| public static boolean isInplace(@NotNull Editor editor, @NotNull PsiElement place) { |
| final RefactoringSupportProvider supportProvider = LanguageRefactoringSupport.INSTANCE.forLanguage(place.getLanguage()); |
| return supportProvider != null && |
| (editor.getUserData(InplaceRefactoring.INTRODUCE_RESTART) == null || !editor.getUserData(InplaceRefactoring.INTRODUCE_RESTART)) && |
| editor.getUserData(AbstractInplaceIntroducer.ACTIVE_INTRODUCE) == null && |
| editor.getSettings().isVariableInplaceRenameEnabled() && |
| supportProvider.isInplaceIntroduceAvailable(place, place) && |
| !ApplicationManager.getApplication().isUnitTestMode(); |
| } |
| |
| @Nullable |
| public static GrVariable findVariable(@NotNull PsiFile file, int startOffset, int endOffset) { |
| GrVariable var = PsiImplUtil.findElementInRange(file, startOffset, endOffset, GrVariable.class); |
| if (var == null) { |
| final GrVariableDeclaration variableDeclaration = |
| PsiImplUtil.findElementInRange(file, startOffset, endOffset, GrVariableDeclaration.class); |
| if (variableDeclaration == null) return null; |
| final GrVariable[] variables = variableDeclaration.getVariables(); |
| if (variables.length == 1) { |
| var = variables[0]; |
| } |
| } |
| if (var instanceof GrParameter || var instanceof GrField) { |
| return null; |
| } |
| return var; |
| } |
| |
| @Nullable |
| public static GrVariable findVariable(@NotNull GrStatement statement) { |
| if (!(statement instanceof GrVariableDeclaration)) return null; |
| final GrVariableDeclaration variableDeclaration = (GrVariableDeclaration)statement; |
| final GrVariable[] variables = variableDeclaration.getVariables(); |
| |
| GrVariable var = null; |
| if (variables.length == 1) { |
| var = variables[0]; |
| } |
| if (var instanceof GrParameter || var instanceof GrField) { |
| return null; |
| } |
| return var; |
| } |
| |
| |
| @Nullable |
| public static GrExpression findExpression(PsiFile file, int startOffset, int endOffset) { |
| GrExpression selectedExpr = PsiImplUtil.findElementInRange(file, startOffset, endOffset, GrExpression.class); |
| return findExpression(selectedExpr); |
| } |
| |
| @Nullable |
| public static GrExpression findExpression(GrStatement selectedExpr) { |
| if (!(selectedExpr instanceof GrExpression)) return null; |
| |
| GrExpression selected = (GrExpression)selectedExpr; |
| while (selected instanceof GrParenthesizedExpression) selected = ((GrParenthesizedExpression)selected).getOperand(); |
| if (selected == null) return null; |
| PsiType type = selected.getType(); |
| if (type != null) type = TypeConversionUtil.erasure(type); |
| |
| if (PsiType.VOID.equals(type)) { |
| return null; |
| } |
| |
| return selected; |
| } |
| |
| @Nullable |
| private Settings showDialog(@NotNull GrIntroduceContext context) { |
| |
| // Add occurrences highlighting |
| ArrayList<RangeHighlighter> highlighters = new ArrayList<RangeHighlighter>(); |
| HighlightManager highlightManager = null; |
| if (context.getEditor() != null) { |
| highlightManager = HighlightManager.getInstance(context.getProject()); |
| EditorColorsManager colorsManager = EditorColorsManager.getInstance(); |
| TextAttributes attributes = colorsManager.getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); |
| if (context.getOccurrences().length > 1) { |
| highlightManager.addOccurrenceHighlights(context.getEditor(), context.getOccurrences(), attributes, true, highlighters); |
| } |
| } |
| |
| GrIntroduceDialog<Settings> dialog = getDialog(context); |
| |
| dialog.show(); |
| if (dialog.isOK()) { |
| if (context.getEditor() != null) { |
| for (RangeHighlighter highlighter : highlighters) { |
| highlightManager.removeSegmentHighlighter(context.getEditor(), highlighter); |
| } |
| } |
| return dialog.getSettings(); |
| } |
| else { |
| if (context.getOccurrences().length > 1) { |
| WindowManager.getInstance().getStatusBar(context.getProject()) |
| .setInfo(GroovyRefactoringBundle.message("press.escape.to.remove.the.highlighting")); |
| } |
| } |
| return null; |
| } |
| |
| @Nullable |
| public static PsiElement findAnchor(@NotNull PsiElement[] occurrences, |
| @NotNull PsiElement container) { |
| if (occurrences.length == 0) return null; |
| |
| PsiElement candidate; |
| if (occurrences.length == 1) { |
| candidate = findContainingStatement(occurrences[0]); |
| } |
| else { |
| candidate = occurrences[0]; |
| while (candidate != null && candidate.getParent() != container) { |
| candidate = candidate.getParent(); |
| } |
| } |
| |
| final GrStringInjection injection = PsiTreeUtil.getParentOfType(candidate, GrStringInjection.class); |
| if (injection != null && !injection.getText().contains("\n")) { |
| candidate = findContainingStatement(injection); |
| } |
| |
| if (candidate == null) return null; |
| |
| if ((container instanceof GrWhileStatement) && |
| candidate.equals(((GrWhileStatement)container).getCondition())) { |
| return container; |
| } |
| if ((container instanceof GrIfStatement) && |
| candidate.equals(((GrIfStatement)container).getCondition())) { |
| return container; |
| } |
| if ((container instanceof GrForStatement) && |
| candidate.equals(((GrForStatement)container).getClause())) { |
| return container; |
| } |
| |
| while (candidate instanceof GrIfStatement && |
| candidate.getParent() instanceof GrIfStatement && |
| ((GrIfStatement)candidate.getParent()).getElseBranch() == candidate) { |
| candidate = candidate.getParent(); |
| } |
| return candidate; |
| } |
| |
| public static void assertStatement(@Nullable PsiElement anchor, @NotNull PsiElement scope) { |
| if (!(anchor instanceof GrStatement)) { |
| LogMessageEx.error(LOG, "cannot find anchor for variable", scope.getText()); |
| } |
| } |
| |
| @Nullable |
| private static PsiElement findContainingStatement(@Nullable PsiElement candidate) { |
| while (candidate != null && (candidate.getParent() instanceof GrLabeledStatement || !(PsiUtil.isExpressionStatement(candidate)))) { |
| candidate = candidate.getParent(); |
| if (candidate instanceof GrCaseLabel) candidate = candidate.getParent(); |
| } |
| return candidate; |
| } |
| |
| public static void deleteLocalVar(GrVariable var) { |
| final PsiElement parent = var.getParent(); |
| if (((GrVariableDeclaration)parent).getVariables().length == 1) { |
| parent.delete(); |
| } |
| else { |
| GrExpression initializer = var.getInitializerGroovy(); |
| if (initializer != null) initializer.delete(); //don't special check for tuple, but this line is for the tuple case |
| var.delete(); |
| } |
| } |
| |
| @Nullable |
| public static GrVariable resolveLocalVar(@NotNull GrIntroduceContext context) { |
| final GrVariable var = context.getVar(); |
| if (var != null) { |
| return var; |
| } |
| |
| return resolveLocalVar(context.getExpression()); |
| } |
| |
| @Nullable |
| public static GrVariable resolveLocalVar(@Nullable GrExpression expression) { |
| if (expression instanceof GrReferenceExpression) { |
| final GrReferenceExpression ref = (GrReferenceExpression)expression; |
| |
| final PsiElement resolved = ref.resolve(); |
| if (PsiUtil.isLocalVariable(resolved)) { |
| return (GrVariable)resolved; |
| } |
| return null; |
| } |
| |
| return null; |
| } |
| |
| public static boolean hasLhs(@NotNull final PsiElement[] occurrences) { |
| for (PsiElement element : occurrences) { |
| if (element instanceof GrReferenceExpression) { |
| if (PsiUtil.isLValue((GroovyPsiElement)element)) return true; |
| if (ControlFlowUtils.isIncOrDecOperand((GrReferenceExpression)element)) return true; |
| } |
| } |
| return false; |
| } |
| |
| @NotNull |
| public static PsiElement getCurrentPlace(@Nullable GrExpression expr, |
| @Nullable GrVariable var, |
| @Nullable StringPartInfo stringPartInfo) { |
| if (var != null) return var; |
| if (expr != null) return expr; |
| if (stringPartInfo != null) return stringPartInfo.getLiteral(); |
| |
| throw new IncorrectOperationException(); |
| } |
| |
| public interface Validator extends NameValidator { |
| boolean isOK(GrIntroduceDialog dialog); |
| } |
| } |