diff --git a/python/src/META-INF/python-core-common.xml b/python/src/META-INF/python-core-common.xml index bd98166afb02..0f77daa4edaf 100644 --- a/python/src/META-INF/python-core-common.xml +++ b/python/src/META-INF/python-core-common.xml @@ -416,6 +416,7 @@ + diff --git a/python/src/com/jetbrains/python/codeInsight/implement/PyImplementMethodsHandler.kt b/python/src/com/jetbrains/python/codeInsight/implement/PyImplementMethodsHandler.kt new file mode 100644 index 000000000000..665e97fe15dc --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/implement/PyImplementMethodsHandler.kt @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2017 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.codeInsight.implement + +import com.intellij.lang.LanguageCodeInsightActionHandler +import com.intellij.openapi.editor.Editor +import com.intellij.openapi.project.Project +import com.intellij.psi.PsiFile +import com.jetbrains.python.codeInsight.override.PyOverrideImplementUtil +import com.jetbrains.python.psi.PyFile +import com.jetbrains.python.psi.types.TypeEvalContext + +class PyImplementMethodsHandler : LanguageCodeInsightActionHandler { + + override fun isValidFor(editor: Editor?, file: PsiFile?): Boolean { + return editor != null && file is PyFile && PyOverrideImplementUtil.getContextClass(editor, file) != null + } + + override fun invoke(project: Project, editor: Editor, file: PsiFile) { + val cls = PyOverrideImplementUtil.getContextClass(editor, file) + if (cls != null) { + PyOverrideImplementUtil.chooseAndImplementMethods(project, editor, cls, TypeEvalContext.codeCompletion(project, file)) + } + } + + override fun startInWriteAction() = false +} \ No newline at end of file diff --git a/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java b/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java index 462133aa869a..15abbecdaf10 100644 --- a/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java +++ b/python/src/com/jetbrains/python/codeInsight/override/PyOverrideImplementUtil.java @@ -35,6 +35,7 @@ import com.intellij.psi.PsiFile; import com.intellij.psi.PsiWhiteSpace; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.ui.SpeedSearchComparator; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyFunctionBuilder; @@ -43,7 +44,10 @@ import com.jetbrains.python.psi.types.*; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; /** * @author Alexey.Ivanov @@ -70,46 +74,54 @@ public class PyOverrideImplementUtil { return pyClass; } - public static void chooseAndOverrideMethods(final Project project, @NotNull final Editor editor, @NotNull final PyClass pyClass) { - - + public static void chooseAndOverrideMethods(@NotNull Project project, + @NotNull Editor editor, + @NotNull PyClass cls, + @NotNull TypeEvalContext context) { FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.CODEASSISTS_OVERRIDE_IMPLEMENT); - chooseAndOverrideOrImplementMethods(project, editor, pyClass); - } - - private static void chooseAndOverrideOrImplementMethods(final Project project, - @NotNull final Editor editor, - @NotNull final PyClass pyClass) { - PyPsiUtils.assertValid(pyClass); + PyPsiUtils.assertValid(cls); ApplicationManager.getApplication().assertReadAccessAllowed(); - final Set result = new HashSet<>(); - TypeEvalContext context = TypeEvalContext.codeCompletion(project, null); - final Collection superFunctions = getAllSuperFunctions(pyClass, context); - - - result.addAll(superFunctions); - chooseAndOverrideOrImplementMethods(project, editor, pyClass, result, "Select Methods to Override", false); + chooseAndOverrideOrImplementMethods(project, editor, cls, getAllSuperMethods(cls, context), false); } - public static void chooseAndOverrideOrImplementMethods(@NotNull final Project project, - @NotNull final Editor editor, - @NotNull final PyClass pyClass, - @NotNull final Collection superFunctions, - @NotNull final String title, final boolean implement) { - List elements = new ArrayList<>(); - for (PyFunction function : superFunctions) { - final String name = function.getName(); + public static void chooseAndImplementMethods(@NotNull Project project, + @NotNull Editor editor, + @NotNull PyClass cls, + @NotNull TypeEvalContext context) { + chooseAndImplementMethods(project, editor, cls, getAllSuperAbstractMethods(cls, context)); + } + + public static void chooseAndImplementMethods(@NotNull Project project, + @NotNull Editor editor, + @NotNull PyClass cls, + @NotNull Collection methods) { + FeatureUsageTracker.getInstance().triggerFeatureUsed(ProductivityFeatureNames.CODEASSISTS_OVERRIDE_IMPLEMENT); + + PyPsiUtils.assertValid(cls); + ApplicationManager.getApplication().assertReadAccessAllowed(); + + chooseAndOverrideOrImplementMethods(project, editor, cls, methods, true); + } + + private static void chooseAndOverrideOrImplementMethods(@NotNull Project project, + @NotNull Editor editor, + @NotNull PyClass cls, + @NotNull Collection methods, + boolean implement) { + final List elements = new ArrayList<>(); + for (PyFunction method : methods) { + final String name = method.getName(); if (name == null || PyUtil.isClassPrivateName(name)) { continue; } - if (pyClass.findMethodByName(name, false, null) == null) { - final PyMethodMember member = new PyMethodMember(function); + if (cls.findMethodByName(name, false, null) == null) { + final PyMethodMember member = new PyMethodMember(method); elements.add(member); } } - if (elements.size() == 0) { + if (elements.isEmpty()) { return; } @@ -126,14 +138,13 @@ public class PyOverrideImplementUtil { }; } }; - chooser.setTitle(title); + chooser.setTitle(implement ? "Select Methods to Implement" : "Select Methods to Override"); chooser.setCopyJavadocVisible(false); chooser.show(); if (chooser.getExitCode() != DialogWrapper.OK_EXIT_CODE) { return; } - List membersToOverride = chooser.getSelectedElements(); - overrideMethods(editor, pyClass, membersToOverride, implement); + overrideMethods(editor, cls, chooser.getSelectedElements(), implement); } public static void overrideMethods(final Editor editor, final PyClass pyClass, final List membersToOverride, @@ -326,11 +337,26 @@ public class PyOverrideImplementUtil { return toClass.getName(); } + @NotNull + public static List getAllSuperAbstractMethods(@NotNull PyClass cls, @NotNull TypeEvalContext context) { + return ContainerUtil.filter(getAllSuperMethods(cls, context), method -> isAbstractMethodForClass(method, cls, context)); + } + + private static boolean isAbstractMethodForClass(@NotNull PyFunction method, @NotNull PyClass cls, @NotNull TypeEvalContext context) { + final String methodName = method.getName(); + if (methodName == null || + cls.findMethodByName(methodName, false, context) != null || + cls.findClassAttribute(methodName, false, context) != null) { + return false; + } + return PyUtil.isDecoratedAsAbstract(method) || raisesNotImplementedError(method); + } + /** * Returns all super functions available through MRO. */ @NotNull - public static List getAllSuperFunctions(@NotNull PyClass pyClass, @NotNull TypeEvalContext context) { + public static List getAllSuperMethods(@NotNull PyClass pyClass, @NotNull TypeEvalContext context) { final Map functions = Maps.newLinkedHashMap(); for (final PyClassLikeType type : pyClass.getAncestorTypes(context)) { if (type != null) { diff --git a/python/src/com/jetbrains/python/codeInsight/override/PyOverrideMethodsHandler.java b/python/src/com/jetbrains/python/codeInsight/override/PyOverrideMethodsHandler.java index c7e4d4195a6a..4e3ae62cd59b 100644 --- a/python/src/com/jetbrains/python/codeInsight/override/PyOverrideMethodsHandler.java +++ b/python/src/com/jetbrains/python/codeInsight/override/PyOverrideMethodsHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -21,23 +21,28 @@ import com.intellij.openapi.project.Project; import com.intellij.psi.PsiFile; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyFile; +import com.jetbrains.python.psi.types.TypeEvalContext; import org.jetbrains.annotations.NotNull; /** * @author Alexey.Ivanov */ public class PyOverrideMethodsHandler implements LanguageCodeInsightActionHandler { + + @Override public boolean isValidFor(Editor editor, PsiFile file) { return (file instanceof PyFile) && (PyOverrideImplementUtil.getContextClass(editor, file) != null); } + @Override public void invoke(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile file) { - PyClass aClass = PyOverrideImplementUtil.getContextClass(editor, file); + final PyClass aClass = PyOverrideImplementUtil.getContextClass(editor, file); if (aClass != null) { - PyOverrideImplementUtil.chooseAndOverrideMethods(project, editor, aClass); + PyOverrideImplementUtil.chooseAndOverrideMethods(project, editor, aClass, TypeEvalContext.userInitiated(project, file)); } } + @Override public boolean startInWriteAction() { return false; } diff --git a/python/src/com/jetbrains/python/inspections/PyAbstractClassInspection.java b/python/src/com/jetbrains/python/inspections/PyAbstractClassInspection.java index a6bf2f1ea85e..a781634f9c22 100644 --- a/python/src/com/jetbrains/python/inspections/PyAbstractClassInspection.java +++ b/python/src/com/jetbrains/python/inspections/PyAbstractClassInspection.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2017 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. @@ -30,7 +30,6 @@ import org.jetbrains.annotations.Nls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.util.Collection; import java.util.HashSet; import java.util.Set; @@ -62,18 +61,12 @@ public class PyAbstractClassInspection extends PyInspection { if (isAbstract(pyClass)) { return; } - final Set toBeImplemented = new HashSet<>(); - final Collection functions = PyOverrideImplementUtil.getAllSuperFunctions(pyClass, myTypeEvalContext); - for (PyFunction method : functions) { - if (isAbstractMethodForClass(method, pyClass)) { - toBeImplemented.add(method); - } - } + final Set toImplement = new HashSet<>(PyOverrideImplementUtil.getAllSuperAbstractMethods(pyClass, myTypeEvalContext)); final ASTNode nameNode = pyClass.getNameNode(); - if (!toBeImplemented.isEmpty() && nameNode != null) { + if (!toImplement.isEmpty() && nameNode != null) { registerProblem(nameNode.getPsi(), PyBundle.message("INSP.NAME.abstract.class.$0.must.implement", pyClass.getName()), - new PyImplementMethodsQuickFix(pyClass, toBeImplemented)); + new PyImplementMethodsQuickFix(pyClass, toImplement)); } } @@ -95,15 +88,5 @@ public class PyAbstractClassInspection extends PyInspection { } return false; } - - private static boolean isAbstractMethodForClass(@NotNull PyFunction method, @NotNull PyClass cls) { - final String methodName = method.getName(); - if (methodName == null || - cls.findMethodByName(methodName, false, null) != null || - cls.findClassAttribute(methodName, false, null) != null) { - return false; - } - return PyUtil.isDecoratedAsAbstract(method) || PyOverrideImplementUtil.raisesNotImplementedError(method); - } } } diff --git a/python/src/com/jetbrains/python/inspections/quickfix/PyImplementMethodsQuickFix.java b/python/src/com/jetbrains/python/inspections/quickfix/PyImplementMethodsQuickFix.java index 2ad01a3fbf48..e0c8358ecb64 100644 --- a/python/src/com/jetbrains/python/inspections/quickfix/PyImplementMethodsQuickFix.java +++ b/python/src/com/jetbrains/python/inspections/quickfix/PyImplementMethodsQuickFix.java @@ -21,6 +21,7 @@ import com.intellij.openapi.editor.Editor; import com.intellij.openapi.project.Project; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.util.containers.ContainerUtil; import com.jetbrains.python.PyBundle; import com.jetbrains.python.codeInsight.override.PyMethodMember; import com.jetbrains.python.codeInsight.override.PyOverrideImplementUtil; @@ -29,15 +30,15 @@ import com.jetbrains.python.psi.PyFunction; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; -import java.util.ArrayList; import java.util.Set; public class PyImplementMethodsQuickFix extends LocalQuickFixOnPsiElement { + @NotNull private final Set myToImplement; - public PyImplementMethodsQuickFix(PyClass aClass, Set toImplement) { - super(aClass); + public PyImplementMethodsQuickFix(@NotNull PyClass cls, @NotNull Set toImplement) { + super(cls); myToImplement = toImplement; } @@ -47,6 +48,7 @@ public class PyImplementMethodsQuickFix extends LocalQuickFixOnPsiElement { return PyBundle.message("QFIX.NAME.implement.methods"); } + @Override @NonNls @NotNull public String getFamilyName() { @@ -59,17 +61,10 @@ public class PyImplementMethodsQuickFix extends LocalQuickFixOnPsiElement { if (editor != null && startElement instanceof PyClass) { if (ApplicationManager.getApplication().isUnitTestMode()) { - ArrayList list = new ArrayList<>(); - for (PyFunction function: myToImplement) { - list.add(new PyMethodMember(function)); - } - PyOverrideImplementUtil.overrideMethods(editor, (PyClass)startElement, list, true); + PyOverrideImplementUtil.overrideMethods(editor, (PyClass)startElement, ContainerUtil.map(myToImplement, PyMethodMember::new), true); } else { - PyOverrideImplementUtil - .chooseAndOverrideOrImplementMethods(project, editor, - (PyClass)startElement, myToImplement, - "Select Methods to Implement", true); + PyOverrideImplementUtil.chooseAndImplementMethods(project, editor, (PyClass)startElement, myToImplement); } } }