From 35ef49d99f21d3d52505500311a8ee92b41185ae Mon Sep 17 00:00:00 2001 From: Dennis Ushakov Date: Tue, 19 Jan 2010 20:13:27 +0300 Subject: [PATCH] pull up snake --- .../jetbrains/python/psi/impl/PyPsiUtils.java | 33 ++++++ .../refactoring/classes/PyMemberInfo.java | 16 +++ .../classes/PyMemberInfoStorage.java | 3 + .../classes/pullUp/PullUpConflictsUtil.java | 8 ++ .../classes/pullUp/PyPullUpDialog.java | 5 +- .../classes/pullUp/PyPullUpHandler.java | 15 ++- .../classes/pullUp/PyPullUpHelper.java | 110 ++++++++++++++++++ .../pullup/existingsuperclass.after.py | 11 ++ .../refactoring/pullup/existingsuperclass.py | 11 ++ .../refactoring/pullup/simple.after.py | 8 ++ python/testData/refactoring/pullup/simple.py | 7 ++ .../refactoring/pullup/superclass.after.py | 11 ++ .../testData/refactoring/pullup/superclass.py | 11 ++ .../refactoring/classes/PyPullUpTest.java | 67 +++++++++++ 14 files changed, 308 insertions(+), 8 deletions(-) create mode 100644 python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpHelper.java create mode 100644 python/testData/refactoring/pullup/existingsuperclass.after.py create mode 100644 python/testData/refactoring/pullup/existingsuperclass.py create mode 100644 python/testData/refactoring/pullup/simple.after.py create mode 100644 python/testData/refactoring/pullup/simple.py create mode 100644 python/testData/refactoring/pullup/superclass.after.py create mode 100644 python/testData/refactoring/pullup/superclass.py create mode 100644 python/testSrc/com/jetbrains/python/refactoring/classes/PyPullUpTest.java diff --git a/python/src/com/jetbrains/python/psi/impl/PyPsiUtils.java b/python/src/com/jetbrains/python/psi/impl/PyPsiUtils.java index 2cdb4f0b3b17..d7e0335adb8d 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyPsiUtils.java +++ b/python/src/com/jetbrains/python/psi/impl/PyPsiUtils.java @@ -4,6 +4,7 @@ package com.jetbrains.python.psi.impl; import com.intellij.lang.ASTNode; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Pair; @@ -19,6 +20,7 @@ import org.jetbrains.annotations.Nullable; public class PyPsiUtils { public static final Key> SELECTION_BREAKS_AST_NODE = new Key>("python.selection.breaks.ast.node"); + private static final Logger LOG = Logger.getInstance(PyPsiUtils.class.getName()); private PyPsiUtils() { } @@ -72,4 +74,35 @@ public class PyPsiUtils { oldExpression.replace(newExpression); } } + + public static void addToEnd(@NotNull final PsiElement psiElement, @NotNull final PsiElement... newElements) { + final ASTNode psiNode = psiElement.getNode(); + LOG.assertTrue(psiNode != null); + for (PsiElement newElement : newElements) { + //noinspection ConstantConditions + psiNode.addChild(newElement.getNode()); + } + } + + public static void addBeforeInParent(@NotNull final PsiElement anchor, @NotNull final PsiElement... newElements) { + final PsiElement psiParent = anchor.getParent(); + LOG.assertTrue(psiParent != null); + final ASTNode parentNode = psiParent.getNode(); + final ASTNode anchorNode = anchor.getNode(); + LOG.assertTrue(parentNode != null); + LOG.assertTrue(anchorNode != null); + for (PsiElement newElement : newElements) { + //noinspection ConstantConditions + parentNode.addChild(newElement.getNode(), anchorNode); + } + } + + public static void removeElements(@NotNull final PsiElement... elements) { + final ASTNode parentNode = elements[0].getParent().getNode(); + LOG.assertTrue(parentNode != null); + for (PsiElement element : elements) { + //noinspection ConstantConditions + parentNode.removeChild(element.getNode()); + } + } } \ No newline at end of file diff --git a/python/src/com/jetbrains/python/refactoring/classes/PyMemberInfo.java b/python/src/com/jetbrains/python/refactoring/classes/PyMemberInfo.java index 258ea08d94c6..d0263826909a 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/PyMemberInfo.java +++ b/python/src/com/jetbrains/python/refactoring/classes/PyMemberInfo.java @@ -2,6 +2,7 @@ package com.jetbrains.python.refactoring.classes; import com.intellij.refactoring.classMembers.MemberInfoBase; import com.jetbrains.python.psi.*; +import com.jetbrains.python.refactoring.classes.ui.PyClassCellRenderer; /** * @author Dennis.Ushakov @@ -21,6 +22,8 @@ public class PyMemberInfo extends MemberInfoBase { overrides = true; } } + } else if (member instanceof PyClass) { + displayName = PyClassCellRenderer.getClassText((PyClass)member); } } @@ -37,4 +40,17 @@ public class PyMemberInfo extends MemberInfoBase { builder.append(")"); return builder.toString(); } + + @Override + public boolean equals(Object obj) { + if (obj instanceof PyMemberInfo) { + return getMember().equals(((PyMemberInfo)obj).getMember()); + } + return false; + } + + @Override + public int hashCode() { + return getMember().hashCode(); + } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/PyMemberInfoStorage.java b/python/src/com/jetbrains/python/refactoring/classes/PyMemberInfoStorage.java index 88e3e3c886a5..84dde35d7abc 100644 --- a/python/src/com/jetbrains/python/refactoring/classes/PyMemberInfoStorage.java +++ b/python/src/com/jetbrains/python/refactoring/classes/PyMemberInfoStorage.java @@ -55,6 +55,9 @@ public class PyMemberInfoStorage extends AbstractMemberInfoStorage selectedMemberInfos, PyClass superClass) { - + private static void pullUpWithHelper(PyClass clazz, Collection selectedMemberInfos, PyClass superClass) { + PsiNavigateUtil.navigate(PyPullUpHelper.pullUp(clazz, selectedMemberInfos, superClass)); } @Override @@ -66,4 +65,8 @@ public class PyPullUpHandler extends PyClassRefactoringHandler { protected String getHelpId() { return "refactoring.pullMembersUp"; } + + public boolean isEnabledOnElements(PsiElement[] elements) { + return elements.length == 1 && elements[0] instanceof PyClass; + } } diff --git a/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpHelper.java b/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpHelper.java new file mode 100644 index 000000000000..d29cdf3c53aa --- /dev/null +++ b/python/src/com/jetbrains/python/refactoring/classes/pullUp/PyPullUpHelper.java @@ -0,0 +1,110 @@ +package com.jetbrains.python.refactoring.classes.pullUp; + +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.command.CommandProcessor; +import com.intellij.openapi.diagnostic.Logger; +import com.intellij.openapi.project.Project; +import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiFileFactory; +import com.intellij.refactoring.RefactoringBundle; +import com.jetbrains.python.PythonFileType; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyElement; +import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.PyUtil; +import com.jetbrains.python.psi.impl.PyPsiUtils; +import com.jetbrains.python.refactoring.classes.PyMemberInfo; + +import java.util.*; + +/** + * @author Dennis.Ushakov + */ +public class PyPullUpHelper { + private static final Logger LOG = Logger.getInstance(PyPullUpHelper.class.getName()); + private PyPullUpHelper() {} + + public static PyElement pullUp(final PyClass clazz, final Collection selectedMemberInfos, final PyClass superClass) { + final Set superClasses = new HashSet(); + final List methods = new ArrayList(); + for (PyMemberInfo member : selectedMemberInfos) { + final PyElement element = member.getMember(); + if (element instanceof PyFunction) methods.add((PyFunction)element); + else if (element instanceof PyClass) superClasses.add(element.getName()); + else LOG.error("unmatched member class " + element.getClass()); + } + CommandProcessor.getInstance().executeCommand(clazz.getProject(), new Runnable() { + public void run() { + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + // move methods + moveElements(methods, clazz, superClass); + + // move superclasses declarations + moveSuperclasses(clazz, superClasses, superClass); + } + }); + } + }, RefactoringBundle.message("pull.members.up.title"), null); + + return superClass; + } + + private static void moveSuperclasses(PyClass clazz, Set superClasses, PyClass superClass) { + if (superClasses.size() == 0) return; + final Project project = clazz.getProject(); + final List toAdd = new ArrayList(); + PsiElement[] elements = clazz.getSuperClassExpressions(); + for (PsiElement element : elements) { + if (superClasses.contains(element.getText())) { + toAdd.add(element); + PyUtil.removeListNode(element); + } + } + elements = superClass.getSuperClassExpressions(); + + if (elements.length > 0) { + PsiElement parent = elements[elements.length - 1].getParent(); + for (PsiElement element : toAdd) { + PyUtil.addListNode(parent, element, parent.getLastChild().getNode(), false, true); + } + } else { + addSuperclasses(project, superClass, superClasses); + } + } + + private static void addSuperclasses(Project project, PyClass superClass, Collection superClasses) { + final StringBuilder builder = new StringBuilder("("); + for (String element : superClasses) { + if (builder.length() > 1) builder.append(","); + builder.append(element); + } + builder.append(")"); + final PsiFile file = PsiFileFactory.getInstance(project).createFileFromText(superClass.getName() + "temp", PythonFileType.INSTANCE, builder.toString()); + final PsiElement expression = file.getFirstChild().getFirstChild(); + PsiElement colon = superClass.getFirstChild(); + while (colon != null && !colon.getText().equals(":")) { + colon = colon.getNextSibling(); + } + PyPsiUtils.addBeforeInParent(colon, expression); + } + + private static void moveElements(List methods, PyClass clazz, PyClass superClass) { + if (methods.size() == 0) return; + final Project project = clazz.getProject(); + final PsiElement[] elements = methods.toArray(new PsiElement[methods.size()]); + PyPsiUtils.removeElements(elements); + final StringBuilder builder = new StringBuilder(); + for (PsiElement element : elements) { + builder.append(element.getText()).append("\n"); + } + final PsiFile file = PsiFileFactory.getInstance(project).createFileFromText(clazz.getName() + "temp", PythonFileType.INSTANCE, builder.toString()); + + if (superClass.getMethods().length != 0) { + PyPsiUtils.addBeforeInParent(superClass.getMethods()[0], file.getChildren()); + } else { + PyPsiUtils.addToEnd(superClass, file.getChildren()); + } + } +} diff --git a/python/testData/refactoring/pullup/existingsuperclass.after.py b/python/testData/refactoring/pullup/existingsuperclass.after.py new file mode 100644 index 000000000000..877e489b8024 --- /dev/null +++ b/python/testData/refactoring/pullup/existingsuperclass.after.py @@ -0,0 +1,11 @@ +class Foo: + def foo(self): + print("a") + +class Zope(Hand,Foo): + def _mine(self): + print "zope" + +class Boo(): + def boo(self): + print "rrrrr" \ No newline at end of file diff --git a/python/testData/refactoring/pullup/existingsuperclass.py b/python/testData/refactoring/pullup/existingsuperclass.py new file mode 100644 index 000000000000..2e3ece4c2ba3 --- /dev/null +++ b/python/testData/refactoring/pullup/existingsuperclass.py @@ -0,0 +1,11 @@ +class Foo: + def foo(self): + print("a") + +class Zope(Hand): + def _mine(self): + print "zope" + +class Boo(Foo): + def boo(self): + print "rrrrr" \ No newline at end of file diff --git a/python/testData/refactoring/pullup/simple.after.py b/python/testData/refactoring/pullup/simple.after.py new file mode 100644 index 000000000000..49fef2067e0f --- /dev/null +++ b/python/testData/refactoring/pullup/simple.after.py @@ -0,0 +1,8 @@ +class Foo: + def boo(self): + print "rrrrr" + def foo(self): + print("a") + +class Boo(Foo): + \ No newline at end of file diff --git a/python/testData/refactoring/pullup/simple.py b/python/testData/refactoring/pullup/simple.py new file mode 100644 index 000000000000..68476d991756 --- /dev/null +++ b/python/testData/refactoring/pullup/simple.py @@ -0,0 +1,7 @@ +class Foo: + def foo(self): + print("a") + +class Boo(Foo): + def boo(self): + print "rrrrr" \ No newline at end of file diff --git a/python/testData/refactoring/pullup/superclass.after.py b/python/testData/refactoring/pullup/superclass.after.py new file mode 100644 index 000000000000..ec98b7be0fed --- /dev/null +++ b/python/testData/refactoring/pullup/superclass.after.py @@ -0,0 +1,11 @@ +class Foo: + def foo(self): + print("a") + +class Zope(Foo): + def _mine(self): + print "zope" + +class Boo(): + def boo(self): + print "rrrrr" \ No newline at end of file diff --git a/python/testData/refactoring/pullup/superclass.py b/python/testData/refactoring/pullup/superclass.py new file mode 100644 index 000000000000..17ef750a94fa --- /dev/null +++ b/python/testData/refactoring/pullup/superclass.py @@ -0,0 +1,11 @@ +class Foo: + def foo(self): + print("a") + +class Zope: + def _mine(self): + print "zope" + +class Boo(Foo): + def boo(self): + print "rrrrr" \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/refactoring/classes/PyPullUpTest.java b/python/testSrc/com/jetbrains/python/refactoring/classes/PyPullUpTest.java new file mode 100644 index 000000000000..a54a1d42b712 --- /dev/null +++ b/python/testSrc/com/jetbrains/python/refactoring/classes/PyPullUpTest.java @@ -0,0 +1,67 @@ +package com.jetbrains.python.refactoring.classes; + +import com.intellij.openapi.project.Project; +import com.intellij.psi.search.ProjectScope; +import com.intellij.psi.stubs.StubIndex; +import com.jetbrains.python.PythonTestUtil; +import com.jetbrains.python.fixtures.PyLightFixtureTestCase; +import com.jetbrains.python.psi.PyClass; +import com.jetbrains.python.psi.PyElement; +import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.stubs.PyClassNameIndex; +import com.jetbrains.python.refactoring.classes.pullUp.PyPullUpHelper; + +import java.util.Collection; +import java.util.Collections; + +/** + * @author Dennis.Ushakov + */ +public class PyPullUpTest extends PyLightFixtureTestCase { + public void testSimple() throws Exception { + doHelperTest("Boo", ".boo", "Foo"); + } + + public void testSuperclass() throws Exception { + doHelperTest("Boo", "Foo", "Zope"); + } + + public void testExistingsuperclass() throws Exception { + doHelperTest("Boo", "Foo", "Zope"); + } + + private void doHelperTest(final String className, final String memberName, final String superClassName) throws Exception { + String baseName = "/" + getTestName(true); + myFixture.configureByFile(baseName + ".py"); + final PyClass clazz = findClass(className); + final PyElement member = findMember(className, memberName); + final PyClass superClass = findClass(superClassName); + PyPullUpHelper.pullUp(clazz, Collections.singleton(new PyMemberInfo(member)), superClass); + myFixture.checkResultByFile(baseName + ".after.py"); + } + + private PyElement findMember(String className, String memberName) { + if (!memberName.contains(".")) return findClass(memberName); + return findMethod(className, memberName.substring(1)); + } + + private PyFunction findMethod(final String className, final String name) { + final PyClass clazz = findClass(className); + final PyFunction method = clazz.findMethodByName(name, false); + assertNotNull(method); + return method; + } + + private PyClass findClass(final String name) { + final Project project = myFixture.getProject(); + final Collection classes = StubIndex.getInstance().get(PyClassNameIndex.KEY, name, project, + ProjectScope.getProjectScope(project)); + assertEquals(1, classes.size()); + return classes.iterator().next(); + } + + @Override + protected String getTestDataPath() { + return PythonTestUtil.getTestDataPath() + "/refactoring/pullup/"; + } +}