diff --git a/python/src/com/jetbrains/python/codeInsight/PyStdlibModuleMembersProvider.java b/python/src/com/jetbrains/python/codeInsight/PyStdlibModuleMembersProvider.java index 214977ced24c..0871455a94e3 100644 --- a/python/src/com/jetbrains/python/codeInsight/PyStdlibModuleMembersProvider.java +++ b/python/src/com/jetbrains/python/codeInsight/PyStdlibModuleMembersProvider.java @@ -13,7 +13,7 @@ import java.util.Collections; public class PyStdlibModuleMembersProvider extends PyModuleMembersProvider { @Override protected Collection getMembersByQName(PyFile module, String qName, ResolveImportUtil.PointInImport point) { - if (qName.equals("os") && point.role == ResolveImportUtil.PointInImport.ROLE.AS_MODULE) { + if (qName.equals("os") && point == ResolveImportUtil.PointInImport.AS_MODULE) { return Collections.singletonList(new PyDynamicMember("path")); } return Collections.emptyList(); diff --git a/python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java b/python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java index ff1cafceac55..3b6310050727 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java @@ -8,8 +8,6 @@ import com.intellij.lang.ASTNode; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.util.Condition; -import com.intellij.openapi.util.Iconable; -import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; @@ -20,9 +18,9 @@ import com.jetbrains.python.PyNames; import com.jetbrains.python.PyTokenTypes; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.resolve.*; +import com.jetbrains.python.psi.types.PyModuleType; import com.jetbrains.python.psi.types.PyType; import com.jetbrains.python.psi.types.TypeEvalContext; -import com.jetbrains.python.sdk.PythonSdkType; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -93,19 +91,7 @@ public class PyImportReferenceImpl extends PyReferenceImpl { if (type != null) { Object[] variants = getTypeCompletionVariants(myElement, type); if (!alreadyHasImportKeyword()) { - for (int i=0; i < variants.length; i+=1) { - Object item = variants[i]; - if (item instanceof LookupElementBuilder) { - variants[i] = ((LookupElementBuilder)item).setInsertHandler(ImportKeywordHandler.INSTANCE); - } - else if (item instanceof PsiNamedElement) { - final PsiNamedElement element = (PsiNamedElement)item; - variants[i] = LookupElementBuilder - .create(element.getName()) // it can't really have null name - .setIcon(element.getIcon(0)) - .setInsertHandler(ImportKeywordHandler.INSTANCE); - } - } + replaceInsertHandler(variants, ImportKeywordHandler.INSTANCE); } return variants; } @@ -119,6 +105,22 @@ public class PyImportReferenceImpl extends PyReferenceImpl { } } + private static void replaceInsertHandler(Object[] variants, final InsertHandler insertHandler) { + for (int i=0; i < variants.length; i+=1) { + Object item = variants[i]; + if (item instanceof LookupElementBuilder) { + variants[i] = ((LookupElementBuilder)item).setInsertHandler(insertHandler); + } + else if (item instanceof PsiNamedElement) { + final PsiNamedElement element = (PsiNamedElement)item; + variants[i] = LookupElementBuilder + .create(element.getName()) // it can't really have null name + .setIcon(element.getIcon(0)) + .setInsertHandler(insertHandler); + } + } + } + private boolean alreadyHasImportKeyword() { ASTNode node = myElement.getNode(); while (node != null) { @@ -180,7 +182,7 @@ public class PyImportReferenceImpl extends PyReferenceImpl { else { // null source, must be a "from ... import" relative_level = from_import.getRelativeLevel(); if (relative_level > 0) { - PsiDirectory relative_dir = ResolveImportUtil.stepBackFrom(myCurrentFile, relative_level); + PsiDirectory relative_dir = ResolveImportUtil.stepBackFrom(myCurrentFile, relative_level-1); if (relative_dir != null) { addImportedNames(from_import.getImportElements()); fillFromDir(relative_dir, null); @@ -246,51 +248,21 @@ public class PyImportReferenceImpl extends PyReferenceImpl { } // adds variants found under given dir - private void fillFromDir(PsiDirectory target_dir, @Nullable InsertHandler handler) { + private void fillFromDir(PsiDirectory target_dir, @Nullable InsertHandler insertHandler) { if (target_dir != null) { - for (PsiElement dir_item : target_dir.getChildren()) { - if (dir_item != myCurrentFile) { - if (dir_item instanceof PsiDirectory) { - final PsiDirectory dir = (PsiDirectory)dir_item; - if (dir.findFile(PyNames.INIT_DOT_PY) != null) { - final String name = dir.getName(); - if (PyNames.isIdentifier(name)) { - myObjects.add(LookupElementBuilder - .create(name) - .setTypeText(getPresentablePath(dir.getParent())) - .setIcon(dir.getIcon(Iconable.ICON_FLAG_CLOSED))); - } - } - } - else if (dir_item instanceof PsiFile) { // plain file - String filename = ((PsiFile)dir_item).getName(); - if (!PyNames.INIT_DOT_PY.equals(filename) && filename.endsWith(PyNames.DOT_PY)) { - final String name = filename.substring(0, filename.length() - PyNames.DOT_PY.length()); - if (PyNames.isIdentifier(name)) { - final PsiDirectory dir = ((PsiFile)dir_item).getContainingDirectory(); - myObjects.add(LookupElementBuilder - .create(name) - .setTypeText(getPresentablePath(dir)) - .setInsertHandler(handler) - .setIcon(dir_item.getIcon(0))); - } - } - } + PsiFile initPy = target_dir.findFile(PyNames.INIT_DOT_PY); + if (initPy instanceof PyFile) { + PyModuleType moduleType = new PyModuleType((PyFile)initPy); + ProcessingContext context = new ProcessingContext(); + context.put(PyType.CTX_NAMES, myNamesAlready); + Object[] completionVariants = moduleType.getCompletionVariants("", (PyExpression)getElement(), context); + if (insertHandler != null) { + replaceInsertHandler(completionVariants, insertHandler); } + myObjects.addAll(Arrays.asList(completionVariants)); } } } - - private String getPresentablePath(PsiDirectory directory) { - if (directory == null) { - return ""; - } - final String path = directory.getVirtualFile().getPath(); - if (path.contains(PythonSdkType.SKELETON_DIR_NAME)) { - return ""; - } - return FileUtil.toSystemDependentName(path); - } } /** diff --git a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java index 0b5f154b246b..8de0b8b3b26f 100644 --- a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java +++ b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java @@ -27,9 +27,6 @@ import org.jetbrains.annotations.Nullable; import java.util.*; -import static com.jetbrains.python.psi.resolve.ResolveImportUtil.PointInImport.ROLE.AS_MODULE; -import static com.jetbrains.python.psi.resolve.ResolveImportUtil.PointInImport.ROLE.AS_NAME; - /** * @author dcheryasov */ @@ -937,41 +934,23 @@ public class ResolveImportUtil { return null; } - /** - * Points to an import statement and role as found by {@link #getPointInImport(PsiReference)}. - * Immutable. - */ - public static class PointInImport { - public final PyFromImportStatement fromImportStatement; - public final PyImportStatement importStatement; - public final ROLE role; + public static enum PointInImport { + /** + * The reference is not inside an import statement. + */ + NONE, - PointInImport(PyFromImportStatement fromImportStatement, PyImportStatement importStatement, ROLE role) { - this.fromImportStatement = fromImportStatement; - this.importStatement = importStatement; - this.role = role; - } + /** + * The reference is inside import and refers to a module + */ + AS_MODULE, - public static enum ROLE { - /** - * The reference is not inside an import statement. - */ - NONE, - - /** - * The reference is inside import and refers to a module - */ - AS_MODULE, - - /** - * The reference is inside import and refers to a name imported from a module - */ - AS_NAME - } + /** + * The reference is inside import and refers to a name imported from a module + */ + AS_NAME } - public static final PointInImport NOT_IN_IMPORT = new PointInImport(null, null, PointInImport.ROLE.NONE); - /** * @param element what we test (identifier, reference, import element, etc) * @return the how the element relates to an enclosing import statement, if any @@ -982,23 +961,17 @@ public class ResolveImportUtil { PyImportElement.class, PyFromImportStatement.class ); if (parent instanceof PyFromImportStatement) { - return new PointInImport((PyFromImportStatement)parent, null, AS_MODULE); // from foo ... + return PointInImport.AS_MODULE; // from foo ... } if (parent instanceof PyImportElement) { PsiElement statement = parent.getParent(); if (statement instanceof PyImportStatement) { - return new PointInImport(null, (PyImportStatement)statement, AS_MODULE); // import foo,... + return PointInImport.AS_MODULE; // import foo,... } else if (statement instanceof PyFromImportStatement) { - PyFromImportStatement importer = (PyFromImportStatement)statement; // from ??? import foo - if (importer.getImportSource() == null && importer.getRelativeLevel() > 0) { - return new PointInImport(importer, null, AS_MODULE); // from . import foo,... - } - else { - return new PointInImport(importer, null, AS_NAME); - } // from bar import foo,... + return PointInImport.AS_NAME; } } - return NOT_IN_IMPORT; + return PointInImport.NONE; } } diff --git a/python/src/com/jetbrains/python/psi/types/PyModuleMembersProvider.java b/python/src/com/jetbrains/python/psi/types/PyModuleMembersProvider.java index 9f363475bff4..b3e763d411a9 100644 --- a/python/src/com/jetbrains/python/psi/types/PyModuleMembersProvider.java +++ b/python/src/com/jetbrains/python/psi/types/PyModuleMembersProvider.java @@ -30,7 +30,7 @@ public abstract class PyModuleMembersProvider { @Nullable public PsiElement resolveMember(PyFile module, String name) { - for (PyDynamicMember o : getMembers(module, ResolveImportUtil.NOT_IN_IMPORT)) { + for (PyDynamicMember o : getMembers(module, ResolveImportUtil.PointInImport.NONE)) { if (o.getName().equals(name)) { return o.resolve(module); } diff --git a/python/src/com/jetbrains/python/psi/types/PyModuleType.java b/python/src/com/jetbrains/python/psi/types/PyModuleType.java index 6ecff12beafd..25d1e4335166 100644 --- a/python/src/com/jetbrains/python/psi/types/PyModuleType.java +++ b/python/src/com/jetbrains/python/psi/types/PyModuleType.java @@ -25,9 +25,6 @@ import java.util.Collections; import java.util.List; import java.util.Set; -import static com.jetbrains.python.psi.resolve.ResolveImportUtil.PointInImport.ROLE.*; -// .impl looks impure - /** * @author yole */ @@ -113,9 +110,9 @@ public class PyModuleType implements PyType { // Modules don't descend from obje } } - if (point.role == NONE || point.role == AS_NAME) { // when not imported from, add regular attributes + if (point == ResolveImportUtil.PointInImport.NONE || point == ResolveImportUtil.PointInImport.AS_NAME) { // when not imported from, add regular attributes final VariantsProcessor processor = new VariantsProcessor(location); - processor.setPlainNamesOnly(point.role == AS_NAME); // no parens after imported function names + processor.setPlainNamesOnly(point == ResolveImportUtil.PointInImport.AS_NAME); // no parens after imported function names myModule.processDeclarations(processor, ResolveState.initial(), null, location); if (names_already != null) { for (LookupElement le : processor.getResultList()) { @@ -130,7 +127,7 @@ public class PyModuleType implements PyType { // Modules don't descend from obje result.addAll(processor.getResultList()); } } - if (point.role == AS_MODULE || point.role == AS_NAME) { // when imported from somehow, add submodules + if (point == ResolveImportUtil.PointInImport.AS_MODULE || point == ResolveImportUtil.PointInImport.AS_NAME) { // when imported from somehow, add submodules for (PsiFileSystemItem pfsi : getSubmodulesList()) { if (pfsi == location.getContainingFile().getOriginalFile()) continue; String s = pfsi.getName(); diff --git a/python/testData/completion/relativeImport/pkg/__init__.py b/python/testData/completion/relativeImport/pkg/__init__.py index e69de29bb2d1..d4856850902f 100644 --- a/python/testData/completion/relativeImport/pkg/__init__.py +++ b/python/testData/completion/relativeImport/pkg/__init__.py @@ -0,0 +1 @@ +def xyzzy(): pass diff --git a/python/testData/completion/relativeImport/pkg/name.after.py b/python/testData/completion/relativeImport/pkg/name.after.py new file mode 100644 index 000000000000..4c9b50badb6f --- /dev/null +++ b/python/testData/completion/relativeImport/pkg/name.after.py @@ -0,0 +1 @@ +from . import xyzzy \ No newline at end of file diff --git a/python/testData/completion/relativeImport/pkg/name.py b/python/testData/completion/relativeImport/pkg/name.py new file mode 100644 index 000000000000..af9abbb2af04 --- /dev/null +++ b/python/testData/completion/relativeImport/pkg/name.py @@ -0,0 +1 @@ +from . import x \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index 275adcb814fc..2e9a35243463 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -282,4 +282,11 @@ public class PythonCompletionTest extends PyLightFixtureTestCase { myFixture.completeBasic(); myFixture.checkResultByFile("completion/relativeImport/pkg/main.after.py"); } + + public void testRelativeImportNameFromInitPy() { // PY-2816 + myFixture.copyDirectoryToProject("completion/relativeImport", "relativeImport"); + myFixture.configureByFile("relativeImport/pkg/name.py"); + myFixture.completeBasic(); + myFixture.checkResultByFile("completion/relativeImport/pkg/name.after.py"); + } }