diff --git a/python/src/com/jetbrains/python/psi/PyUtil.java b/python/src/com/jetbrains/python/psi/PyUtil.java index f6f0c0703e7f..e135709437fc 100644 --- a/python/src/com/jetbrains/python/psi/PyUtil.java +++ b/python/src/com/jetbrains/python/psi/PyUtil.java @@ -24,10 +24,7 @@ import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.ui.popup.Balloon; import com.intellij.openapi.ui.popup.JBPopupFactory; import com.intellij.openapi.wm.WindowManager; -import com.intellij.psi.PsiDocumentManager; -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiFile; -import com.intellij.psi.TokenType; +import com.intellij.psi.*; import com.intellij.ui.awt.RelativePoint; import com.intellij.util.IncorrectOperationException; import com.jetbrains.python.PythonLanguage; @@ -40,6 +37,7 @@ import java.awt.*; import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.regex.Pattern; public class PyUtil { private PyUtil() { @@ -202,6 +200,14 @@ public class PyUtil { return node != null && node.getElementType().equals(TokenType.WHITE_SPACE); } + + @Nullable + public static PsiElement getFirstNonCommentAfter(PsiElement start) { + PsiElement seeker = start; + while (seeker instanceof PsiWhiteSpace || seeker instanceof PsiComment) seeker = seeker.getNextSibling(); + return seeker; + } + /** * @see PyUtil#flattenedParens */ @@ -395,4 +401,16 @@ public class PyUtil { if (! isLast) node.addChild(gen.createComma(project), beforeThis); } + + private static Pattern IDENTIFIER_PATTERN = Pattern.compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*"); + + /** + * @param name to be tested + * @return true iff the name is a valid Python identifier. + * Note: it allows unicode names which only Py3k supports. + */ + public static boolean isIdentifier(String name) { + return IDENTIFIER_PATTERN.matcher(name).matches(); + } + } diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java index 5c6f99e2f103..b59e80a5bc65 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceExpressionImpl.java @@ -30,6 +30,7 @@ import com.intellij.psi.impl.source.resolve.ResolveCache; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; +import com.intellij.util.ProcessingContext; import com.intellij.util.containers.SortedList; import com.jetbrains.python.PyElementTypes; import com.jetbrains.python.PyIcons; @@ -391,15 +392,24 @@ public class PyReferenceExpressionImpl extends PyElementImpl implements PyRefere final PyExpression qualifier = getQualifier(); if (qualifier != null) { PyType qualifierType = qualifier.getType(); + ProcessingContext ctx = new ProcessingContext(); + final Set names_already = new HashSet(); + ctx.put(PyType.CTX_NAMES, names_already); if (qualifierType != null) { - ArrayList variants = new ArrayList(); + Collection variants = new ArrayList(); if (qualifier instanceof PyQualifiedExpression) { Collection attrs = collectAssignedAttributes((PyQualifiedExpression)qualifier); variants.addAll(attrs); - Collections.addAll(variants, qualifierType.getCompletionVariants(this)); + for (PyExpression ex : attrs) { + if (ex instanceof PyReferenceExpression) { + PyReferenceExpression refex = (PyReferenceExpression)ex; + names_already.add(refex.getReferencedName()); + } + } + Collections.addAll(variants, qualifierType.getCompletionVariants(this, ctx)); return variants.toArray(); } - else return qualifierType.getCompletionVariants(this); + else return qualifierType.getCompletionVariants(this, ctx); } return NO_VARIANTS; } diff --git a/python/src/com/jetbrains/python/psi/types/PyClassType.java b/python/src/com/jetbrains/python/psi/types/PyClassType.java index d091a0b2fa3f..f2303895fb04 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassType.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassType.java @@ -1,14 +1,17 @@ package com.jetbrains.python.psi.types; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.codeInsight.lookup.LookupItem; import com.intellij.psi.PsiElement; import com.intellij.psi.ResolveState; +import com.intellij.util.ProcessingContext; import com.jetbrains.python.psi.PyClass; import com.jetbrains.python.psi.PyExpression; import com.jetbrains.python.psi.PyReferenceExpression; +import com.jetbrains.python.psi.impl.PyBuiltinCache; import com.jetbrains.python.psi.resolve.PyResolveUtil; import com.jetbrains.python.psi.resolve.ResolveProcessor; import com.jetbrains.python.psi.resolve.VariantsProcessor; -import com.jetbrains.python.psi.impl.PyBuiltinCache; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -95,13 +98,30 @@ public class PyClassType implements PyType { return null; } - public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression) { + public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression, ProcessingContext context) { + Set names_already = context.get(PyType.CTX_NAMES); final VariantsProcessor processor = new VariantsProcessor(new PyResolveUtil.FilterNotInstance(myClass)); myClass.processDeclarations(processor, ResolveState.initial(), null, referenceExpression); List ret = new ArrayList(); - ret.addAll(processor.getResultList()); + if (names_already != null) { + for (LookupElement le : processor.getResultList()) { + String name = le.getLookupString(); + if (names_already.contains(name)) continue; + names_already.add(name); + ret.add(le); + } + } + else ret.addAll(processor.getResultList()); for (PyClass ancestor : myClass.getSuperClasses()) { - ret.addAll(Arrays.asList((new PyClassType(ancestor, true)).getCompletionVariants(referenceExpression))); + Object[] ancestry = (new PyClassType(ancestor, true)).getCompletionVariants(referenceExpression, context); + for (Object ob : ancestry) { + if (ob instanceof LookupItem) { + LookupItem item = (LookupItem)ob; + item.setAttribute(item.TAIL_TEXT_ATTR, " | " + ancestor.getName()); // from where it's inherited + item.setAttribute(item.TAIL_TEXT_SMALL_ATTR, ""); // make it gray + } + } + ret.addAll(Arrays.asList(ancestry)); } return ret.toArray(); } diff --git a/python/src/com/jetbrains/python/psi/types/PyModuleType.java b/python/src/com/jetbrains/python/psi/types/PyModuleType.java index eb65609c1fe4..204df513931a 100644 --- a/python/src/com/jetbrains/python/psi/types/PyModuleType.java +++ b/python/src/com/jetbrains/python/psi/types/PyModuleType.java @@ -1,13 +1,16 @@ package com.jetbrains.python.psi.types; +import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementFactory; import com.intellij.codeInsight.lookup.LookupItem; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.util.ProcessingContext; import com.jetbrains.python.PyNames; import com.jetbrains.python.psi.PyFile; -import com.jetbrains.python.psi.PyReferenceExpression; import com.jetbrains.python.psi.PyImportElement; +import com.jetbrains.python.psi.PyReferenceExpression; +import com.jetbrains.python.psi.PyUtil; import com.jetbrains.python.psi.resolve.ResolveImportUtil; import com.jetbrains.python.psi.resolve.VariantsProcessor; import org.jetbrains.annotations.NotNull; @@ -22,6 +25,7 @@ public class PyModuleType implements PyType { // Maybe make it a PyClassType ref private final PsiFile myModule; protected static Set ourPossibleFields; + static { ourPossibleFields = new HashSet(); /*ourPossibleFields.addAll(PyObjectType.ourPossibleFields);*/ @@ -68,18 +72,33 @@ public class PyModuleType implements PyType { // Maybe make it a PyClassType ref return result; } - public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression) { + public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression, ProcessingContext context) { + Set names_already = context.get(PyType.CTX_NAMES); List result = new ArrayList(); if (PsiTreeUtil.getParentOfType(referenceExpression, PyImportElement.class) == null) { // we're not in an import final VariantsProcessor processor = new VariantsProcessor(); myModule.processDeclarations(processor, ResolveState.initial(), null, referenceExpression); - result.addAll(processor.getResultList()); + if (names_already != null) { + for (LookupElement le : processor.getResultList()) { + String name = le.getLookupString(); + if (!names_already.contains(name)) { + result.add(le); + names_already.add(name); + } + } + } + else result.addAll(processor.getResultList()); } LookupElementFactory maker = LookupElementFactory.getInstance(); for (PsiFileSystemItem pfsi : getSubmodulesList()) { String s = pfsi.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); + if (!PyUtil.isIdentifier(s)) continue; // file is e.g. a script with a strange name, not a module + if (names_already != null) { + if (names_already.contains(s)) continue; + else names_already.add(s); + } LookupItem item = (LookupItem)maker.createLookupElement(pfsi, s); item.setPresentableText(s); // not raw filename result.add(item); diff --git a/python/src/com/jetbrains/python/psi/types/PyNoneType.java b/python/src/com/jetbrains/python/psi/types/PyNoneType.java index be5ccc015519..0c38df0dda5e 100644 --- a/python/src/com/jetbrains/python/psi/types/PyNoneType.java +++ b/python/src/com/jetbrains/python/psi/types/PyNoneType.java @@ -2,6 +2,7 @@ package com.jetbrains.python.psi.types; import com.intellij.psi.PsiElement; import com.intellij.util.ArrayUtil; +import com.intellij.util.ProcessingContext; import com.jetbrains.python.psi.PyReferenceExpression; /** @@ -17,7 +18,7 @@ public class PyNoneType implements PyType { // TODO must extend ClassType. It's return null; } - public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression) { + public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression, ProcessingContext context) { return ArrayUtil.EMPTY_OBJECT_ARRAY; } diff --git a/python/src/com/jetbrains/python/psi/types/PyType.java b/python/src/com/jetbrains/python/psi/types/PyType.java index 11ce2ef84611..36d37936fd96 100644 --- a/python/src/com/jetbrains/python/psi/types/PyType.java +++ b/python/src/com/jetbrains/python/psi/types/PyType.java @@ -1,9 +1,13 @@ package com.jetbrains.python.psi.types; +import com.intellij.openapi.util.Key; import com.intellij.psi.PsiElement; +import com.intellij.util.ProcessingContext; import com.jetbrains.python.psi.PyReferenceExpression; import org.jetbrains.annotations.Nullable; +import java.util.Set; + /** * Represents a type of an expression. * @author yole @@ -20,10 +24,16 @@ public interface PyType { /** * Proposes completion variants from type's attributes. - * @param referenceExpression - * @return + * @param referenceExpression which is to be completed + * @param context to share state between nested invocations + * @return completion variants good for {@link com.intellij.psi.PsiReference#getVariants} return value. */ - Object[] getCompletionVariants(final PyReferenceExpression referenceExpression); + Object[] getCompletionVariants(final PyReferenceExpression referenceExpression, ProcessingContext context); + + /** + * Context key for access to a set of names already found by variant search. + */ + Key> CTX_NAMES = new Key>("Completion variants names"); /** * @return name of the type