Added "Toggle alias", conflict panel, descriptions; refactored intentions.

This commit is contained in:
Dmitry Cheryasov
2009-10-13 07:22:24 +03:00
parent 75f56b5601
commit c6f61bb2d0
11 changed files with 558 additions and 139 deletions
@@ -76,6 +76,12 @@
<descriptionDirectoryName>ImportFromToImportIntention</descriptionDirectoryName>
</intentionAction>
<intentionAction>
<className>com.jetbrains.python.codeInsight.intentions.ImportToggleAliasIntention</className>
<category>Python</category>
<descriptionDirectoryName>ImportToggleAliasIntention</descriptionDirectoryName>
</intentionAction>
<fileStructureGroupRuleProvider implementation="com.jetbrains.python.codeInsight.intentions.PyRefGroupRuleProvider"/>
<stubElementTypeHolder class="com.jetbrains.python.PyElementTypes"/>
@@ -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 ...''
+66 -1
View File
@@ -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<String> _Keywords = new HashSet<String>();
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<String> 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();
}
}
@@ -0,0 +1,43 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.codeInsight.intentions.AskNameDialog">
<grid id="cbd77" binding="contentPane" layout-manager="GridLayoutManager" row-count="1" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="10" left="10" bottom="10" right="10"/>
<constraints>
<xy x="48" y="254" width="436" height="64"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<grid id="e3588" layout-manager="GridLayoutManager" row-count="1" 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>
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties/>
<border type="none"/>
<children>
<component id="409e3" class="javax.swing.JLabel">
<constraints>
<grid row="0" 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>
<labelFor value="8af36"/>
<text value="&amp;Alias:"/>
</properties>
</component>
<component id="8af36" class="javax.swing.JTextField" binding="myAliasTextField" default-binding="true">
<constraints>
<grid row="0" 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>
</children>
</grid>
</children>
</grid>
</form>
@@ -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();
}
}
@@ -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<Pair<PsiElement, PsiElement>> findDefinitions(
String name, Collection<? extends PsiReference> references, @Nullable Collection<? extends PsiElement> ignored
) {
List<Pair<PsiElement, PsiElement>> conflicts = new ArrayList<Pair<PsiElement, PsiElement>>();
REF_LOOP:
for (PsiReference ref : references) {
ResolveProcessor processor = new ResolveProcessor(name);
PyResolveUtil.treeCrawlUp(processor, ref.getElement());
PsiElement result = processor.getResult();
if (result != null) {
List<NameDefiner> 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<PsiElement, PsiElement>(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<Pair<PsiElement, PsiElement>> conflicts, String obscured, @Nullable String name) {
if (conflicts.size() > 0) {
Usage[] usages = new Usage[conflicts.size()];
int i = 0;
for (Pair<PsiElement, PsiElement> 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;
}
}
@@ -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<Pair<PsiElement, PsiElement>> conflicts = new ArrayList<Pair<PsiElement, PsiElement>>();
for (PsiReference ref : references.keySet()) {
ResolveProcessor processor = new ResolveProcessor(top_name);
PyResolveUtil.treeCrawlUp(processor, ref.getElement());
PsiElement result = processor.getResult();
if (result != null) {
List<NameDefiner> 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<PsiElement, PsiElement>(ref.getElement(), result));
}
}
if (conflicts.size() > 0) {
Usage[] usages = new Usage[conflicts.size()];
int i = 0;
for (Pair<PsiElement, PsiElement> 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;
}
}
@@ -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);
}
@@ -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<PsiReference> references = new ArrayList<PsiReference>();
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;
}
}
@@ -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;
}
}
@@ -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.*;