mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
A more complete fix for PY-147. Proposes several ways to import potentially importable names,
adds necessary import statements, handles name clashes.
Also, bits of semi-related refactoring here and there.
This commit is contained in:
@@ -137,4 +137,4 @@ runcfg.labels.interpreter_options=Interpreter &options:
|
||||
runcfg.labels.working_directory=&Working directory:
|
||||
runcfg.captions.script_parameters_dialog=Enter script parameters
|
||||
runcfg.captions.interpreter_options_dialog=Enter interpreter options
|
||||
ACT.qualify.with.module=Qualify with module
|
||||
ACT.qualify.with.module=Qualify with an imported module
|
||||
@@ -11,25 +11,25 @@ import com.intellij.codeInspection.HintAction;
|
||||
import com.intellij.codeInspection.LocalQuickFix;
|
||||
import com.intellij.codeInspection.ProblemDescriptor;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.ResolveState;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PythonLanguage;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.ResolveImportUtil;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.PyClassScopeProcessor;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.ResolveImportUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class AddImportAction implements HintAction, QuestionAction, LocalQuickFix {
|
||||
private final PsiReference myReference;
|
||||
private final Project myProject;
|
||||
private static final Logger LOG = Logger.getInstance("#" + AddImportAction.class.getName());
|
||||
|
||||
public AddImportAction(final PsiReference reference) {
|
||||
myReference = reference;
|
||||
@@ -203,42 +203,14 @@ public class AddImportAction implements HintAction, QuestionAction, LocalQuickFi
|
||||
private void execute(final PsiFile file) {
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
public void run() {
|
||||
final String referenceName = getRefName();
|
||||
if (ResolveImportUtil.resolveInRoots(file, referenceName) != null) {
|
||||
// TODO: annotate the case of multiple files
|
||||
final PyImportStatement importNodeToInsert = PythonLanguage.getInstance().getElementGenerator().createImportStatementFromText(
|
||||
myProject, "import " + referenceName + "\n\n"
|
||||
);
|
||||
try {
|
||||
file.addBefore(importNodeToInsert, getInsertPosition(file));
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
String name = getRefName();
|
||||
if (ResolveImportUtil.resolveInRoots(file, name) != null) { // TODO: think about multiple possible resole results
|
||||
AddImportHelper.addImportStatement(file, name, null, file.getProject());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static PsiElement getInsertPosition(final PsiFile file) {
|
||||
PsiElement feeler = file.getFirstChild();
|
||||
LOG.assertTrue(feeler != null);
|
||||
// skip initial comments and whitespace and try to get just below the last import stmt
|
||||
PsiElement seeker = feeler;
|
||||
do {
|
||||
if (PyUtil.instanceOf(feeler, PyImportStatement.class, PyFromImportStatement.class)) {
|
||||
seeker = feeler;
|
||||
feeler = feeler.getNextSibling();
|
||||
}
|
||||
else if (PyUtil.instanceOf(feeler, PsiWhiteSpace.class, PsiComment.class)) {
|
||||
seeker = feeler;
|
||||
feeler = feeler.getNextSibling();
|
||||
}
|
||||
else break; // some other statement, stop
|
||||
} while (feeler != null);
|
||||
return seeker;
|
||||
}
|
||||
|
||||
public boolean startInWriteAction() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
package com.jetbrains.python.actions;
|
||||
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.PsiComment;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiWhiteSpace;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.jetbrains.python.PythonLanguage;
|
||||
import com.jetbrains.python.psi.PyFromImportStatement;
|
||||
import com.jetbrains.python.psi.PyImportStatement;
|
||||
import com.jetbrains.python.psi.PyUtil;
|
||||
|
||||
/**
|
||||
* Does the actual job of adding an import statement into a file.
|
||||
* User: dcheryasov
|
||||
* Date: Apr 24, 2009 3:17:59 AM
|
||||
*/
|
||||
// intentional package level access
|
||||
class AddImportHelper {
|
||||
private static final Logger LOG = Logger.getInstance("#" + AddImportHelper.class.getName());
|
||||
|
||||
private static PsiElement getInsertPosition(final PsiFile file) {
|
||||
PsiElement feeler = file.getFirstChild();
|
||||
LOG.assertTrue(feeler != null);
|
||||
// skip initial comments and whitespace and try to get just below the last import stmt
|
||||
PsiElement seeker = feeler;
|
||||
do {
|
||||
if (PyUtil.instanceOf(feeler, PyImportStatement.class, PyFromImportStatement.class)) {
|
||||
seeker = feeler;
|
||||
feeler = feeler.getNextSibling();
|
||||
}
|
||||
else if (PyUtil.instanceOf(feeler, PsiWhiteSpace.class, PsiComment.class)) {
|
||||
seeker = feeler;
|
||||
feeler = feeler.getNextSibling();
|
||||
}
|
||||
else break; // some other statement, stop
|
||||
} while (feeler != null);
|
||||
return seeker;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an import statement, presumably below all other initial imports in the file.
|
||||
* @param file where to operate
|
||||
* @param name which to import (qualified is OK)
|
||||
* @param asName optional na,e for 'as' clause
|
||||
* @param project to which the file presumably belongs
|
||||
*/
|
||||
public static void addImportStatement(PsiFile file, String name, String asName, Project project) {
|
||||
String as_clause;
|
||||
if (asName == null) as_clause = "";
|
||||
else as_clause = " as " + asName;
|
||||
final PyImportStatement importNodeToInsert = PythonLanguage.getInstance().getElementGenerator().createImportStatementFromText(
|
||||
project, "import " + name + as_clause + "\n\n"
|
||||
);
|
||||
try {
|
||||
file.addBefore(importNodeToInsert, getInsertPosition(file));
|
||||
}
|
||||
catch (IncorrectOperationException e) {
|
||||
LOG.error(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package com.jetbrains.python.actions;
|
||||
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
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
|
||||
*/
|
||||
// visibility is intentionally package-level
|
||||
class ImportCandidateHolder {
|
||||
private final PsiElement myImportable;
|
||||
private final PyImportElement myImportElement;
|
||||
private final PsiFile myFile;
|
||||
private final String myPath;
|
||||
private final String myAsName;
|
||||
|
||||
/**
|
||||
* 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 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 asName name to use in a new import statement for 'as' clause, if an import is added.
|
||||
*/
|
||||
public ImportCandidateHolder(
|
||||
@NotNull PsiElement importable, @NotNull PsiFile file,
|
||||
@Nullable PyImportElement importElement, @Nullable String path, @Nullable String asName
|
||||
) {
|
||||
myFile = file;
|
||||
myImportable = importable;
|
||||
myImportElement = importElement;
|
||||
myPath = path;
|
||||
myAsName = asName;
|
||||
assert importElement != null || path != null; // one of these must be present
|
||||
}
|
||||
|
||||
public PsiElement getImportable() {
|
||||
return myImportable;
|
||||
}
|
||||
|
||||
public PyImportElement getImportElement() {
|
||||
return myImportElement;
|
||||
}
|
||||
|
||||
public PsiFile getFile() {
|
||||
return myFile;
|
||||
}
|
||||
|
||||
public String getPath() {
|
||||
return myPath;
|
||||
}
|
||||
|
||||
public String getAsName() {
|
||||
return myAsName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 importPath known path to import the name.
|
||||
* @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, String importPath, PyImportElement source) {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
PsiElement parent = null;
|
||||
if (source != null) {
|
||||
parent = source.getParent();
|
||||
if (parent instanceof PyFromImportStatement) {
|
||||
sb.append(name);
|
||||
}
|
||||
else {
|
||||
sb.append(source.getVisibleName()).append(".").append(name);
|
||||
}
|
||||
}
|
||||
else {
|
||||
sb.append(importPath).append(".").append(name);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
|
||||
public String getPresentableText(String myName) {
|
||||
StringBuffer sb = new StringBuffer(getQualifiedName(myName, myPath, myImportElement));
|
||||
PsiElement parent = null;
|
||||
if (myImportElement != null) {
|
||||
parent = myImportElement.getParent();
|
||||
}
|
||||
if (myImportable instanceof PyFunction) {
|
||||
sb.append("(");
|
||||
// below: ", ".join([x.getRepr(False) for x in getParameters()])
|
||||
PyParameter[] params = ((PyFunction)myImportable).getParameterList().getParameters();
|
||||
String[] param_reprs = new String[params.length];
|
||||
for (int i=0; i < params.length; i += 1) param_reprs[i] = params[i].getRepr(false);
|
||||
PyUtil.joinSubarray(param_reprs, 0, params.length, ", ", sb);
|
||||
sb.append(")");
|
||||
}
|
||||
else if (myImportable instanceof PyClass) {
|
||||
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();
|
||||
PyUtil.joinSubarray(super_names, 0, supers.length, ", ", sb);
|
||||
sb.append(")");
|
||||
}
|
||||
}
|
||||
if (parent instanceof PyFromImportStatement) {
|
||||
sb.append(" from ").append(((PyFromImportStatement)parent).getImportSource().getReferencedName()); // no NPE, we won't add a sourceless import stmt
|
||||
}
|
||||
else if (myImportElement == null) { // no import, only file
|
||||
sb.append(" # add import");
|
||||
if (myAsName != null) sb.append(" as ").append(myAsName);
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
@@ -6,9 +6,8 @@ import com.intellij.openapi.command.CommandProcessor;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.colors.EditorColorsManager;
|
||||
import com.intellij.openapi.editor.colors.EditorColorsScheme;
|
||||
import com.intellij.openapi.ui.popup.PopupChooserBuilder;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.popup.PopupChooserBuilder;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.ui.SimpleColoredComponent;
|
||||
@@ -30,15 +29,17 @@ import java.util.List;
|
||||
public class ImportFromExistingAction implements QuestionAction {
|
||||
|
||||
PyElement myTarget;
|
||||
List<Pair<PyImportElement, PsiElement>> mySources; // list of <import, imported_item>
|
||||
List<ImportCandidateHolder> mySources; // list of <import, imported_item>
|
||||
Editor myEditor;
|
||||
String myName;
|
||||
|
||||
/**
|
||||
* @param target element to become qualified as imported.
|
||||
* @param sources clauses of import to be used.
|
||||
* @param name relevant name ot the target element (e.g. of identifier in an experssion).
|
||||
* @param editor target's editor.
|
||||
*/
|
||||
public ImportFromExistingAction(@NotNull PyElement target, @NotNull List<Pair<PyImportElement, PsiElement>> sources, String name, Editor editor) {
|
||||
public ImportFromExistingAction(@NotNull PyElement target, @NotNull List<ImportCandidateHolder> sources, String name, Editor editor) {
|
||||
mySources = sources;
|
||||
myTarget = target;
|
||||
myEditor = editor;
|
||||
@@ -54,35 +55,31 @@ public class ImportFromExistingAction implements QuestionAction {
|
||||
PsiDocumentManager.getInstance(myTarget.getProject()).commitAllDocuments();
|
||||
if (!myTarget.isValid()) return false;
|
||||
if ((myTarget instanceof PyQualifiedExpression) && ((((PyQualifiedExpression)myTarget).getQualifier() != null))) return false; // we cannot be qualified
|
||||
for (Pair<PyImportElement, PsiElement> src : mySources) {
|
||||
if (!src.getFirst().isValid()) return false;
|
||||
if (!src.getSecond().isValid()) return false;
|
||||
for (ImportCandidateHolder item : mySources) {
|
||||
if (!item.getImportable().isValid()) return false;
|
||||
if (!item.getFile().isValid()) return false;
|
||||
if (item.getImportElement() != null && !item.getImportElement().isValid()) return false;
|
||||
}
|
||||
// act
|
||||
if (mySources.size() > 1) {
|
||||
selectSourceAndDo();
|
||||
}
|
||||
else doWriteAction(mySources.get(0).getFirst());
|
||||
else doWriteAction(mySources.get(0));
|
||||
return true;
|
||||
}
|
||||
|
||||
private void selectSourceAndDo() {
|
||||
// GUI part
|
||||
QualifiedHolder[] items = new QualifiedHolder[mySources.size()];
|
||||
int i = 0;
|
||||
for (Pair<PyImportElement, PsiElement> pair : mySources) {
|
||||
items[i] = new QualifiedHolder(pair.getFirst(), pair.getSecond(), myName);
|
||||
i += 1;
|
||||
}
|
||||
ImportCandidateHolder[] items = mySources.toArray(new ImportCandidateHolder[mySources.size()]); // silly JList can't handle modern collections
|
||||
final JList list = new JList(items);
|
||||
list.setCellRenderer(new CellRenderer());
|
||||
list.setCellRenderer(new CellRenderer(myName));
|
||||
|
||||
Runnable runnable = new Runnable() {
|
||||
public void run() {
|
||||
int index = list.getSelectedIndex();
|
||||
if (index < 0) return;
|
||||
PsiDocumentManager.getInstance(myTarget.getProject()).commitAllDocuments();
|
||||
doWriteAction(mySources.get(index).getFirst());
|
||||
doWriteAction(mySources.get(index));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -94,34 +91,41 @@ public class ImportFromExistingAction implements QuestionAction {
|
||||
;
|
||||
}
|
||||
|
||||
private void doIt(final PyImportElement src) {
|
||||
// did user choose 'import' or 'from import'?
|
||||
PsiElement parent = src.getParent();
|
||||
if (parent instanceof PyFromImportStatement) {
|
||||
// add another import element right after the one we got
|
||||
final PyElementGenerator gen = PythonLanguage.getInstance().getElementGenerator();
|
||||
final Project project = myTarget.getProject();
|
||||
PsiElement new_elt = gen.
|
||||
createFromText(project, PyImportElement.class, "from foo import " + myName, new int[]{0,6})
|
||||
;
|
||||
PyUtil.addListNode(parent, new_elt, null, false, true);
|
||||
private void doIt(final ImportCandidateHolder item) {
|
||||
PyImportElement src = item.getImportElement();
|
||||
final PyElementGenerator gen = PythonLanguage.getInstance().getElementGenerator();
|
||||
if (src != null) { // use existing import
|
||||
// did user choose 'import' or 'from import'?
|
||||
PsiElement parent = src.getParent();
|
||||
if (parent instanceof PyFromImportStatement) {
|
||||
// add another import element right after the one we got
|
||||
final Project project = myTarget.getProject();
|
||||
PsiElement new_elt = gen.
|
||||
createFromText(project, PyImportElement.class, "from foo import " + myName, new int[]{0, 6});
|
||||
PyUtil.addListNode(parent, new_elt, null, false, true);
|
||||
}
|
||||
else { // just 'import'
|
||||
// all we need is to qualify our target
|
||||
myTarget.replace(gen.createExpressionFromText(myTarget.getProject(), src.getVisibleName() + "." + myName));
|
||||
}
|
||||
}
|
||||
else { // just 'import'
|
||||
// all we need is to qualify our target
|
||||
myTarget.replace(
|
||||
PythonLanguage.getInstance().
|
||||
getElementGenerator().
|
||||
createExpressionFromText(myTarget.getProject(), src.getVisibleName()+ "." + myName)
|
||||
);
|
||||
else { // no existing import, add it then use it
|
||||
Project project = myTarget.getProject();
|
||||
AddImportHelper.addImportStatement(myTarget.getContainingFile(), item.getPath(), item.getAsName(), project);
|
||||
String qual_name;
|
||||
if (item.getAsName() != null) qual_name = item.getAsName();
|
||||
else qual_name = item.getPath();
|
||||
myTarget.replace(gen.createExpressionFromText(project, qual_name + "." + myName));
|
||||
}
|
||||
}
|
||||
|
||||
private void doWriteAction(final PyImportElement src) {
|
||||
private void doWriteAction(final ImportCandidateHolder item) {
|
||||
PsiElement src = item.getImportable();
|
||||
CommandProcessor.getInstance().executeCommand(src.getProject(), new Runnable() {
|
||||
public void run() {
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
public void run() {
|
||||
doIt(src);
|
||||
doIt(item);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -129,64 +133,13 @@ public class ImportFromExistingAction implements QuestionAction {
|
||||
}
|
||||
|
||||
|
||||
// items to store in list
|
||||
private static class QualifiedHolder {
|
||||
final PyImportElement mySrc;
|
||||
final PsiElement myItem;
|
||||
final String myName;
|
||||
|
||||
public QualifiedHolder(PyImportElement src, PsiElement item, String name) {
|
||||
mySrc = src;
|
||||
myItem = item;
|
||||
myName = name;
|
||||
}
|
||||
|
||||
public Icon getIcon() {
|
||||
return myItem.getIcon(0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuffer sb = new StringBuffer();
|
||||
PsiElement parent = mySrc.getParent();
|
||||
if (parent instanceof PyFromImportStatement) {
|
||||
sb.append(myName);
|
||||
}
|
||||
else {
|
||||
sb.append(mySrc.getVisibleName()).append(".").append(myName);
|
||||
}
|
||||
if (myItem instanceof PyFunction) {
|
||||
sb.append("(");
|
||||
// below: ", ".join([x.getRepr(False) for x in getParameters()])
|
||||
PyParameter[] params = ((PyFunction)myItem).getParameterList().getParameters();
|
||||
String[] param_reprs = new String[params.length];
|
||||
for (int i=0; i < params.length; i += 1) param_reprs[i] = params[i].getRepr(false);
|
||||
PyUtil.joinSubarray(param_reprs, 0, params.length, ", ", sb);
|
||||
sb.append(")");
|
||||
}
|
||||
else if (myItem instanceof PyClass) {
|
||||
PyClass[] supers = ((PyClass)myItem).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();
|
||||
PyUtil.joinSubarray(super_names, 0, supers.length, ", ", sb);
|
||||
sb.append(")");
|
||||
}
|
||||
}
|
||||
if (parent instanceof PyFromImportStatement) {
|
||||
sb.append(" from ").append(((PyFromImportStatement)parent).getImportSource().getReferencedName());
|
||||
}
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
|
||||
// Stolen from FQNameCellRenderer
|
||||
private static class CellRenderer extends SimpleColoredComponent implements ListCellRenderer {
|
||||
private final Font FONT;
|
||||
private final String myName;
|
||||
|
||||
public CellRenderer() {
|
||||
public CellRenderer(String name) {
|
||||
myName = name;
|
||||
EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme();
|
||||
FONT = new Font(scheme.getEditorFontName(), Font.PLAIN, scheme.getEditorFontSize());
|
||||
setOpaque(true);
|
||||
@@ -203,9 +156,9 @@ public class ImportFromExistingAction implements QuestionAction {
|
||||
|
||||
clear();
|
||||
|
||||
QualifiedHolder item = (QualifiedHolder)value;
|
||||
setIcon(item.getIcon());
|
||||
String item_name = item.toString();
|
||||
ImportCandidateHolder item = (ImportCandidateHolder)value;
|
||||
setIcon(item.getImportable().getIcon(0));
|
||||
String item_name = item.getPresentableText(myName);
|
||||
append(item_name, SimpleTextAttributes.REGULAR_ATTRIBUTES);
|
||||
setFont(FONT);
|
||||
if (isSelected) {
|
||||
|
||||
@@ -6,21 +6,23 @@ import com.intellij.codeInsight.hint.HintManager;
|
||||
import com.intellij.codeInspection.HintAction;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.psi.PyElement;
|
||||
import com.jetbrains.python.psi.PyImportElement;
|
||||
import com.jetbrains.python.psi.PyQualifiedExpression;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Handles cases when an unresolved name may be imported from one of existing imported modules.
|
||||
* The object contains a list of modules from which given name might be imported.
|
||||
* The object contains a list of import candidates and serves only to show the initial hint;
|
||||
* the actual work is done in ImportFromExistingAction..
|
||||
* User: dcheryasov
|
||||
* Date: Apr 15, 2009 2:06:25 PM
|
||||
*/
|
||||
@@ -28,36 +30,43 @@ public class ImportFromExistingFix implements HintAction {
|
||||
|
||||
PyElement myNode;
|
||||
|
||||
List<Pair<PyImportElement, PsiElement>> myImports; // from where and what to import
|
||||
List<ImportCandidateHolder> myImports; // from where and what to import
|
||||
String myName;
|
||||
|
||||
/**
|
||||
* Creates a new, empty fix object.
|
||||
* @param node to which the fix applies.
|
||||
* @param name the unresolved identifier portion of node's text
|
||||
*/
|
||||
public ImportFromExistingFix(PyElement node, String name) {
|
||||
myNode = node;
|
||||
myImports = new ArrayList<Pair<PyImportElement, PsiElement>>();
|
||||
myImports = new ArrayList<ImportCandidateHolder>();
|
||||
myName = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a mew fix object with one import variant.
|
||||
* @param node to which the fix applies.
|
||||
* @param source from which the name is importable.
|
||||
* @param name
|
||||
* Adds another import source.
|
||||
* @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 importElement an existing import element that can be a source for the importable.
|
||||
*/
|
||||
public ImportFromExistingFix(PyElement node, PyImportElement source, PsiElement item, String name) {
|
||||
this(node, name);
|
||||
addImport(source, item);
|
||||
public void addImport(@NotNull PsiElement importable, @NotNull PsiFile file, @Nullable PyImportElement importElement) {
|
||||
myImports.add(new ImportCandidateHolder(importable, file, importElement, null, null));
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds another import source.
|
||||
* @param source an import statement from which the name is 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
|
||||
* @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 asName name to use to import the path as: "import path as asName"
|
||||
*/
|
||||
public void addImport(PyImportElement source, PsiElement item) {
|
||||
myImports.add(new Pair<PyImportElement, PsiElement>(source, item));
|
||||
public void addImport(
|
||||
@NotNull PsiElement importable, @NotNull PsiFile file,
|
||||
@Nullable PyImportElement importElement, @Nullable String path, @Nullable String asName
|
||||
) {
|
||||
myImports.add(new ImportCandidateHolder(importable, file, importElement, path, asName));
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -74,9 +83,10 @@ public class ImportFromExistingFix implements HintAction {
|
||||
if (myNode == null || !myNode.isValid() || myNode.getName() == null || myImports.size() <= 0) {
|
||||
return false; // TODO: also return false if an on-the-fly unambiguous fix is possible?
|
||||
}
|
||||
if ((myNode instanceof PyQualifiedExpression) && ((((PyQualifiedExpression)myNode).getQualifier() != null))) return false; // we cannot be qualified
|
||||
final String message = ShowAutoImportPass.getMessage(
|
||||
myImports.size() > 1,
|
||||
myImports.get(0).getFirst().getVisibleName()+"."+myNode.getName()
|
||||
ImportCandidateHolder.getQualifiedName(myName, myImports.get(0).getPath(), myImports.get(0).getImportElement())
|
||||
);
|
||||
final ImportFromExistingAction action = new ImportFromExistingAction(myNode, myImports, myName, editor);
|
||||
HintManager.getInstance().showQuestionHint(
|
||||
|
||||
@@ -2,13 +2,16 @@ package com.jetbrains.python.inspections;
|
||||
|
||||
import com.intellij.codeHighlighting.HighlightDisplayLevel;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.lang.ASTNode;
|
||||
import com.intellij.lang.annotation.HighlightSeverity;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.psi.PsiPolyVariantReference;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.stubs.StubIndex;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.jetbrains.python.PyBundle;
|
||||
import com.jetbrains.python.PyTokenTypes;
|
||||
import com.jetbrains.python.actions.AddFieldQuickFix;
|
||||
import com.jetbrains.python.actions.AddImportAction;
|
||||
import com.jetbrains.python.actions.AddMethodQuickFix;
|
||||
@@ -17,6 +20,9 @@ import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache;
|
||||
import com.jetbrains.python.psi.resolve.CollectProcessor;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.ResolveImportUtil;
|
||||
import com.jetbrains.python.psi.stubs.PyClassNameIndex;
|
||||
import com.jetbrains.python.psi.stubs.PyFunctionNameIndex;
|
||||
import com.jetbrains.python.psi.types.PyClassType;
|
||||
import com.jetbrains.python.psi.types.PyModuleType;
|
||||
import com.jetbrains.python.psi.types.PyNoneType;
|
||||
@@ -25,7 +31,7 @@ import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Marks references that fail to resolve.
|
||||
@@ -76,6 +82,8 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
static HintAction proposeImportFixes(final PyElement node, String ref_text) {
|
||||
boolean worthy_fix = false;
|
||||
ImportFromExistingFix fix = null;
|
||||
Set<String> seen_file_names = new HashSet<String>(); // true import names
|
||||
Set<String> seen_as_names = new HashSet<String>(); // 'as' parts: we don't want to clas with them either
|
||||
// maybe the name is importable via some exisitng 'import foo' statement, and only needs a qualifier.
|
||||
// walk up collecting all such statements and analyzing
|
||||
CollectProcessor import_prc = new CollectProcessor(PyImportStatement.class);
|
||||
@@ -88,10 +96,13 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
final PyReferenceExpression src = ielt.getImportReference();
|
||||
if (src != null) {
|
||||
PsiElement dst = src.resolve();
|
||||
if (dst instanceof PyFile) {
|
||||
PsiElement res = ((PyFile)dst).findExportedName(ref_text);
|
||||
if (dst instanceof PyFile) {
|
||||
PyFile dst_file = (PyFile)dst;
|
||||
seen_file_names.add(ielt.getImportReference().getReferencedName()); // ref is ok or matching would fail
|
||||
seen_as_names.add(ielt.getVisibleName());
|
||||
PsiElement res = (dst_file).findExportedName(ref_text);
|
||||
if (res != null) {
|
||||
fix.addImport(ielt, res);
|
||||
fix.addImport(res, dst_file, ielt);
|
||||
worthy_fix = true;
|
||||
}
|
||||
}
|
||||
@@ -114,9 +125,11 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
if (src != null) {
|
||||
PsiElement dst = src.resolve();
|
||||
if (dst instanceof PyFile) {
|
||||
PsiElement res = ((PyFile)dst).findExportedName(ref_text);
|
||||
PyFile dst_file = (PyFile)dst;
|
||||
seen_file_names.add(from_stmt.getImportSource().getReferencedName()); // source is ok, else it won't match and we'd not be adding it
|
||||
PsiElement res = (dst_file).findExportedName(ref_text);
|
||||
if (res != null) {
|
||||
fix.addImport(ielts[ielts.length-1], res); // last element; action expects to add to tail
|
||||
fix.addImport(res, dst_file, ielts[ielts.length-1]); // last element; action expects to add to tail
|
||||
worthy_fix = true;
|
||||
}
|
||||
}
|
||||
@@ -124,10 +137,78 @@ public class PyUnresolvedReferencesInspection extends LocalInspectionTool {
|
||||
}
|
||||
}
|
||||
}
|
||||
// maybe some unimported file has it, too
|
||||
// NOTE: current indices have limitations, only finding direct definitions of classes and functions.
|
||||
Project project = node.getProject();
|
||||
GlobalSearchScope scope = null; // GlobalSearchScope.projectScope(project);
|
||||
List<PsiElement> symbols = new ArrayList<PsiElement>();
|
||||
symbols.addAll(StubIndex.getInstance().get(PyClassNameIndex.KEY, ref_text, project, scope));
|
||||
symbols.addAll(StubIndex.getInstance().get(PyFunctionNameIndex.KEY, ref_text, project, scope));
|
||||
if (symbols.size() > 0) {
|
||||
if (fix == null) fix = new ImportFromExistingFix(node, ref_text); // it might have been created in the previous scan, or not.
|
||||
for (PsiElement symbol : symbols) {
|
||||
if (symbol.getParent() instanceof PsiFile) { // we only want top-level symbols
|
||||
PsiFile srcfile = symbol.getContainingFile();
|
||||
if (srcfile != null) {
|
||||
VirtualFile vfile = srcfile.getVirtualFile();
|
||||
if (vfile != null) {
|
||||
String import_path = ResolveImportUtil.findShortestImportableName(node, vfile);
|
||||
if (import_path != null && !seen_file_names.contains(import_path)) {
|
||||
// a new, valid hit
|
||||
String as_name = null;
|
||||
if (seen_as_names.contains(import_path)) {
|
||||
// an 'as' name somewhere above eclipses the true name. get us a unique 'as' name.
|
||||
as_name = propseAsName(node.getContainingFile(), import_path);
|
||||
seen_as_names.add(as_name); // just in case
|
||||
}
|
||||
fix.addImport(symbol, srcfile, null, import_path, as_name);
|
||||
seen_file_names.add(import_path); // just in case, again
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (worthy_fix) return fix;
|
||||
else return null;
|
||||
}
|
||||
|
||||
|
||||
private final static String[] AS_PREFIXES = {"other_", "one_more_", "different_", "pseudo_", "true_"};
|
||||
|
||||
// a no-frills recursive accumulating scan
|
||||
private static void collectIdentifiers(ASTNode node, Collection<String> dst) {
|
||||
ASTNode seeker = node.getFirstChildNode();
|
||||
while (seeker != null) {
|
||||
if (seeker.getElementType() == PyTokenTypes.IDENTIFIER) dst.add(seeker.getText());
|
||||
else collectIdentifiers(seeker, dst);
|
||||
seeker = seeker.getTreeNext();
|
||||
}
|
||||
}
|
||||
|
||||
// find an unique name that does not clash with anything in the file, using ref_test and import_path as hints
|
||||
private static String propseAsName(PsiFile file, String import_path) {
|
||||
// a somehow brute-force approach: collect all identifiers wholesale and avoid clashes with any of them
|
||||
Set<String> ident_set = new HashSet<String>();
|
||||
collectIdentifiers(file.getNode(), ident_set);
|
||||
// try flattened import path
|
||||
String path_name = import_path.replace('.', '_');
|
||||
if (! ident_set.contains(path_name)) return path_name;
|
||||
// ...with prefixes: a highly improbable situation already
|
||||
for (String prefix : AS_PREFIXES) {
|
||||
String variant = prefix + path_name;
|
||||
if (! ident_set.contains(variant)) return variant;
|
||||
}
|
||||
// if nothing helped, just bluntly add a number to the end. guaranteed to finish in ident_set.size() iterations.
|
||||
int cnt = 1;
|
||||
while (cnt < Integer.MAX_VALUE) {
|
||||
String variant = path_name + Integer.toString(cnt);
|
||||
if (! ident_set.contains(variant)) return variant;
|
||||
cnt += 1;
|
||||
}
|
||||
return "SHOOSHPANCHICK"; // no, this cannot happen in a life-size file, just keeps inspections happy
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitPyElement(final PyElement node) {
|
||||
super.visitPyElement(node); //To change body of overridden methods use File | Settings | File Templates.
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.jetbrains.python.psi.PyElementVisitor;
|
||||
import com.jetbrains.python.psi.PyFromImportStatement;
|
||||
import com.jetbrains.python.psi.PyImportElement;
|
||||
import com.jetbrains.python.psi.PyReferenceExpression;
|
||||
import com.jetbrains.python.psi.resolve.ResolveImportUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
||||
@@ -27,6 +27,7 @@ import com.jetbrains.python.PyElementTypes;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyAsScopeProcessor;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.ResolveImportUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.jetbrains.python.psi.impl;
|
||||
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.ResolveImportUtil;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.lang.ASTNode;
|
||||
|
||||
+122
-15
@@ -1,4 +1,4 @@
|
||||
package com.jetbrains.python.psi.impl;
|
||||
package com.jetbrains.python.psi.resolve;
|
||||
|
||||
import com.intellij.ide.DataManager;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
@@ -13,12 +13,10 @@ import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.containers.HashSet;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveUtil;
|
||||
import com.jetbrains.python.psi.resolve.ResolveProcessor;
|
||||
import com.jetbrains.python.psi.resolve.VariantsProcessor;
|
||||
import com.jetbrains.python.psi.impl.PyImportResolver;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
@@ -51,7 +49,7 @@ public class ResolveImportUtil {
|
||||
* @return the object importRef refers to, or null.
|
||||
*/
|
||||
@Nullable
|
||||
static PsiElement resolveImportReference(final PyReferenceExpression importRef) {
|
||||
public static PsiElement resolveImportReference(final PyReferenceExpression importRef) {
|
||||
if (importRef == null) return null; // fail fast
|
||||
final String referencedName = importRef.getReferencedName();
|
||||
if (referencedName == null) return null;
|
||||
@@ -153,6 +151,66 @@ public class ResolveImportUtil {
|
||||
return null; // not resolved by any means
|
||||
}
|
||||
|
||||
|
||||
public static void visitRoots(final PsiElement elt, @NotNull final SdkRootVisitor visitor) {
|
||||
// real search
|
||||
final Module module = ModuleUtil.findModuleForPsiElement(elt);
|
||||
if (module != null) {
|
||||
// TODO: implement a proper module-like approach in PyCharm for "project's dirs on pythonpath", minding proper search order
|
||||
// Module-based approach works only in the IDEA plugin.
|
||||
ModuleRootManager rootManager = ModuleRootManager.getInstance(module);
|
||||
// look in module sources
|
||||
boolean source_entries_missing = true;
|
||||
for (ContentEntry entry: rootManager.getContentEntries()) {
|
||||
VirtualFile root_file = entry.getFile();
|
||||
|
||||
if (!visitor.visitRoot(root_file)) return;
|
||||
for (VirtualFile folder : entry.getSourceFolderFiles()) {
|
||||
source_entries_missing = false;
|
||||
if (!visitor.visitRoot(folder)) return;
|
||||
}
|
||||
}
|
||||
if (source_entries_missing) {
|
||||
// fallback for a case without any source entries: use project root
|
||||
VirtualFile project_root = module.getProject().getBaseDir();
|
||||
if (!visitor.visitRoot(project_root)) return;
|
||||
}
|
||||
// else look in SDK roots
|
||||
RootPolicy<PsiElement> resolvePolicy = new RootPolicy<PsiElement>() {
|
||||
@Nullable
|
||||
public PsiElement visitJdkOrderEntry(final JdkOrderEntry jdkOrderEntry, final PsiElement value) {
|
||||
if (value != null) return value; // for chaining in processOrder()
|
||||
visitGivenRoots(jdkOrderEntry.getRootFiles(OrderRootType.SOURCES), visitor);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
rootManager.processOrder(resolvePolicy, null);
|
||||
}
|
||||
else {
|
||||
// no module, another way to look in SDK roots
|
||||
final PsiFile elt_psifile = elt.getContainingFile();
|
||||
if (elt_psifile != null) { // formality
|
||||
final VirtualFile elt_vfile = elt_psifile.getVirtualFile();
|
||||
if (elt_vfile != null) { // reality
|
||||
for (OrderEntry entry: ProjectRootManager.getInstance(elt.getProject()).getFileIndex().getOrderEntriesForFile(elt_vfile
|
||||
)
|
||||
) {
|
||||
if (!visitGivenRoots(entry.getFiles(OrderRootType.SOURCES), visitor)) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private static boolean visitGivenRoots(final VirtualFile[] roots, SdkRootVisitor visitor) {
|
||||
for (VirtualFile root: roots) {
|
||||
if (! visitor.visitRoot(root)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO: rewrite using visitRoots
|
||||
/**
|
||||
* Looks for a name among element's module's roots; if there's no module, then among project's roots.
|
||||
* @param elt PSI element that defines the module and/or the project.
|
||||
@@ -206,7 +264,7 @@ public class ResolveImportUtil {
|
||||
public PsiElement visitJdkOrderEntry(final JdkOrderEntry jdkOrderEntry, final PsiElement value) {
|
||||
if (value != null) return value;
|
||||
LookupRootVisitor visitor = new LookupRootVisitor(refName, elt.getManager());
|
||||
visitRoots(jdkOrderEntry.getRootFiles(OrderRootType.SOURCES), visitor);
|
||||
visitGivenRoots(jdkOrderEntry.getRootFiles(OrderRootType.SOURCES), visitor);
|
||||
return visitor.getResult();
|
||||
}
|
||||
};
|
||||
@@ -256,12 +314,6 @@ public class ResolveImportUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
public static void visitRoots(final VirtualFile[] roots, SdkRootVisitor visitor) {
|
||||
for (VirtualFile root: roots) {
|
||||
if (! visitor.visitRoot(root)) break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Tries to find referencedName under a root.
|
||||
@param root where to look for the referenced name.
|
||||
@@ -285,7 +337,6 @@ public class ResolveImportUtil {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
interface SdkRootVisitor {
|
||||
/**
|
||||
* @param root what we're visiting.
|
||||
@@ -486,7 +537,7 @@ public class ResolveImportUtil {
|
||||
@Nullable
|
||||
public PsiElement visitJdkOrderEntry(final JdkOrderEntry jdkOrderEntry, final PsiElement value) {
|
||||
if (value != null) return value;
|
||||
visitRoots(jdkOrderEntry.getRootFiles(OrderRootType.SOURCES), visitor);
|
||||
visitGivenRoots(jdkOrderEntry.getRootFiles(OrderRootType.SOURCES), visitor);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
@@ -497,4 +548,60 @@ public class ResolveImportUtil {
|
||||
return variants.toArray(new Object[variants.size()]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to find roots that contain given vfile, and among them the root that contains at the smallest depth.
|
||||
*/
|
||||
private static class PathChoosingVisitor implements SdkRootVisitor {
|
||||
|
||||
private final VirtualFile myFile;
|
||||
private String myFname;
|
||||
private String myResult = null;
|
||||
private int myDots = Integer.MAX_VALUE; // how many dots in the path
|
||||
|
||||
private PathChoosingVisitor(VirtualFile file) {
|
||||
myFile = file;
|
||||
// cut off the ext
|
||||
myFname = file.getPath();
|
||||
int pos = myFname.lastIndexOf('.');
|
||||
if (pos > 0) myFname = myFname.substring(0, pos);
|
||||
}
|
||||
|
||||
public boolean visitRoot(VirtualFile root) {
|
||||
// does it ever fit?
|
||||
String root_name = root.getPath()+"/";
|
||||
if (myFname.startsWith(root_name)) {
|
||||
String bet = myFname.substring(root_name.length()).replace('/', '.'); // "/usr/share/python/foo/bar" -> "foo.bar"
|
||||
// count the dots
|
||||
int dots = 0;
|
||||
for (int i = 0; i < bet.length(); i += 1) if (bet.charAt(i) == '.') dots += 1;
|
||||
// a better variant?
|
||||
if (dots < myDots) {
|
||||
myDots = dots;
|
||||
myResult = bet;
|
||||
}
|
||||
}
|
||||
return true; // visit all roots
|
||||
}
|
||||
|
||||
public String getResult() {
|
||||
return myResult;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Looks for a way to import given file.
|
||||
* @param foothold an element in the file to import to (maybe the file itself); used to determine module, roots, etc.
|
||||
* @param vfile file which importable name we want to find.
|
||||
* @return a possibly qualified name under which the file may be imported, or null. If there's more than one way (overlapping roots),
|
||||
* the name with fewest qualifiers is selected.
|
||||
*/
|
||||
@Nullable
|
||||
public static String findShortestImportableName(PsiElement foothold, VirtualFile vfile) {
|
||||
PathChoosingVisitor visitor = new PathChoosingVisitor(vfile);
|
||||
visitRoots(foothold, visitor);
|
||||
return visitor.getResult();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import com.jetbrains.python.psi.NameDefiner;
|
||||
import com.jetbrains.python.psi.PyFile;
|
||||
import com.jetbrains.python.psi.PyReferenceExpression;
|
||||
import com.jetbrains.python.psi.PyUtil;
|
||||
import com.jetbrains.python.psi.impl.ResolveImportUtil;
|
||||
import com.jetbrains.python.psi.resolve.ResolveImportUtil;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
|
||||
@@ -5,7 +5,7 @@ import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.ResolveState;
|
||||
import com.jetbrains.python.psi.PyReferenceExpression;
|
||||
import com.jetbrains.python.psi.resolve.VariantsProcessor;
|
||||
import com.jetbrains.python.psi.impl.ResolveImportUtil;
|
||||
import com.jetbrains.python.psi.resolve.ResolveImportUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
Reference in New Issue
Block a user