diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml
index e5f141d4d12e..636b43166b6f 100644
--- a/python/src/META-INF/python-plugin-common.xml
+++ b/python/src/META-INF/python-plugin-common.xml
@@ -76,6 +76,12 @@
ImportFromToImportIntention
+
+ com.jetbrains.python.codeInsight.intentions.ImportToggleAliasIntention
+ Python
+ ImportToggleAliasIntention
+
+
diff --git a/python/src/com/jetbrains/python/PyBundle.properties b/python/src/com/jetbrains/python/PyBundle.properties
index 07e98db4a692..bedeb4938ac9 100644
--- a/python/src/com/jetbrains/python/PyBundle.properties
+++ b/python/src/com/jetbrains/python/PyBundle.properties
@@ -33,7 +33,9 @@ QFIX.action.failed=Action failed
QFIX.remove.trailing.semicolon=Remove trailing semicolon
# Intentions: INTN
-INTN.Family.convert.import=Convert import
+INTN.Family.convert.import.unqualify=Convert 'import' to 'from ... import'
+INTN.Family.convert.import.qualify=Convert 'from ... import' to 'import'
+INTN.Family.toggle.import.alias=Toggle import alias
INTN.convert.to.from.$0.import=Convert to ''from {0} import ...''
diff --git a/python/src/com/jetbrains/python/PyNames.java b/python/src/com/jetbrains/python/PyNames.java
index 00e6c605414d..991e980ce463 100644
--- a/python/src/com/jetbrains/python/PyNames.java
+++ b/python/src/com/jetbrains/python/PyNames.java
@@ -5,9 +5,10 @@ import org.jetbrains.annotations.NonNls;
import java.util.HashSet;
import java.util.Set;
import java.util.Collections;
+import java.util.regex.Pattern;
/**
- * @author yole
+ * @author dcheryasov
*/
public class PyNames {
private PyNames() {
@@ -122,4 +123,68 @@ public class PyNames {
// canonical names, not forced by interpreter
public static final String CANONICAL_SELF = "self";
+
+ @NonNls private static final Set _Keywords = new HashSet();
+ static {
+ _Keywords.add("and");
+ _Keywords.add("del");
+ _Keywords.add("from");
+ _Keywords.add("not");
+ _Keywords.add("while");
+ _Keywords.add("as");
+ _Keywords.add("elif");
+ _Keywords.add("global");
+ _Keywords.add("or");
+ _Keywords.add("with");
+ _Keywords.add("assert");
+ _Keywords.add("else");
+ _Keywords.add("if");
+ _Keywords.add("pass");
+ _Keywords.add("yield");
+ _Keywords.add("break");
+ _Keywords.add("except");
+ _Keywords.add("import");
+ _Keywords.add("print");
+ _Keywords.add("class");
+ _Keywords.add("exec");
+ _Keywords.add("in");
+ _Keywords.add("raise");
+ _Keywords.add("continue");
+ _Keywords.add("finally");
+ _Keywords.add("is");
+ _Keywords.add("return");
+ _Keywords.add("def");
+ _Keywords.add("for");
+ _Keywords.add("lambda");
+ _Keywords.add("try");
+ }
+
+ /**
+ * Contains keywords as of CPython 2.5.
+ */
+ public static Set Keywords = Collections.unmodifiableSet(_Keywords);
+
+ /**
+ * TODO: dependency on language level.
+ * @param name what to check
+ * @return true iff the name is either a keyword or a reserved name, like None.
+ *
+ */
+ public static boolean isReserved(@NonNls String name) {
+ return Keywords.contains(name) || NONE.equals(name) || "as".equals(name) || "with".equals(name);
+ }
+
+ // NOTE: includes unicode only good for py3k
+ private final static Pattern IDENTIFIER_PATTERN = Pattern.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*");
+
+ /**
+ * TODO: dependency on language level.
+ * @param name what to check
+ * @return true iff name is not reserved and is a well-formed identifier.
+ */
+ public static boolean isIdentifier(@NonNls String name) {
+ return ! isReserved(name) && IDENTIFIER_PATTERN.matcher(name).matches();
+ }
+
+
}
diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/AskNameDialog.form b/python/src/com/jetbrains/python/codeInsight/intentions/AskNameDialog.form
new file mode 100644
index 000000000000..eab22ef2e28e
--- /dev/null
+++ b/python/src/com/jetbrains/python/codeInsight/intentions/AskNameDialog.form
@@ -0,0 +1,43 @@
+
+
diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/AskNameDialog.java b/python/src/com/jetbrains/python/codeInsight/intentions/AskNameDialog.java
new file mode 100644
index 000000000000..3cbbf1b8a922
--- /dev/null
+++ b/python/src/com/jetbrains/python/codeInsight/intentions/AskNameDialog.java
@@ -0,0 +1,40 @@
+package com.jetbrains.python.codeInsight.intentions;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.DialogWrapper;
+import com.jetbrains.python.PyNames;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+
+public class AskNameDialog extends DialogWrapper {
+ private JPanel contentPane;
+ private JTextField myAliasTextField;
+
+ protected AskNameDialog(Project project) {
+ super(project);
+ myAliasTextField.addActionListener(
+ new ActionListener() {
+ public void actionPerformed(ActionEvent actionEvent) {
+ AskNameDialog.this.setOKActionEnabled(PyNames.isIdentifier(myAliasTextField.getText()));
+ }
+ }
+ );
+ init();
+ }
+
+ @Override
+ protected JComponent createCenterPanel() {
+ return contentPane;
+ }
+
+ @Override
+ public JComponent getPreferredFocusedComponent() {
+ return myAliasTextField;
+ }
+
+ public String getAlias() {
+ return myAliasTextField.getText();
+ }
+}
diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/DeclarationConflictChecker.java b/python/src/com/jetbrains/python/codeInsight/intentions/DeclarationConflictChecker.java
new file mode 100644
index 000000000000..278a29deaa14
--- /dev/null
+++ b/python/src/com/jetbrains/python/codeInsight/intentions/DeclarationConflictChecker.java
@@ -0,0 +1,88 @@
+package com.jetbrains.python.codeInsight.intentions;
+
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.util.Pair;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiReference;
+import com.intellij.usages.Usage;
+import com.intellij.usages.UsageTarget;
+import com.intellij.usages.UsageViewManager;
+import com.intellij.usages.UsageViewPresentation;
+import com.jetbrains.python.psi.NameDefiner;
+import com.jetbrains.python.psi.resolve.PyResolveUtil;
+import com.jetbrains.python.psi.resolve.ResolveProcessor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * An utility class that checks local definitions of a given name and can show a conflicts panel.
+ * User: dcheryasov
+ * Date: Oct 11, 2009 5:59:12 AM
+ */
+public class DeclarationConflictChecker {
+ private DeclarationConflictChecker() { /* Don't instantiate */ }
+
+ /**
+ * For each reference in the collection, finds a definition of name visible from the point of the reference. Returns a list of
+ * such definitions.
+ * @param name what to look for.
+ * @param references references to check.
+ * @param ignored if an element defining the name is also listed here, ignore it.
+ * @return a list of pairs (referring element, element that defines name).
+ */
+ @NotNull
+ public static List> findDefinitions(
+ String name, Collection extends PsiReference> references, @Nullable Collection extends PsiElement> ignored
+ ) {
+ List> conflicts = new ArrayList>();
+ REF_LOOP:
+ for (PsiReference ref : references) {
+ ResolveProcessor processor = new ResolveProcessor(name);
+ PyResolveUtil.treeCrawlUp(processor, ref.getElement());
+ PsiElement result = processor.getResult();
+ if (result != null) {
+ List definers = processor.getDefiners();
+ if (definers != null && definers.size() > 0) {
+ result = definers.get(0); // in this case, processor's result is one hop of resolution too far from what we want.
+ }
+ if (ignored != null) for (PsiElement ignorable : ignored) {
+ if (result == ignorable) continue REF_LOOP;
+ }
+ conflicts.add(new Pair(ref.getElement(), result));
+ }
+ }
+ return conflicts;
+ }
+
+ /**
+ * Shows a panel with name redefinition conflicts, if needed.
+ * @param project
+ * @param conflicts what {@link #findDefinitions} would return
+ * @param obscured name or its topmost qualifier that is obscured, used at top of pane.
+ * @param name full name (maybe qualified) to show as obscured and display as qualifier in "would be" chunks.
+ * @return true iff conflicts is not empty and the panel is shown.
+ */
+ public static boolean showConflicts(Project project, List> conflicts, String obscured, @Nullable String name) {
+ if (conflicts.size() > 0) {
+ Usage[] usages = new Usage[conflicts.size()];
+ int i = 0;
+ for (Pair pair : conflicts) {
+ usages[i] = new NameUsage(pair.getFirst(), pair.getSecond(), name != null? name : obscured, name != null);
+ i += 1;
+ }
+ UsageViewPresentation prsnt = new UsageViewPresentation();
+ prsnt.setTabText("Name '" + obscured + "' obscured by local redefinitions. ");
+ prsnt.setCodeUsagesString("Name '" + obscured + "' obscured. Cannot convert import.");
+ prsnt.setUsagesWord("occurrence");
+ prsnt.setUsagesString("occurrences");
+ UsageViewManager.getInstance(project).showUsages(UsageTarget.EMPTY_ARRAY, usages, prsnt);
+ return true;
+ }
+ return false;
+ }
+}
+
diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/ImportFromToImportIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/ImportFromToImportIntention.java
index 33e41b675c8e..3cd2976fc140 100644
--- a/python/src/com/jetbrains/python/codeInsight/intentions/ImportFromToImportIntention.java
+++ b/python/src/com/jetbrains/python/codeInsight/intentions/ImportFromToImportIntention.java
@@ -1,39 +1,29 @@
package com.jetbrains.python.codeInsight.intentions;
import com.intellij.codeInsight.intention.IntentionAction;
-import com.intellij.ide.util.EditSourceUtil;
import com.intellij.lang.ASTNode;
import com.intellij.lang.Language;
import com.intellij.openapi.editor.Editor;
-import com.intellij.openapi.editor.markup.TextAttributes;
-import com.intellij.openapi.fileEditor.FileEditorLocation;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.ui.MessageType;
-import com.intellij.openapi.util.Pair;
-import com.intellij.pom.Navigatable;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiReference;
import com.intellij.psi.ResolveResult;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.util.PsiTreeUtil;
-import com.intellij.usages.*;
-import com.intellij.usages.rules.PsiElementUsage;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.containers.HashMap;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.PythonLanguage;
+import static com.jetbrains.python.codeInsight.intentions.DeclarationConflictChecker.findDefinitions;
+import static com.jetbrains.python.codeInsight.intentions.DeclarationConflictChecker.showConflicts;
import com.jetbrains.python.psi.*;
import static com.jetbrains.python.psi.PyUtil.sure;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
-import com.jetbrains.python.psi.resolve.ResolveProcessor;
import org.jetbrains.annotations.NotNull;
-import org.jetbrains.annotations.Nullable;
-import javax.swing.*;
-import java.awt.*;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.Arrays;
import java.util.Map;
/**
@@ -50,11 +40,6 @@ public class ImportFromToImportIntention implements IntentionAction {
private PyReferenceExpression myModuleReference = null;
private String myModuleName = null;
- @Nullable
- private static PyFromImportStatement findStatement(Editor editor, PsiFile file) {
- return PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyFromImportStatement.class);
- }
-
@NotNull
public String getText() {
String name = myModuleName != null? myModuleName : "...";
@@ -63,11 +48,11 @@ public class ImportFromToImportIntention implements IntentionAction {
@NotNull
public String getFamilyName() {
- return PyBundle.message("INTN.Family.convert.import");
+ return PyBundle.message("INTN.Family.convert.import.qualify");
}
public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
- myFromImportStatement = findStatement(editor, file);
+ myFromImportStatement = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyFromImportStatement.class);
if (myFromImportStatement != null) {
myModuleReference = myFromImportStatement.getImportSource();
if (myModuleReference != null) myModuleName = PyResolveUtil.toPath(myModuleReference, ".");
@@ -111,33 +96,14 @@ public class ImportFromToImportIntention implements IntentionAction {
feeler = top_qualifier.getQualifier();
} while (feeler != null);
String top_name = top_qualifier.getName();
- List> conflicts = new ArrayList>();
- for (PsiReference ref : references.keySet()) {
- ResolveProcessor processor = new ResolveProcessor(top_name);
- PyResolveUtil.treeCrawlUp(processor, ref.getElement());
- PsiElement result = processor.getResult();
- if (result != null) {
- List definers = processor.getDefiners();
- if (definers != null && definers.size() > 0) {
- result = definers.get(0); // in this case, processor's result is one hop of resolution too far from what we want.
- }
- conflicts.add(new Pair(ref.getElement(), result));
- }
- }
- if (conflicts.size() > 0) {
- Usage[] usages = new Usage[conflicts.size()];
- int i = 0;
- for (Pair pair : conflicts) {
- usages[i] = new UUsage(pair.getFirst(), pair.getSecond(), myModuleName);
- i += 1;
- }
- UsageViewPresentation prsnt = new UsageViewPresentation();
- prsnt.setTabText("Name '" + top_name + "' obscured by local redefinitions. ");
- prsnt.setCodeUsagesString("Name '" + top_name + "' obscured. Cannot convert import.");
- prsnt.setUsagesWord("occurrence");
- prsnt.setUsagesString("occurrences");
- UsageViewManager.getInstance(project).showUsages(UsageTarget.EMPTY_ARRAY, usages, prsnt);
- return;
+ if (
+ showConflicts(
+ project,
+ findDefinitions(top_name, references.keySet(), Arrays.asList(myFromImportStatement.getImportElements())),
+ top_name, myModuleName
+ )
+ ) {
+ return; // got conflicts
}
// add qualifiers
@@ -183,90 +149,3 @@ public class ImportFromToImportIntention implements IntentionAction {
}
}
-class UUsage implements PsiElementUsage {
-
- private final PsiElement myElement;
- private final PsiElement myCulprit;
-
- static final TextAttributes SLANTED;
- private final String myPrefix;
-
- static {
- SLANTED = TextAttributes.ERASE_MARKER.clone();
- SLANTED.setFontType(Font.ITALIC);
- }
-
-
- public UUsage(PsiElement element, PsiElement culprit, String prefix) {
- myElement = element;
- myCulprit = culprit;
- myPrefix = prefix;
- }
-
- public FileEditorLocation getLocation() {
- return null;
- }
-
- @NotNull
- public UsagePresentation getPresentation() {
- return new UsagePresentation() {
- @Nullable
- public Icon getIcon() {
- return myElement.getIcon(0);
- }
-
- @NotNull
- public TextChunk[] getText() {
- TextChunk[] chunks = new TextChunk[3];
- PsiFile file = myElement.getContainingFile();
- int lineno = file.getViewProvider().getDocument().getLineNumber(myElement.getTextOffset());
- chunks[0] = new TextChunk(SLANTED, "(" + lineno + ") ");
- chunks[1] = new TextChunk(TextAttributes.ERASE_MARKER, myElement.getText());
- chunks[2] = new TextChunk(SLANTED, " would become " + myPrefix + "." + myElement.getText());
- return chunks;
- }
-
- @NotNull
- public String getPlainText() {
- return myElement.getText();
- }
-
- public String getTooltipText() {
- return myElement.getText();
- }
- };
- }
-
- public boolean isValid() {
- return true;
- }
-
- public boolean isReadOnly() {
- return false;
- }
-
- public void selectInEditor() { }
-
- public void highlightInEditor() { }
-
- public void navigate(boolean requestFocus) {
- Navigatable descr = EditSourceUtil.getDescriptor(myElement);
- if (descr != null) descr.navigate(requestFocus);
- }
-
- public boolean canNavigate() {
- return EditSourceUtil.canNavigate(myElement);
- }
-
- public boolean canNavigateToSource() {
- return false;
- }
-
- public PsiElement getElement() {
- return myCulprit;
- }
-
- public boolean isNonCodeUsage() {
- return false;
- }
-}
\ No newline at end of file
diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/ImportToImportFromIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/ImportToImportFromIntention.java
index c803d18e1809..99355790fcc4 100644
--- a/python/src/com/jetbrains/python/codeInsight/intentions/ImportToImportFromIntention.java
+++ b/python/src/com/jetbrains/python/codeInsight/intentions/ImportToImportFromIntention.java
@@ -53,7 +53,7 @@ public class ImportToImportFromIntention implements IntentionAction {
@NotNull
public String getFamilyName() {
- return PyBundle.message("INTN.Family.convert.import");
+ return PyBundle.message("INTN.Family.convert.import.unqualify");
}
@Nullable
@@ -94,7 +94,7 @@ public class ImportToImportFromIntention implements IntentionAction {
if (myQualifierName.equals(PyResolveUtil.toPath(ref, "."))) { // filter out other names that might resolve to our target
PsiElement elt = ref.getElement();
PsiElement parent_elt = elt.getParent();
- if (parent_elt != null && parent_elt instanceof PyQualifiedExpression) { // really qualified by us, not just referencing?
+ if (parent_elt instanceof PyQualifiedExpression) { // really qualified by us, not just referencing?
PsiElement resolved = ref.resolve();
if (resolved == myReferee) references.add(ref);
}
diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/ImportToggleAliasIntention.java b/python/src/com/jetbrains/python/codeInsight/intentions/ImportToggleAliasIntention.java
new file mode 100644
index 000000000000..f801ea84adbd
--- /dev/null
+++ b/python/src/com/jetbrains/python/codeInsight/intentions/ImportToggleAliasIntention.java
@@ -0,0 +1,169 @@
+package com.jetbrains.python.codeInsight.intentions;
+
+import com.intellij.codeInsight.intention.IntentionAction;
+import com.intellij.lang.ASTNode;
+import com.intellij.lang.Language;
+import com.intellij.openapi.editor.Editor;
+import com.intellij.openapi.project.Project;
+import com.intellij.openapi.ui.MessageType;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.psi.PsiReference;
+import com.intellij.psi.search.PsiElementProcessor;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.util.IncorrectOperationException;
+import com.jetbrains.python.PyBundle;
+import com.jetbrains.python.PythonLanguage;
+import static com.jetbrains.python.codeInsight.intentions.DeclarationConflictChecker.findDefinitions;
+import static com.jetbrains.python.codeInsight.intentions.DeclarationConflictChecker.showConflicts;
+import com.jetbrains.python.psi.*;
+import static com.jetbrains.python.psi.PyUtil.sure;
+import com.jetbrains.python.psi.resolve.PyResolveUtil;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Adds an alias to "import foo" or "from foo import bar" import elements, or removes it if it's already present.
+ * User: dcheryasov
+ * Date: Oct 9, 2009 6:07:19 PM
+ */
+public class ImportToggleAliasIntention implements IntentionAction {
+
+ private PyImportElement myImportElement;
+ private PyFromImportStatement myFromImportStatement;
+ private PyImportStatement myImportStatement;
+ private String myAlias;
+
+
+ @NotNull
+ public String getText() {
+ String add_name = "Add alias";
+ if (myImportElement != null) {
+ PyReferenceExpression refex = myImportElement.getImportReference();
+ if (refex != null) {
+ String name = refex.getReferencedName();
+ if (name != null && !"".equals(name)) {
+ add_name = "Add alias to '" + name + "'";
+ }
+ }
+ }
+ return myAlias == null? add_name : "Remove alias '" + myAlias + "'";
+ }
+
+ @NotNull
+ public String getFamilyName() {
+ return PyBundle.message("INTN.Family.toggle.import.alias");
+ }
+
+ public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) {
+ myImportElement = PsiTreeUtil.getParentOfType(file.findElementAt(editor.getCaretModel().getOffset()), PyImportElement.class);
+ if (myImportElement != null && myImportElement .isValid()) {
+ PyTargetExpression target = myImportElement.getAsName();
+ if (target != null && target.isValid()) myAlias = target.getName();
+ else myAlias = null;
+ myFromImportStatement = PsiTreeUtil.getParentOfType(myImportElement, PyFromImportStatement.class);
+ if (myFromImportStatement != null && myFromImportStatement.isValid()) {
+ return true;
+ }
+ else {
+ myImportStatement = PsiTreeUtil.getParentOfType(myImportElement, PyImportStatement.class);
+ return myImportStatement != null && myImportStatement.isValid();
+ }
+ }
+ return false;
+ }
+
+ public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException {
+ // sanity check: isAvailable must have set it.
+ assert myImportElement != null;
+ assert myImportStatement != null || myFromImportStatement != null;
+ //
+ final String target_name; // we set in in the source
+ final String remove_name; // we replace it in the source
+ PyReferenceExpression reference = sure(myImportElement.getImportReference());
+ // search for references to us with the right name
+ try {
+ String imported_name = PyResolveUtil.toPath(reference, ".");
+ if (myAlias != null) {
+ // have to remove alias, rename everything to original
+ target_name = imported_name;
+ remove_name = myAlias;
+ }
+ else {
+ // ask for and add alias
+ AskNameDialog dialog = new AskNameDialog(project);
+ dialog.setTitle("Alias for '" + imported_name + "'?");
+ dialog.show();
+ if (!dialog.isOK()) return; // 'Cancel' button cancels everything
+ target_name = dialog.getAlias();
+ remove_name = imported_name;
+ }
+ final PsiElement referee = reference.resolve();
+ if (referee != null && imported_name != null) {
+ final Collection references = new ArrayList();
+ PsiTreeUtil.processElements(file, new PsiElementProcessor() {
+ public boolean execute(PsiElement element) {
+ if (element instanceof PyReferenceExpression && PsiTreeUtil.getParentOfType(element, PyImportElement.class) == null) {
+ PyReferenceExpression ref = (PyReferenceExpression)element;
+ if (remove_name.equals(PyResolveUtil.toPath(ref, "."))) { // filter out other names that might resolve to our target
+ PsiElement resolved = ref.resolve();
+ if (resolved == referee) references.add(ref);
+ }
+ }
+ return true;
+ }
+ });
+ // no references here is OK by us.
+ if (showConflicts(project, findDefinitions(target_name, references, null), target_name, null)) {
+ return; // got conflicts
+ }
+
+ // alter the import element
+ Language language = myImportElement.getLanguage();
+ assert language instanceof PythonLanguage;
+ PythonLanguage pythonLanguage = (PythonLanguage)language;
+ PyElementGenerator generator = pythonLanguage.getElementGenerator();
+ if (myAlias != null) {
+ // remove alias
+ ASTNode node = sure(myImportElement.getNode());
+ ASTNode parent = sure(node.getTreeParent());
+ node = sure(node.getFirstChildNode()); // this is the reference
+ node = sure(node.getTreeNext()); // things past the reference: space, 'as', and alias
+ parent.removeRange(node, null);
+ }
+ else {
+ // add alias
+ ASTNode my_ielt_node = sure(myImportElement.getNode());
+ PyImportElement fountain = generator.createFromText(project, PyImportElement.class, "import foo as "+target_name, new int[]{0,2});
+ ASTNode graft_node = sure(fountain.getNode()); // at import elt
+ graft_node = sure(graft_node.getFirstChildNode()); // at ref
+ graft_node = sure(graft_node.getTreeNext()); // space
+ my_ielt_node.addChild((ASTNode)graft_node.clone());
+ graft_node = sure(graft_node.getTreeNext()); // 'as'
+ my_ielt_node.addChild((ASTNode)graft_node.clone());
+ graft_node = sure(graft_node.getTreeNext()); // space
+ my_ielt_node.addChild((ASTNode)graft_node.clone());
+ graft_node = sure(graft_node.getTreeNext()); // alias
+ my_ielt_node.addChild((ASTNode)graft_node.clone());
+ }
+ // alter references
+ for (PsiReference ref : references) {
+ ASTNode ref_name_node = sure(sure(ref.getElement()).getNode());
+ ASTNode parent = sure(ref_name_node.getTreeParent());
+ ASTNode new_name_node = generator.createExpressionFromText(project, target_name).getNode();
+ assert new_name_node != null;
+ parent.replaceChild(ref_name_node, new_name_node);
+ }
+ }
+ }
+ catch (IncorrectOperationException ignored) {
+ PyUtil.showBalloon(project, PyBundle.message("QFIX.action.failed"), MessageType.WARNING);
+ }
+ }
+
+ public boolean startInWriteAction() {
+ return true;
+ }
+}
diff --git a/python/src/com/jetbrains/python/codeInsight/intentions/NameUsage.java b/python/src/com/jetbrains/python/codeInsight/intentions/NameUsage.java
new file mode 100644
index 000000000000..5c2e500c6f4f
--- /dev/null
+++ b/python/src/com/jetbrains/python/codeInsight/intentions/NameUsage.java
@@ -0,0 +1,127 @@
+package com.jetbrains.python.codeInsight.intentions;
+
+import com.intellij.ide.util.EditSourceUtil;
+import com.intellij.openapi.editor.Document;
+import com.intellij.openapi.editor.markup.TextAttributes;
+import com.intellij.openapi.fileEditor.FileEditorLocation;
+import com.intellij.pom.Navigatable;
+import com.intellij.psi.PsiElement;
+import com.intellij.psi.PsiFile;
+import com.intellij.usages.TextChunk;
+import com.intellij.usages.UsagePresentation;
+import com.intellij.usages.rules.PsiElementUsage;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import javax.swing.*;
+import java.awt.*;
+
+/**
+ * Simplistic usage object for demonstration of name clashes, etc.
+ * User: dcheryasov
+ * Date: Oct 11, 2009 6:24:05 AM
+ */
+class NameUsage implements PsiElementUsage {
+
+ private final PsiElement myElement;
+ private final PsiElement myCulprit;
+
+ static final TextAttributes SLANTED;
+ private final String myName;
+ private final boolean myIsPrefix;
+
+ static {
+ SLANTED = TextAttributes.ERASE_MARKER.clone();
+ SLANTED.setFontType(Font.ITALIC);
+ }
+
+ /**
+ * Creates a conflict search panel usage.
+ * @param element where conflict happens.
+ * @param culprit where name is redefined; usages with the same culprit are grouped.
+ * @param name redefinition of it is what the conflict is about.
+ * @param prefix if true, show name as a prefix to element's name in "would be" part.
+ */
+ public NameUsage(PsiElement element, PsiElement culprit, String name, boolean prefix) {
+ myElement = element;
+ myCulprit = culprit;
+ myName = name;
+ myIsPrefix = prefix;
+ }
+
+ public FileEditorLocation getLocation() {
+ return null;
+ }
+
+ @NotNull
+ public UsagePresentation getPresentation() {
+ return new UsagePresentation() {
+ @Nullable
+ public Icon getIcon() {
+ return myElement.getIcon(0);
+ }
+
+ @NotNull
+ public TextChunk[] getText() {
+ if (myElement.isValid()) {
+ TextChunk[] chunks = new TextChunk[3];
+ PsiFile file = myElement.getContainingFile();
+ String line_id = "...";
+ final Document document = file.getViewProvider().getDocument();
+ if (document != null) {
+ line_id = String.valueOf(document.getLineNumber(myElement.getTextOffset()));
+ }
+ chunks[0] = new TextChunk(SLANTED, "(" + line_id + ") ");
+ chunks[1] = new TextChunk(TextAttributes.ERASE_MARKER, myElement.getText());
+ StringBuilder sb = new StringBuilder(" would become ").append(myName);
+ if (myIsPrefix) sb.append(".").append(myElement.getText());
+ chunks[2] = new TextChunk(SLANTED, sb.toString());
+ return chunks;
+ }
+ else return new TextChunk[]{new TextChunk(SLANTED, "?")};
+ }
+
+ @NotNull
+ public String getPlainText() {
+ return myElement.getText();
+ }
+
+ public String getTooltipText() {
+ return myElement.getText();
+ }
+ };
+ }
+
+ public boolean isValid() {
+ return true;
+ }
+
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ public void selectInEditor() { }
+
+ public void highlightInEditor() { }
+
+ public void navigate(boolean requestFocus) {
+ Navigatable descr = EditSourceUtil.getDescriptor(myElement);
+ if (descr != null) descr.navigate(requestFocus);
+ }
+
+ public boolean canNavigate() {
+ return EditSourceUtil.canNavigate(myElement);
+ }
+
+ public boolean canNavigateToSource() {
+ return false;
+ }
+
+ public PsiElement getElement() {
+ return myCulprit;
+ }
+
+ public boolean isNonCodeUsage() {
+ return false;
+ }
+}
diff --git a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java
index 4f39a9b81f8a..e391f31c07b1 100644
--- a/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java
+++ b/python/src/com/jetbrains/python/psi/resolve/PyResolveUtil.java
@@ -19,8 +19,8 @@ package com.jetbrains.python.psi.resolve;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
-import com.intellij.psi.ResolveState;
import com.intellij.psi.PsiNamedElement;
+import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.intellij.psi.util.PsiTreeUtil;
import com.jetbrains.python.psi.*;