From 166b126019761b83bc42937d2920cd8f3ddc937c Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Tue, 7 Aug 2012 12:08:52 +0200 Subject: [PATCH] show module names in second completion (PY-7066) --- .../PyClassNameCompletionContributor.java | 31 +++++++++++++++---- .../imports/ImportFromExistingAction.java | 7 ++--- .../imports/PythonReferenceImporter.java | 16 ++++++---- .../src/com/jetbrains/python/psi/PyUtil.java | 4 +-- .../python/psi/types/PyModuleType.java | 29 ++++++++--------- .../className/module/module.after.py | 3 ++ .../completion/className/module/module.py | 1 + .../python/PyClassNameCompletionTest.java | 9 +++++- 8 files changed, 65 insertions(+), 35 deletions(-) create mode 100644 python/testData/completion/className/module/module.after.py create mode 100644 python/testData/completion/className/module/module.py diff --git a/python/src/com/jetbrains/python/codeInsight/completion/PyClassNameCompletionContributor.java b/python/src/com/jetbrains/python/codeInsight/completion/PyClassNameCompletionContributor.java index 705a85a806f9..1ed586de4420 100644 --- a/python/src/com/jetbrains/python/codeInsight/completion/PyClassNameCompletionContributor.java +++ b/python/src/com/jetbrains/python/codeInsight/completion/PyClassNameCompletionContributor.java @@ -9,18 +9,22 @@ import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Conditions; -import com.intellij.openapi.util.Iconable; +import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.*; +import com.intellij.psi.search.FileTypeIndex; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.stubs.StubIndex; import com.intellij.psi.stubs.StubIndexKey; import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.PythonFileType; import com.jetbrains.python.codeInsight.imports.AddImportHelper; +import com.jetbrains.python.codeInsight.imports.PythonReferenceImporter; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.impl.PyPsiUtils; import com.jetbrains.python.psi.search.PyProjectScopeBuilder; import com.jetbrains.python.psi.stubs.PyClassNameIndex; import com.jetbrains.python.psi.stubs.PyFunctionNameIndex; +import com.jetbrains.python.psi.types.PyModuleType; import java.util.Collection; @@ -40,9 +44,25 @@ public class PyClassNameCompletionContributor extends CompletionContributor { if (PsiTreeUtil.getParentOfType(element, PyImportStatementBase.class) != null) { return; } - addVariantsFromIndex(result, parameters.getOriginalFile(), PyClassNameIndex.KEY, CLASS_INSERT_HANDLER, + addVariantsFromIndex(result, parameters.getOriginalFile(), PyClassNameIndex.KEY, IMPORTING_INSERT_HANDLER, Conditions.alwaysTrue()); addVariantsFromIndex(result, parameters.getOriginalFile(), PyFunctionNameIndex.KEY, FUNCTION_INSERT_HANDLER, TOPLEVEL_FUNCTION); + addVariantsFromModules(result, parameters.getOriginalFile()); + } + } + + private static void addVariantsFromModules(CompletionResultSet result, PsiFile targetFile) { + Collection files = FileTypeIndex.getFiles(PythonFileType.INSTANCE, PyProjectScopeBuilder.excludeSdkTestsScope(targetFile)); + for (VirtualFile file : files) { + PsiFile pyFile = targetFile.getManager().findFile(file); + if (pyFile == null) continue; + PsiFileSystemItem importable = (PsiFileSystemItem) PyUtil.turnInitIntoDir(pyFile); + if (PythonReferenceImporter.isImportableModule(targetFile, importable)) { + LookupElementBuilder element = PyModuleType.buildFileLookupElement(importable, null); + if (element != null) { + result.addElement(element.withInsertHandler(IMPORTING_INSERT_HANDLER)); + } + } } } @@ -65,8 +85,7 @@ public class PyClassNameCompletionContributor extends CompletionContributor { for (final String elementName : CompletionUtil.sortMatching(resultSet.getPrefixMatcher(), keys)) { for (T element : StubIndex.getInstance().get(key, elementName, project, scope)) { if (condition.value(element)) { - resultSet.addElement(LookupElementBuilder.create(element) - .withIcon(element.getIcon(Iconable.ICON_FLAG_CLOSED)) + resultSet.addElement(LookupElementBuilder.createWithIcon(element) .withTailText(" " + ((NavigationItem)element).getPresentation().getLocationString(), true) .withInsertHandler(insertHandler)); } @@ -74,9 +93,9 @@ public class PyClassNameCompletionContributor extends CompletionContributor { } } - private static final InsertHandler CLASS_INSERT_HANDLER = new InsertHandler() { + private static final InsertHandler IMPORTING_INSERT_HANDLER = new InsertHandler() { public void handleInsert(final InsertionContext context, final LookupElement item) { - addImportForLookupElement(context, item, context.getTailOffset() - 1); + addImportForLookupElement(context, item, context.getTailOffset() - 1); } }; diff --git a/python/src/com/jetbrains/python/codeInsight/imports/ImportFromExistingAction.java b/python/src/com/jetbrains/python/codeInsight/imports/ImportFromExistingAction.java index f3042d1074ac..7104fd0ccc0d 100644 --- a/python/src/com/jetbrains/python/codeInsight/imports/ImportFromExistingAction.java +++ b/python/src/com/jetbrains/python/codeInsight/imports/ImportFromExistingAction.java @@ -9,7 +9,6 @@ import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectFileIndex; -import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.ui.popup.PopupChooserBuilder; import com.intellij.openapi.util.AsyncResult; import com.intellij.openapi.util.Comparing; @@ -124,7 +123,7 @@ public class ImportFromExistingAction implements QuestionAction { final Project project = myTarget.getProject(); final PyElementGenerator gen = PyElementGenerator.getInstance(project); AddImportHelper.ImportPriority priority = AddImportHelper.getImportPriority(myTarget, item.getFile()); - if (isRoot(project, item.getFile())) { + if (isRoot(item.getFile())) { AddImportHelper.addImportStatement(myTarget.getContainingFile(), myName, null, priority); } else { @@ -170,11 +169,11 @@ public class ImportFromExistingAction implements QuestionAction { } } - public static boolean isRoot(Project project, PsiFileSystemItem directory) { + public static boolean isRoot(PsiFileSystemItem directory) { if (directory == null) return true; VirtualFile vFile = directory.getVirtualFile(); if (vFile == null) return true; - ProjectFileIndex fileIndex = ProjectRootManager.getInstance(project).getFileIndex(); + ProjectFileIndex fileIndex = ProjectFileIndex.SERVICE.getInstance(directory.getProject()); return Comparing.equal(fileIndex.getClassRootForFile(vFile), vFile) || Comparing.equal(fileIndex.getContentRootForFile(vFile), vFile) || Comparing.equal(fileIndex.getSourceRootForFile(vFile), vFile); diff --git a/python/src/com/jetbrains/python/codeInsight/imports/PythonReferenceImporter.java b/python/src/com/jetbrains/python/codeInsight/imports/PythonReferenceImporter.java index d6115ac1811f..7ab97445ebed 100644 --- a/python/src/com/jetbrains/python/codeInsight/imports/PythonReferenceImporter.java +++ b/python/src/com/jetbrains/python/codeInsight/imports/PythonReferenceImporter.java @@ -157,7 +157,7 @@ public class PythonReferenceImporter implements ReferenceImporter { if (isIndexableTopLevel(symbol)) { // we only want top-level symbols PsiFileSystemItem srcfile = symbol instanceof PsiFileSystemItem ? ((PsiFileSystemItem)symbol).getParent() : symbol.getContainingFile(); if (srcfile != null && srcfile != existing_import_file && srcfile != node.getContainingFile() && - (ImportFromExistingAction.isRoot(project, srcfile) || PyNames.isIdentifier(FileUtil.getNameWithoutExtension(srcfile.getName()))) && + (ImportFromExistingAction.isRoot(srcfile) || PyNames.isIdentifier(FileUtil.getNameWithoutExtension(srcfile.getName()))) && !isShadowedModule(srcfile)) { PyQualifiedName import_path = ResolveImportUtil.findCanonicalImportPath(srcfile, node); if (import_path != null && !seen_file_names.contains(import_path.toString())) { @@ -216,11 +216,7 @@ public class PythonReferenceImporter implements ReferenceImporter { List result = new ArrayList(); PsiFile[] files = FilenameIndex.getFilesByName(project, reftext + ".py", scope); for (PsiFile file : files) { - PsiDirectory parent = file.getParent(); - if (parent != null && file != targetFile && - (parent.findFile(PyNames.INIT_DOT_PY) != null || - ImportFromExistingAction.isRoot(project, parent) || - parent == targetFile.getParent())) { + if (isImportableModule(targetFile, file)) { result.add(file); } } @@ -235,6 +231,14 @@ public class PythonReferenceImporter implements ReferenceImporter { return result; } + public static boolean isImportableModule(PsiFile targetFile, PsiFileSystemItem file) { + PsiDirectory parent = (PsiDirectory)file.getParent(); + return parent != null && file != targetFile && + (parent.findFile(PyNames.INIT_DOT_PY) != null || + ImportFromExistingAction.isRoot(parent) || + parent == targetFile.getParent()); + } + private static boolean isIndexableTopLevel(PsiElement symbol) { if (symbol instanceof PsiFileSystemItem) { return true; diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index 27f2b1eae848..86f27b4630cb 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -633,8 +633,8 @@ public class PyUtil { @Nullable public static PsiElement turnInitIntoDir(PsiElement target) { - if (target instanceof PyFile && PyNames.INIT_DOT_PY.equals(((PyFile)target).getName())) { - return ((PyFile)target).getContainingDirectory(); + if (target instanceof PyFile && isPackage((PsiFile) target)) { + return ((PsiFile)target).getContainingDirectory(); } return target; } diff --git a/python/src/com/jetbrains/python/psi/types/PyModuleType.java b/python/src/com/jetbrains/python/psi/types/PyModuleType.java index 3baebf8100bd..3fd2245a8471 100644 --- a/python/src/com/jetbrains/python/psi/types/PyModuleType.java +++ b/python/src/com/jetbrains/python/psi/types/PyModuleType.java @@ -273,7 +273,7 @@ public class PyModuleType implements PyType { // Modules don't descend from obje return result.toArray(); } - private void addImportedSubmodules(PyExpression location, Set names_already, List result) { + private void addImportedSubmodules(PyExpression location, Set exiatingNames, List result) { PsiFile file = location.getContainingFile(); if (file instanceof PyFile) { PyFile pyFile = (PyFile)file; @@ -286,10 +286,10 @@ public class PyModuleType implements PyType { // Modules don't descend from obje } LookupElement element = null; if (target instanceof PsiFileSystemItem) { - element = buildFileLookupElement(location, names_already, (PsiFileSystemItem) target); + element = buildFileLookupElement((PsiFileSystemItem) target, exiatingNames); } else if (target instanceof PsiNamedElement) { - element = LookupElementBuilder.create((PsiNamedElement)target).withIcon(target.getIcon(0)); + element = LookupElementBuilder.createWithIcon((PsiNamedElement)target); } if (element != null) { result.add(element); @@ -304,26 +304,23 @@ public class PyModuleType implements PyType { // Modules don't descend from obje Set names_already) { List result = new ArrayList(); for (PsiFileSystemItem item : getSubmodulesList(directory)) { - LookupElement lookupElement = buildFileLookupElement(location, names_already, item); - if (lookupElement != null) { - result.add(lookupElement); + if (item != location.getContainingFile().getOriginalFile()) { + LookupElement lookupElement = buildFileLookupElement(item, names_already); + if (lookupElement != null) { + result.add(lookupElement); + } } } return result; } @Nullable - private static LookupElement buildFileLookupElement(PsiElement location, - Set names_already, - PsiFileSystemItem item) { - if (item == location.getContainingFile().getOriginalFile()) return null; - String s = item.getName(); - int pos = s.lastIndexOf('.'); // it may not contain a dot, except in extension; cut it off. - if (pos > 0) s = s.substring(0, pos); + public static LookupElementBuilder buildFileLookupElement(PsiFileSystemItem item, @Nullable Set existingNames) { + String s = FileUtil.getNameWithoutExtension(item.getName()); if (!PyNames.isIdentifier(s)) return null; - if (names_already != null) { - if (names_already.contains(s)) return null; - else names_already.add(s); + if (existingNames != null) { + if (existingNames.contains(s)) return null; + else existingNames.add(s); } return LookupElementBuilder.create(item, s) .withTypeText(getPresentablePath((PsiDirectory)item.getParent())) diff --git a/python/testData/completion/className/module/module.after.py b/python/testData/completion/className/module/module.after.py new file mode 100644 index 000000000000..ed09c232ace0 --- /dev/null +++ b/python/testData/completion/className/module/module.after.py @@ -0,0 +1,3 @@ +import exceptions + +exceptions \ No newline at end of file diff --git a/python/testData/completion/className/module/module.py b/python/testData/completion/className/module/module.py new file mode 100644 index 000000000000..5ad0df6e0856 --- /dev/null +++ b/python/testData/completion/className/module/module.py @@ -0,0 +1 @@ +exc \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PyClassNameCompletionTest.java b/python/testSrc/com/jetbrains/python/PyClassNameCompletionTest.java index cb99ad2876e1..853fd36f5c29 100644 --- a/python/testSrc/com/jetbrains/python/PyClassNameCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PyClassNameCompletionTest.java @@ -33,11 +33,18 @@ public class PyClassNameCompletionTest extends PyTestCase { doTest(); } + public void testModule() { + doTest(); + } + private void doTest() { final String path = "/completion/className/" + getTestName(true); myFixture.copyDirectoryToProject(path, ""); myFixture.configureFromTempProjectFile(getTestName(true) + ".py"); myFixture.complete(CompletionType.BASIC, 2); - myFixture.checkResultByFile(path + "/" + getTestName(true) + ".after.py"); + if (myFixture.getLookupElements() != null) { + myFixture.finishLookup(); + } + myFixture.checkResultByFile(path + "/" + getTestName(true) + ".after.py", true); } }