mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Revert "Revert "First step of extract method: CodeFragment""
This reverts commit a6ee7cccd3b9162b007a6d2aca4b086ceceb39ef.
This commit is contained in:
@@ -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<String, List<PyElement>> modifiedInsideMap = new HashMap<String, List<PyElement>>();
|
||||
|
||||
final Set<String> inElements = new HashSet<String>();
|
||||
final Set<String> outElements = new HashSet<String>();
|
||||
|
||||
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<PyElement> 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<PyElement> list = modifiedInsideMap.get(name);
|
||||
if (list == null) {
|
||||
list = new ArrayList<PyElement>();
|
||||
modifiedInsideMap.put(name, list);
|
||||
}
|
||||
list.add(element);
|
||||
}
|
||||
|
||||
// if name is already in out parameters
|
||||
if (inElements.contains(name)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Instruction> outerInstructions = new HashSet<Instruction>();
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -27,5 +27,5 @@ import org.jetbrains.annotations.Nullable;
|
||||
*/
|
||||
public interface PyContinueStatement extends PyStatement {
|
||||
@Nullable
|
||||
PyLoopStatement getLoop();
|
||||
PyLoopStatement getLoopStatement();
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -40,7 +40,7 @@ public class PyContinueStatementImpl extends PyElementImpl implements PyContinue
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public PyLoopStatement getLoop() {
|
||||
public PyLoopStatement getLoopStatement() {
|
||||
return PsiTreeUtil.getParentOfType(this, PyLoopStatement.class);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -39,4 +39,9 @@ public class RefactoringProvider extends DefaultRefactoringSupportProvider {
|
||||
public RefactoringActionHandler getExtractSuperClassHandler() {
|
||||
return new PyExtractSuperclassHandler();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RefactoringActionHandler getExtractMethodHandler() {
|
||||
return new PyExtractMethodHandler();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user