PY-9679 Properly highlight top-level symbol dependencies in the dialog

This commit is contained in:
Mikhail Golubev
2015-03-03 18:34:36 +03:00
parent b4af749371
commit 6febc26d0b
9 changed files with 179 additions and 110 deletions
+1 -1
View File
@@ -356,7 +356,7 @@
<codeInsight.overrideMethod language="Python" implementationClass="com.jetbrains.python.codeInsight.override.PyOverrideMethodsHandler"/>
<lang.refactoringSupport language="Python" implementationClass="com.jetbrains.python.refactoring.PyRefactoringProvider"/>
<lang.refactoringSupport.classMembersRefactoringSupport language="Python"
implementationClass="com.jetbrains.python.refactoring.classes.PyClassMembersRefactoringSupport"/>
implementationClass="com.jetbrains.python.refactoring.classes.PyMembersRefactoringSupport"/>
<inlineActionHandler implementation="com.jetbrains.python.refactoring.inline.PyInlineLocalHandler"/>
<codeInsight.gotoSuper language="Python" implementationClass="com.jetbrains.python.codeInsight.PyGotoSuperHandler"/>
<gotoDeclarationHandler implementation="com.jetbrains.python.codeInsight.PyBreakContinueGotoProvider" order="FIRST"/>
@@ -17,8 +17,6 @@ package com.jetbrains.python.refactoring.classes;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.actionSystem.LangDataKeys;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.application.ex.ApplicationManagerEx;
import com.intellij.openapi.editor.CaretModel;
import com.intellij.openapi.editor.Document;
@@ -79,7 +77,7 @@ public abstract class PyClassRefactoringHandler implements RefactoringActionHand
if (!inClass(clazz, project, editor, "refactoring.pull.up.error.cannot.perform.refactoring.not.inside.class")) return;
assert clazz != null;
final PyMemberInfoStorage infoStorage = PyClassMembersRefactoringSupport.getSelectedMemberInfos(clazz, element1, element2);
final PyMemberInfoStorage infoStorage = PyMembersRefactoringSupport.getSelectedMemberInfos(clazz, element1, element2);
doRefactorImpl(project, clazz, infoStorage, editor);
}
@@ -16,14 +16,15 @@
package com.jetbrains.python.refactoring.classes;
import com.intellij.refactoring.classMembers.DependentMembersCollectorBase;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.refactoring.classes.membersManager.MembersManager;
/**
* @author Dennis.Ushakov
*/
public class PyDependentMembersCollector extends DependentMembersCollectorBase<PyElement,PyClass> {
public PyDependentMembersCollector(PyClass clazz, PyClass superClass) {
public class PyDependentClassMembersCollector extends DependentMembersCollectorBase<PyElement,PyClass> {
public PyDependentClassMembersCollector(PyClass clazz, PyClass superClass) {
super(clazz, superClass);
}
@@ -22,12 +22,14 @@ import com.intellij.refactoring.classMembers.DependentMembersCollectorBase;
import com.intellij.refactoring.classMembers.MemberInfoBase;
import com.jetbrains.python.psi.PyClass;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.refactoring.classes.membersManager.PyMemberInfo;
import com.jetbrains.python.refactoring.move.PyDependentModuleMembersCollector;
/**
* @author Dennis.Ushakov
*/
public class PyClassMembersRefactoringSupport implements ClassMembersRefactoringSupport {
public class PyMembersRefactoringSupport implements ClassMembersRefactoringSupport {
public static PyMemberInfoStorage getSelectedMemberInfos(PyClass clazz, PsiElement element1, PsiElement element2) {
final PyMemberInfoStorage infoStorage = new PyMemberInfoStorage(clazz);
@@ -40,7 +42,13 @@ public class PyClassMembersRefactoringSupport implements ClassMembersRefactoring
}
public DependentMembersCollectorBase createDependentMembersCollector(Object clazz, Object superClass) {
return new PyDependentMembersCollector((PyClass)clazz, (PyClass)superClass);
if (clazz instanceof PyClass) {
return new PyDependentClassMembersCollector((PyClass)clazz, (PyClass)superClass);
}
else if (clazz instanceof PyFile) {
return new PyDependentModuleMembersCollector(((PyFile)clazz));
}
return null;
}
public boolean isProperMember(MemberInfoBase member) {
@@ -0,0 +1,49 @@
package com.jetbrains.python.refactoring.move;
import com.intellij.psi.PsiElement;
import com.intellij.refactoring.classMembers.DependentMembersCollectorBase;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyFile;
import com.jetbrains.python.psi.PyRecursiveElementVisitor;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.resolve.PyResolveContext;
import org.jetbrains.annotations.NotNull;
/**
* Collects dependencies of the top-level symbols in the given module. This information is used then to highlight them
* in "Move ..." dialog the same way as it's done for members of classes in various class-related refactorings.
*
* @see PyModuleMemberInfoModel
*
* @author Mikhail Golubev
*/
public class PyDependentModuleMembersCollector extends DependentMembersCollectorBase<PyElement, PyFile> {
private final PyFile myModule;
public PyDependentModuleMembersCollector(@NotNull PyFile module) {
super(module, null);
myModule = module;
}
@Override
public void collect(final PyElement member) {
if (member.getContainingFile() == myModule) {
final PyResolveContext resolveContext = PyResolveContext.defaultContext();
member.accept(new PyRecursiveElementVisitor() {
@Override
public void visitElement(PsiElement element) {
for (PsiElement result : PyUtil.multiResolveTopPriority(element, resolveContext)) {
if (isValidSameModuleDependency(result) && result != member) {
myCollection.add(((PyElement)result));
}
}
super.visitElement(element);
}
});
}
}
private boolean isValidSameModuleDependency(@NotNull PsiElement element) {
return PyMoveClassOrFunctionDelegate.canMoveElement(element) && element.getContainingFile() == myModule;
}
}
@@ -0,0 +1,32 @@
package com.jetbrains.python.refactoring.move;
import com.intellij.refactoring.classMembers.MemberInfoBase;
import com.jetbrains.python.psi.PyElement;
/**
* Helper object that describes top-level symbol of the module (class, function or assignment) for
* the table in "Move ..." dialog. Concrete properties of given symbol is described in {@link PyModuleMemberInfoModel}.
*
* @author Mikhail Golubev
* @see PyModuleMemberInfoModel
*/
class PyModuleMemberInfo extends MemberInfoBase<PyElement> {
public PyModuleMemberInfo(PyElement member) {
super(member);
}
@Override
public boolean isStatic() {
return true;
}
@Override
public String getDisplayName() {
return getMember().getName();
}
@Override
public String toString() {
return "PyModuleMemberInfo(" + getMember() + ")";
}
}
@@ -0,0 +1,52 @@
package com.jetbrains.python.refactoring.move;
import com.intellij.refactoring.classMembers.AbstractUsesDependencyMemberInfoModel;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyFile;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* @author Mikhail Golubev
*/
class PyModuleMemberInfoModel extends AbstractUsesDependencyMemberInfoModel<PyElement, PyFile, PyModuleMemberInfo> {
final PyFile myPyFile;
public PyModuleMemberInfoModel(@NotNull PyFile file) {
super(file, null, false);
myPyFile = file;
}
@Override
public boolean isAbstractEnabled(PyModuleMemberInfo member) {
return false;
}
@Override
protected int doCheck(@NotNull PyModuleMemberInfo memberInfo, int problem) {
return problem == ERROR ? WARNING : problem;
}
@NotNull
public List<PyElement> collectTopLevelSymbols() {
final List<PyElement> result = new ArrayList<PyElement>();
result.addAll(myPyFile.getTopLevelAttributes());
result.addAll(myPyFile.getTopLevelClasses());
result.addAll(myPyFile.getTopLevelFunctions());
return result;
}
@NotNull
public List<PyModuleMemberInfo> collectTopLevelSymbolsInfo() {
return ContainerUtil.mapNotNull(collectTopLevelSymbols(), new Function<PyElement, PyModuleMemberInfo>() {
@Override
public PyModuleMemberInfo fun(PyElement element) {
return new PyModuleMemberInfo(element);
}
});
}
}
@@ -46,12 +46,24 @@ public class PyMoveClassOrFunctionDelegate extends MoveHandlerDelegate {
@Override
public boolean canMove(PsiElement[] elements, @Nullable PsiElement targetContainer) {
for (PsiElement element : elements) {
if ((element instanceof PyClass || element instanceof PyFunction) && PyUtil.isTopLevel(element)) continue;
return false;
if (!canMoveElement(element)) {
return false;
}
}
return super.canMove(elements, targetContainer);
}
/**
* Checks that given element is suitable for "Move ..." refactoring. Currently it means that it's top-level function, class or
* target expression.
*
* @param element PSI element to check
* @return whether this element is acceptable for "Move ..." refactoring
*/
public static boolean canMoveElement(@NotNull PsiElement element) {
return (element instanceof PyClass || element instanceof PyFunction) && PyUtil.isTopLevel(element);
}
@Override
public void doMove(Project project,
PsiElement[] elements,
@@ -10,7 +10,6 @@ import com.intellij.openapi.util.io.FileUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiNamedElement;
import com.intellij.refactoring.classMembers.MemberInfoBase;
import com.intellij.refactoring.classMembers.MemberInfoChange;
import com.intellij.refactoring.classMembers.MemberInfoModel;
import com.intellij.refactoring.ui.AbstractMemberSelectionTable;
@@ -31,7 +30,6 @@ import org.jetbrains.annotations.TestOnly;
import javax.swing.*;
import javax.swing.table.TableCellRenderer;
import java.awt.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -46,7 +44,7 @@ public class PyMoveTopLevelSymbolDialog extends RefactoringDialog {
private static PyMoveTopLevelSymbolDialog ourInstanceToReplace = null;
private final TopLevelSymbolsSelectionTable myMemberSelectionTable;
private final ModuleMemberInfoModel myModuleMemberModel;
private final PyModuleMemberInfoModel myModuleMemberModel;
private JPanel myCenterPanel;
private JPanel myTablePanel;
private TextFieldWithBrowseButton myBrowseFieldWithButton;
@@ -115,13 +113,15 @@ public class PyMoveTopLevelSymbolDialog extends RefactoringDialog {
TextComponentAccessor.TEXT_FIELD_WHOLE_TEXT);
final PyFile pyFile = (PyFile)firstElement.getContainingFile();
myModuleMemberModel = new ModuleMemberInfoModel(pyFile);
myModuleMemberModel = new PyModuleMemberInfoModel(pyFile);
final List<TopLevelSymbolInfo> symbolsInfo = myModuleMemberModel.getTopLevelSymbolInfo();
for (TopLevelSymbolInfo info : symbolsInfo) {
final List<PyModuleMemberInfo> symbolsInfos = myModuleMemberModel.collectTopLevelSymbolsInfo();
for (PyModuleMemberInfo info : symbolsInfos) {
info.setChecked(elements.contains(info.getMember()));
}
myMemberSelectionTable = new TopLevelSymbolsSelectionTable(symbolsInfo, myModuleMemberModel);
myModuleMemberModel.memberInfoChanged(new MemberInfoChange<PyElement, PyModuleMemberInfo>(symbolsInfos));
myMemberSelectionTable = new TopLevelSymbolsSelectionTable(symbolsInfos, myModuleMemberModel);
myMemberSelectionTable.addMemberInfoChangeListener(myModuleMemberModel);
// MoveMemberDialog for Java uses SeparatorFactory.createSeparator instead of custom border
myTablePanel.add(ScrollPaneFactory.createScrollPane(myMemberSelectionTable), BorderLayout.CENTER);
@@ -156,9 +156,9 @@ public class PyMoveTopLevelSymbolDialog extends RefactoringDialog {
@NotNull
public List<PyElement> getSelectedTopLevelSymbols() {
return ContainerUtil.map(myMemberSelectionTable.getSelectedMemberInfos(), new Function<TopLevelSymbolInfo, PyElement>() {
return ContainerUtil.map(myMemberSelectionTable.getSelectedMemberInfos(), new Function<PyModuleMemberInfo, PyElement>() {
@Override
public PyElement fun(TopLevelSymbolInfo info) {
public PyElement fun(PyModuleMemberInfo info) {
return info.getMember();
}
});
@@ -175,95 +175,12 @@ public class PyMoveTopLevelSymbolDialog extends RefactoringDialog {
}
}
static class ModuleMemberInfoModel implements MemberInfoModel<PyElement, TopLevelSymbolInfo> {
final PyFile myPyFile;
public ModuleMemberInfoModel(@NotNull PyFile pyFile) {
myPyFile = pyFile;
}
@NotNull
public List<PyElement> getTopLevelSymbols() {
final List<PyElement> result = new ArrayList<PyElement>();
result.addAll(myPyFile.getTopLevelAttributes());
result.addAll(myPyFile.getTopLevelClasses());
result.addAll(myPyFile.getTopLevelFunctions());
return result;
}
@NotNull
public List<TopLevelSymbolInfo> getTopLevelSymbolInfo() {
return ContainerUtil.mapNotNull(getTopLevelSymbols(), new Function<PyElement, TopLevelSymbolInfo>() {
@Override
public TopLevelSymbolInfo fun(PyElement element) {
return new TopLevelSymbolInfo(element);
}
});
}
@Override
public boolean isMemberEnabled(TopLevelSymbolInfo member) {
return true;
}
@Override
public boolean isCheckedWhenDisabled(TopLevelSymbolInfo member) {
return false;
}
@Override
public boolean isAbstractEnabled(TopLevelSymbolInfo member) {
return false;
}
@Override
public boolean isAbstractWhenDisabled(TopLevelSymbolInfo member) {
return false;
}
@Override
public Boolean isFixedAbstract(TopLevelSymbolInfo member) {
return null;
}
@Override
public int checkForProblems(@NotNull TopLevelSymbolInfo member) {
return 0;
}
@Override
public String getTooltipText(TopLevelSymbolInfo member) {
return "";
}
@Override
public void memberInfoChanged(MemberInfoChange<PyElement, TopLevelSymbolInfo> event) {
}
}
static class TopLevelSymbolInfo extends MemberInfoBase<PyElement> {
public TopLevelSymbolInfo(PyElement member) {
super(member);
}
@Override
public boolean isStatic() {
return true;
}
@Override
public String getDisplayName() {
return getMember().getName();
}
}
static class TopLevelSymbolsSelectionTable extends AbstractMemberSelectionTable<PyElement, TopLevelSymbolInfo> {
public TopLevelSymbolsSelectionTable(@NotNull Collection<TopLevelSymbolInfo> memberInfos,
@Nullable MemberInfoModel<PyElement, TopLevelSymbolInfo> memberInfoModel) {
static class TopLevelSymbolsSelectionTable extends AbstractMemberSelectionTable<PyElement, PyModuleMemberInfo> {
public TopLevelSymbolsSelectionTable(@NotNull Collection<PyModuleMemberInfo> memberInfos,
@Nullable MemberInfoModel<PyElement, PyModuleMemberInfo> memberInfoModel) {
super(memberInfos, memberInfoModel, null);
// TODO: It's better to make AbstractMemberSelectionTable more flexible than to patch model like that
myTableModel = new MyTableModel<PyElement, TopLevelSymbolInfo>(this) {
myTableModel = new MyTableModel<PyElement, PyModuleMemberInfo>(this) {
@Override
public String getColumnName(int column) {
return column == DISPLAY_NAME_COLUMN ? "Symbol" : super.getColumnName(column);
@@ -278,7 +195,7 @@ public class PyMoveTopLevelSymbolDialog extends RefactoringDialog {
@Nullable
@Override
protected Object getAbstractColumnValue(TopLevelSymbolInfo memberInfo) {
protected Object getAbstractColumnValue(PyModuleMemberInfo memberInfo) {
return null;
}
@@ -288,12 +205,12 @@ public class PyMoveTopLevelSymbolDialog extends RefactoringDialog {
}
@Override
protected void setVisibilityIcon(TopLevelSymbolInfo memberInfo, RowIcon icon) {
protected void setVisibilityIcon(PyModuleMemberInfo memberInfo, RowIcon icon) {
}
@Override
protected Icon getOverrideIcon(TopLevelSymbolInfo memberInfo) {
protected Icon getOverrideIcon(PyModuleMemberInfo memberInfo) {
return null;
}
}