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 references, @Nullable Collection 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.*;