From 0a8cdc39ac85ffadea70c16f56d452732591dabd Mon Sep 17 00:00:00 2001 From: Oleg Shpynov Date: Wed, 27 Jan 2010 11:18:14 +0300 Subject: [PATCH] Revert "Revert "First step of extract method: CodeFragment"" This reverts commit a6ee7cccd3b9162b007a6d2aca4b086ceceb39ef. --- .../codeFragment/PyCodeFragmentBuilder.java | 112 +++++++++++++++++ .../codeFragment/PyCodeFragmentUtil.java | 113 ++++++++++++++++++ .../controlflow/PyControlFlowBuilder.java | 2 +- .../python/psi/PyBinaryExpression.java | 3 + .../python/psi/PyContinueStatement.java | 2 +- .../psi/impl/PyBinaryExpressionImpl.java | 14 ++- .../psi/impl/PyBinaryExpressionNavigator.java | 23 ++++ .../psi/impl/PyContinueStatementImpl.java | 2 +- .../refactoring/PyExtractMethodHandler.java | 38 ++++++ .../refactoring/RefactoringProvider.java | 5 + 10 files changed, 307 insertions(+), 7 deletions(-) create mode 100644 python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentBuilder.java create mode 100644 python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java create mode 100644 python/src/com/jetbrains/python/psi/impl/PyBinaryExpressionNavigator.java create mode 100644 python/src/com/jetbrains/python/refactoring/PyExtractMethodHandler.java diff --git a/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentBuilder.java b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentBuilder.java new file mode 100644 index 000000000000..ee37a0c21938 --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentBuilder.java @@ -0,0 +1,112 @@ +package com.jetbrains.python.codeInsight.codeFragment; + +import com.intellij.codeInsight.codeFragment.CodeFragmentUtil; +import com.intellij.codeInsight.codeFragment.Position; +import com.intellij.psi.PsiElement; +import com.intellij.psi.ResolveResult; +import com.jetbrains.python.psi.*; + +import java.util.*; + +/** + * @author oleg + */ +public class PyCodeFragmentBuilder extends PyRecursiveElementVisitor { + final Map> modifiedInsideMap = new HashMap>(); + + final Set inElements = new HashSet(); + final Set outElements = new HashSet(); + + private final int startOffset; + private final int endOffset; + + public PyCodeFragmentBuilder(int start, int end) { + startOffset = start; + endOffset = end; + } + + @Override + public void visitPyTargetExpression(final PyTargetExpression node) { + visitDeclaration(node); + } + + @Override + public void visitPyNamedParameter(final PyNamedParameter node) { + visitDeclaration(node); + } + + @Override + public void visitPyReferenceExpression(final PyReferenceExpression element) { + final Position position = CodeFragmentUtil.getPosition(element, startOffset, endOffset); + final String name = element.getName(); + + // Collect in variables + if (position == Position.INSIDE) { + for (ResolveResult result : element.multiResolve(false)) { + final PsiElement declaration = result.getElement(); + final Position pos = CodeFragmentUtil.getPosition(declaration, startOffset, endOffset); + // If declaration is before add it to input + if (pos == Position.BEFORE) { + inElements.add(name); + break; + } + } + } + + // Collect out variables + if (position == Position.AFTER) { + // if name is already in out parameters + if (outElements.contains(name)) { + return; + } + for (ResolveResult result : element.multiResolve(false)) { + final PsiElement declaration = result.getElement(); + final Position pos = CodeFragmentUtil.getPosition(declaration, startOffset, endOffset); + // If declaration is inside + if (pos == Position.INSIDE) { + outElements.add(name); + break; + } + // If declaration is before we look for modifications inside + if (pos == Position.BEFORE) { + final List list = modifiedInsideMap.get(name); + boolean modificationSeen = false; + if (list != null) { + for (PyElement modification : list) { + if (modification.getReference().isReferenceTo(declaration)) { + outElements.add(name); + modificationSeen = true; + break; + } + } + if (modificationSeen) { + break; + } + } + } + } + } + } + + private void visitDeclaration(final PyElement element) { + final Position position = CodeFragmentUtil.getPosition(element, startOffset, endOffset); + final String name = element.getName(); + + // Collect in variables + if (position == Position.INSIDE) { + + // Add modification inside + List list = modifiedInsideMap.get(name); + if (list == null) { + list = new ArrayList(); + modifiedInsideMap.put(name, list); + } + list.add(element); + } + + // if name is already in out parameters + if (inElements.contains(name)) { + return; + } + } +} diff --git a/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java new file mode 100644 index 000000000000..1197f5f1e9cd --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/codeFragment/PyCodeFragmentUtil.java @@ -0,0 +1,113 @@ +package com.jetbrains.python.codeInsight.codeFragment; + +import com.intellij.codeInsight.codeFragment.CannotCreateCodeFragmentException; +import com.intellij.codeInsight.codeFragment.CodeFragment; +import com.intellij.codeInsight.codeFragment.CodeFragmentUtil; +import com.intellij.codeInsight.codeFragment.Position; +import com.intellij.codeInsight.controlflow.ConditionalInstruction; +import com.intellij.codeInsight.controlflow.Instruction; +import com.intellij.psi.PsiElement; +import com.intellij.util.containers.HashSet; +import com.jetbrains.python.PyBundle; +import com.jetbrains.python.codeInsight.controlflow.ScopeOwner; +import com.jetbrains.python.codeInsight.controlflow.WriteInstruction; +import com.jetbrains.python.psi.*; +import com.jetbrains.python.psi.impl.PyBinaryExpressionNavigator; +import com.jetbrains.python.psi.impl.PyForStatementNavigator; +import org.jetbrains.annotations.NotNull; + +/** + * @author oleg + */ +public class PyCodeFragmentUtil { + private PyCodeFragmentUtil() { + } + + public static CodeFragment createCodeFragment(@NotNull final ScopeOwner owner, + @NotNull final PsiElement startInScope, + @NotNull final PsiElement endInScope) throws CannotCreateCodeFragmentException { + final int start = startInScope.getTextOffset(); + final int end = endInScope.getTextOffset() + endInScope.getTextLength(); + + // Check for class or function inside code fragment + owner.acceptChildren(new PyRecursiveElementVisitor(){ + @Override + public void visitPyClass(final PyClass node) { + if (CodeFragmentUtil.getPosition(node, start, end) == Position.INSIDE){ + throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.class.declaration.inside")); + } + } + + @Override + public void visitPyFunction(final PyFunction node) { + if (CodeFragmentUtil.getPosition(node, start, end) == Position.INSIDE){ + throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.class.declaration.inside")); + } + } + }); + + // Control flow inspection + final HashSet outerInstructions = new HashSet(); + boolean returnInstructionInside = false; + final Instruction[] flow = owner.getControlFlow().getInstructions(); + for (Instruction instruction : flow) { + final PsiElement element = instruction.getElement(); + if (element!=null && CodeFragmentUtil.elementFit(element, start, end)){ + if (element instanceof PyReturnStatement){ + returnInstructionInside = true; + } + if (element instanceof PyBreakStatement && !CodeFragmentUtil.elementFit(((PyBreakStatement) element).getLoopStatement(), start, end)){ + throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.no.corresponding.loop.for.break")); + } + if (element instanceof PyContinueStatement && !CodeFragmentUtil.elementFit(((PyContinueStatement) element).getLoopStatement(), start, end)){ + throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.no.corresponding.loop.for.continue")); + } + + for (Instruction next : instruction.allSucc()) { + // Ignore conditional instruction + if (next instanceof ConditionalInstruction){ + continue; + } + final PsiElement nextElement = next.getElement(); + // Ignore binary operations control flow + if (nextElement != null && PyBinaryExpressionNavigator.getBinaryExpressionByOperand(nextElement) != null){ + continue; + } + // We ignore except blocks + if (nextElement instanceof PyExceptPart){ + continue; + } + if (!CodeFragmentUtil.elementFit(nextElement, start, end)){ + outerInstructions.add(next); + } + } + } + } + + // If we see more than 1 outer instruction, controlflow is interrupted + if (outerInstructions.size() > 2){ + throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.execution.flow.is.interrupted")); + } + if (outerInstructions.size() == 2){ + boolean errorFound = true; + for (Instruction outerInstruction : outerInstructions) { + // Here we check control flow when for statement content is beeing extracted + if (outerInstruction instanceof WriteInstruction && + PyForStatementNavigator.getPyForStatementByIterable(outerInstruction.getElement())!=null){ + errorFound = false; + break; + } + } + if (errorFound){ + throw new CannotCreateCodeFragmentException(PyBundle.message("refactoring.extract.method.error.cannot.perform.refactoring.when.execution.flow.is.interrupted")); + } + } + + // Building code fragment + final PyCodeFragmentBuilder builder = new PyCodeFragmentBuilder(start, end); + owner.acceptChildren(builder); + return new CodeFragment(builder.inElements, builder.outElements, returnInstructionInside); + } + + +} diff --git a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java index 87c58cdf4e19..de563d6e87af 100644 --- a/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java +++ b/python/src/com/jetbrains/python/codeInsight/controlflow/PyControlFlowBuilder.java @@ -253,7 +253,7 @@ public class PyControlFlowBuilder extends PyRecursiveElementVisitor { final Instruction nextInstruction = new InstructionImpl(myBuilder, node); myBuilder.addNode(nextInstruction); myBuilder.checkPending(nextInstruction); - final PyLoopStatement loop = node.getLoop(); + final PyLoopStatement loop = node.getLoopStatement(); if (loop != null) { final Instruction instruction = myBuilder.findInstructionByElement(loop); if (instruction != null) { diff --git a/python/src/com/jetbrains/python/psi/PyBinaryExpression.java b/python/src/com/jetbrains/python/psi/PyBinaryExpression.java index 204d8ef28b11..68a3631ef290 100644 --- a/python/src/com/jetbrains/python/psi/PyBinaryExpression.java +++ b/python/src/com/jetbrains/python/psi/PyBinaryExpression.java @@ -16,6 +16,7 @@ package com.jetbrains.python.psi; +import com.intellij.psi.PsiElement; import org.jetbrains.annotations.Nullable; /** @@ -29,6 +30,8 @@ public interface PyBinaryExpression extends PyExpression { PyExpression getLeftExpression(); @Nullable PyExpression getRightExpression(); PyElementType getOperator(); + @Nullable + PsiElement getPsiOperator(); boolean isOperator(String chars); PyExpression getOppositeExpression(PyExpression expression) diff --git a/python/src/com/jetbrains/python/psi/PyContinueStatement.java b/python/src/com/jetbrains/python/psi/PyContinueStatement.java index 4e3b8fc467d0..4ca53715d35d 100644 --- a/python/src/com/jetbrains/python/psi/PyContinueStatement.java +++ b/python/src/com/jetbrains/python/psi/PyContinueStatement.java @@ -27,5 +27,5 @@ import org.jetbrains.annotations.Nullable; */ public interface PyContinueStatement extends PyStatement { @Nullable - PyLoopStatement getLoop(); + PyLoopStatement getLoopStatement(); } diff --git a/python/src/com/jetbrains/python/psi/impl/PyBinaryExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyBinaryExpressionImpl.java index 5af13fdc3a86..579a54666291 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyBinaryExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyBinaryExpressionImpl.java @@ -17,6 +17,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; +import com.intellij.psi.PsiElement; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; @@ -24,6 +25,7 @@ import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.types.PyType; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; /** * Created by IntelliJ IDEA. @@ -55,11 +57,15 @@ public class PyBinaryExpressionImpl extends PyElementImpl implements PyBinaryExp @PsiCached public PyElementType getOperator() { + final PsiElement psiOperator = getPsiOperator(); + return psiOperator != null ? (PyElementType)psiOperator.getNode().getElementType() : null; + } + + @Nullable + public PsiElement getPsiOperator() { ASTNode node = getNode(); - if (node != null) { - final ASTNode child = node.findChildByType(PyElementTypes.BINARY_OPS); - if (child != null) return (PyElementType)child.getElementType(); - } + final ASTNode child = node.findChildByType(PyElementTypes.BINARY_OPS); + if (child != null) return child.getPsi(); return null; } diff --git a/python/src/com/jetbrains/python/psi/impl/PyBinaryExpressionNavigator.java b/python/src/com/jetbrains/python/psi/impl/PyBinaryExpressionNavigator.java new file mode 100644 index 000000000000..65b051fbbc32 --- /dev/null +++ b/python/src/com/jetbrains/python/psi/impl/PyBinaryExpressionNavigator.java @@ -0,0 +1,23 @@ +package com.jetbrains.python.psi.impl; + +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.psi.PyBinaryExpression; +import org.jetbrains.annotations.Nullable; + +/** + * @author oleg + */ +public class PyBinaryExpressionNavigator { + @Nullable + public static PyBinaryExpression getBinaryExpressionByOperand(final PsiElement element) { + final PyBinaryExpression expression = PsiTreeUtil.getParentOfType(element, PyBinaryExpression.class, false); + if (expression == null){ + return null; + } + if (expression.getPsiOperator() == element){ + return expression; + } + return null; + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyContinueStatementImpl.java b/python/src/com/jetbrains/python/psi/impl/PyContinueStatementImpl.java index a0c48e478990..cec24f2d96b6 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyContinueStatementImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyContinueStatementImpl.java @@ -40,7 +40,7 @@ public class PyContinueStatementImpl extends PyElementImpl implements PyContinue } @Nullable - public PyLoopStatement getLoop() { + public PyLoopStatement getLoopStatement() { return PsiTreeUtil.getParentOfType(this, PyLoopStatement.class); } } diff --git a/python/src/com/jetbrains/python/refactoring/PyExtractMethodHandler.java b/python/src/com/jetbrains/python/refactoring/PyExtractMethodHandler.java new file mode 100644 index 000000000000..649dd0316ab3 --- /dev/null +++ b/python/src/com/jetbrains/python/refactoring/PyExtractMethodHandler.java @@ -0,0 +1,38 @@ +package com.jetbrains.python.refactoring; + +import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.ScrollType; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.refactoring.RefactoringActionHandler; +import org.jetbrains.annotations.NotNull; + +/** + * @author oleg + */ +public class PyExtractMethodHandler implements RefactoringActionHandler { + public void invoke(@NotNull final Project project, final Editor editor, final PsiFile file, final DataContext dataContext) { + editor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE); + // select editor text fragment + if (!editor.getSelectionModel().hasSelection()) { + editor.getSelectionModel().selectLineAtCaret(); + } + invokeOnEditor(project, editor, file); + } + + + public void invoke(@NotNull final Project project, @NotNull final PsiElement[] elements, final DataContext dataContext) { + + } + + private void invokeOnEditor(final Project project, final Editor editor, final PsiFile file) { + final SelectionModel selectionModel = editor.getSelectionModel(); + final int start = selectionModel.getSelectionStart(); + final int end = selectionModel.getSelectionEnd(); + + } + +} diff --git a/python/src/com/jetbrains/python/refactoring/RefactoringProvider.java b/python/src/com/jetbrains/python/refactoring/RefactoringProvider.java index 9e9e4b9bbabe..aecb5dfee243 100644 --- a/python/src/com/jetbrains/python/refactoring/RefactoringProvider.java +++ b/python/src/com/jetbrains/python/refactoring/RefactoringProvider.java @@ -39,4 +39,9 @@ public class RefactoringProvider extends DefaultRefactoringSupportProvider { public RefactoringActionHandler getExtractSuperClassHandler() { return new PyExtractSuperclassHandler(); } + + @Override + public RefactoringActionHandler getExtractMethodHandler() { + return new PyExtractMethodHandler(); + } }