Merge remote branch 'origin/master'

This commit is contained in:
Dmitry Trofimov
2011-08-26 19:02:50 +04:00
12 changed files with 478 additions and 110 deletions
@@ -45,7 +45,7 @@ public class AddFieldQuickFix implements LocalQuickFix {
}
@Nullable
public static PsiElement appendToInit(PyFunction init, Function<String, PyStatement> callback) {
public static PsiElement appendToMethod(PyFunction init, Function<String, PyStatement> callback) {
// add this field as the last stmt of the constructor
final PyStatementList stmt_list = init.getStatementList();
PyStatement[] stmts = stmt_list.getStatements(); // NOTE: rather wasteful, consider iterable stmt list
@@ -93,7 +93,7 @@ public class AddFieldQuickFix implements LocalQuickFix {
if (cls != null && item_name != null) {
PyFunction init = cls.findMethodByName(PyNames.INIT, false);
if (init != null) {
return appendToInit(init, callback);
return appendToMethod(init, callback);
}
else { // no init! boldly copy ancestor's.
for (PyClass ancestor : cls.iterateAncestorClasses()) {
@@ -105,7 +105,7 @@ public class AddFieldQuickFix implements LocalQuickFix {
return null;
}
appendToInit(new_init, callback);
appendToMethod(new_init, callback);
PsiElement add_anchor = null;
PyFunction[] meths = cls.getMethods();
@@ -11,8 +11,8 @@ import com.jetbrains.python.refactoring.classes.extractSuperclass.PyExtractSuper
import com.jetbrains.python.refactoring.classes.pullUp.PyPullUpHandler;
import com.jetbrains.python.refactoring.classes.pushDown.PyPushDownHandler;
import com.jetbrains.python.refactoring.extractmethod.PyExtractMethodHandler;
import com.jetbrains.python.refactoring.introduce.constant.ConstantIntroduceHandler;
import com.jetbrains.python.refactoring.introduce.field.FieldIntroduceHandler;
import com.jetbrains.python.refactoring.introduce.constant.PyIntroduceConstantHandler;
import com.jetbrains.python.refactoring.introduce.field.PyIntroduceFieldHandler;
import com.jetbrains.python.refactoring.introduce.variable.PyIntroduceVariableHandler;
/**
@@ -26,12 +26,12 @@ public class RefactoringProvider extends RefactoringSupportProvider {
@Override
public RefactoringActionHandler getIntroduceConstantHandler() {
return new ConstantIntroduceHandler();
return new PyIntroduceConstantHandler();
}
@Override
public RefactoringActionHandler getIntroduceFieldHandler() {
return new FieldIntroduceHandler();
return new PyIntroduceFieldHandler();
}
@Override
@@ -19,6 +19,8 @@ import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.IntroduceTargetChooser;
import com.intellij.refactoring.RefactoringActionHandler;
import com.intellij.refactoring.introduce.inplace.InplaceVariableIntroducer;
import com.intellij.refactoring.introduce.inplace.OccurrencesChooser;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.util.Function;
import com.jetbrains.python.PyBundle;
@@ -57,21 +59,34 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
while (true);
}
protected static void ensureName(IntroduceOperation operation) {
if (operation.getName() == null) {
final Collection<String> suggestedNames = operation.getSuggestedNames();
if (suggestedNames.size() > 0) {
operation.setName(suggestedNames.iterator().next());
}
else {
operation.setName("x");
}
}
}
public enum InitPlace {
SAME_METHOD,
CONSTRUCTOR,
SET_UP
}
private static void replaceExpression(PyExpression newExpression, Project project, PsiElement expression) {
@Nullable
private static PsiElement replaceExpression(PyExpression newExpression, Project project, PsiElement expression) {
PyExpressionStatement statement = PsiTreeUtil.getParentOfType(expression, PyExpressionStatement.class);
if (statement != null) {
if (statement.getExpression() == expression) {
statement.delete();
return;
return null;
}
}
PyPsiUtils.replaceExpression(project, expression, newExpression);
return PyPsiUtils.replaceExpression(project, expression, newExpression);
}
private final IntroduceValidator myValidator;
@@ -83,7 +98,7 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
}
public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) {
performAction(project, editor, file, null, InitPlace.SAME_METHOD, false, false, false);
performAction(new IntroduceOperation(project, editor, file, null, false, false, false));
}
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
@@ -134,16 +149,18 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
}
public void performAction(@NotNull final Project project, Editor editor, PsiFile file, String name, boolean replaceAll, boolean hasConstructor, boolean isTestClass) {
performAction(project, editor, file, name, InitPlace.SAME_METHOD, replaceAll, hasConstructor, isTestClass);
performAction(new IntroduceOperation(project, editor, file, name, replaceAll, hasConstructor, isTestClass));
}
public void performAction(@NotNull final Project project, Editor editor, PsiFile file, String name, InitPlace initInConstructor, boolean replaceAll, boolean hasConstructor, boolean isTestClass) {
public void performAction(IntroduceOperation operation) {
final PsiFile file = operation.getFile();
if (!CommonRefactoringUtil.checkReadOnlyStatus(file)) {
return;
}
PsiElement element1 = null;
PsiElement element2 = null;
final Editor editor = operation.getEditor();
final SelectionModel selectionModel = editor.getSelectionModel();
boolean singleElementSelection = false;
if (selectionModel.hasSelection()) {
@@ -162,7 +179,7 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
}
}
else {
if (smartIntroduce(file, editor, name, initInConstructor, replaceAll, hasConstructor, isTestClass)) {
if (smartIntroduce(operation)) {
return;
}
final CaretModel caretModel = editor.getCaretModel();
@@ -173,6 +190,7 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
element2 = file.findElementAt(document.getLineEndOffset(lineNumber) - 1);
}
}
final Project project = operation.getProject();
if (element1 == null || element2 == null) {
showCannotPerformError(project, editor);
return;
@@ -194,7 +212,8 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
if (!checkIntroduceContext(file, editor, element1)) {
return;
}
performActionOnElement(editor, element1, name, initInConstructor, replaceAll, hasConstructor, isTestClass);
operation.setElement(element1);
performActionOnElement(operation);
}
private void showCannotPerformError(Project project, Editor editor) {
@@ -202,7 +221,9 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
"refactoring.extractMethod");
}
private boolean smartIntroduce(final PsiFile file, final Editor editor, final String name, final InitPlace initInConstructor, final boolean replaceAll, final boolean hasConstructor, final boolean isTestClass) {
private boolean smartIntroduce(final IntroduceOperation operation) {
final Editor editor = operation.getEditor();
final PsiFile file = operation.getFile();
int offset = editor.getCaretModel().getOffset();
PsiElement elementAtCaret = file.findElementAt(offset);
if (!checkIntroduceContext(file, editor, elementAtCaret)) return true;
@@ -217,14 +238,16 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
elementAtCaret = elementAtCaret.getParent();
}
if (expressions.size() == 1 || ApplicationManager.getApplication().isUnitTestMode()) {
performActionOnElement(editor, expressions.get(0), name, initInConstructor, replaceAll, hasConstructor, isTestClass);
operation.setElement(expressions.get(0));
performActionOnElement(operation);
return true;
}
else if (expressions.size() > 1) {
IntroduceTargetChooser.showChooser(editor, expressions, new Pass<PyExpression>() {
@Override
public void pass(PyExpression pyExpression) {
performActionOnElement(editor, pyExpression, name, initInConstructor, replaceAll, hasConstructor, isTestClass);
operation.setElement(pyExpression);
performActionOnElement(operation);
}
}, new Function<PyExpression, String>() {
public String fun(PyExpression pyExpression) {
@@ -261,56 +284,94 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
return true;
}
private void performActionOnElement(Editor editor,
@NotNull PsiElement element,
String name,
InitPlace initInConstructor,
boolean replaceAll,
boolean hasConstructor,
boolean isTestClass) {
final Project project = element.getProject();
if (!checkEnabled(project, editor, element, myDialogTitle)) {
private void performActionOnElement(IntroduceOperation operation) {
final Project project = operation.getProject();
if (!checkEnabled(project, operation.getEditor(), operation.getElement(), myDialogTitle)) {
return;
}
final PsiElement element = operation.getElement();
final PsiElement parent = element.getParent();
final PyExpression initializer = parent instanceof PyAssignmentStatement ?
((PyAssignmentStatement)parent).getAssignedValue() :
(PyExpression)element;
operation.setInitializer(initializer);
final List<PsiElement> occurrences;
if (initializer.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE) == null) {
occurrences = getOccurrences(element, initializer);
operation.setOccurrences(getOccurrences(element, initializer));
}
operation.setSuggestedNames(getSuggestedNames(initializer));
if (operation.getOccurrences().size() == 0) {
operation.setReplaceAll(false);
}
performActionOnElementOccurrences(operation);
}
protected void performActionOnElementOccurrences(final IntroduceOperation operation) {
final Editor editor = operation.getEditor();
if (editor.getSettings().isVariableInplaceRenameEnabled() && !ApplicationManager.getApplication().isUnitTestMode()) {
ensureName(operation);
new OccurrencesChooser<PsiElement>(editor)
.showChooser(operation.getElement(), operation.getOccurrences(), new Pass<OccurrencesChooser.ReplaceChoice>() {
@Override
public void pass(OccurrencesChooser.ReplaceChoice replaceChoice) {
operation.setReplaceAll(replaceChoice == OccurrencesChooser.ReplaceChoice.ALL);
performInplaceIntroduce(operation);
}
});
}
else {
occurrences = Collections.emptyList();
performIntroduceWithDialog(operation);
}
Collection<String> possibleNames = getSuggestedNames(initializer);
replaceAll &= occurrences.size() > 0;
}
protected void performInplaceIntroduce(IntroduceOperation operation) {
final PyAssignmentStatement statement = performRefactoring(operation);
PyTargetExpression target = (PyTargetExpression) statement.getTargets() [0];
final List<PsiElement> occurrences = operation.getOccurrences();
final InplaceVariableIntroducer<PsiElement> introducer =
new PyInplaceVariableIntroducer(target, operation, occurrences);
introducer.performInplaceRename(false, new LinkedHashSet<String>(operation.getSuggestedNames()));
}
if (name == null) {
PyIntroduceDialog dialog = new PyIntroduceDialog(project, initializer, myDialogTitle, myValidator, occurrences.size(), possibleNames, getHelpId(), hasConstructor, isTestClass);
protected void performIntroduceWithDialog(IntroduceOperation operation) {
final Project project = operation.getProject();
if (operation.getName() == null) {
PyIntroduceDialog dialog = new PyIntroduceDialog(project, myDialogTitle, myValidator, getHelpId(), operation);
dialog.show();
if (!dialog.isOK()) {
return;
}
name = dialog.getName();
replaceAll = dialog.doReplaceAllOccurrences();
initInConstructor = dialog.getInitPlace();
operation.setName(dialog.getName());
operation.setReplaceAll(dialog.doReplaceAllOccurrences());
operation.setInitPlace(dialog.getInitPlace());
}
initInConstructor = initInConstructor != null ? initInConstructor : InitPlace.SAME_METHOD;
String assignmentText = name + " = " + initializer.getText().replace("\n", " ");
PsiElement anchor = replaceAll ? findAnchor(occurrences) : PsiTreeUtil.getParentOfType(initializer, PyStatement.class);
PyAssignmentStatement declaration = createDeclaration(project, assignmentText, anchor);
assert name != null;
declaration = performReplace(project, element, declaration, initializer, occurrences, name, replaceAll, initInConstructor);
declaration = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(declaration);
PyAssignmentStatement declaration = performRefactoring(operation);
final Editor editor = operation.getEditor();
editor.getCaretModel().moveToOffset(declaration.getTextRange().getEndOffset());
editor.getSelectionModel().removeSelection();
}
protected PyAssignmentStatement performRefactoring(IntroduceOperation operation) {
PyAssignmentStatement declaration = createDeclaration(operation);
declaration = performReplace(declaration, operation);
declaration = CodeInsightUtilBase.forcePsiPostprocessAndRestoreElement(declaration);
return declaration;
}
public PyAssignmentStatement createDeclaration(IntroduceOperation operation) {
final Project project = operation.getProject();
final PyExpression initializer = operation.getInitializer();
String assignmentText = operation.getName() + " = " + initializer.getText().replace("\n", " ");
PsiElement anchor = operation.isReplaceAll()
? findAnchor(operation.getOccurrences())
: PsiTreeUtil.getParentOfType(initializer, PyStatement.class);
return createDeclaration(project, assignmentText, anchor);
}
protected abstract String getHelpId();
protected PyAssignmentStatement createDeclaration(Project project, String assignmentText, PsiElement anchor) {
@@ -333,40 +394,47 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
return PyRefactoringUtil.getOccurrences(expression, context);
}
private PyAssignmentStatement performReplace(@NotNull final Project project,
final PsiElement element,
@NotNull final PyAssignmentStatement declaration,
@NotNull final PsiElement expression,
@NotNull final List<PsiElement> occurrences,
@NotNull final String name,
final boolean replaceAll,
final InitPlace initInConstructor) {
private PyAssignmentStatement performReplace(@NotNull final PyAssignmentStatement declaration,
final IntroduceOperation operation) {
final PyExpression expression = operation.getInitializer();
final Project project = operation.getProject();
return new WriteCommandAction<PyAssignmentStatement>(project, expression.getContainingFile()) {
protected void run(final Result<PyAssignmentStatement> result) throws Throwable {
final Pair<PsiElement, TextRange> data = expression.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE);
if (data == null) {
result.setResult((PyAssignmentStatement)addDeclaration(expression, declaration, occurrences, replaceAll, initInConstructor));
}
else {
result.setResult((PyAssignmentStatement)addDeclaration(data.first, declaration, occurrences, replaceAll, initInConstructor));
}
result.setResult(addDeclaration(operation, declaration));
PyExpression newExpression = createExpression(project, name, declaration);
PyExpression newExpression = createExpression(project, operation.getName(), declaration);
if (replaceAll) {
for (PsiElement occurrence : occurrences) {
replaceExpression(newExpression, project, occurrence);
if (operation.isReplaceAll()) {
List<PsiElement> newOccurrences = new ArrayList<PsiElement>();
for (PsiElement occurrence : operation.getOccurrences()) {
final PsiElement replaced = replaceExpression(newExpression, project, occurrence);
if (replaced != null) {
newOccurrences.add(replaced);
}
}
operation.setOccurrences(newOccurrences);
}
else {
replaceExpression(newExpression, project, expression);
}
postRefactoring(element);
postRefactoring(operation.getElement());
}
}.execute().getResultObject();
}
@Nullable
public PyAssignmentStatement addDeclaration(IntroduceOperation operation, PyAssignmentStatement declaration) {
final PsiElement expression = operation.getInitializer();
final Pair<PsiElement, TextRange> data = expression.getUserData(PyPsiUtils.SELECTION_BREAKS_AST_NODE);
if (data == null) {
return (PyAssignmentStatement)addDeclaration(expression, declaration, operation);
}
else {
return (PyAssignmentStatement)addDeclaration(data.first, declaration, operation);
}
}
protected PyExpression createExpression(Project project, String name, PyAssignmentStatement declaration) {
return PyElementGenerator.getInstance(project).createExpressionFromText(name);
}
@@ -374,10 +442,25 @@ abstract public class IntroduceHandler implements RefactoringActionHandler {
@Nullable
protected abstract PsiElement addDeclaration(@NotNull final PsiElement expression,
@NotNull final PsiElement declaration,
@NotNull final List<PsiElement> occurrences,
final boolean replaceAll,
final InitPlace initInConstructor);
@NotNull IntroduceOperation operation);
protected void postRefactoring(PsiElement element) {
}
private static class PyInplaceVariableIntroducer extends InplaceVariableIntroducer<PsiElement> {
private final PyTargetExpression myTarget;
public PyInplaceVariableIntroducer(PyTargetExpression target,
IntroduceOperation operation,
List<PsiElement> occurrences) {
super(target, operation.getEditor(), operation.getProject(), "Introduce Variable",
occurrences.toArray(new PsiElement[occurrences.size()]), null);
myTarget = target;
}
@Override
protected PsiElement checkLocalScope() {
return myTarget.getContainingFile();
}
}
}
@@ -0,0 +1,122 @@
package com.jetbrains.python.refactoring.introduce;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.jetbrains.python.psi.PyExpression;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
/**
* @author yole
*/
public class IntroduceOperation {
private final Project myProject;
private final Editor myEditor;
private final PsiFile myFile;
private String myName;
private IntroduceHandler.InitPlace myInitPlace = IntroduceHandler.InitPlace.SAME_METHOD;
private boolean myReplaceAll;
private final boolean myHasConstructor;
private final boolean myTestClass;
private PsiElement myElement;
private PyExpression myInitializer;
private List<PsiElement> myOccurrences = Collections.emptyList();
private Collection<String> mySuggestedNames;
public IntroduceOperation(Project project,
Editor editor,
PsiFile file,
String name,
boolean replaceAll,
boolean hasConstructor,
boolean testClass) {
myProject = project;
myEditor = editor;
myFile = file;
myName = name;
myReplaceAll = replaceAll;
myHasConstructor = hasConstructor;
myTestClass = testClass;
}
public String getName() {
return myName;
}
public void setName(@Nullable String name) {
myName = name;
}
public Project getProject() {
return myProject;
}
public Editor getEditor() {
return myEditor;
}
public PsiFile getFile() {
return myFile;
}
public PsiElement getElement() {
return myElement;
}
public void setElement(PsiElement element) {
myElement = element;
}
public IntroduceHandler.InitPlace getInitPlace() {
return myInitPlace;
}
public void setInitPlace(IntroduceHandler.InitPlace initPlace) {
myInitPlace = initPlace;
}
public boolean isReplaceAll() {
return myReplaceAll;
}
public void setReplaceAll(boolean replaceAll) {
myReplaceAll = replaceAll;
}
public PyExpression getInitializer() {
return myInitializer;
}
public void setInitializer(PyExpression initializer) {
myInitializer = initializer;
}
public List<PsiElement> getOccurrences() {
return myOccurrences;
}
public void setOccurrences(List<PsiElement> occurrences) {
myOccurrences = occurrences;
}
public Collection<String> getSuggestedNames() {
return mySuggestedNames;
}
public void setSuggestedNames(Collection<String> suggestedNames) {
mySuggestedNames = suggestedNames;
}
public boolean hasConstructor() {
return myHasConstructor;
}
public boolean isTestClass() {
return myTestClass;
}
}
@@ -42,26 +42,22 @@ public class PyIntroduceDialog extends DialogWrapper implements PyIntroduceSetti
private final String myHelpId;
public PyIntroduceDialog(@NotNull final Project project,
@NotNull PyExpression expression,
@NotNull final String caption,
@NotNull final IntroduceValidator validator,
final int occurrencesCount,
final Collection<String> possibleNames,
final String helpId,
boolean hasConstructor,
boolean isTestClass) {
final IntroduceOperation operation) {
super(project, true);
myOccurrencesCount = occurrencesCount;
myOccurrencesCount = operation.getOccurrences().size();
myValidator = validator;
myProject = project;
myExpression = expression;
myExpression = operation.getInitializer();
myHelpId = helpId;
setUpNameComboBox(possibleNames);
setUpNameComboBox(operation.getSuggestedNames());
setModal(true);
setTitle(caption);
init();
setupDialog(hasConstructor, isTestClass);
setupDialog(operation.hasConstructor(), operation.isTestClass());
updateControls();
}
@@ -7,25 +7,23 @@ import com.jetbrains.python.codeInsight.imports.AddImportHelper;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.refactoring.introduce.IntroduceHandler;
import com.jetbrains.python.refactoring.introduce.IntroduceOperation;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
import java.util.List;
/**
* @author Alexey.Ivanov
*/
public class ConstantIntroduceHandler extends IntroduceHandler {
public ConstantIntroduceHandler() {
public class PyIntroduceConstantHandler extends IntroduceHandler {
public PyIntroduceConstantHandler() {
super(new ConstantValidator(), PyBundle.message("refactoring.introduce.constant.dialog.title"));
}
@Override
protected PsiElement addDeclaration(@NotNull final PsiElement expression,
@NotNull final PsiElement declaration,
@NotNull final List<PsiElement> occurrences,
final boolean replaceAll,
InitPlace initInConstructor) {
@NotNull final IntroduceOperation operation) {
final PsiElement anchor = expression.getContainingFile();
assert anchor instanceof PyFile;
return anchor.addBefore(declaration, AddImportHelper.getFileInsertPosition((PyFile)anchor));
@@ -1,6 +1,9 @@
package com.jetbrains.python.refactoring.introduce.field;
import com.intellij.lang.ASTNode;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.application.AccessToken;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
@@ -13,6 +16,7 @@ import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.introduce.inplace.InplaceVariableIntroducer;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.util.Function;
import com.jetbrains.python.PyNames;
@@ -20,27 +24,26 @@ import com.jetbrains.python.actions.AddFieldQuickFix;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyFunctionBuilder;
import com.jetbrains.python.refactoring.introduce.IntroduceHandler;
import com.jetbrains.python.refactoring.introduce.IntroduceOperation;
import com.jetbrains.python.refactoring.introduce.variable.PyIntroduceVariableHandler;
import com.jetbrains.python.testing.PythonUnitTestUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import javax.swing.*;
import java.util.*;
/**
* @author Dennis.Ushakov
*/
public class FieldIntroduceHandler extends IntroduceHandler {
public class PyIntroduceFieldHandler extends IntroduceHandler {
public FieldIntroduceHandler() {
public PyIntroduceFieldHandler() {
super(new IntroduceFieldValidator(), RefactoringBundle.message("introduce.field.title"));
}
public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) {
performAction(project, editor, file, null, InitPlace.SAME_METHOD, false, true, isTestClass(file, editor));
performAction(new IntroduceOperation(project, editor, file, null, false, true, isTestClass(file, editor)));
}
private static boolean isTestClass(PsiFile file, Editor editor) {
@@ -76,22 +79,23 @@ public class FieldIntroduceHandler extends IntroduceHandler {
@Nullable
@Override
protected PsiElement addDeclaration(@NotNull PsiElement expression, @NotNull PsiElement declaration, @NotNull List<PsiElement> occurrences,
boolean replaceAll, InitPlace initInConstructor) {
protected PsiElement addDeclaration(@NotNull PsiElement expression, @NotNull PsiElement declaration, @NotNull IntroduceOperation operation) {
final PsiElement expr = expression instanceof PyClass ? expression : expression.getParent();
PsiElement anchor = PyUtil.getContainingClassOrSelf(expr);
assert anchor instanceof PyClass;
final PyClass clazz = (PyClass)anchor;
final Project project = anchor.getProject();
if (initInConstructor == InitPlace.CONSTRUCTOR && !inConstructor(clazz, expression)) {
if (operation.getInitPlace() == InitPlace.CONSTRUCTOR && !inConstructor(expression)) {
return AddFieldQuickFix.addFieldToInit(project, clazz, "", new AddFieldDeclaration(declaration));
} else if (initInConstructor == InitPlace.SET_UP) {
return addFieldToSetUp(project, clazz, declaration);
} else if (operation.getInitPlace() == InitPlace.SET_UP) {
return addFieldToSetUp(clazz, new AddFieldDeclaration(declaration));
}
return PyIntroduceVariableHandler.doIntroduceVariable(expression, declaration, occurrences, replaceAll);
return PyIntroduceVariableHandler.doIntroduceVariable(expression, declaration, operation.getOccurrences(), operation.isReplaceAll());
}
private boolean inConstructor(@Nullable PyClass clazz, @NotNull PsiElement expression) {
private static boolean inConstructor(@NotNull PsiElement expression) {
final PsiElement expr = expression instanceof PyClass ? expression : expression.getParent();
PyClass clazz = PyUtil.getContainingClassOrSelf(expr);
PsiElement current = PyUtil.getConcealingParent(expression);
if (clazz != null && current != null && current instanceof PyFunction) {
PyFunction init = clazz.findMethodByName(PyNames.INIT, false);
@@ -103,18 +107,18 @@ public class FieldIntroduceHandler extends IntroduceHandler {
}
@Nullable
private static PsiElement addFieldToSetUp(Project project, PyClass clazz, PsiElement declaration) {
private static PsiElement addFieldToSetUp(PyClass clazz, final Function<String, PyStatement> callback) {
final PyFunction init = clazz.findMethodByName(PythonUnitTestUtil.TESTCASE_SETUP_NAME, false);
if (init != null) {
return AddFieldQuickFix.appendToInit(init, new AddFieldDeclaration(declaration));
return AddFieldQuickFix.appendToMethod(init, callback);
}
final PyFunctionBuilder builder = new PyFunctionBuilder(PythonUnitTestUtil.TESTCASE_SETUP_NAME);
builder.parameter(PyNames.CANONICAL_SELF);
PyFunction setUp = builder.buildFunction(project, LanguageLevel.getDefault());
PyFunction setUp = builder.buildFunction(clazz.getProject(), LanguageLevel.getDefault());
final PyStatementList statements = clazz.getStatementList();
final PsiElement anchor = statements.getFirstChild();
setUp = (PyFunction)statements.addBefore(setUp, anchor);
return AddFieldQuickFix.appendToInit(setUp, new AddFieldDeclaration(declaration));
return AddFieldQuickFix.appendToMethod(setUp, callback);
}
@Override
@@ -217,4 +221,89 @@ public class FieldIntroduceHandler extends IntroduceHandler {
text.replaceFirst(PyNames.CANONICAL_SELF + "\\.", self_name + "."));
}
}
@Override
protected void performInplaceIntroduce(IntroduceOperation operation) {
final PyAssignmentStatement statement = performRefactoring(operation);
// put caret on identifier after "self."
putCaretOnFieldName(operation.getEditor(), statement);
PyTargetExpression target = (PyTargetExpression) statement.getTargets() [0];
final List<PsiElement> occurrences = operation.getOccurrences();
final InplaceVariableIntroducer<PsiElement> introducer =
new PyInplaceFieldIntroducer(target, operation, occurrences);
introducer.performInplaceRename(false, new LinkedHashSet<String>(operation.getSuggestedNames()));
}
private static void putCaretOnFieldName(Editor editor, PyAssignmentStatement statement) {
final PsiElement elementAtOffset = statement.getContainingFile().findElementAt(editor.getCaretModel().getOffset());
PyQualifiedExpression qExpr = PsiTreeUtil.getParentOfType(elementAtOffset, PyQualifiedExpression.class);
if (qExpr != null && qExpr.getQualifier() == null) {
qExpr = PsiTreeUtil.getParentOfType(qExpr, PyQualifiedExpression.class);
}
if (qExpr != null) {
final ASTNode nameElement = qExpr.getNameElement();
if (nameElement != null) {
final int offset = nameElement.getTextRange().getStartOffset();
editor.getCaretModel().moveToOffset(offset);
}
}
}
private static class PyInplaceFieldIntroducer extends InplaceVariableIntroducer<PsiElement> {
private final PyTargetExpression myTarget;
private final PyIntroduceFieldPanel myPanel;
public PyInplaceFieldIntroducer(PyTargetExpression target,
IntroduceOperation operation,
List<PsiElement> occurrences) {
super(target, operation.getEditor(), operation.getProject(), "Introduce Field",
occurrences.toArray(new PsiElement[occurrences.size()]), null);
myTarget = target;
if (!inConstructor(target)) {
myPanel = new PyIntroduceFieldPanel(myProject, operation.isTestClass());
}
else {
myPanel = null;
}
}
@Override
protected PsiElement checkLocalScope() {
return myTarget.getContainingFile();
}
@Override
protected JComponent getComponent() {
return myPanel == null ? null : myPanel.getRootPanel();
}
@Override
public void finish() {
super.finish();
if (myPanel != null && myPanel.getInitPlace() != InitPlace.SAME_METHOD) {
final AccessToken accessToken = ApplicationManager.getApplication().acquireWriteActionLock(getClass());
try {
final PyAssignmentStatement initializer = PsiTreeUtil.getParentOfType(myTarget, PyAssignmentStatement.class);
assert initializer != null;
final Function<String, PyStatement> callback = new Function<String, PyStatement>() {
@Override
public PyStatement fun(String s) {
return initializer;
}
};
final PyClass pyClass = PyUtil.getContainingClassOrSelf(initializer);
if (myPanel.getInitPlace() == InitPlace.CONSTRUCTOR) {
AddFieldQuickFix.addFieldToInit(myProject, pyClass, "", callback);
}
else if (myPanel.getInitPlace() == InitPlace.SET_UP) {
addFieldToSetUp(pyClass, callback);
}
initializer.delete();
}
finally {
accessToken.finish();
}
}
}
}
}
@@ -0,0 +1,40 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.refactoring.introduce.field.PyIntroduceFieldPanel">
<grid id="27dc6" binding="myRootPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="500" height="400"/>
</constraints>
<properties>
<focusable value="false"/>
</properties>
<border type="none"/>
<children>
<component id="7f91c" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelFor value="fa68d"/>
<text value="&amp;Initialize in"/>
</properties>
</component>
<vspacer id="31684">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="fa68d" class="javax.swing.JComboBox" binding="myInitializerPlaceCombo">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<model>
<item value="current method"/>
<item value="constructor"/>
</model>
</properties>
</component>
</children>
</grid>
</form>
@@ -0,0 +1,38 @@
package com.jetbrains.python.refactoring.introduce.field;
import com.intellij.openapi.project.Project;
import com.intellij.refactoring.introduce.inplace.KeyboardComboSwitcher;
import com.jetbrains.python.refactoring.introduce.IntroduceHandler;
import javax.swing.*;
/**
* @author yole
*/
public class PyIntroduceFieldPanel {
private JPanel myRootPanel;
private JComboBox myInitializerPlaceCombo;
public PyIntroduceFieldPanel(Project project, boolean testClass) {
KeyboardComboSwitcher.setupActions(myInitializerPlaceCombo, project);
if (testClass) {
((DefaultComboBoxModel) myInitializerPlaceCombo.getModel()).addElement("setUp() method");
}
}
public JPanel getRootPanel() {
return myRootPanel;
}
public IntroduceHandler.InitPlace getInitPlace() {
final int index = myInitializerPlaceCombo.getSelectedIndex();
switch (index) {
case 1:
return IntroduceHandler.InitPlace.CONSTRUCTOR;
case 2:
return IntroduceHandler.InitPlace.SET_UP;
default:
return IntroduceHandler.InitPlace.SAME_METHOD;
}
}
}
@@ -5,6 +5,7 @@ import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.psi.PyStatement;
import com.jetbrains.python.refactoring.introduce.IntroduceHandler;
import com.jetbrains.python.refactoring.introduce.IntroduceOperation;
import org.jetbrains.annotations.NotNull;
import java.util.List;
@@ -20,10 +21,8 @@ public class PyIntroduceVariableHandler extends IntroduceHandler {
@Override
protected PsiElement addDeclaration(@NotNull final PsiElement expression,
@NotNull final PsiElement declaration,
@NotNull final List<PsiElement> occurrences,
final boolean replaceAll,
InitPlace initInConstructor) {
return doIntroduceVariable(expression, declaration, occurrences, replaceAll);
@NotNull IntroduceOperation operation) {
return doIntroduceVariable(expression, declaration, operation.getOccurrences(), operation.isReplaceAll());
}
public static PsiElement doIntroduceVariable(PsiElement expression,
@@ -2,7 +2,7 @@ package com.jetbrains.python.refactoring;
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
import com.jetbrains.python.psi.LanguageLevel;
import com.jetbrains.python.refactoring.introduce.constant.ConstantIntroduceHandler;
import com.jetbrains.python.refactoring.introduce.constant.PyIntroduceConstantHandler;
/**
* @author yole
@@ -32,7 +32,7 @@ public class PyIntroduceConstantTest extends PyLightFixtureTestCase {
private void doTest() {
myFixture.configureByFile("/refactoring/introduceConstant/" + getTestName(true) + ".py");
ConstantIntroduceHandler handler = new ConstantIntroduceHandler();
PyIntroduceConstantHandler handler = new PyIntroduceConstantHandler();
handler.performAction(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile(), "a", true, false, false);
myFixture.checkResultByFile("/refactoring/introduceConstant/" + getTestName(true) + ".after.py");
}
@@ -2,7 +2,8 @@ package com.jetbrains.python.refactoring;
import com.jetbrains.python.fixtures.PyLightFixtureTestCase;
import com.jetbrains.python.refactoring.introduce.IntroduceHandler;
import com.jetbrains.python.refactoring.introduce.field.FieldIntroduceHandler;
import com.jetbrains.python.refactoring.introduce.IntroduceOperation;
import com.jetbrains.python.refactoring.introduce.field.PyIntroduceFieldHandler;
/**
* @author yole
@@ -22,8 +23,10 @@ public class PyIntroduceFieldTest extends PyLightFixtureTestCase {
private void doTest(IntroduceHandler.InitPlace initPlace) {
myFixture.configureByFile("/refactoring/introduceField/" + getTestName(true) + ".py");
FieldIntroduceHandler handler = new FieldIntroduceHandler();
handler.performAction(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile(), "a", initPlace, true, false, false);
PyIntroduceFieldHandler handler = new PyIntroduceFieldHandler();
final IntroduceOperation introduceOperation = new IntroduceOperation(myFixture.getProject(), myFixture.getEditor(), myFixture.getFile(), "a", true, false, false);
introduceOperation.setInitPlace(initPlace);
handler.performAction(introduceOperation);
myFixture.checkResultByFile("/refactoring/introduceField/" + getTestName(true) + ".after.py");
}
}