mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Added "Toggle alias", conflict panel, descriptions; refactored intentions.
This commit is contained in:
@@ -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 ...''
|
||||
|
||||
|
||||
@@ -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="&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();
|
||||
}
|
||||
}
|
||||
+88
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
+13
-134
@@ -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;
|
||||
}
|
||||
}
|
||||
+2
-2
@@ -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);
|
||||
}
|
||||
|
||||
+169
@@ -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.*;
|
||||
|
||||
Reference in New Issue
Block a user