blob: e360c9e4668521fcf4d7948eb1f4fad50ab1fd6f [file] [log] [blame]
/*
* Copyright 2000-2013 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.refactoring.changeSignature;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.ui.Messages;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.changeSignature.ChangeSignatureHandler;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.search.PySuperMethodsSearch;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* User : ktisha
*/
public class PyChangeSignatureHandler implements ChangeSignatureHandler {
@Nullable
@Override
public PsiElement findTargetMember(PsiFile file, Editor editor) {
PsiElement element = PyUtil.findNonWhitespaceAtOffset(file, editor.getCaretModel().getOffset());
return findTargetMember(element);
}
@Nullable
@Override
public PsiElement findTargetMember(@Nullable PsiElement element) {
final PyCallExpression callExpression = PsiTreeUtil.getParentOfType(element, PyCallExpression.class);
if (callExpression != null) {
return callExpression.resolveCalleeFunction(PyResolveContext.defaultContext());
}
return PsiTreeUtil.getParentOfType(element, PyFunction.class);
}
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) {
PsiElement element = findTargetMember(file, editor);
if (element == null) {
element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
}
invokeOnElement(project, element, editor);
}
@Override
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, @Nullable DataContext dataContext) {
if (elements.length != 1) return;
Editor editor = dataContext == null ? null : CommonDataKeys.EDITOR.getData(dataContext);
invokeOnElement(project, elements[0], editor);
}
@Nullable
@Override
public String getTargetNotFoundMessage() {
return PyBundle.message("refactoring.change.signature.error.wrong.caret.position.method.name");
}
private static void invokeOnElement(Project project, PsiElement element, Editor editor) {
if (element instanceof PyLambdaExpression) {
String message =
RefactoringBundle.getCannotRefactorMessage("Caret is positioned on lambda call.");
CommonRefactoringUtil.showErrorHint(project, editor, message,
REFACTORING_NAME, REFACTORING_NAME);
return;
}
if (!(element instanceof PyFunction)) {
String message =
RefactoringBundle.getCannotRefactorMessage(
PyBundle.message("refactoring.change.signature.error.wrong.caret.position.method.name"));
CommonRefactoringUtil.showErrorHint(project, editor, message,
REFACTORING_NAME, REFACTORING_NAME);
return;
}
if (isNotUnderSourceRoot(project, element.getContainingFile(), editor)) return;
final PyFunction superMethod = getSuperMethod((PyFunction)element);
if (superMethod == null) return;
if (!superMethod.equals(element)) {
element = superMethod;
if (isNotUnderSourceRoot(project, superMethod.getContainingFile(), editor)) return;
}
final PyFunction function = (PyFunction)element;
final PyParameter[] parameters = function.getParameterList().getParameters();
for (PyParameter p : parameters) {
if (p instanceof PyTupleParameter) {
String message =
RefactoringBundle.getCannotRefactorMessage("Function contains tuple parameters");
CommonRefactoringUtil.showErrorHint(project, editor, message,
REFACTORING_NAME, REFACTORING_NAME);
return;
}
}
final PyMethodDescriptor method = new PyMethodDescriptor((PyFunction)element);
PyChangeSignatureDialog dialog = new PyChangeSignatureDialog(project, method);
dialog.show();
}
private static boolean isNotUnderSourceRoot(@NotNull final Project project,
@Nullable final PsiFile psiFile,
@Nullable final Editor editor) {
if (psiFile == null) return true;
final VirtualFile virtualFile = psiFile.getVirtualFile();
if (virtualFile != null) {
if (ProjectRootManager.getInstance(project).getFileIndex().isInLibraryClasses(virtualFile)) {
String message =
RefactoringBundle.getCannotRefactorMessage("Function is not under the source root");
CommonRefactoringUtil.showErrorHint(project, editor, message,
REFACTORING_NAME, REFACTORING_NAME);
return true;
}
}
return false;
}
@Nullable
protected static PyFunction getSuperMethod(@Nullable PyFunction function) {
if (function == null) return null;
final PyClass containingClass = function.getContainingClass();
if (containingClass == null) {
return function;
}
final PyFunction deepestSuperMethod = PySuperMethodsSearch.findDeepestSuperMethod(function);
if (!deepestSuperMethod.equals(function)) {
final PyClass baseClass = deepestSuperMethod.getContainingClass();
final PyBuiltinCache cache = PyBuiltinCache.getInstance(baseClass);
String baseClassName = baseClass == null? "" : baseClass.getName();
if (cache.isBuiltin(baseClass))
return function;
final String message = PyBundle.message(
"refactoring.change.signature.find.usages.of.base.class",
function.getName(),
containingClass.getName(),
baseClassName);
int choice;
if (ApplicationManagerEx.getApplicationEx().isUnitTestMode()) {
choice = 0;
}
else {
choice = Messages.showYesNoCancelDialog(function.getProject(), message,
REFACTORING_NAME, Messages.getQuestionIcon());
}
switch (choice) {
case Messages.YES:
return deepestSuperMethod;
case Messages.NO:
return function;
default:
return null;
}
}
return function;
}
}