Don't autocomplete magic and private names unless asked (PY-568).

This commit is contained in:
Dmitry Cheryasov
2010-04-08 18:14:49 +03:00
parent 9b6526dd20
commit 97873ffa69
6 changed files with 225 additions and 214 deletions
@@ -692,4 +692,16 @@ public class PyUtil {
}
else return target; // don't touch non-dirs
}
/**
* Counts initial underscores of an identifier.
* @param name identifier
* @return 0 if no initial underscores found, 1 if there's only one underscore, 2 if there's two or more initial underscores.
*/
public static int getInitialUnderscores(String name) {
int underscores=0;
if (name.startsWith("__")) underscores = 2;
else if (name.startsWith("_")) underscores = 1;
return underscores;
}
}
@@ -1,16 +1,28 @@
package com.jetbrains.python.psi.impl;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleUtil;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiManager;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ProcessingContext;
import com.jetbrains.python.psi.PyExpression;
import com.jetbrains.python.psi.PyUtil;
import com.jetbrains.python.psi.RatedResolveResult;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.resolve.PyResolveUtil;
import com.jetbrains.python.psi.resolve.ResolveImportUtil;
import com.jetbrains.python.psi.resolve.SdkRootVisitor;
import com.jetbrains.python.psi.resolve.VariantsProcessor;
import com.jetbrains.python.psi.types.PyType;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.*;
import java.util.HashSet;
/**
* @author yole
@@ -51,7 +63,149 @@ public class PyImportReferenceImpl extends PyReferenceImpl {
}
else {
// complete to possible modules
return ResolveImportUtil.suggestImportVariants(myElement);
List<Object> variants = new ArrayList<Object>();
PsiFile current_file = myElement.getContainingFile();
if (current_file != null) current_file = current_file.getOriginalFile();
int relative_level = 0;
final Set<String> names_already = new HashSet<String>(); // don't propose already imported names
String ref_name = myElement.getName();
Condition<PsiElement> node_filter = new PyResolveUtil.FilterNameNotIn(names_already);
Condition<String> underscore_filter = new UnderscoreFilter(PyUtil.getInitialUnderscores(ref_name));
// are we in "import _" or "from foo import _"?
PyFromImportStatement from_import = PsiTreeUtil.getParentOfType(myElement, PyFromImportStatement.class);
if (from_import != null && myElement.getParent() != from_import) { // in "from foo import _"
PyReferenceExpression src = from_import.getImportSource();
if (src != null) {
PsiElement mod_candidate = src.getReference().resolve();
if (mod_candidate instanceof PyExpression) {
addImportedNames(from_import.getImportElements(), names_already, underscore_filter); // don't propose already imported items
// collect what's within module file
final VariantsProcessor processor = new VariantsProcessor(myElement, node_filter, underscore_filter);
PyResolveUtil.treeCrawlUp(processor, true, mod_candidate);
variants.addAll(processor.getResultList());
// try to collect submodules
PyExpression module = (PyExpression)mod_candidate;
PyType qualifierType = module.getType();
if (qualifierType != null) {
ProcessingContext ctx = new ProcessingContext();
for (Object ex : variants) { // just in case: file's definitions shadow submodules
if (ex instanceof PyReferenceExpression) {
names_already.add(((PyReferenceExpression)ex).getReferencedName());
}
}
// collect submodules
ctx.put(PyType.CTX_NAMES, names_already);
Collections.addAll(variants, qualifierType.getCompletionVariants(myElement, ctx));
}
return variants.toArray();
}
}
else { // null source, must be a "from ... import"
relative_level = from_import.getRelativeLevel();
if (relative_level > 0) {
PsiDirectory relative_dir = ResolveImportUtil.stepBackFrom(current_file, relative_level);
if (relative_dir != null) {
addImportedNames(from_import.getImportElements(), names_already, underscore_filter);
fillFromDir(relative_dir, current_file, underscore_filter, variants);
}
}
}
}
// in "import _" or "from _ import"
if (from_import != null) addImportedNames(from_import.getImportElements(), names_already, underscore_filter);
else {
names_already.add(PyNames.FUTURE_MODULE); // never add it to "import ..."
PyImportStatement import_stmt = PsiTreeUtil.getParentOfType(myElement, PyImportStatement.class);
if (import_stmt != null) {
addImportedNames(import_stmt.getImportElements(), names_already, underscore_filter);
}
}
// look at current dir
if (current_file != null && relative_level == 0 && ! ResolveImportUtil.isAbsoluteImportEnabledFor(current_file)) {
fillFromDir(current_file.getParent(), current_file, underscore_filter, variants);
}
if (relative_level == 0) {
// look in SDK
final CollectingRootVisitor visitor = new CollectingRootVisitor(((PyReferenceExpression)myElement).getManager());
final Module module = ModuleUtil.findModuleForPsiElement(myElement);
if (module != null) {
ModuleRootManager.getInstance(module).processOrder(new ResolveImportUtil.SdkRootVisitingPolicy(visitor), null);
for (String name : visitor.getResult()) {
if (PyNames.isIdentifier(name) && underscore_filter.value(name)) variants.add(name); // to thwart stuff like "__phello__.foo"
}
}
}
return ArrayUtil.toObjectArray(variants);
}
}
private static void addImportedNames(PyImportElement[] import_elts, Collection<String> collected_names, Condition<String> filter) {
if (import_elts != null && collected_names != null) {
for (PyImportElement ielt : import_elts) {
String s;
PyReferenceExpression ref = ielt.getImportReference();
if (ref != null) {
s = ref.getReferencedName();
if (s != null && filter.value(s)) collected_names.add(s);
}
}
}
}
// adds variants found under given dir
private static void fillFromDir(PsiDirectory target_dir, PsiFile source_file, Condition<String> filter, List<Object> variants) {
if (target_dir != null) {
for (PsiElement dir_item : target_dir.getChildren()) {
if (dir_item != source_file) {
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) && filter.value(name)) variants.add(name);
}
}
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) && filter.value(name)) variants.add(name);
}
}
}
}
}
}
private static class CollectingRootVisitor implements SdkRootVisitor {
Set<String> result;
PsiManager psimgr;
static String cutExt(String name) {
return name.substring(0, Math.max(name.length() - PyNames.DOT_PY.length(), 0));
}
public CollectingRootVisitor(PsiManager psimgr) {
result = new com.intellij.util.containers.HashSet<String>();
this.psimgr = psimgr;
}
public boolean visitRoot(final VirtualFile root) {
for (VirtualFile vfile : root.getChildren()) {
if (vfile.getName().endsWith(PyNames.DOT_PY)) {
PsiFile pfile = psimgr.findFile(vfile);
if (pfile != null) result.add(cutExt(pfile.getName()));
}
else if (vfile.isDirectory() && (vfile.findChild(PyNames.INIT_DOT_PY) != null)) {
PsiDirectory pdir = psimgr.findDirectory(vfile);
if (pdir != null) result.add(pdir.getName());
}
}
return true; // continue forever
}
public Collection<String> getResult() {
return result;
}
}
}
@@ -284,7 +284,9 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference
final PsiElement realContext = PyPsiUtils.getRealContext(myElement);
// include our own names
final VariantsProcessor processor = new VariantsProcessor(myElement);
final int underscores = PyUtil.getInitialUnderscores(myElement.getName());
final UnderscoreFilter filter = new UnderscoreFilter(underscores);
final VariantsProcessor processor = new VariantsProcessor(myElement, null, filter);
PyResolveUtil.treeCrawlUp(processor, realContext); // names from here
PyResolveUtil.scanOuterContext(processor, realContext); // possible names from around us at call time
@@ -315,7 +317,8 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference
if (from_import_stmt != null) {
final PyReferenceExpression import_src = from_import_stmt.getImportSource();
if (import_src != null) {
processor.setNotice(import_src.getName());
final String imported_name = import_src.getName();
processor.setNotice(imported_name);
PyResolveUtil.treeCrawlUp(processor, true, import_src.getReference().resolve()); // names from that module
}
}
@@ -324,11 +327,13 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference
processor.setNotice("__builtin__");
PyResolveUtil.treeCrawlUp(processor, true, PyBuiltinCache.getInstance(getElement()).getBuiltinsFile()); // names from __builtin__
// if we're a normal module, add module's attrs
PsiFile f = realContext.getContainingFile();
if (f instanceof PyFile) {
for (String name : PyModuleType.getPossibleInstanceMembers()) {
ret.add(LookupElementBuilder.create(name).setIcon(Icons.FIELD_ICON));
if (underscores >= 2) {
// if we're a normal module, add module's attrs
PsiFile f = realContext.getContainingFile();
if (f instanceof PyFile) {
for (String name : PyModuleType.getPossibleInstanceMembers()) {
ret.add(LookupElementBuilder.create(name).setIcon(Icons.FIELD_ICON));
}
}
}
@@ -419,4 +424,24 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference
public int hashCode() {
return myElement.hashCode();
}
/**
* Logical conjunction.
*/
protected static class UnderscoreFilter implements Condition<String> {
private int myAllowed; // how many starting underscores is allowed: 0 is none, 1 is only one, 2 is two and more.
public UnderscoreFilter(int allowed) {
myAllowed = allowed;
}
public boolean value(String name) {
if (name == null) return false;
if (name.length() < 1) return false; // empty strings make no sense
int have_underscores = 0;
if (name.charAt(0) == '_') have_underscores = 1;
if (have_underscores != 0 && name.length() > 1 && name.charAt(1) == '_') have_underscores = 2;
return myAllowed >= have_underscores;
}
}
}
@@ -294,29 +294,30 @@ public class PyResolveUtil {
/**
* Accepts only targets that are not the given object.
*/
public static class FilterNotInstance implements Condition<Object> {
public static class FilterNotInstance implements Condition<PsiElement> {
Object instance;
public FilterNotInstance(Object instance) {
this.instance = instance;
}
public boolean value(final Object target) {
public boolean value(final PsiElement target) {
return (instance != target);
}
}
/**
* Accepts only names not contained in a given collection.
*/
public static class FilterNameNotIn implements Condition<Object> {
public static class FilterNameNotIn implements Condition<PsiElement> {
private final Collection<String> myNames;
public FilterNameNotIn(Collection<String> names) {
myNames = names;
}
public boolean value(Object target) {
public boolean value(PsiElement target) {
if (target instanceof PsiNamedElement) {
return !myNames.contains(((PsiNamedElement)target).getName());
}
@@ -37,7 +37,7 @@ public class ResolveImportUtil {
}
};
private static boolean isAbsoluteImportEnabledFor(PsiElement foothold) {
public static boolean isAbsoluteImportEnabledFor(PsiElement foothold) {
if (foothold != null) {
PsiFile file = foothold.getContainingFile();
if (file instanceof PyFile) {
@@ -71,7 +71,7 @@ public class ResolveImportUtil {
* @return found directory, or null.
*/
@Nullable
private static PsiDirectory stepBackFrom(PsiFile base, int depth) {
public static PsiDirectory stepBackFrom(PsiFile base, int depth) {
PsiDirectory result;
if (base != null) {
base = base.getOriginalFile(); // just to make sure
@@ -540,37 +540,6 @@ public class ResolveImportUtil {
}
}
static class CollectingRootVisitor implements SdkRootVisitor {
Set<String> result;
PsiManager psimgr;
static String cutExt(String name) {
return name.substring(0, Math.max(name.length() - PyNames.DOT_PY.length(), 0));
}
public CollectingRootVisitor(PsiManager psimgr) {
result = new HashSet<String>();
this.psimgr = psimgr;
}
public boolean visitRoot(final VirtualFile root) {
for (VirtualFile vfile : root.getChildren()) {
if (vfile.getName().endsWith(PyNames.DOT_PY)) {
PsiFile pfile = psimgr.findFile(vfile);
if (pfile != null) result.add(cutExt(pfile.getName()));
}
else if (vfile.isDirectory() && (vfile.findChild(PyNames.INIT_DOT_PY) != null)) {
PsiDirectory pdir = psimgr.findDirectory(vfile);
if (pdir != null) result.add(pdir.getName());
}
}
return true; // continue forever
}
public Collection<String> getResult() {
return result;
}
}
/**
Tries to find referencedName under the parent element. Used to resolve any names that look imported.
@@ -645,163 +614,6 @@ public class ResolveImportUtil {
}
private static void addImportedNames(PyImportElement[] import_elts, Collection<String> collected_names, Condition<Object> filter) {
if (import_elts != null && collected_names != null) {
for (PyImportElement ielt : import_elts) {
String s;
PyReferenceExpression ref = ielt.getImportReference();
if (ref != null) {
s = ref.getReferencedName();
if (s != null && filter.value(s)) collected_names.add(s);
}
}
}
}
/**
* Logical conjunction.
*/
static class And<T> implements Condition<T> {
private Condition<T> myOne;
private Condition<T> myTwo;
public And(Condition<T> one, Condition<T> two) {
myOne = one;
myTwo = two;
}
public boolean value(T t) {
return myOne.value(t) && myTwo.value(t);
}
}
static class UnderscoreFilter implements Condition<Object> {
private int myAllowed; // how many starting underscores is allowed: 0 is none, 1 is only one, 2 is two and more.
public UnderscoreFilter(int allowed) {
myAllowed = allowed;
}
public boolean value(Object o) {
if (o == null) return false;
String name = o.toString();
if (name.length() < 1) return false; // empty strings make no sense
int have_underscores = 0;
if (name.charAt(0) == '_') have_underscores = 1;
if (have_underscores != 0 && name.length() > 1 && name.charAt(1) == '_') have_underscores = 2;
return myAllowed >= have_underscores;
}
}
/**
* Finds reasonable names to import to complete a patrial name.
* @param partial_ref reference containing the partial name.
* @return an array of names ready for getVariants().
*/
public static Object[] suggestImportVariants(final PyReferenceExpression partial_ref) {
List<Object> variants = new ArrayList<Object>();
if (partial_ref == null) return variants.toArray();
PsiFile current_file = partial_ref.getContainingFile();
if (current_file != null) current_file = current_file.getOriginalFile();
int relative_level = 0;
final Set<String> names_already = new java.util.HashSet<String>(); // don't propose already imported names
int underscores=0; // the number of underscores we're interested in
String ref_name = partial_ref.getName();
if (ref_name.startsWith("__")) underscores = 2;
else if (ref_name.startsWith("_")) underscores = 1;
final And<Object> filter = new And<Object>(new PyResolveUtil.FilterNameNotIn(names_already), new UnderscoreFilter(underscores));
// are we in "import _" or "from foo import _"?
PyFromImportStatement from_import = PsiTreeUtil.getParentOfType(partial_ref, PyFromImportStatement.class);
if (from_import != null && partial_ref.getParent() != from_import) { // in "from foo import _"
PyReferenceExpression src = from_import.getImportSource();
if (src != null) {
PsiElement mod_candidate = src.getReference().resolve();
if (mod_candidate instanceof PyExpression) {
addImportedNames(from_import.getImportElements(), names_already, filter); // don't propose already imported items
// collect what's within module file
final VariantsProcessor processor = new VariantsProcessor(partial_ref, filter
);
PyResolveUtil.treeCrawlUp(processor, true, mod_candidate);
variants.addAll(processor.getResultList());
// try to collect submodules
PyExpression module = (PyExpression)mod_candidate;
PyType qualifierType = module.getType();
if (qualifierType != null) {
ProcessingContext ctx = new ProcessingContext();
for (Object ex : variants) { // just in case: file's definitions shadow submodules
if (ex instanceof PyReferenceExpression) {
names_already.add(((PyReferenceExpression)ex).getReferencedName());
}
}
// collect submodules
ctx.put(PyType.CTX_NAMES, names_already);
Collections.addAll(variants, qualifierType.getCompletionVariants(partial_ref, ctx));
}
return variants.toArray();
}
}
else { // null source, must be a "from ... import"
relative_level = from_import.getRelativeLevel();
if (relative_level > 0) {
PsiDirectory relative_dir = stepBackFrom(current_file, relative_level);
if (relative_dir != null) {
addImportedNames(from_import.getImportElements(), names_already, filter);
fillFromDir(relative_dir, current_file, filter, variants);
}
}
}
}
// in "import _" or "from _ import"
if (from_import != null) addImportedNames(from_import.getImportElements(), names_already, filter);
else {
names_already.add(PyNames.FUTURE_MODULE); // never add it to "import ..."
PyImportStatement import_stmt = PsiTreeUtil.getParentOfType(partial_ref, PyImportStatement.class);
if (import_stmt != null) {
addImportedNames(import_stmt.getImportElements(), names_already, filter);
}
}
// look at current dir
if (current_file != null && relative_level == 0 && ! isAbsoluteImportEnabledFor(current_file)) {
fillFromDir(current_file.getParent(), current_file, filter, variants);
}
if (relative_level == 0) {
// look in SDK
final CollectingRootVisitor visitor = new CollectingRootVisitor(partial_ref.getManager());
final Module module = ModuleUtil.findModuleForPsiElement(partial_ref);
if (module != null) {
ModuleRootManager.getInstance(module).processOrder(new SdkRootVisitingPolicy(visitor), null);
for (String name : visitor.getResult()) {
if (PyNames.isIdentifier(name) && filter.value(name)) variants.add(name); // to thwart stuff like "__phello__.foo"
}
}
}
return ArrayUtil.toObjectArray(variants);
}
// adds variants found under given dir
private static void fillFromDir(PsiDirectory target_dir, PsiFile source_file, Condition<Object> filter, List<Object> variants) {
if (target_dir != null) {
for (PsiElement dir_item : target_dir.getChildren()) {
if (dir_item != source_file) {
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) && filter.value(name)) variants.add(name);
}
}
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) && filter.value(name)) variants.add(name);
}
}
}
}
}
}
/**
* Tries to find roots that contain given vfile, and among them the root that contains at the smallest depth.
@@ -859,7 +671,7 @@ public class ResolveImportUtil {
}
private static class SdkRootVisitingPolicy extends RootPolicy<PsiElement> {
public static class SdkRootVisitingPolicy extends RootPolicy<PsiElement> {
private final SdkRootVisitor myVisitor;
public SdkRootVisitingPolicy(SdkRootVisitor visitor) {
@@ -21,16 +21,23 @@ public class VariantsProcessor implements PsiScopeProcessor {
protected final PsiElement myContext;
protected String myNotice;
protected Condition<Object> myFilter;
protected Condition<PsiElement> myNodeFilter;
protected Condition<String> myNameFilter;
public VariantsProcessor(PsiElement context) {
// empty
myContext = context;
}
public VariantsProcessor(PsiElement context, final Condition<Object> filter) {
public VariantsProcessor(PsiElement context, final Condition<PsiElement> nodefilter) {
myContext = context;
myFilter = filter;
myNodeFilter = nodefilter;
}
public VariantsProcessor(PsiElement context, final Condition<PsiElement> nodefilter, final Condition<String> namefilter) {
myContext = context;
myNodeFilter = nodefilter;
myNameFilter = namefilter;
}
public void setNotice(@Nullable String notice) {
@@ -61,19 +68,19 @@ public class VariantsProcessor implements PsiScopeProcessor {
}
public boolean execute(PsiElement element, ResolveState substitutor) {
if (myFilter != null && !myFilter.value(element)) return true; // skip whatever the filter rejects
if (myNodeFilter != null && !myNodeFilter.value(element)) return true; // skip whatever the filter rejects
// TODO: refactor to look saner; much code duplication
if (element instanceof PsiNamedElement) {
final PsiNamedElement psiNamedElement = (PsiNamedElement)element;
final String name = psiNamedElement.getName();
if (name != null && !myVariants.containsKey(name)) {
if (name != null && !myVariants.containsKey(name) && myNameFilter != null && myNameFilter.value(name)) {
myVariants.put(name, setupItem(LookupElementBuilder.create(psiNamedElement).setIcon(element.getIcon(0))));
}
}
else if (element instanceof PyReferenceExpression) {
PyReferenceExpression expr = (PyReferenceExpression)element;
String referencedName = expr.getReferencedName();
if (referencedName != null && !myVariants.containsKey(referencedName)) {
if (referencedName != null && !myVariants.containsKey(referencedName) && myNameFilter != null && myNameFilter.value(referencedName)) {
myVariants.put(referencedName, setupItem(LookupElementBuilder.create(referencedName)));
}
}
@@ -85,7 +92,7 @@ public class VariantsProcessor implements PsiScopeProcessor {
Icon icon = element.getIcon(0);
// things like PyTargetExpression cannot have a general icon, but here we only have variables
if (icon == null) icon = Icons.VARIABLE_ICON;
if (referencedName != null && !myVariants.containsKey(referencedName)) {
if (referencedName != null && !myVariants.containsKey(referencedName) && myNameFilter != null && myNameFilter.value(referencedName)) {
LookupElementBuilder lookup_item = setupItem(LookupElementBuilder.create(referencedName).setIcon(icon));
if (definer instanceof PyImportElement) { // set notice to imported module name if needed
PsiElement maybe_from_import = definer.getParent();