added invert boolean action

This commit is contained in:
Ekaterina Tuzova
2013-04-24 15:17:53 +04:00
parent 832243a6a8
commit f8722877f6
14 changed files with 500 additions and 0 deletions
@@ -906,6 +906,11 @@
</project-components>
<actions>
<action id="PyInvertBooleanAction" class="com.jetbrains.python.refactoring.invertBoolean.PyInvertBooleanAction" text="Invert Boolean">
<add-to-group group-id="RefactoringMenu" anchor="last" />
</action>
<group id="PyProjectViewGroup" internal="true" class="com.intellij.ide.actions.NonEmptyActionGroup" popup="true" text="Python">
<action id="Devmode.AnalyzeReturns" class="com.jetbrains.python.devmode.AnalyzeReturnsAction" text="Analyze return types"
internal="true"/>
@@ -0,0 +1,42 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.refactoring.invertBoolean.PyInvertBooleanDialog">
<grid id="3c0f9" binding="myPanel" 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="54" y="99" width="434" height="60"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="5b95d" class="javax.swing.JLabel" binding="myLabel">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<inheritsPopupMenu value="false"/>
<nextFocusableComponent value=""/>
<text value=""/>
</properties>
</component>
<component id="ceab0" class="javax.swing.JTextField" binding="myNameField">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="6" anchor="8" fill="1" indent="0" use-parent-layout="false">
<preferred-size width="150" height="-1"/>
</grid>
</constraints>
<properties/>
<clientProperties>
<caretAspectRatio class="java.lang.Float" value="0.04"/>
</clientProperties>
</component>
<component id="42aac" class="javax.swing.JLabel" binding="myCaptionLabel">
<constraints>
<grid row="0" column="0" row-span="1" col-span="2" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value=""/>
</properties>
</component>
</children>
</grid>
</form>
@@ -0,0 +1,64 @@
package com.jetbrains.python.refactoring.invertBoolean;
import com.intellij.lang.Language;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.editor.Editor;
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.actions.BaseRefactoringAction;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PythonLanguage;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
/**
* User : ktisha
*/
public class PyInvertBooleanAction extends BaseRefactoringAction {
@Override
protected boolean isAvailableInEditorOnly() {
return true;
}
@Override
protected boolean isEnabledOnElements(@NotNull PsiElement[] elements) {
if (elements.length == 1) {
return isApplicable(elements[0]);
}
return false;
}
private static boolean isApplicable(@NotNull final PsiElement element) {
if (element instanceof PyTargetExpression) {
final PyAssignmentStatement assignmentStatement = PsiTreeUtil.getParentOfType(element, PyAssignmentStatement.class);
if (assignmentStatement != null) {
final PyExpression assignedValue = assignmentStatement.getAssignedValue();
if (assignedValue == null) return false;
final String name = assignedValue.getText();
return name != null && (PyNames.TRUE.equals(name) || PyNames.FALSE.equals(name));
}
}
if (element instanceof PyNamedParameter) {
final PyExpression defaultValue = ((PyNamedParameter)element).getDefaultValue();
if (defaultValue instanceof PyBoolLiteralExpression) return true;
}
return element.getParent() instanceof PyBoolLiteralExpression;
}
@Override
protected boolean isAvailableOnElementInEditorAndFile(@NotNull final PsiElement element, @NotNull final Editor editor, @NotNull PsiFile file, @NotNull DataContext context) {
return isApplicable(element);
}
@Override
protected RefactoringActionHandler getHandler(@NotNull DataContext dataContext) {
return new PyInvertBooleanHandler();
}
@Override
protected boolean isAvailableForLanguage(Language language) {
return language.isKindOf(PythonLanguage.getInstance());
}
}
@@ -0,0 +1,62 @@
package com.jetbrains.python.refactoring.invertBoolean;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.rename.RenameUtil;
import com.intellij.refactoring.ui.RefactoringDialog;
import com.intellij.refactoring.util.CommonRefactoringUtil;
import com.intellij.usageView.UsageViewUtil;
import javax.swing.*;
/**
* User : ktisha
*/
public class PyInvertBooleanDialog extends RefactoringDialog {
private JTextField myNameField;
private JPanel myPanel;
private JLabel myLabel;
private JLabel myCaptionLabel;
private final PsiElement myElement;
public PyInvertBooleanDialog(final PsiElement element) {
super(element.getProject(), false);
myElement = element;
final String name = element instanceof PsiNamedElement ? ((PsiNamedElement)element).getName() : element.getText();
myNameField.setText(name);
myLabel.setLabelFor(myNameField);
final String typeString = UsageViewUtil.getType(myElement);
myLabel.setText(RefactoringBundle.message("invert.boolean.name.of.inverted.element", typeString));
myCaptionLabel.setText(RefactoringBundle.message("invert.0.1",
typeString,
UsageViewUtil.getDescriptiveName(myElement)));
setTitle(PyInvertBooleanHandler.REFACTORING_NAME);
init();
}
public JComponent getPreferredFocusedComponent() {
return myNameField;
}
protected void doAction() {
Project project = myElement.getProject();
final String name = myNameField.getText().trim();
if (name.length() == 0 || !RenameUtil.isValidName(myProject, myElement, name)) {
CommonRefactoringUtil.showErrorMessage(PyInvertBooleanHandler.REFACTORING_NAME,
RefactoringBundle.message("please.enter.a.valid.name.for.inverted.element",
UsageViewUtil.getType(myElement)),
"refactoring.invertBoolean", project);
return;
}
invokeRefactoring(new PyInvertBooleanProcessor(myElement, name));
}
protected JComponent createCenterPanel() {
return myPanel;
}
}
@@ -0,0 +1,55 @@
package com.jetbrains.python.refactoring.invertBoolean;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.LangDataKeys;
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.jetbrains.python.psi.PyAssignmentStatement;
import com.jetbrains.python.psi.PyNamedParameter;
import org.jetbrains.annotations.NotNull;
/**
* User : ktisha
*/
public class PyInvertBooleanHandler implements RefactoringActionHandler {
static final String REFACTORING_NAME = RefactoringBundle.message("invert.boolean.title");
@Override
public void invoke(@NotNull Project project, Editor editor, PsiFile file, DataContext dataContext) {
PsiElement element = LangDataKeys.PSI_ELEMENT.getData(dataContext);
if (element == null && editor != null && file != null) {
element = file.findElementAt(editor.getCaretModel().getOffset());
}
final PyAssignmentStatement assignmentStatement = PsiTreeUtil.getParentOfType(element, PyAssignmentStatement.class);
if (assignmentStatement != null) {
invoke(assignmentStatement.getTargets()[0]);
}
else if (element instanceof PyNamedParameter) {
invoke(element);
}
else {
CommonRefactoringUtil.showErrorHint(project, editor, RefactoringBundle.getCannotRefactorMessage(
RefactoringBundle.message("error.wrong.caret.position.local.or.expression.name")), REFACTORING_NAME, "refactoring.invertBoolean");
}
}
@Override
public void invoke(@NotNull Project project, @NotNull PsiElement[] elements, DataContext dataContext) {
if (elements.length == 1) {
final PyAssignmentStatement assignmentStatement = PsiTreeUtil.getParentOfType(elements[0], PyAssignmentStatement.class);
if (assignmentStatement != null) {
invoke(assignmentStatement.getTargets()[0]);
}
}
}
private static void invoke(@NotNull final PsiElement element) {
new PyInvertBooleanDialog(element).show();
}
}
@@ -0,0 +1,184 @@
package com.jetbrains.python.refactoring.invertBoolean;
import com.intellij.openapi.util.Ref;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.SmartPointerManager;
import com.intellij.psi.SmartPsiElementPointer;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.refactoring.BaseRefactoringProcessor;
import com.intellij.refactoring.rename.RenameProcessor;
import com.intellij.refactoring.rename.RenameUtil;
import com.intellij.refactoring.util.MoveRenameUsageInfo;
import com.intellij.usageView.UsageInfo;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.HashMap;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
/**
* User : ktisha
*/
public class PyInvertBooleanProcessor extends BaseRefactoringProcessor {
private PsiElement myElement;
private final RenameProcessor myRenameProcessor;
private final Map<UsageInfo, SmartPsiElementPointer> myToInvert = new HashMap<UsageInfo, SmartPsiElementPointer>();
private final SmartPointerManager mySmartPointerManager;
public PyInvertBooleanProcessor(@NotNull final PsiElement namedElement, @NotNull final String newName) {
super(namedElement.getProject());
myElement = namedElement;
mySmartPointerManager = SmartPointerManager.getInstance(myProject);
myRenameProcessor = new RenameProcessor(myProject, namedElement, newName, false, false);
}
@Override
@NotNull
protected UsageViewDescriptor createUsageViewDescriptor(UsageInfo[] usages) {
return new PyInvertBooleanUsageViewDescriptor(myElement);
}
@Override
protected boolean preprocessUsages(Ref<UsageInfo[]> refUsages) {
if (myRenameProcessor.preprocessUsages(refUsages)) {
prepareSuccessful();
return true;
}
return false;
}
@Override
@NotNull
protected UsageInfo[] findUsages() {
final List<SmartPsiElementPointer> toInvert = new ArrayList<SmartPsiElementPointer>();
addRefsToInvert(toInvert, myElement);
final UsageInfo[] renameUsages = myRenameProcessor.findUsages();
final Map<PsiElement, UsageInfo> expressionsToUsages = new HashMap<PsiElement, UsageInfo>();
final List<UsageInfo> result = new ArrayList<UsageInfo>();
for (UsageInfo renameUsage : renameUsages) {
expressionsToUsages.put(renameUsage.getElement(), renameUsage);
result.add(renameUsage);
}
for (SmartPsiElementPointer pointer : toInvert) {
final PyExpression expression = (PyExpression)pointer.getElement();
if (!expressionsToUsages.containsKey(expression) && expression != null) {
final UsageInfo usageInfo = new UsageInfo(expression);
expressionsToUsages.put(expression, usageInfo);
result.add(usageInfo);
myToInvert.put(usageInfo, pointer);
} else {
myToInvert.put(expressionsToUsages.get(expression), pointer);
}
}
return result.toArray(new UsageInfo[result.size()]);
}
private void addRefsToInvert(@NotNull final List<SmartPsiElementPointer> toInvert, @NotNull final PsiElement psiElement) {
final Collection<PsiReference> refs = ReferencesSearch.search(psiElement).findAll();
for (PsiReference ref : refs) {
final PsiElement element = ref.getElement();
if (element instanceof PyTargetExpression) {
final PyTargetExpression target = (PyTargetExpression)element;
final PyAssignmentStatement parent = PsiTreeUtil.getParentOfType(target, PyAssignmentStatement.class);
if (parent != null && parent.getTargets().length == 1) {
final PyExpression value = parent.getAssignedValue();
if (value != null)
toInvert.add(mySmartPointerManager.createSmartPsiElementPointer(value));
}
}
else if (element.getParent() instanceof PyPrefixExpression) {
toInvert.add(mySmartPointerManager.createSmartPsiElementPointer(element.getParent()));
}
else if (element instanceof PyReferenceExpression) {
final PyReferenceExpression refExpr = (PyReferenceExpression)element;
toInvert.add(mySmartPointerManager.createSmartPsiElementPointer(refExpr));
}
}
if (psiElement instanceof PyNamedParameter) {
final PyExpression defaultValue = ((PyNamedParameter)psiElement).getDefaultValue();
if (defaultValue != null)
toInvert.add(mySmartPointerManager.createSmartPsiElementPointer(defaultValue));
}
}
@NotNull
private static UsageInfo[] extractUsagesForElement(@NotNull final PsiElement element, @NotNull final UsageInfo[] usages) {
final ArrayList<UsageInfo> extractedUsages = new ArrayList<UsageInfo>(usages.length);
for (UsageInfo usage : usages) {
if (usage instanceof MoveRenameUsageInfo) {
MoveRenameUsageInfo usageInfo = (MoveRenameUsageInfo)usage;
if (element.equals(usageInfo.getReferencedElement())) {
extractedUsages.add(usageInfo);
}
}
}
return extractedUsages.toArray(new UsageInfo[extractedUsages.size()]);
}
@Override
protected void performRefactoring(UsageInfo[] usages) {
for (final PsiElement element : myRenameProcessor.getElements()) {
try {
RenameUtil.doRename(element, myRenameProcessor.getNewName(element), extractUsagesForElement(element, usages), myProject, null);
}
catch (final IncorrectOperationException e) {
RenameUtil.showErrorMessage(e, element, myProject);
return;
}
}
for (UsageInfo usage : usages) {
final SmartPsiElementPointer pointerToInvert = myToInvert.get(usage);
if (pointerToInvert != null) {
PsiElement expression = pointerToInvert.getElement();
if (expression != null) {
final PyExpression replacement = invertExpression(expression);
expression.replace(replacement);
}
}
}
}
@NotNull
private PyExpression invertExpression(@NotNull final PsiElement expression) {
final PyElementGenerator elementGenerator = PyElementGenerator.getInstance(myProject);
if (expression instanceof PyBoolLiteralExpression) {
final String value = ((PyBoolLiteralExpression)expression).getValue() ? PyNames.FALSE : PyNames.TRUE;
return elementGenerator.createExpressionFromText(LanguageLevel.forElement(expression), value);
}
if (expression instanceof PyReferenceExpression && (PyNames.FALSE.equals(expression.getText()) ||
PyNames.TRUE.equals(expression.getText()))) {
final String value = PyNames.TRUE.equals(expression.getText()) ? PyNames.FALSE : PyNames.TRUE;
return elementGenerator.createExpressionFromText(LanguageLevel.forElement(expression), value);
}
else if (expression instanceof PyPrefixExpression) {
if (((PyPrefixExpression)expression).getOperator() == PyTokenTypes.NOT_KEYWORD) {
final PyExpression operand = ((PyPrefixExpression)expression).getOperand();
if (operand != null)
return elementGenerator.createExpressionFromText(LanguageLevel.forElement(expression), operand.getText());
}
}
return elementGenerator.createExpressionFromText(LanguageLevel.forElement(expression), "not " + expression.getText());
}
@Override
protected String getCommandName() {
return PyInvertBooleanHandler.REFACTORING_NAME;
}
}
@@ -0,0 +1,36 @@
package com.jetbrains.python.refactoring.invertBoolean;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.usageView.UsageViewBundle;
import com.intellij.usageView.UsageViewDescriptor;
import com.intellij.usageView.UsageViewUtil;
import org.jetbrains.annotations.NotNull;
/**
* User : ktisha
*/
public class PyInvertBooleanUsageViewDescriptor implements UsageViewDescriptor {
private final PsiElement myElement;
public PyInvertBooleanUsageViewDescriptor(final PsiElement element) {
myElement = element;
}
@NotNull
public PsiElement[] getElements() {
return new PsiElement[] {myElement};
}
public String getProcessedElementsHeader() {
return RefactoringBundle.message("invert.boolean.elements.header", UsageViewUtil.getType(myElement));
}
public String getCodeReferencesText(int usagesCount, int filesCount) {
return RefactoringBundle.message("invert.boolean.refs.to.invert", UsageViewBundle.getReferencesString(usagesCount, filesCount));
}
public String getCommentReferencesText(int usagesCount, int filesCount) {
return null;
}
}
@@ -0,0 +1,3 @@
def foo():
notVa<caret>r = False
return notVar
@@ -0,0 +1,3 @@
def foo():
va<caret>r = True
return not var
@@ -0,0 +1,3 @@
def foo(notVar=False):
var1 = True
return not notVar
@@ -0,0 +1,3 @@
def foo(v<caret>ar=True):
var1 = True
return var
@@ -0,0 +1,3 @@
def foo():
notVa<caret>r = False
return not notVar
@@ -0,0 +1,3 @@
def foo():
va<caret>r = True
return var
@@ -0,0 +1,34 @@
package com.jetbrains.python.refactoring;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.testFramework.TestDataPath;
import com.jetbrains.python.fixtures.PyTestCase;
import com.jetbrains.python.refactoring.invertBoolean.PyInvertBooleanProcessor;
/**
* User : ktisha
*/
@TestDataPath("$CONTENT_ROOT/../testData/refactoring/invertBoolean/")
public class PyInvertBooleanTest extends PyTestCase {
public void testSimple() { doTest(); }
public void testNegate() { doTest(); }
public void testParameter() { doTest(); }
private void doTest() {
myFixture.configureByFile("refactoring/invertBoolean/" + getTestName(true) + ".before.py");
final PsiElement element = myFixture.getElementAtCaret();
assertTrue(element instanceof PsiNamedElement);
final PsiNamedElement target = (PsiNamedElement)element;
final String name = target.getName();
assertNotNull(name);
new PyInvertBooleanProcessor(target, "not"+ StringUtil.toTitleCase(name)).run();
myFixture.checkResultByFile("refactoring/invertBoolean/" + getTestName(true) + ".after.py");
}
}