| /* |
| * Copyright 2000-2013 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.jetbrains.python.refactoring.inline; |
| |
| import com.intellij.codeInsight.TargetElementUtilBase; |
| import com.intellij.codeInsight.highlighting.HighlightManager; |
| import com.intellij.lang.Language; |
| import com.intellij.lang.refactoring.InlineActionHandler; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.command.CommandProcessor; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.editor.colors.EditorColors; |
| import com.intellij.openapi.editor.colors.EditorColorsManager; |
| import com.intellij.openapi.editor.markup.TextAttributes; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.util.Pair; |
| import com.intellij.openapi.util.TextRange; |
| import com.intellij.openapi.wm.WindowManager; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.CodeStyleManager; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.util.PsiTreeUtil; |
| import com.intellij.refactoring.RefactoringBundle; |
| import com.intellij.refactoring.util.CommonRefactoringUtil; |
| import com.intellij.refactoring.util.RefactoringMessageDialog; |
| import com.intellij.util.Function; |
| import com.intellij.util.Query; |
| import com.intellij.util.containers.ContainerUtil; |
| import com.jetbrains.python.PyBundle; |
| import com.jetbrains.python.PyTokenTypes; |
| import com.jetbrains.python.PythonLanguage; |
| import com.jetbrains.python.codeInsight.controlflow.ReadWriteInstruction; |
| import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; |
| import com.jetbrains.python.psi.*; |
| import com.jetbrains.python.psi.impl.PyAugAssignmentStatementImpl; |
| import com.jetbrains.python.psi.impl.PyPsiUtils; |
| import com.jetbrains.python.refactoring.PyDefUseUtil; |
| import com.jetbrains.python.refactoring.PyReplaceExpressionUtil; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| /** |
| * @author Dennis.Ushakov |
| */ |
| public class PyInlineLocalHandler extends InlineActionHandler { |
| private static final Logger LOG = Logger.getInstance(PyInlineLocalHandler.class.getName()); |
| |
| private static final String REFACTORING_NAME = RefactoringBundle.message("inline.variable.title"); |
| private static final Pair<PyStatement, Boolean> EMPTY_DEF_RESULT = Pair.create(null, false); |
| private static final String HELP_ID = "python.reference.inline"; |
| |
| public static PyInlineLocalHandler getInstance() { |
| return Extensions.findExtension(EP_NAME, PyInlineLocalHandler.class); |
| } |
| |
| @Override |
| public boolean isEnabledForLanguage(Language l) { |
| return l instanceof PythonLanguage; |
| } |
| |
| @Override |
| public boolean canInlineElement(PsiElement element) { |
| return element instanceof PyTargetExpression; |
| } |
| |
| @Override |
| public void inlineElement(Project project, Editor editor, PsiElement element) { |
| if (editor == null) { |
| return; |
| } |
| final PsiReference psiReference = TargetElementUtilBase.findReference(editor); |
| PyReferenceExpression refExpr = null; |
| if (psiReference != null) { |
| final PsiElement refElement = psiReference.getElement(); |
| if (refElement instanceof PyReferenceExpression) { |
| refExpr = (PyReferenceExpression) refElement; |
| } |
| } |
| invoke(project, editor, (PyTargetExpression)element, refExpr); |
| } |
| |
| public static void invoke(final Project project, final Editor editor, final PyTargetExpression local, PyReferenceExpression refExpr) { |
| if (!CommonRefactoringUtil.checkReadOnlyStatus(project, local)) return; |
| |
| final HighlightManager highlightManager = HighlightManager.getInstance(project); |
| final TextAttributes writeAttributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.WRITE_SEARCH_RESULT_ATTRIBUTES); |
| |
| final String localName = local.getName(); |
| final ScopeOwner containerBlock = getContext(local); |
| LOG.assertTrue(containerBlock != null); |
| |
| |
| final Pair<PyStatement, Boolean> defPair = getAssignmentToInline(containerBlock, refExpr, local, project); |
| final PyStatement def = defPair.first; |
| if (def == null || getValue(def) == null){ |
| final String key = defPair.second ? "variable.has.no.dominating.definition" : "variable.has.no.initializer"; |
| final String message = RefactoringBundle.getCannotRefactorMessage(RefactoringBundle.message(key, localName)); |
| CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HELP_ID); |
| return; |
| } |
| |
| if (def instanceof PyAssignmentStatement && ((PyAssignmentStatement)def).getTargets().length > 1){ |
| highlightManager.addOccurrenceHighlights(editor, new PsiElement[] {def}, writeAttributes, true, null); |
| final String message = RefactoringBundle.getCannotRefactorMessage(PyBundle.message("refactoring.inline.local.multiassignment", localName)); |
| CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HELP_ID); |
| return; |
| } |
| |
| final PsiElement[] refsToInline = PyDefUseUtil.getPostRefs(containerBlock, local, getObject(def)); |
| if (refsToInline.length == 0) { |
| final String message = RefactoringBundle.message("variable.is.never.used", localName); |
| CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HELP_ID); |
| return; |
| } |
| |
| final TextAttributes attributes = EditorColorsManager.getInstance().getGlobalScheme().getAttributes(EditorColors.SEARCH_RESULT_ATTRIBUTES); |
| if (editor != null && !ApplicationManager.getApplication().isUnitTestMode()) { |
| highlightManager.addOccurrenceHighlights(editor, refsToInline, attributes, true, null); |
| final int occurrencesCount = refsToInline.length; |
| final String occurrencesString = RefactoringBundle.message("occurrences.string", occurrencesCount); |
| final String question = RefactoringBundle.message("inline.local.variable.prompt", localName) + " " + occurrencesString; |
| final RefactoringMessageDialog dialog = new RefactoringMessageDialog(REFACTORING_NAME, question, HELP_ID, "OptionPane.questionIcon", true, project); |
| dialog.show(); |
| if (!dialog.isOK()){ |
| WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); |
| return; |
| } |
| } |
| |
| final PsiFile workingFile = local.getContainingFile(); |
| for (PsiElement ref : refsToInline) { |
| final PsiFile otherFile = ref.getContainingFile(); |
| if (!otherFile.equals(workingFile)) { |
| final String message = RefactoringBundle.message("variable.is.referenced.in.multiple.files", localName); |
| CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HELP_ID); |
| return; |
| } |
| } |
| |
| for (final PsiElement ref : refsToInline) { |
| final List<PsiElement> elems = new ArrayList<PsiElement>(); |
| final List<ReadWriteInstruction> latestDefs = PyDefUseUtil.getLatestDefs(containerBlock, local.getName(), ref, false); |
| for (ReadWriteInstruction i : latestDefs) { |
| elems.add(i.getElement()); |
| } |
| final PsiElement[] defs = elems.toArray(new PsiElement[elems.size()]); |
| boolean isSameDefinition = true; |
| for (PsiElement otherDef : defs) { |
| isSameDefinition &= isSameDefinition(def, otherDef); |
| } |
| if (!isSameDefinition) { |
| if (editor != null) { |
| highlightManager.addOccurrenceHighlights(editor, defs, writeAttributes, true, null); |
| highlightManager.addOccurrenceHighlights(editor, new PsiElement[]{ref}, attributes, true, null); |
| final String message = RefactoringBundle.getCannotRefactorMessage( |
| RefactoringBundle.message("variable.is.accessed.for.writing.and.used.with.inlined", localName)); |
| CommonRefactoringUtil.showErrorHint(project, editor, message, REFACTORING_NAME, HELP_ID); |
| } |
| WindowManager.getInstance().getStatusBar(project).setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); |
| return; |
| } |
| } |
| |
| |
| CommandProcessor.getInstance().executeCommand(project, new Runnable() { |
| public void run() { |
| ApplicationManager.getApplication().runWriteAction(new Runnable() { |
| public void run() { |
| final PsiElement[] exprs = new PsiElement[refsToInline.length]; |
| final PyExpression value = prepareValue(def, localName, project); |
| final PyExpression withParent = PyElementGenerator.getInstance(project).createExpressionFromText("(" + value.getText() + ")"); |
| final PsiElement lastChild = def.getLastChild(); |
| if (lastChild != null && lastChild.getNode().getElementType() == PyTokenTypes.END_OF_LINE_COMMENT) { |
| final PsiElement parent = def.getParent(); |
| if (parent != null) parent.addBefore(lastChild, def); |
| } |
| |
| for (int i = 0, refsToInlineLength = refsToInline.length; i < refsToInlineLength; i++) { |
| final PsiElement element = refsToInline[i]; |
| if (PyReplaceExpressionUtil.isNeedParenthesis((PyExpression)element, value)) { |
| exprs[i] = element.replace(withParent); |
| } else { |
| exprs[i] = element.replace(value); |
| } |
| } |
| final PsiElement next = def.getNextSibling(); |
| if (next instanceof PsiWhiteSpace) { |
| PyPsiUtils.removeElements(next); |
| } |
| PyPsiUtils.removeElements(def); |
| |
| final List<TextRange> ranges = ContainerUtil.mapNotNull(exprs, new Function<PsiElement, TextRange>() { |
| @Override |
| public TextRange fun(PsiElement element) { |
| final PyStatement parentalStatement = PsiTreeUtil.getParentOfType(element, PyStatement.class, false); |
| return parentalStatement != null ? parentalStatement.getTextRange() : null; |
| } |
| }); |
| CodeStyleManager.getInstance(project).reformatText(workingFile, ranges); |
| |
| if (editor != null && !ApplicationManager.getApplication().isUnitTestMode()) { |
| highlightManager.addOccurrenceHighlights(editor, exprs, attributes, true, null); |
| WindowManager.getInstance().getStatusBar(project) |
| .setInfo(RefactoringBundle.message("press.escape.to.remove.the.highlighting")); |
| } |
| } |
| }); |
| } |
| }, RefactoringBundle.message("inline.command", localName), null); |
| } |
| |
| private static boolean isSameDefinition(PyStatement def, PsiElement otherDef) { |
| if (otherDef instanceof PyTargetExpression) otherDef = otherDef.getParent(); |
| return otherDef == def; |
| } |
| |
| private static ScopeOwner getContext(PyTargetExpression local) { |
| ScopeOwner context = PsiTreeUtil.getParentOfType(local, PyFunction.class); |
| if (context == null) { |
| context = PsiTreeUtil.getParentOfType(local, PyClass.class); |
| } |
| if (context == null) { |
| context = (PyFile)local.getContainingFile(); |
| } |
| return context; |
| } |
| |
| private static Pair<PyStatement, Boolean> getAssignmentToInline(ScopeOwner containerBlock, PyReferenceExpression expr, |
| PyTargetExpression local, Project project) { |
| if (expr != null) { |
| try { |
| final List<ReadWriteInstruction> candidates = PyDefUseUtil.getLatestDefs(containerBlock, local.getName(), expr, true); |
| if (candidates.size() == 1) { |
| final PyStatement expression = getAssignmentByLeftPart((PyElement)candidates.get(0).getElement()); |
| return Pair.create(expression, false); |
| } |
| return Pair.create(null, candidates.size() > 0); |
| } |
| catch (PyDefUseUtil.InstructionNotFoundException ignored) { |
| } |
| } |
| final Query<PsiReference> query = ReferencesSearch.search(local, GlobalSearchScope.allScope(project), false); |
| final PsiReference first = query.findFirst(); |
| |
| final PyElement lValue = first != null ? (PyElement)first.resolve() : null; |
| return lValue != null ? Pair.create(getAssignmentByLeftPart(lValue), false) : EMPTY_DEF_RESULT; |
| } |
| |
| @Nullable |
| private static PyStatement getAssignmentByLeftPart(PyElement candidate) { |
| final PsiElement parent = candidate.getParent(); |
| return parent instanceof PyAssignmentStatement || parent instanceof PyAugAssignmentStatement ? (PyStatement)parent : null; |
| } |
| |
| @Nullable |
| private static PyExpression getValue(@Nullable PyStatement def) { |
| if (def == null) return null; |
| if (def instanceof PyAssignmentStatement) { |
| return ((PyAssignmentStatement)def).getAssignedValue(); |
| } |
| return ((PyAugAssignmentStatement)def).getValue(); |
| } |
| |
| @Nullable |
| private static PyExpression getObject(@Nullable PyStatement def) { |
| if (def == null) return null; |
| if (def instanceof PyAssignmentStatement) { |
| return ((PyAssignmentStatement)def).getTargets()[0]; |
| } |
| return ((PyAugAssignmentStatement)def).getTarget(); |
| } |
| |
| @NotNull |
| private static PyExpression prepareValue(@NotNull PyStatement def, @NotNull String localName, @NotNull Project project) { |
| final PyExpression value = getValue(def); |
| assert value != null; |
| if (def instanceof PyAugAssignmentStatementImpl) { |
| final PyAugAssignmentStatementImpl expression = (PyAugAssignmentStatementImpl)def; |
| final PsiElement operation = expression.getOperation(); |
| assert operation != null; |
| final String op = operation.getText().replace('=', ' '); |
| return PyElementGenerator.getInstance(project).createExpressionFromText(localName + " " + op + value.getText() + ")"); |
| } |
| return value; |
| } |
| } |