mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-17265 Introduce new dialog for escalating local functions and methods
This dialog is similar to that previously used to move module members.
This commit is contained in:
@@ -465,7 +465,7 @@
|
||||
<automaticRenamerFactory implementation="com.jetbrains.python.refactoring.rename.PyParametersRenameFactory"/>
|
||||
|
||||
<moveFileHandler implementation="com.jetbrains.python.refactoring.move.PyMoveFileHandler"/>
|
||||
<refactoring.moveHandler implementation="com.jetbrains.python.refactoring.move.PyMoveModuleMembersDelegate"/>
|
||||
<refactoring.moveHandler implementation="com.jetbrains.python.refactoring.move.PyMoveSymbolDelegate"/>
|
||||
|
||||
<refactoring.changeSignatureUsageProcessor implementation="com.jetbrains.python.refactoring.changeSignature.PyChangeSignatureUsageProcessor" id="Python"/>
|
||||
|
||||
@@ -830,13 +830,6 @@
|
||||
<add-to-group group-id="DebugMainMenu" relative-to-action="StepInto" anchor="after"/>
|
||||
<add-to-group group-id="XDebugger.ToolWindow.TopToolbar" relative-to-action="StepInto" anchor="after"/>
|
||||
</action>
|
||||
|
||||
<action id="PyConvertLocalFunctionToTopLevelFunctionAction" class="com.jetbrains.python.refactoring.makeFunctionTopLevel.PyMakeFunctionTopLevelRefactoring"
|
||||
text="Make Top-Level Function"
|
||||
description="Convert local function or method to top-level function, transforming names from enclosing scope and instance attributes into parameters">
|
||||
<add-to-group group-id="RefactoringMenu"/>
|
||||
</action>
|
||||
|
||||
</actions>
|
||||
|
||||
<extensions defaultExtensionNs="com.intellij.spellchecker">
|
||||
|
||||
@@ -662,9 +662,11 @@ refactoring.move.module.members.error.cannot.use.module.name.$0=Cannot use modul
|
||||
refactoring.move.module.members.error.selection=Cannot perform refactoring using selected element(s)
|
||||
|
||||
# Make function top-level
|
||||
refactoring.make.function.top.level.function=Convert to top-level function
|
||||
refactoring.make.method.top.level=Make method top-level
|
||||
refactoring.make.local.function.top.level=Make local function top-level
|
||||
refactoring.make.method.top.level.dialog.title=Make Method Top-Level
|
||||
refactoring.make.method.top.level.dialog.description=Create top-level function from the method {0}
|
||||
|
||||
refactoring.make.local.function.top.level.dialog.title=Make Local Function Top-Level
|
||||
refactoring.make.local.function.top.level.dialog.description=Create top-level function from the local function {0}
|
||||
refactoring.make.function.top.level.error.nonlocal.writes=Cannot move function with nonlocal writes
|
||||
refactoring.make.function.top.level.error.self.reads=Cannot move function that contains usages of "self" parameter from outer scope
|
||||
refactoring.make.function.top.level.error.outer.scope.reads=Cannon move method that references names from the outer scope
|
||||
|
||||
+4
-8
@@ -18,7 +18,6 @@ package com.jetbrains.python.refactoring.makeFunctionTopLevel;
|
||||
import com.intellij.codeInsight.controlflow.ControlFlow;
|
||||
import com.intellij.codeInsight.controlflow.Instruction;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
@@ -52,13 +51,13 @@ import static com.jetbrains.python.psi.PyUtil.as;
|
||||
public abstract class PyBaseMakeFunctionTopLevelProcessor extends BaseRefactoringProcessor {
|
||||
protected final PyFunction myFunction;
|
||||
protected final PyResolveContext myResolveContext;
|
||||
protected final Editor myEditor;
|
||||
protected final PyElementGenerator myGenerator;
|
||||
protected final String myDestinationPath;
|
||||
|
||||
public PyBaseMakeFunctionTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull Editor editor) {
|
||||
public PyBaseMakeFunctionTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull String destinationPath) {
|
||||
super(targetFunction.getProject());
|
||||
myFunction = targetFunction;
|
||||
myEditor = editor;
|
||||
myDestinationPath = destinationPath;
|
||||
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(myProject, targetFunction.getContainingFile());
|
||||
myResolveContext = PyResolveContext.defaultContext().withTypeEvalContext(typeEvalContext);
|
||||
myGenerator = PyElementGenerator.getInstance(myProject);
|
||||
@@ -99,10 +98,7 @@ public abstract class PyBaseMakeFunctionTopLevelProcessor extends BaseRefactorin
|
||||
assert ApplicationManager.getApplication().isWriteAccessAllowed();
|
||||
|
||||
updateUsages(newParameters, usages);
|
||||
final PyFunction newFunction = replaceFunction(createNewFunction(newParameters));
|
||||
|
||||
myEditor.getSelectionModel().removeSelection();
|
||||
myEditor.getCaretModel().moveToOffset(newFunction.getTextOffset());
|
||||
replaceFunction(createNewFunction(newParameters));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.makeFunctionTopLevel;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.psi.PyFunction;
|
||||
import com.jetbrains.python.refactoring.move.PyBaseMoveDialog;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public class PyMakeFunctionTopLevelDialog extends PyBaseMoveDialog {
|
||||
|
||||
public PyMakeFunctionTopLevelDialog(@NotNull Project project,
|
||||
@NotNull PyFunction function,
|
||||
@NotNull String sourcePath,
|
||||
@NotNull String destinationPath) {
|
||||
super(project, sourcePath, destinationPath);
|
||||
String functionName = function.getName();
|
||||
if (StringUtil.isNotEmpty(functionName)) {
|
||||
functionName += "()";
|
||||
}
|
||||
if (function.getContainingClass() != null) {
|
||||
setTitle(PyBundle.message("refactoring.make.method.top.level.dialog.title"));
|
||||
myDescription.setText(PyBundle.message("refactoring.make.method.top.level.dialog.description", functionName));
|
||||
}
|
||||
else {
|
||||
setTitle(PyBundle.message("refactoring.make.local.function.top.level.dialog.title"));
|
||||
myDescription.setText(PyBundle.message("refactoring.make.local.function.top.level.dialog.description", functionName));
|
||||
}
|
||||
if (function.getContainingClass() != null) {
|
||||
myDescription.setText("Make top-level function from method ");
|
||||
}
|
||||
myExtraPanel.setVisible(false);
|
||||
init();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getHelpId() {
|
||||
return "python.reference.makeFunctionTopLevel";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected String getDimensionServiceKey() {
|
||||
return "#com.jetbrains.python.refactoring.move.PyMakeFunctionTopLevelDialog";
|
||||
}
|
||||
}
|
||||
-144
@@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.makeFunctionTopLevel;
|
||||
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
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.psi.util.PsiTreeUtil;
|
||||
import com.intellij.refactoring.RefactoringActionHandler;
|
||||
import com.intellij.refactoring.RefactoringBundle;
|
||||
import com.intellij.refactoring.util.CommonRefactoringUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
||||
import com.jetbrains.python.psi.PyFunction;
|
||||
import com.jetbrains.python.psi.PyReferenceExpression;
|
||||
import com.jetbrains.python.psi.PyUtil;
|
||||
import com.jetbrains.python.psi.search.PyOverridingMethodsSearch;
|
||||
import com.jetbrains.python.psi.search.PySuperMethodsSearch;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import com.jetbrains.python.refactoring.PyBaseRefactoringAction;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public class PyMakeFunctionTopLevelRefactoring extends PyBaseRefactoringAction {
|
||||
public static final String ID = "py.make.function.top.level";
|
||||
|
||||
@Override
|
||||
protected boolean isAvailableInEditorOnly() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnabledOnElementInsideEditor(@NotNull PsiElement element,
|
||||
@NotNull Editor editor,
|
||||
@NotNull PsiFile file,
|
||||
@NotNull DataContext context) {
|
||||
return findTargetFunction(element) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isEnabledOnElementsOutsideEditor(@NotNull PsiElement[] elements) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyFunction findTargetFunction(@NotNull PsiElement element) {
|
||||
if (isLocalFunction(element) || isSuitableInstanceMethod(element)) {
|
||||
return (PyFunction)element;
|
||||
}
|
||||
// e.g. caret is on "def" keyword
|
||||
if (isLocalFunction(element.getParent()) || isSuitableInstanceMethod(element.getParent())) {
|
||||
return (PyFunction)element.getParent();
|
||||
}
|
||||
final PyReferenceExpression refExpr = PsiTreeUtil.getParentOfType(element, PyReferenceExpression.class);
|
||||
if (refExpr == null) {
|
||||
return null;
|
||||
}
|
||||
final PsiElement resolved = refExpr.getReference().resolve();
|
||||
if (isLocalFunction(resolved) || isSuitableInstanceMethod(resolved)) {
|
||||
return (PyFunction)resolved;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isSuitableInstanceMethod(@Nullable PsiElement element) {
|
||||
final PyFunction function = as(element, PyFunction.class);
|
||||
if (function == null || function.getContainingClass() == null) {
|
||||
return false;
|
||||
}
|
||||
final String funcName = function.getName();
|
||||
if (funcName == null || PyUtil.isSpecialName(funcName)) {
|
||||
return false;
|
||||
}
|
||||
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(function.getProject(), function.getContainingFile());
|
||||
if (PySuperMethodsSearch.search(function, typeEvalContext).findFirst() != null) return false;
|
||||
if (PyOverridingMethodsSearch.search(function, true).findFirst() != null) return false;
|
||||
if (function.getDecoratorList() != null || function.getModifier() != null) return false;
|
||||
if (function.getContainingClass().findPropertyByCallable(function) != null) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isLocalFunction(@Nullable PsiElement resolved) {
|
||||
return resolved instanceof PyFunction && PsiTreeUtil.getParentOfType(resolved, ScopeOwner.class, true) instanceof PyFunction;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
protected RefactoringActionHandler getHandler(@NotNull DataContext dataContext) {
|
||||
return new RefactoringActionHandler() {
|
||||
@Override
|
||||
public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) {
|
||||
final PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
|
||||
if (element != null) {
|
||||
final PyFunction function = findTargetFunction(element);
|
||||
if (function != null) {
|
||||
final PyBaseMakeFunctionTopLevelProcessor processor;
|
||||
if (isSuitableInstanceMethod(function)) {
|
||||
processor = new PyMakeMethodTopLevelProcessor(function, editor);
|
||||
}
|
||||
else {
|
||||
processor = new PyMakeLocalFunctionTopLevelProcessor(function, editor);
|
||||
}
|
||||
try {
|
||||
processor.run();
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
throw e;
|
||||
}
|
||||
CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.getMessage(), ID, project);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
|
||||
// should be called only from the editor
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
+3
-5
@@ -16,7 +16,6 @@
|
||||
package com.jetbrains.python.refactoring.makeFunctionTopLevel;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.usageView.UsageInfo;
|
||||
@@ -41,15 +40,14 @@ import static com.jetbrains.python.psi.PyUtil.as;
|
||||
*/
|
||||
public class PyMakeLocalFunctionTopLevelProcessor extends PyBaseMakeFunctionTopLevelProcessor {
|
||||
|
||||
protected PyMakeLocalFunctionTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull Editor editor) {
|
||||
super(targetFunction, editor);
|
||||
setPreviewUsages(false);
|
||||
public PyMakeLocalFunctionTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull String destination) {
|
||||
super(targetFunction, destination);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected String getRefactoringName() {
|
||||
return PyBundle.message("refactoring.make.local.function.top.level");
|
||||
return PyBundle.message("refactoring.make.local.function.top.level.dialog.title");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+3
-7
@@ -17,8 +17,6 @@ package com.jetbrains.python.refactoring.makeFunctionTopLevel;
|
||||
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.util.Comparing;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
@@ -53,16 +51,14 @@ public class PyMakeMethodTopLevelProcessor extends PyBaseMakeFunctionTopLevelPro
|
||||
private final MultiMap<String, PyReferenceExpression> myAttributeReferences = MultiMap.create();
|
||||
private final Set<PsiElement> myReadsOfSelfParam = new HashSet<>();
|
||||
|
||||
public PyMakeMethodTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull Editor editor) {
|
||||
super(targetFunction, editor);
|
||||
// It's easier to debug without preview
|
||||
setPreviewUsages(!ApplicationManager.getApplication().isInternal());
|
||||
public PyMakeMethodTopLevelProcessor(@NotNull PyFunction targetFunction, @NotNull String destination) {
|
||||
super(targetFunction, destination);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
protected String getRefactoringName() {
|
||||
return PyBundle.message("refactoring.make.method.top.level");
|
||||
return PyBundle.message("refactoring.make.method.top.level.dialog.title");
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+103
-30
@@ -17,16 +17,16 @@ package com.jetbrains.python.refactoring.move;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.SelectionModel;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.refactoring.BaseRefactoringProcessor;
|
||||
import com.intellij.refactoring.RefactoringBundle;
|
||||
import com.intellij.refactoring.move.MoveCallback;
|
||||
@@ -35,63 +35,95 @@ import com.intellij.refactoring.util.CommonRefactoringUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.psi.PyElement;
|
||||
import com.jetbrains.python.psi.PyFile;
|
||||
import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import com.jetbrains.python.psi.search.PyOverridingMethodsSearch;
|
||||
import com.jetbrains.python.psi.search.PySuperMethodsSearch;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import com.jetbrains.python.refactoring.makeFunctionTopLevel.PyMakeFunctionTopLevelDialog;
|
||||
import com.jetbrains.python.refactoring.makeFunctionTopLevel.PyMakeLocalFunctionTopLevelProcessor;
|
||||
import com.jetbrains.python.refactoring.makeFunctionTopLevel.PyMakeMethodTopLevelProcessor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.jetbrains.python.psi.PyUtil.as;
|
||||
|
||||
/**
|
||||
* @author vlan
|
||||
*/
|
||||
public class PyMoveModuleMembersDelegate extends MoveHandlerDelegate {
|
||||
public class PyMoveSymbolDelegate extends MoveHandlerDelegate {
|
||||
@Override
|
||||
public boolean canMove(PsiElement[] elements, @Nullable PsiElement targetContainer) {
|
||||
if (!super.canMove(elements, targetContainer)) {
|
||||
return false;
|
||||
}
|
||||
// Local function or method
|
||||
if (findTargetFunction(elements[0]) != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Top-level module member
|
||||
for (PsiElement element : elements) {
|
||||
if (!PyMoveModuleMembersHelper.isMovableModuleMember(element)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return super.canMove(elements, targetContainer);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void doMove(Project project,
|
||||
PsiElement[] elements,
|
||||
@Nullable PsiElement targetContainer,
|
||||
@Nullable MoveCallback callback) {
|
||||
final List<PsiNamedElement> initialElements = Lists.newArrayList();
|
||||
for (PsiElement element : elements) {
|
||||
final PsiNamedElement e = PyMoveModuleMembersHelper.extractNamedElement(element);
|
||||
if (e == null) {
|
||||
return;
|
||||
}
|
||||
initialElements.add(e);
|
||||
}
|
||||
public void doMove(Project project, PsiElement[] elements, @Nullable PsiElement targetContainer, @Nullable MoveCallback callback) {
|
||||
String initialPath = null;
|
||||
if (targetContainer instanceof PsiFile) {
|
||||
final VirtualFile virtualFile = ((PsiFile)targetContainer).getVirtualFile();
|
||||
if (virtualFile != null) {
|
||||
initialPath = FileUtil.toSystemDependentName(virtualFile.getPath());
|
||||
}
|
||||
initialPath = StringUtil.notNullize(PyPsiUtils.getContainingFilePath(targetContainer));
|
||||
}
|
||||
if (initialPath == null) {
|
||||
initialPath = StringUtil.notNullize(PyPsiUtils.getContainingFilePath(elements[0]));
|
||||
}
|
||||
final PyMoveModuleMembersDialog dialog = new PyMoveModuleMembersDialog(project, initialElements, initialPath, initialPath);
|
||||
if (!dialog.showAndGet()) {
|
||||
return;
|
||||
|
||||
final BaseRefactoringProcessor processor;
|
||||
final PyFunction function = findTargetFunction(elements[0]);
|
||||
if (function != null) {
|
||||
final PyMakeFunctionTopLevelDialog dialog = new PyMakeFunctionTopLevelDialog(project, function, initialPath, initialPath);
|
||||
if (!dialog.showAndGet()) {
|
||||
return;
|
||||
}
|
||||
if (function.getContainingClass() != null) {
|
||||
processor = new PyMakeMethodTopLevelProcessor(function, dialog.getTargetPath());
|
||||
}
|
||||
else {
|
||||
processor = new PyMakeLocalFunctionTopLevelProcessor(function, dialog.getTargetPath());
|
||||
}
|
||||
processor.setPreviewUsages(dialog.isPreviewUsages());
|
||||
}
|
||||
final String destination = dialog.getTargetPath();
|
||||
final boolean previewUsages = dialog.isPreviewUsages();
|
||||
try {
|
||||
else {
|
||||
final List<PsiNamedElement> initialElements = Lists.newArrayList();
|
||||
for (PsiElement element : elements) {
|
||||
final PsiNamedElement e = PyMoveModuleMembersHelper.extractNamedElement(element);
|
||||
if (e == null) {
|
||||
return;
|
||||
}
|
||||
initialElements.add(e);
|
||||
}
|
||||
final PyMoveModuleMembersDialog dialog = new PyMoveModuleMembersDialog(project, initialElements, initialPath, initialPath);
|
||||
if (!dialog.showAndGet()) {
|
||||
return;
|
||||
}
|
||||
final boolean previewUsages = dialog.isPreviewUsages();
|
||||
final PsiNamedElement[] selectedElements = ContainerUtil.findAllAsArray(dialog.getSelectedTopLevelSymbols(), PsiNamedElement.class);
|
||||
final BaseRefactoringProcessor processor = new PyMoveModuleMembersProcessor(project, selectedElements, destination, previewUsages);
|
||||
processor = new PyMoveModuleMembersProcessor(project, selectedElements, dialog.getTargetPath(), previewUsages);
|
||||
}
|
||||
|
||||
try {
|
||||
processor.run();
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
if (ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
throw e;
|
||||
}
|
||||
CommonRefactoringUtil.showErrorMessage(RefactoringBundle.message("error.title"), e.getMessage(), null, project);
|
||||
}
|
||||
}
|
||||
@@ -121,7 +153,7 @@ public class PyMoveModuleMembersDelegate extends MoveHandlerDelegate {
|
||||
// Fallback to the old way to select single element to move
|
||||
final PsiNamedElement e = PyMoveModuleMembersHelper.extractNamedElement(element);
|
||||
if (e != null && PyMoveModuleMembersHelper.hasMovableElementType(e)) {
|
||||
if (PyMoveModuleMembersHelper.isMovableModuleMember(e)) {
|
||||
if (PyMoveModuleMembersHelper.isMovableModuleMember(e) || findTargetFunction(e) != null) {
|
||||
doMove(project, new PsiElement[]{e}, targetContainer, null);
|
||||
}
|
||||
else {
|
||||
@@ -154,4 +186,45 @@ public class PyMoveModuleMembersDelegate extends MoveHandlerDelegate {
|
||||
return body != null && selectionRange.contains(body.getTextRange());
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static PyFunction findTargetFunction(@NotNull PsiElement element) {
|
||||
if (isLocalFunction(element) || isSuitableInstanceMethod(element)) {
|
||||
return (PyFunction)element;
|
||||
}
|
||||
// e.g. caret is on "def" keyword
|
||||
if (isLocalFunction(element.getParent()) || isSuitableInstanceMethod(element.getParent())) {
|
||||
return (PyFunction)element.getParent();
|
||||
}
|
||||
final PyReferenceExpression refExpr = PsiTreeUtil.getParentOfType(element, PyReferenceExpression.class);
|
||||
if (refExpr == null) {
|
||||
return null;
|
||||
}
|
||||
final PsiElement resolved = refExpr.getReference().resolve();
|
||||
if (isLocalFunction(resolved) || isSuitableInstanceMethod(resolved)) {
|
||||
return (PyFunction)resolved;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static boolean isSuitableInstanceMethod(@Nullable PsiElement element) {
|
||||
final PyFunction function = as(element, PyFunction.class);
|
||||
if (function == null || function.getContainingClass() == null) {
|
||||
return false;
|
||||
}
|
||||
final String funcName = function.getName();
|
||||
if (funcName == null || PyUtil.isSpecialName(funcName)) {
|
||||
return false;
|
||||
}
|
||||
final TypeEvalContext typeEvalContext = TypeEvalContext.userInitiated(function.getProject(), function.getContainingFile());
|
||||
if (PySuperMethodsSearch.search(function, typeEvalContext).findFirst() != null) return false;
|
||||
if (PyOverridingMethodsSearch.search(function, true).findFirst() != null) return false;
|
||||
if (function.getDecoratorList() != null || function.getModifier() != null) return false;
|
||||
if (function.getContainingClass().findPropertyByCallable(function) != null) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean isLocalFunction(@Nullable PsiElement resolved) {
|
||||
return resolved instanceof PyFunction && PsiTreeUtil.getParentOfType(resolved, ScopeOwner.class, true) instanceof PyFunction;
|
||||
}
|
||||
}
|
||||
@@ -15,71 +15,73 @@
|
||||
*/
|
||||
package com.jetbrains.python.refactoring;
|
||||
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
import com.intellij.testFramework.TestActionEvent;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.fixtures.PyTestCase;
|
||||
import com.jetbrains.python.psi.LanguageLevel;
|
||||
import com.jetbrains.python.refactoring.makeFunctionTopLevel.PyMakeFunctionTopLevelRefactoring;
|
||||
import com.jetbrains.python.psi.PyFunction;
|
||||
import com.jetbrains.python.psi.impl.PyPsiUtils;
|
||||
import com.jetbrains.python.refactoring.makeFunctionTopLevel.PyMakeLocalFunctionTopLevelProcessor;
|
||||
import com.jetbrains.python.refactoring.makeFunctionTopLevel.PyMakeMethodTopLevelProcessor;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author Mikhail Golubev
|
||||
*/
|
||||
public class PyMakeFunctionTopLevelTest extends PyTestCase {
|
||||
|
||||
public void doTest(boolean enabled, @Nullable String message) {
|
||||
public void doTest(@Nullable String message) {
|
||||
myFixture.configureByFile(getTestName(true) + ".py");
|
||||
final PyMakeFunctionTopLevelRefactoring action = new PyMakeFunctionTopLevelRefactoring();
|
||||
// Similar to com.intellij.testFramework.fixtures.CodeInsightTestFixture.testAction()
|
||||
final TestActionEvent event = new TestActionEvent(action);
|
||||
action.beforeActionPerformedUpdate(event);
|
||||
assertEquals(enabled, event.getPresentation().isEnabledAndVisible());
|
||||
if (enabled) {
|
||||
try {
|
||||
action.actionPerformed(event);
|
||||
myFixture.checkResultByFile(getTestName(true) + ".after.py");
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
if (message == null) {
|
||||
fail("Refactoring failed unexpectedly with message: " + e.getMessage());
|
||||
final PyFunction function = assertInstanceOf(myFixture.getElementAtCaret(), PyFunction.class);
|
||||
final String destination = PyPsiUtils.getContainingFilePath(function);
|
||||
assertNotNull(destination);
|
||||
try {
|
||||
WriteCommandAction.runWriteCommandAction(myFixture.getProject(), new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (function.getContainingClass() != null) {
|
||||
new PyMakeMethodTopLevelProcessor(function, destination).run();
|
||||
}
|
||||
else {
|
||||
new PyMakeLocalFunctionTopLevelProcessor(function, destination).run();
|
||||
}
|
||||
}
|
||||
assertEquals(message, e.getMessage());
|
||||
});
|
||||
myFixture.checkResultByFile(getTestName(true) + ".after.py");
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
if (message == null) {
|
||||
fail("Refactoring failed unexpectedly with message: " + e.getMessage());
|
||||
}
|
||||
assertEquals(message, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void doMultiFileTest() throws IOException {
|
||||
final String rootBeforePath = getTestName(true) + "/before";
|
||||
final String rootAfterPath = getTestName(true) + "/after";
|
||||
final VirtualFile copiedDirectory = myFixture.copyDirectoryToProject(rootBeforePath, "");
|
||||
myFixture.configureByFile("main.py");
|
||||
myFixture.testAction(new PyMakeFunctionTopLevelRefactoring());
|
||||
PlatformTestUtil.assertDirectoriesEqual(getVirtualFileByName(getTestDataPath() + rootAfterPath), copiedDirectory);
|
||||
}
|
||||
//private void doMultiFileTest() throws IOException {
|
||||
// final String rootBeforePath = getTestName(true) + "/before";
|
||||
// final String rootAfterPath = getTestName(true) + "/after";
|
||||
// final VirtualFile copiedDirectory = myFixture.copyDirectoryToProject(rootBeforePath, "");
|
||||
// myFixture.configureByFile("main.py");
|
||||
// myFixture.testAction(new PyMakeFunctionTopLevelRefactoring());
|
||||
// PlatformTestUtil.assertDirectoriesEqual(getVirtualFileByName(getTestDataPath() + rootAfterPath), copiedDirectory);
|
||||
//}
|
||||
|
||||
private void doTestSuccess() {
|
||||
doTest(true, null);
|
||||
doTest(null);
|
||||
}
|
||||
|
||||
private void doTestFailure(@NotNull String message) {
|
||||
doTest(true, message);
|
||||
doTest(message);
|
||||
}
|
||||
|
||||
private static boolean isActionEnabled() {
|
||||
final PyMakeFunctionTopLevelRefactoring action = new PyMakeFunctionTopLevelRefactoring();
|
||||
final TestActionEvent event = new TestActionEvent(action);
|
||||
action.beforeActionPerformedUpdate(event);
|
||||
return event.getPresentation().isEnabled();
|
||||
}
|
||||
//private static boolean isActionEnabled() {
|
||||
// final PyMakeFunctionTopLevelRefactoring action = new PyMakeFunctionTopLevelRefactoring();
|
||||
// final TestActionEvent event = new TestActionEvent(action);
|
||||
// action.beforeActionPerformedUpdate(event);
|
||||
// return event.getPresentation().isEnabled();
|
||||
//}
|
||||
|
||||
// PY-6637
|
||||
public void testLocalFunctionSimple() {
|
||||
@@ -87,43 +89,43 @@ public class PyMakeFunctionTopLevelTest extends PyTestCase {
|
||||
}
|
||||
|
||||
// PY-6637
|
||||
public void testRefactoringAvailability() {
|
||||
myFixture.configureByFile(getTestName(true) + ".py");
|
||||
|
||||
final PsiFile file = myFixture.getFile();
|
||||
moveByText("func");
|
||||
assertFalse(isActionEnabled());
|
||||
moveByText("local");
|
||||
assertTrue(isActionEnabled());
|
||||
|
||||
// move to "def" keyword
|
||||
myFixture.getEditor().getCaretModel().moveCaretRelatively(-3, 0, false, false, false);
|
||||
final PsiElement tokenAtCaret = file.findElementAt(myFixture.getCaretOffset());
|
||||
assertNotNull(tokenAtCaret);
|
||||
assertEquals(tokenAtCaret.getNode().getElementType(), PyTokenTypes.DEF_KEYWORD);
|
||||
assertTrue(isActionEnabled());
|
||||
|
||||
moveByText("method");
|
||||
assertTrue(isActionEnabled());
|
||||
|
||||
moveByText("static_method");
|
||||
assertFalse(isActionEnabled());
|
||||
moveByText("class_method");
|
||||
assertFalse(isActionEnabled());
|
||||
|
||||
// Overridden method
|
||||
moveByText("overridden_method");
|
||||
assertFalse(isActionEnabled());
|
||||
|
||||
// Overriding method
|
||||
moveByText("upper");
|
||||
assertFalse(isActionEnabled());
|
||||
|
||||
moveByText("property");
|
||||
assertFalse(isActionEnabled());
|
||||
moveByText("__magic__");
|
||||
assertFalse(isActionEnabled());
|
||||
}
|
||||
//public void testRefactoringAvailability() {
|
||||
// myFixture.configureByFile(getTestName(true) + ".py");
|
||||
//
|
||||
// final PsiFile file = myFixture.getFile();
|
||||
// moveByText("func");
|
||||
// assertFalse(isActionEnabled());
|
||||
// moveByText("local");
|
||||
// assertTrue(isActionEnabled());
|
||||
//
|
||||
// // move to "def" keyword
|
||||
// myFixture.getEditor().getCaretModel().moveCaretRelatively(-3, 0, false, false, false);
|
||||
// final PsiElement tokenAtCaret = file.findElementAt(myFixture.getCaretOffset());
|
||||
// assertNotNull(tokenAtCaret);
|
||||
// assertEquals(tokenAtCaret.getNode().getElementType(), PyTokenTypes.DEF_KEYWORD);
|
||||
// assertTrue(isActionEnabled());
|
||||
//
|
||||
// moveByText("method");
|
||||
// assertTrue(isActionEnabled());
|
||||
//
|
||||
// moveByText("static_method");
|
||||
// assertFalse(isActionEnabled());
|
||||
// moveByText("class_method");
|
||||
// assertFalse(isActionEnabled());
|
||||
//
|
||||
// // Overridden method
|
||||
// moveByText("overridden_method");
|
||||
// assertFalse(isActionEnabled());
|
||||
//
|
||||
// // Overriding method
|
||||
// moveByText("upper");
|
||||
// assertFalse(isActionEnabled());
|
||||
//
|
||||
// moveByText("property");
|
||||
// assertFalse(isActionEnabled());
|
||||
// moveByText("__magic__");
|
||||
// assertFalse(isActionEnabled());
|
||||
//}
|
||||
|
||||
// PY-6637
|
||||
public void testLocalFunctionNonlocalReferenceToOuterScope() {
|
||||
@@ -182,9 +184,9 @@ public class PyMakeFunctionTopLevelTest extends PyTestCase {
|
||||
doTestSuccess();
|
||||
}
|
||||
|
||||
public void testMethodImportUpdates() throws IOException {
|
||||
doMultiFileTest();
|
||||
}
|
||||
//public void testMethodImportUpdates() throws IOException {
|
||||
// doMultiFileTest();
|
||||
//}
|
||||
|
||||
public void testMethodCalledViaClass() {
|
||||
doTestSuccess();
|
||||
|
||||
Reference in New Issue
Block a user