show variants from __init__.py in completion of relative imports; remove duplicate logic for calculating completion variants for a directory; simplify ResolveImportUtil.getPointInImport() (PY-2816)

This commit is contained in:
Dmitry Jemerov
2011-02-22 19:18:58 +01:00
parent 14626e2e8e
commit e17d3d9f65
9 changed files with 61 additions and 109 deletions
@@ -13,7 +13,7 @@ import java.util.Collections;
public class PyStdlibModuleMembersProvider extends PyModuleMembersProvider {
@Override
protected Collection<PyDynamicMember> 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();
@@ -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<LookupElement> 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<LookupElement> handler) {
private void fillFromDir(PsiDirectory target_dir, @Nullable InsertHandler<LookupElement> 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 "<built-in>";
}
return FileUtil.toSystemDependentName(path);
}
}
/**
@@ -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;
}
}
@@ -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);
}
@@ -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();
@@ -0,0 +1 @@
def xyzzy(): pass
@@ -0,0 +1 @@
from . import xyzzy
@@ -0,0 +1 @@
from . import x<caret>
@@ -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");
}
}