diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index e9e18e77ff07..fdcdb5b4af13 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -7,6 +7,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.Condition; import com.intellij.openapi.util.Pair; import com.intellij.openapi.wm.WindowManager; import com.intellij.psi.*; @@ -704,4 +705,21 @@ public class PyUtil { else if (name.startsWith("_")) underscores = 1; return underscores; } + + public static class UnderscoreFilter implements Condition { + 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; + } + } } diff --git a/python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java b/python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java index 0164218c6451..52f37bebd2ce 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyImportReferenceImpl.java @@ -70,7 +70,7 @@ public class PyImportReferenceImpl extends PyReferenceImpl { final Set names_already = new HashSet(); // don't propose already imported names String ref_name = myElement.getName(); Condition node_filter = new PyResolveUtil.FilterNameNotIn(names_already); - Condition underscore_filter = new UnderscoreFilter(PyUtil.getInitialUnderscores(ref_name)); + Condition underscore_filter = new PyUtil.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 _" diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceImpl.java index 8e912c3fa26a..af36c2ebc3a7 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReferenceImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceImpl.java @@ -285,7 +285,7 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference // include our own names final int underscores = PyUtil.getInitialUnderscores(myElement.getName()); - final UnderscoreFilter filter = new UnderscoreFilter(underscores); + final PyUtil.UnderscoreFilter filter = new PyUtil.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 @@ -425,23 +425,4 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference return myElement.hashCode(); } - /** - * Logical conjunction. - */ - protected static class UnderscoreFilter implements Condition { - 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; - } - } } diff --git a/python/src/com/jetbrains/python/psi/types/PyClassType.java b/python/src/com/jetbrains/python/psi/types/PyClassType.java index b01c5970ae2b..8cc9e38eaef1 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassType.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassType.java @@ -3,13 +3,16 @@ package com.jetbrains.python.psi.types; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; import com.intellij.openapi.extensions.Extensions; +import com.intellij.openapi.util.Condition; import com.intellij.psi.PsiElement; import com.intellij.psi.ResolveState; import com.intellij.util.ProcessingContext; import com.jetbrains.python.codeInsight.PyDynamicMember; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyReferenceExpression; +import com.jetbrains.python.psi.PyUtil; import com.jetbrains.python.psi.impl.PyBuiltinCache; +import com.jetbrains.python.psi.patterns.ParentMatcher; import com.jetbrains.python.psi.resolve.PyResolveUtil; import com.jetbrains.python.psi.resolve.ResolveProcessor; import com.jetbrains.python.psi.resolve.VariantsProcessor; @@ -115,19 +118,30 @@ public class PyClassType implements PyType { } public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression, ProcessingContext context) { + List class_list = new ParentMatcher(PyClass.class).search(referenceExpression); + boolean within_our_class = class_list != null && class_list.get(0) == this; Set names_already = context.get(PyType.CTX_NAMES); - final VariantsProcessor processor = new VariantsProcessor(referenceExpression, new PyResolveUtil.FilterNotInstance(myClass)); - myClass.processDeclarations(processor, ResolveState.initial(), null, referenceExpression); List ret = new ArrayList(); + Condition underscore_filter = new PyUtil.UnderscoreFilter(PyUtil.getInitialUnderscores(referenceExpression.getName())); + // from providers for(PyClassMembersProvider provider: Extensions.getExtensions(PyClassMembersProvider.EP_NAME)) { for (PyDynamicMember member : provider.getMembers(myClass)) { - ret.add(LookupElementBuilder.create(member.getName()).setIcon(member.getIcon()).setTypeText(member.getShortType())); + final String name = member.getName(); + if (underscore_filter.value(name)) { + ret.add(LookupElementBuilder.create(name).setIcon(member.getIcon()).setTypeText(member.getShortType())); + } } } + // from our own class + final VariantsProcessor processor = new VariantsProcessor( + referenceExpression, new PyResolveUtil.FilterNotInstance(myClass), underscore_filter + ); + myClass.processDeclarations(processor, ResolveState.initial(), null, referenceExpression); if (names_already != null) { for (LookupElement le : processor.getResultList()) { String name = le.getLookupString(); if (names_already.contains(name)) continue; + if (! within_our_class && isClassPrivate(name)) continue; names_already.add(name); ret.add(le); } @@ -137,9 +151,10 @@ public class PyClassType implements PyType { Object[] ancestry = (new PyClassType(ancestor, true)).getCompletionVariants(referenceExpression, context); for (Object ob : ancestry) { if (ob instanceof LookupElementBuilder) { - ret.add(((LookupElementBuilder)ob).setTypeText(ancestor.getName())); + final LookupElementBuilder lookup_elt = (LookupElementBuilder)ob; + if (! isClassPrivate(lookup_elt.getLookupString())) ret.add(lookup_elt.setTypeText(ancestor.getName())); } else { - ret.add(ob); + if (! isClassPrivate(ob.toString())) ret.add(ob); } } ret.addAll(Arrays.asList(ancestry)); @@ -147,6 +162,10 @@ public class PyClassType implements PyType { return ret.toArray(); } + private static boolean isClassPrivate(String lookup_string) { + return lookup_string.startsWith("__") && ! lookup_string.endsWith("__"); + } + public String getName() { PyClass cls = getPyClass(); if (cls != null) diff --git a/python/testData/completion/classPrivate.after.py b/python/testData/completion/classPrivate.after.py new file mode 100644 index 000000000000..300f94e98e90 --- /dev/null +++ b/python/testData/completion/classPrivate.after.py @@ -0,0 +1,3 @@ +class Foo: + __BOO = 1 + z = __BOO + 1 diff --git a/python/testData/completion/classPrivate.py b/python/testData/completion/classPrivate.py new file mode 100644 index 000000000000..3b76dc54ee0f --- /dev/null +++ b/python/testData/completion/classPrivate.py @@ -0,0 +1,3 @@ +class Foo: + __BOO = 1 + z = __B + 1 diff --git a/python/testData/completion/classPrivateInMethod.after.py b/python/testData/completion/classPrivateInMethod.after.py new file mode 100644 index 000000000000..cd96793db593 --- /dev/null +++ b/python/testData/completion/classPrivateInMethod.after.py @@ -0,0 +1,4 @@ +class Foo: + __BOO = 1 + def foo(self): + z = self.__B diff --git a/python/testData/completion/classPrivateInMethod.py b/python/testData/completion/classPrivateInMethod.py new file mode 100644 index 000000000000..be48176abfd4 --- /dev/null +++ b/python/testData/completion/classPrivateInMethod.py @@ -0,0 +1,4 @@ +class Foo: + __BOO = 1 + def foo(self): + z = self.__B diff --git a/python/testData/completion/classPrivateNotInherited.after.py b/python/testData/completion/classPrivateNotInherited.after.py new file mode 100644 index 000000000000..e4998cd00efe --- /dev/null +++ b/python/testData/completion/classPrivateNotInherited.after.py @@ -0,0 +1,6 @@ +class Foo: + __BOO = 1 + + +class Boo(Foo): + z = __B diff --git a/python/testData/completion/classPrivateNotInherited.py b/python/testData/completion/classPrivateNotInherited.py new file mode 100644 index 000000000000..913d6cae65c7 --- /dev/null +++ b/python/testData/completion/classPrivateNotInherited.py @@ -0,0 +1,6 @@ +class Foo: + __BOO = 1 + + +class Boo(Foo): + z = __B diff --git a/python/testData/completion/classPrivateNotPublic.after.py b/python/testData/completion/classPrivateNotPublic.after.py new file mode 100644 index 000000000000..f47f8e8dceb7 --- /dev/null +++ b/python/testData/completion/classPrivateNotPublic.after.py @@ -0,0 +1,4 @@ +class Foo: + __BOO = 1 + +z = Foo.__B diff --git a/python/testData/completion/classPrivateNotPublic.py b/python/testData/completion/classPrivateNotPublic.py new file mode 100644 index 000000000000..4841f2baf48a --- /dev/null +++ b/python/testData/completion/classPrivateNotPublic.py @@ -0,0 +1,4 @@ +class Foo: + __BOO = 1 + +z = Foo.__B diff --git a/python/testData/completion/oneUnderscore.after.py b/python/testData/completion/oneUnderscore.after.py new file mode 100644 index 000000000000..48d8dd898310 --- /dev/null +++ b/python/testData/completion/oneUnderscore.after.py @@ -0,0 +1,4 @@ +_zoo = 1 +__zar = 2 + +_zoo diff --git a/python/testData/completion/oneUnderscore.py b/python/testData/completion/oneUnderscore.py new file mode 100644 index 000000000000..9919f8ad8769 --- /dev/null +++ b/python/testData/completion/oneUnderscore.py @@ -0,0 +1,4 @@ +_zoo = 1 +__zar = 2 + +_z diff --git a/python/testData/completion/twoUnderscores.after.py b/python/testData/completion/twoUnderscores.after.py new file mode 100644 index 000000000000..c6dc064491bc --- /dev/null +++ b/python/testData/completion/twoUnderscores.after.py @@ -0,0 +1,4 @@ +_zoo = 1 +__zar = 2 + +__zar diff --git a/python/testData/completion/twoUnderscores.py b/python/testData/completion/twoUnderscores.py new file mode 100644 index 000000000000..405f902111ae --- /dev/null +++ b/python/testData/completion/twoUnderscores.py @@ -0,0 +1,4 @@ +_zoo = 1 +__zar = 2 + +__z diff --git a/python/testData/completion/twoUnderscoresNotOne.after.py b/python/testData/completion/twoUnderscoresNotOne.after.py new file mode 100644 index 000000000000..48d8dd898310 --- /dev/null +++ b/python/testData/completion/twoUnderscoresNotOne.after.py @@ -0,0 +1,4 @@ +_zoo = 1 +__zar = 2 + +_zoo diff --git a/python/testData/completion/twoUnderscoresNotOne.py b/python/testData/completion/twoUnderscoresNotOne.py new file mode 100644 index 000000000000..989f74e39e58 --- /dev/null +++ b/python/testData/completion/twoUnderscoresNotOne.py @@ -0,0 +1,4 @@ +_zoo = 1 +__zar = 2 + +_ diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index 08cafb98cd4c..7877f94df021 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -55,6 +55,30 @@ public class PythonCompletionTest extends PyLightFixtureTestCase { doTest(); } + public void testClassPrivate() throws Exception { + doTest(); + } + + public void testClassPrivateNotInherited() throws Exception { + doTest(); + } + + public void testClassPrivateNotPublic() throws Exception { + doTest(); + } + + public void testTwoUnderscores() throws Exception { + doTest(); + } + + public void testOneUnderscore() throws Exception { + doTest(); + } + + public void testTwoUnderscoresNotOne() throws Exception { + doTest(); + } + public void testPy255() throws Exception { final String dirname = "completion/"; final String testName = dirname + "moduleClass";