summaryrefslogtreecommitdiff
path: root/python/src/com/jetbrains/python/codeInsight/imports
diff options
context:
space:
mode:
Diffstat (limited to 'python/src/com/jetbrains/python/codeInsight/imports')
-rw-r--r--python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java69
-rw-r--r--python/src/com/jetbrains/python/codeInsight/imports/AutoImportQuickFix.java34
-rw-r--r--python/src/com/jetbrains/python/codeInsight/imports/ImportCandidateHolder.java75
-rw-r--r--python/src/com/jetbrains/python/codeInsight/imports/ImportFromExistingAction.java30
-rw-r--r--python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java2
5 files changed, 151 insertions, 59 deletions
diff --git a/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java b/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java
index 1c09ee24f7dd..d33e97c1e7f9 100644
--- a/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java
+++ b/python/src/com/jetbrains/python/codeInsight/imports/AddImportHelper.java
@@ -21,14 +21,14 @@ import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.roots.ProjectRootManager;
-import com.intellij.openapi.util.text.StringUtil;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.*;
+import com.intellij.psi.util.PsiTreeUtil;
+import com.intellij.psi.util.QualifiedName;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
import com.jetbrains.python.documentation.DocStringUtil;
import com.jetbrains.python.psi.*;
-import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
import com.jetbrains.python.sdk.PythonSdkType;
import org.jetbrains.annotations.NotNull;
@@ -36,6 +36,8 @@ import org.jetbrains.annotations.Nullable;
import java.util.List;
+import static com.jetbrains.python.psi.PyUtil.sure;
+
/**
* Does the actual job of adding an import statement into a file.
* User: dcheryasov
@@ -47,6 +49,34 @@ public class AddImportHelper {
private AddImportHelper() {
}
+ public static void addLocalImportStatement(@NotNull PyElement element, @NotNull String name) {
+ final PyElementGenerator generator = PyElementGenerator.getInstance(element.getProject());
+ final LanguageLevel languageLevel = LanguageLevel.forElement(element);
+
+ final PsiElement anchor = getLocalInsertPosition(element);
+ final PsiElement parentElement = sure(anchor).getParent();
+ if (parentElement != null) {
+ parentElement.addBefore(generator.createImportStatement(languageLevel, name, null), anchor);
+ }
+ }
+
+ public static void addLocalFromImportStatement(@NotNull PyElement element, @NotNull String qualifier, @NotNull String name) {
+ final PyElementGenerator generator = PyElementGenerator.getInstance(element.getProject());
+ final LanguageLevel languageLevel = LanguageLevel.forElement(element);
+
+ final PsiElement anchor = getLocalInsertPosition(element);
+ final PsiElement parentElement = sure(anchor).getParent();
+ if (parentElement != null) {
+ parentElement.addBefore(generator.createFromImportStatement(languageLevel, qualifier, name, null), anchor);
+ }
+
+ }
+
+ @Nullable
+ public static PsiElement getLocalInsertPosition(@NotNull PyElement anchor) {
+ return PsiTreeUtil.getParentOfType(anchor, PyStatement.class, false);
+ }
+
public enum ImportPriority {
BUILTIN, THIRD_PARTY, PROJECT
}
@@ -81,7 +111,8 @@ public class AddImportHelper {
// maybe we arrived at the doc comment stmt; skip over it, too
else if (!skippedOverImports && !skippedOverDoc && file instanceof PyFile) {
PsiElement doc_elt =
- DocStringUtil.findDocStringExpression((PyElement)file); // this gives the literal; its parent is the expr seeker may have encountered
+ DocStringUtil
+ .findDocStringExpression((PyElement)file); // this gives the literal; its parent is the expr seeker may have encountered
if (doc_elt != null && doc_elt.getParent() == feeler) {
feeler = feeler.getNextSibling();
seeker = feeler; // skip over doc even if there's nothing below it
@@ -147,19 +178,13 @@ public class AddImportHelper {
* @param file where to operate
* @param name which to import (qualified is OK)
* @param asName optional name for 'as' clause
+ * @return whether import statement was actually added
*/
public static boolean addImportStatement(PsiFile file, String name, @Nullable String asName, ImportPriority priority) {
- String as_clause;
- if (asName == null) {
- as_clause = "";
- }
- else {
- as_clause = " as " + asName;
- }
if (!(file instanceof PyFile)) {
return false;
}
- List<PyImportElement> existingImports = ((PyFile)file).getImportTargets();
+ final List<PyImportElement> existingImports = ((PyFile)file).getImportTargets();
for (PyImportElement element : existingImports) {
final QualifiedName qName = element.getImportedQName();
if (qName != null && name.equals(qName.toString())) {
@@ -171,7 +196,7 @@ public class AddImportHelper {
final PyElementGenerator generator = PyElementGenerator.getInstance(file.getProject());
final LanguageLevel languageLevel = LanguageLevel.forElement(file);
- final PyImportStatement importNodeToInsert = generator.createImportStatementFromText(languageLevel, "import " + name + as_clause);
+ final PyImportStatement importNodeToInsert = generator.createImportStatement(languageLevel, name, asName);
try {
file.addBefore(importNodeToInsert, getInsertPosition(file, name, priority));
}
@@ -180,6 +205,7 @@ public class AddImportHelper {
}
return true;
}
+
/**
* Adds an "import ... from ..." statement below other top-level imports.
*
@@ -189,20 +215,20 @@ public class AddImportHelper {
* @param asName optional name for 'as' clause
*/
public static void addImportFromStatement(PsiFile file, String from, String name, @Nullable String asName, ImportPriority priority) {
- String asClause = asName == null ? "" : " as " + asName;
-
- final PyFromImportStatement importNodeToInsert = PyElementGenerator.getInstance(file.getProject()).createFromText(
- LanguageLevel.forElement(file), PyFromImportStatement.class, "from " + from + " import " + name + asClause);
+ final PyElementGenerator generator = PyElementGenerator.getInstance(file.getProject());
+ final LanguageLevel languageLevel = LanguageLevel.forElement(file);
+ final PyFromImportStatement nodeToInsert = generator.createFromImportStatement(languageLevel, from, name, asName);
try {
if (InjectedLanguageManager.getInstance(file.getProject()).isInjectedFragment(file)) {
- final PsiElement element = file.addBefore(importNodeToInsert, getInsertPosition(file, from, priority));
+ final PsiElement element = file.addBefore(nodeToInsert, getInsertPosition(file, from, priority));
PsiElement whitespace = element.getNextSibling();
- if (!(whitespace instanceof PsiWhiteSpace))
+ if (!(whitespace instanceof PsiWhiteSpace)) {
whitespace = PsiParserFacade.SERVICE.getInstance(file.getProject()).createWhiteSpaceFromText(" >>> ");
+ }
file.addBefore(whitespace, element);
}
else {
- file.addBefore(importNodeToInsert, getInsertPosition(file, from, priority));
+ file.addBefore(nodeToInsert, getInsertPosition(file, from, priority));
}
}
catch (IncorrectOperationException e) {
@@ -228,7 +254,7 @@ public class AddImportHelper {
}
}
final PyElementGenerator generator = PyElementGenerator.getInstance(file.getProject());
- PyImportElement importElement = generator.createImportElement(LanguageLevel.forElement(file), name);
+ final PyImportElement importElement = generator.createImportElement(LanguageLevel.forElement(file), name);
existingImport.add(importElement);
return true;
}
@@ -239,7 +265,8 @@ public class AddImportHelper {
public static void addImport(final PsiNamedElement target, final PsiFile file, final PyElement element) {
final boolean useQualified = !PyCodeInsightSettings.getInstance().PREFER_FROM_IMPORT;
- final PsiFileSystemItem toImport = target instanceof PsiFileSystemItem ? ((PsiFileSystemItem)target).getParent() : target.getContainingFile();
+ final PsiFileSystemItem toImport =
+ target instanceof PsiFileSystemItem ? ((PsiFileSystemItem)target).getParent() : target.getContainingFile();
final ImportPriority priority = getImportPriority(file, toImport);
final QualifiedName qName = QualifiedNameFinder.findCanonicalImportPath(target, element);
if (qName == null) return;
diff --git a/python/src/com/jetbrains/python/codeInsight/imports/AutoImportQuickFix.java b/python/src/com/jetbrains/python/codeInsight/imports/AutoImportQuickFix.java
index 47c9ca9f8ee5..ce3c07932eb4 100644
--- a/python/src/com/jetbrains/python/codeInsight/imports/AutoImportQuickFix.java
+++ b/python/src/com/jetbrains/python/codeInsight/imports/AutoImportQuickFix.java
@@ -29,6 +29,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileSystemItem;
import com.intellij.psi.PsiReference;
+import com.intellij.psi.util.QualifiedName;
import com.intellij.util.IncorrectOperationException;
import com.jetbrains.python.PyBundle;
import com.jetbrains.python.codeInsight.PyCodeInsightSettings;
@@ -36,7 +37,6 @@ import com.jetbrains.python.psi.PyElement;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyImportElement;
import com.jetbrains.python.psi.PyQualifiedExpression;
-import com.intellij.psi.util.QualifiedName;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -138,7 +138,7 @@ public class AutoImportQuickFix implements LocalQuickFix, HighPriorityAction {
myImports.size() > 1,
ImportCandidateHolder.getQualifiedName(name, myImports.get(0).getPath(), myImports.get(0).getImportElement())
);
- final ImportFromExistingAction action = new ImportFromExistingAction(myNode, myImports, name, myUseQualifiedImport);
+ final ImportFromExistingAction action = new ImportFromExistingAction(myNode, myImports, name, myUseQualifiedImport, false);
action.onDone(new Runnable() {
public void run() {
myExpended = true;
@@ -166,11 +166,16 @@ public class AutoImportQuickFix implements LocalQuickFix, HighPriorityAction {
if (!FileModificationService.getInstance().prepareFileForWrite(file)) return;
if (ImportFromExistingAction.isResolved(myReference)) return;
// act
- ImportFromExistingAction action = new ImportFromExistingAction(myNode, myImports, getNameToImport(), myUseQualifiedImport);
+ ImportFromExistingAction action = createAction();
action.execute(); // assume that action runs in WriteAction on its own behalf
myExpended = true;
}
+ @NotNull
+ protected ImportFromExistingAction createAction() {
+ return new ImportFromExistingAction(myNode, myImports, getNameToImport(), myUseQualifiedImport, false);
+ }
+
public void sortCandidates() {
Collections.sort(myImports);
}
@@ -203,4 +208,27 @@ public class AutoImportQuickFix implements LocalQuickFix, HighPriorityAction {
}
return false;
}
+
+ @NotNull
+ public AutoImportQuickFix forLocalImport() {
+ return new AutoImportQuickFix(myNode, myReference, myUseQualifiedImport) {
+ @NotNull
+ @Override
+ public String getName() {
+ return super.getName() + " locally";
+ }
+
+ @NotNull
+ @Override
+ public String getFamilyName() {
+ return "import locally";
+ }
+
+ @NotNull
+ @Override
+ protected ImportFromExistingAction createAction() {
+ return new ImportFromExistingAction(myNode, myImports, getNameToImport(), myUseQualifiedImport, true);
+ }
+ };
+ }
}
diff --git a/python/src/com/jetbrains/python/codeInsight/imports/ImportCandidateHolder.java b/python/src/com/jetbrains/python/codeInsight/imports/ImportCandidateHolder.java
index b60348d34938..e75df5b9a0d0 100644
--- a/python/src/com/jetbrains/python/codeInsight/imports/ImportCandidateHolder.java
+++ b/python/src/com/jetbrains/python/codeInsight/imports/ImportCandidateHolder.java
@@ -24,18 +24,25 @@ import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiFileSystemItem;
-import com.jetbrains.python.psi.*;
import com.intellij.psi.util.QualifiedName;
+import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* An immutable holder of information for one auto-import candidate.
- * User: dcheryasov
- * Date: Apr 23, 2009 4:17:50 PM
+ * <p/>
+ * There can be do different flavors of such candidates:
+ * <ul>
+ * <li>Candidates based on existing imports in module. In this case {@link #getImportElement()} must return not {@code null}.</li>
+ * <li>Candidates not yet imported. In this case {@link #getPath()} must return not {@code null}.</li>
+ * </ul>
+ * <p/>
+ *
+ * @author dcheryasov
*/
// visibility is intentionally package-level
-class ImportCandidateHolder implements Comparable {
+class ImportCandidateHolder implements Comparable<ImportCandidateHolder> {
private final PsiElement myImportable;
private final PyImportElement myImportElement;
private final PsiFileSystemItem myFile;
@@ -43,15 +50,19 @@ class ImportCandidateHolder implements Comparable {
/**
* Creates new instance.
- * @param importable an element that could be imported either from import element or from file.
- * @param file the file which is the source of the importable
+ *
+ * @param importable an element that could be imported either from import element or from file.
+ * @param file the file which is the source of the importable (module for symbols, containing directory for modules and packages)
* @param importElement an existing import element that can be a source for the importable.
- * @param path import path for the file, as a qualified name (a.b.c)
+ * @param path import path for the file, as a qualified name (a.b.c)
+ * For top-level imported symbols it's <em>qualified name of containing module</em> (or package for __init__.py).
+ * For modules and packages it should be <em>qualified name of their parental package</em>
+ * (empty for modules and packages located at source roots).
+ *
+ * @see com.jetbrains.python.codeInsight.imports.PythonReferenceImporter#proposeImportFix
*/
- public ImportCandidateHolder(
- @NotNull PsiElement importable, @NotNull PsiFileSystemItem file,
- @Nullable PyImportElement importElement, @Nullable QualifiedName path
- ) {
+ public ImportCandidateHolder(@NotNull PsiElement importable, @NotNull PsiFileSystemItem file,
+ @Nullable PyImportElement importElement, @Nullable QualifiedName path) {
myFile = file;
myImportable = importable;
myImportElement = importElement;
@@ -59,18 +70,22 @@ class ImportCandidateHolder implements Comparable {
assert importElement != null || path != null; // one of these must be present
}
+ @NotNull
public PsiElement getImportable() {
return myImportable;
}
+ @Nullable
public PyImportElement getImportElement() {
return myImportElement;
}
+ @NotNull
public PsiFileSystemItem getFile() {
return myFile;
}
+ @Nullable
public QualifiedName getPath() {
return myPath;
}
@@ -78,15 +93,17 @@ class ImportCandidateHolder implements Comparable {
/**
* Helper method that builds an import path, handling all these "import foo", "import foo as bar", "from bar import foo", etc.
* Either importPath or importSource must be not null.
- * @param name what is ultimately imported.
+ *
+ * @param name what is ultimately imported.
* @param importPath known path to import the name.
- * @param source known ImportElement to import the name; its 'as' clause is used if present.
+ * @param source known ImportElement to import the name; its 'as' clause is used if present.
* @return a properly qualified name.
*/
- public static String getQualifiedName(String name, QualifiedName importPath, PyImportElement source) {
- StringBuilder sb = new StringBuilder();
+ @NotNull
+ public static String getQualifiedName(@NotNull String name, @Nullable QualifiedName importPath, @Nullable PyImportElement source) {
+ final StringBuilder sb = new StringBuilder();
if (source != null) {
- PsiElement parent = source.getParent();
+ final PsiElement parent = source.getParent();
if (parent instanceof PyFromImportStatement) {
sb.append(name);
}
@@ -95,7 +112,7 @@ class ImportCandidateHolder implements Comparable {
}
}
else {
- if (importPath.getComponentCount() > 0) {
+ if (importPath != null && importPath.getComponentCount() > 0) {
sb.append(importPath).append(".");
}
sb.append(name);
@@ -103,8 +120,9 @@ class ImportCandidateHolder implements Comparable {
return sb.toString();
}
- public String getPresentableText(String myName) {
- StringBuilder sb = new StringBuilder(getQualifiedName(myName, myPath, myImportElement));
+ @NotNull
+ public String getPresentableText(@NotNull String myName) {
+ final StringBuilder sb = new StringBuilder(getQualifiedName(myName, myPath, myImportElement));
PsiElement parent = null;
if (myImportElement != null) {
parent = myImportElement.getParent();
@@ -113,13 +131,15 @@ class ImportCandidateHolder implements Comparable {
sb.append(((PyFunction)myImportable).getParameterList().getPresentableText(false));
}
else if (myImportable instanceof PyClass) {
- PyClass[] supers = ((PyClass)myImportable).getSuperClasses();
+ final PyClass[] supers = ((PyClass)myImportable).getSuperClasses();
if (supers.length > 0) {
sb.append("(");
// ", ".join(x.getName() for x in getSuperClasses())
- String[] super_names = new String[supers.length];
- for (int i=0; i < supers.length; i += 1) super_names[i] = supers[i].getName();
- sb.append(StringUtil.join(super_names, ", "));
+ final String[] superNames = new String[supers.length];
+ for (int i = 0; i < supers.length; i += 1) {
+ superNames[i] = supers[i].getName();
+ }
+ sb.append(StringUtil.join(superNames, ", "));
sb.append(")");
}
}
@@ -135,10 +155,9 @@ class ImportCandidateHolder implements Comparable {
return sb.toString();
}
- public int compareTo(Object o) {
- ImportCandidateHolder rhs = (ImportCandidateHolder) o;
- int lRelevance = getRelevance();
- int rRelevance = rhs.getRelevance();
+ public int compareTo(@NotNull ImportCandidateHolder rhs) {
+ final int lRelevance = getRelevance();
+ final int rRelevance = rhs.getRelevance();
if (rRelevance != lRelevance) {
return rRelevance - lRelevance;
}
@@ -150,7 +169,7 @@ class ImportCandidateHolder implements Comparable {
}
int getRelevance() {
- Project project = myImportable.getProject();
+ final Project project = myImportable.getProject();
final PsiFile psiFile = myImportable.getContainingFile();
final VirtualFile vFile = psiFile == null ? null : psiFile.getVirtualFile();
if (vFile == null) return 0;
diff --git a/python/src/com/jetbrains/python/codeInsight/imports/ImportFromExistingAction.java b/python/src/com/jetbrains/python/codeInsight/imports/ImportFromExistingAction.java
index 58254d999a90..e7a8151bf52d 100644
--- a/python/src/com/jetbrains/python/codeInsight/imports/ImportFromExistingAction.java
+++ b/python/src/com/jetbrains/python/codeInsight/imports/ImportFromExistingAction.java
@@ -53,6 +53,7 @@ public class ImportFromExistingAction implements QuestionAction {
String myName;
boolean myUseQualifiedImport;
private Runnable myOnDoneCallback;
+ private final boolean myImportLocally;
/**
* @param target element to become qualified as imported.
@@ -60,12 +61,13 @@ public class ImportFromExistingAction implements QuestionAction {
* @param name relevant name ot the target element (e.g. of identifier in an expression).
* @param useQualified if True, use qualified "import modulename" instead of "from modulename import ...".
*/
- public ImportFromExistingAction(@NotNull PyElement target, @NotNull List<ImportCandidateHolder> sources, String name,
- boolean useQualified) {
+ public ImportFromExistingAction(@NotNull PyElement target, @NotNull List<ImportCandidateHolder> sources, @NotNull String name,
+ boolean useQualified, boolean importLocally) {
myTarget = target;
mySources = sources;
myName = name;
myUseQualifiedImport = useQualified;
+ myImportLocally = importLocally;
}
public void onDone(Runnable callback) {
@@ -151,25 +153,41 @@ public class ImportFromExistingAction implements QuestionAction {
if (manager.isInjectedFragment(file)) {
file = manager.getTopLevelFile(myTarget);
}
+ // We are trying to import top-level module or package which thus cannot be qualified
if (isRoot(item.getFile())) {
- AddImportHelper.addImportStatement(file, myName, null, priority);
+ if (myImportLocally) {
+ AddImportHelper.addLocalImportStatement(myTarget, myName);
+ } else {
+ AddImportHelper.addImportStatement(file, myName, null, priority);
+ }
}
else {
- String qualifiedName = item.getPath().toString();
+ final String qualifiedName = item.getPath().toString();
if (myUseQualifiedImport) {
String nameToImport = qualifiedName;
if (item.getImportable() instanceof PsiFileSystemItem) {
nameToImport += "." + myName;
}
- AddImportHelper.addImportStatement(file, nameToImport, null, priority);
+ if (myImportLocally) {
+ AddImportHelper.addLocalImportStatement(myTarget, nameToImport);
+ }
+ else {
+ AddImportHelper.addImportStatement(file, nameToImport, null, priority);
+ }
myTarget.replace(gen.createExpressionFromText(LanguageLevel.forElement(myTarget), qualifiedName + "." + myName));
}
else {
- AddImportHelper.addImportFrom(file, myTarget, qualifiedName, myName, null, priority);
+ if (myImportLocally) {
+ AddImportHelper.addLocalFromImportStatement(myTarget, qualifiedName, myName);
+ }
+ else {
+ AddImportHelper.addImportFromStatement(file, qualifiedName, myName, null, priority);
+ }
}
}
}
+
private void addToExistingImport(PyImportElement src) {
final PyElementGenerator gen = PyElementGenerator.getInstance(myTarget.getProject());
// did user choose 'import' or 'from import'?
diff --git a/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java b/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java
index 5d24e3b89332..d5fd5a64495e 100644
--- a/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java
+++ b/python/src/com/jetbrains/python/codeInsight/imports/PyImportOptimizer.java
@@ -92,7 +92,7 @@ public class PyImportOptimizer implements ImportOptimizer {
for (PyImportElement importElement : importStatement.getImportElements()) {
myMissorted = true;
PsiElement toImport = importElement.resolve();
- final PyImportStatement splitImport = myGenerator.createImportStatementFromText(langLevel, "import " + importElement.getText());
+ final PyImportStatement splitImport = myGenerator.createImportStatement(langLevel, importElement.getText(), null);
prioritize(splitImport, toImport);
}
}