show module names in second completion (PY-7066)

This commit is contained in:
Dmitry Jemerov
2012-08-07 12:08:52 +02:00
parent 757f8bc2ff
commit 166b126019
8 changed files with 65 additions and 35 deletions
@@ -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.<PyClass>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<VirtualFile> 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<LookupElement> CLASS_INSERT_HANDLER = new InsertHandler<LookupElement>() {
private static final InsertHandler<LookupElement> IMPORTING_INSERT_HANDLER = new InsertHandler<LookupElement>() {
public void handleInsert(final InsertionContext context, final LookupElement item) {
addImportForLookupElement(context, item, context.getTailOffset() - 1);
addImportForLookupElement(context, item, context.getTailOffset() - 1);
}
};
@@ -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);
@@ -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<PsiElement> result = new ArrayList<PsiElement>();
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;
@@ -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;
}
@@ -273,7 +273,7 @@ public class PyModuleType implements PyType { // Modules don't descend from obje
return result.toArray();
}
private void addImportedSubmodules(PyExpression location, Set<String> names_already, List<Object> result) {
private void addImportedSubmodules(PyExpression location, Set<String> exiatingNames, List<Object> 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<String> names_already) {
List<LookupElement> result = new ArrayList<LookupElement>();
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<String> 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<String> 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()))
@@ -0,0 +1,3 @@
import exceptions
exceptions
@@ -0,0 +1 @@
exc<caret>
@@ -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);
}
}