PY-22215 Fixed: "Implement Methods..." action for Python

Introduce PyImplementMethodsHandler to support "Implement Methods..." action.
This commit is contained in:
Semyon Proshev
2017-08-18 14:57:36 +03:00
parent 84ca667582
commit e4216949e8
6 changed files with 119 additions and 69 deletions
@@ -416,6 +416,7 @@
<liveTemplateMacro implementation="com.jetbrains.python.codeInsight.liveTemplates.PyIterableVariableMacro"/>
<codeInsight.overrideMethod language="Python" implementationClass="com.jetbrains.python.codeInsight.override.PyOverrideMethodsHandler"/>
<codeInsight.implementMethod language="Python" implementationClass="com.jetbrains.python.codeInsight.implement.PyImplementMethodsHandler"/>
<lang.refactoringSupport language="Python" implementationClass="com.jetbrains.python.refactoring.PyRefactoringProvider"/>
<lang.refactoringSupport.classMembersRefactoringSupport language="Python"
implementationClass="com.jetbrains.python.refactoring.classes.PyMembersRefactoringSupport"/>
@@ -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
}
@@ -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<PyFunction> result = new HashSet<>();
TypeEvalContext context = TypeEvalContext.codeCompletion(project, null);
final Collection<PyFunction> 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<PyFunction> superFunctions,
@NotNull final String title, final boolean implement) {
List<PyMethodMember> 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<PyFunction> 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<PyFunction> methods,
boolean implement) {
final List<PyMethodMember> 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<PyMethodMember> 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<PyMethodMember> membersToOverride,
@@ -326,11 +337,26 @@ public class PyOverrideImplementUtil {
return toClass.getName();
}
@NotNull
public static List<PyFunction> 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<PyFunction> getAllSuperFunctions(@NotNull PyClass pyClass, @NotNull TypeEvalContext context) {
public static List<PyFunction> getAllSuperMethods(@NotNull PyClass pyClass, @NotNull TypeEvalContext context) {
final Map<String, PyFunction> functions = Maps.newLinkedHashMap();
for (final PyClassLikeType type : pyClass.getAncestorTypes(context)) {
if (type != null) {
@@ -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;
}
@@ -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<PyFunction> toBeImplemented = new HashSet<>();
final Collection<PyFunction> functions = PyOverrideImplementUtil.getAllSuperFunctions(pyClass, myTypeEvalContext);
for (PyFunction method : functions) {
if (isAbstractMethodForClass(method, pyClass)) {
toBeImplemented.add(method);
}
}
final Set<PyFunction> 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);
}
}
}
@@ -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<PyFunction> myToImplement;
public PyImplementMethodsQuickFix(PyClass aClass, Set<PyFunction> toImplement) {
super(aClass);
public PyImplementMethodsQuickFix(@NotNull PyClass cls, @NotNull Set<PyFunction> 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<PyMethodMember> 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);
}
}
}