diff --git a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java index 1b486ffd65d8..e5ceefd33c00 100644 --- a/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java +++ b/python/pluginSrc/com/jetbrains/python/psi/impl/PyJavaClassType.java @@ -27,7 +27,7 @@ public class PyJavaClassType implements PyType { } public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression, ProcessingContext context) { - final VariantsProcessor processor = new VariantsProcessor(); + final VariantsProcessor processor = new VariantsProcessor(referenceExpression); myClass.processDeclarations(processor, ResolveState.initial(), null, referenceExpression); return processor.getResult(); } diff --git a/python/src/com/jetbrains/python/codeInsight/PyFunctionInsertHandler.java b/python/src/com/jetbrains/python/codeInsight/PyFunctionInsertHandler.java new file mode 100644 index 000000000000..f22bfb78ee64 --- /dev/null +++ b/python/src/com/jetbrains/python/codeInsight/PyFunctionInsertHandler.java @@ -0,0 +1,41 @@ +package com.jetbrains.python.codeInsight; + +import com.intellij.codeInsight.AutoPopupController; +import com.intellij.codeInsight.completion.InsertionContext; +import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler; +import com.intellij.codeInsight.lookup.LookupElement; +import com.intellij.psi.PsiElement; +import com.intellij.psi.util.PsiTreeUtil; +import com.jetbrains.python.psi.PyFunction; +import com.jetbrains.python.psi.PyReferenceExpression; +import com.jetbrains.python.psi.impl.PyCallExpressionHelper; + +/** + * @author yole + */ +public class PyFunctionInsertHandler extends ParenthesesInsertHandler { + public static PyFunctionInsertHandler INSTANCE = new PyFunctionInsertHandler(); + + @Override + public void handleInsert(InsertionContext context, LookupElement item) { + super.handleInsert(context, item); + if (hasParams(context, item)) { + AutoPopupController.getInstance(context.getProject()).autoPopupParameterInfo(context.getEditor(), (PyFunction) item.getObject()); + } + } + + @Override + protected boolean placeCaretInsideParentheses(InsertionContext context, LookupElement item) { + return hasParams(context, item); + } + + private static boolean hasParams(InsertionContext context, LookupElement item) { + PyFunction function = (PyFunction) item.getObject(); + final PsiElement element = context.getFile().findElementAt(context.getStartOffset()); + PyReferenceExpression refExpr = PsiTreeUtil.getParentOfType(element, PyReferenceExpression.class); + int implicitArgsCount = refExpr != null + ? PyCallExpressionHelper.getImplicitArgumentCount(refExpr, function) + : 0; + return function.getParameterList().getParameters().length > implicitArgsCount; + } +} diff --git a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java index 52b9adc462b1..321136253f06 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java +++ b/python/src/com/jetbrains/python/psi/impl/PyCallExpressionHelper.java @@ -3,13 +3,11 @@ package com.jetbrains.python.psi.impl; import com.intellij.openapi.util.Pair; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiNamedElement; -import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.IncorrectOperationException; import com.jetbrains.python.PyNames; import com.jetbrains.python.PythonLanguage; import com.jetbrains.python.psi.*; import com.jetbrains.python.psi.types.PyClassType; -import com.jetbrains.python.psi.PyDecorator; import com.jetbrains.python.psi.types.PyType; import org.jetbrains.annotations.Nullable; @@ -97,55 +95,71 @@ public class PyCallExpressionHelper { } if (resolved instanceof PyFunction) { EnumSet flags = EnumSet.noneOf(PyFunction.Flag.class); - int implicit_offset = 0; - boolean is_by_instance = isByInstance(us); - if (is_by_instance) implicit_offset += 1; - // wrapped flags? - if (wrapped_flag != null) { - flags.add(wrapped_flag); - flags.add(PyFunction.Flag.WRAPPED); - if (wrapped_flag == PyFunction.Flag.STATICMETHOD && implicit_offset > 0) implicit_offset -= 1; // might have marked it as implicit 'self' - if (wrapped_flag == PyFunction.Flag.CLASSMETHOD && ! is_by_instance) implicit_offset += 1; // Both Foo.method() and foo.method() have implicit the first arg - } - // decorators? - PyFunction method = (PyFunction)resolved; // constructor call? - if (PyNames.INIT.equals(method.getName())) { - String refName = us.getCallee() instanceof PyReferenceExpression - ? ((PyReferenceExpression) us.getCallee()).getReferencedName() - : null; - if (!PyNames.INIT.equals(refName)) { // PY-312 - implicit_offset += 1; - } - } - // look for closest decorator - PyDecoratorList decolist = method.getDecoratorList(); - if (decolist != null) { - PyDecorator[] decos = decolist.getDecorators(); - // TODO: look for all decorators - if (decos.length == 1) { - PyDecorator deco = decos[0]; - String deconame = deco.getName(); - if (deco.isBuiltin()) { - if (PyNames.STATICMETHOD.equals(deconame)) { - flags.add(PyFunction.Flag.STATICMETHOD); - if (implicit_offset > 0) implicit_offset -= 1; // might have marked it as implicit 'self' - } - else if (PyNames.CLASSMETHOD.equals(deconame)) { - flags.add(PyFunction.Flag.CLASSMETHOD); - if (! is_by_instance) implicit_offset += 1; // Both Foo.method() and foo.method() have implicit the first arg - } - // else could be custom decorator processing - } - } - } + int implicit_offset = getImplicitArgumentCount(us.getCallee(), (PyFunction) resolved, wrapped_flag, flags); return new PyCallExpression.PyMarkedFunction((PyFunction)resolved, flags, implicit_offset); } } return null; } - protected static boolean isByInstance(PyCallExpression us) { - PyExpression callee = us.getCallee(); + public static int getImplicitArgumentCount(final PyExpression callReference, PyFunction functionBeingCalled) { + return getImplicitArgumentCount(callReference, functionBeingCalled, null, null); + } + + private static int getImplicitArgumentCount(final PyExpression callReference, + PyFunction method, + @Nullable PyFunction.Flag wrapped_flag, + @Nullable EnumSet flags) { + int implicit_offset = 0; + boolean is_by_instance = isByInstance(callReference); + if (is_by_instance) implicit_offset += 1; + // wrapped flags? + if (wrapped_flag != null) { + if (flags != null) { + flags.add(wrapped_flag); + flags.add(PyFunction.Flag.WRAPPED); + } + if (wrapped_flag == PyFunction.Flag.STATICMETHOD && implicit_offset > 0) implicit_offset -= 1; // might have marked it as implicit 'self' + if (wrapped_flag == PyFunction.Flag.CLASSMETHOD && ! is_by_instance) implicit_offset += 1; // Both Foo.method() and foo.method() have implicit the first arg + } + // decorators? + if (PyNames.INIT.equals(method.getName())) { + String refName = callReference instanceof PyReferenceExpression + ? ((PyReferenceExpression)callReference).getReferencedName() + : null; + if (!PyNames.INIT.equals(refName)) { // PY-312 + implicit_offset += 1; + } + } + // look for closest decorator + PyDecoratorList decolist = method.getDecoratorList(); + if (decolist != null) { + PyDecorator[] decos = decolist.getDecorators(); + // TODO: look for all decorators + if (decos.length == 1) { + PyDecorator deco = decos[0]; + String deconame = deco.getName(); + if (deco.isBuiltin()) { + if (PyNames.STATICMETHOD.equals(deconame)) { + if (flags != null) { + flags.add(PyFunction.Flag.STATICMETHOD); + } + if (implicit_offset > 0) implicit_offset -= 1; // might have marked it as implicit 'self' + } + else if (PyNames.CLASSMETHOD.equals(deconame)) { + if (flags != null) { + flags.add(PyFunction.Flag.CLASSMETHOD); + } + if (! is_by_instance) implicit_offset += 1; // Both Foo.method() and foo.method() have implicit the first arg + } + // else could be custom decorator processing + } + } + } + return implicit_offset; + } + + protected static boolean isByInstance(final PyExpression callee) { if (callee instanceof PyReferenceExpression) { PyExpression qualifier = ((PyReferenceExpression)callee).getQualifier(); if (qualifier != null) { diff --git a/python/src/com/jetbrains/python/psi/impl/PyReferenceImpl.java b/python/src/com/jetbrains/python/psi/impl/PyReferenceImpl.java index 60c0109ee2e8..08aae7fdf1af 100644 --- a/python/src/com/jetbrains/python/psi/impl/PyReferenceImpl.java +++ b/python/src/com/jetbrains/python/psi/impl/PyReferenceImpl.java @@ -276,7 +276,7 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference final PsiElement realContext = PyPsiUtils.getRealContext(myElement); // include our own names - final VariantsProcessor processor = new VariantsProcessor(); + final VariantsProcessor processor = new VariantsProcessor(myElement); PyResolveUtil.treeCrawlUp(processor, realContext); // names from here PyResolveUtil.scanOuterContext(processor, realContext); // possible names from around us at call time diff --git a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java index 5b6f0bf52474..6caf80b2d4af 100644 --- a/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java +++ b/python/src/com/jetbrains/python/psi/resolve/ResolveImportUtil.java @@ -685,7 +685,7 @@ public class ResolveImportUtil { if (mod_candidate instanceof PyExpression) { addImportedNames(from_import.getImportElements(), names_already); // don't propose already imported items // collect what's within module file - final VariantsProcessor processor = new VariantsProcessor(new PyResolveUtil.FilterNameNotIn(names_already)); + final VariantsProcessor processor = new VariantsProcessor(partial_ref, new PyResolveUtil.FilterNameNotIn(names_already)); PyResolveUtil.treeCrawlUp(processor, true, mod_candidate); variants.addAll(processor.getResultList()); // try to collect submodules diff --git a/python/src/com/jetbrains/python/psi/resolve/VariantsProcessor.java b/python/src/com/jetbrains/python/psi/resolve/VariantsProcessor.java index ae9b506ca587..1ae8bc7a4a0e 100644 --- a/python/src/com/jetbrains/python/psi/resolve/VariantsProcessor.java +++ b/python/src/com/jetbrains/python/psi/resolve/VariantsProcessor.java @@ -8,6 +8,7 @@ import com.intellij.psi.PsiNamedElement; import com.intellij.psi.ResolveState; import com.intellij.psi.scope.PsiScopeProcessor; import com.intellij.util.Icons; +import com.jetbrains.python.codeInsight.PyFunctionInsertHandler; import com.jetbrains.python.psi.*; import org.jetbrains.annotations.Nullable; @@ -17,14 +18,17 @@ import java.util.*; public class VariantsProcessor implements PsiScopeProcessor { private final Map myVariants = new HashMap(); + protected final PsiElement myContext; protected String myNotice; protected PyResolveUtil.Filter myFilter; - public VariantsProcessor() { + public VariantsProcessor(PsiElement context) { // empty + myContext = context; } - public VariantsProcessor(final PyResolveUtil.Filter filter) { + public VariantsProcessor(PsiElement context, final PyResolveUtil.Filter filter) { + myContext = context; myFilter = filter; } @@ -33,6 +37,9 @@ public class VariantsProcessor implements PsiScopeProcessor { } protected LookupElementBuilder setupItem(LookupElementBuilder item) { + if (item.getObject() instanceof PyFunction) { + item = item.setInsertHandler(PyFunctionInsertHandler.INSTANCE); + } if (myNotice != null) { return setItemNotice(item, myNotice); } diff --git a/python/src/com/jetbrains/python/psi/types/PyClassType.java b/python/src/com/jetbrains/python/psi/types/PyClassType.java index 1ebe843e3170..b01c5970ae2b 100644 --- a/python/src/com/jetbrains/python/psi/types/PyClassType.java +++ b/python/src/com/jetbrains/python/psi/types/PyClassType.java @@ -116,7 +116,7 @@ public class PyClassType implements PyType { 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)); + final VariantsProcessor processor = new VariantsProcessor(referenceExpression, new PyResolveUtil.FilterNotInstance(myClass)); myClass.processDeclarations(processor, ResolveState.initial(), null, referenceExpression); List ret = new ArrayList(); for(PyClassMembersProvider provider: Extensions.getExtensions(PyClassMembersProvider.EP_NAME)) { diff --git a/python/src/com/jetbrains/python/psi/types/PyModuleType.java b/python/src/com/jetbrains/python/psi/types/PyModuleType.java index 9056a5b56c21..c58fbd7e2f16 100644 --- a/python/src/com/jetbrains/python/psi/types/PyModuleType.java +++ b/python/src/com/jetbrains/python/psi/types/PyModuleType.java @@ -3,19 +3,17 @@ package com.jetbrains.python.psi.types; import com.intellij.codeInsight.lookup.LookupElement; import com.intellij.codeInsight.lookup.LookupElementBuilder; 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.PyFromImportStatement; -import com.jetbrains.python.psi.PyImportElement; import com.jetbrains.python.psi.PyReferenceExpression; import com.jetbrains.python.psi.resolve.ResolveImportUtil; -import static com.jetbrains.python.psi.resolve.ResolveImportUtil.ROLE_IN_IMPORT.*; import com.jetbrains.python.psi.resolve.VariantsProcessor; import org.jetbrains.annotations.NotNull; import java.util.*; + +import static com.jetbrains.python.psi.resolve.ResolveImportUtil.ROLE_IN_IMPORT.NONE; // .impl looks impure /** @@ -79,7 +77,7 @@ public class PyModuleType implements PyType { // Maybe make it a PyClassType ref List result = new ArrayList(); ResolveImportUtil.ROLE_IN_IMPORT role = ResolveImportUtil.getRoleInImport(referenceExpression.getReference()); if (role == NONE) { // when not inside import, add regular attributes - final VariantsProcessor processor = new VariantsProcessor(); + final VariantsProcessor processor = new VariantsProcessor(referenceExpression); myModule.processDeclarations(processor, ResolveState.initial(), null, referenceExpression); if (names_already != null) { for (LookupElement le : processor.getResultList()) { diff --git a/python/testData/completion/selfMethod.after.py b/python/testData/completion/selfMethod.after.py index b24be5e5d278..860760ff2b79 100644 --- a/python/testData/completion/selfMethod.after.py +++ b/python/testData/completion/selfMethod.after.py @@ -1,3 +1,3 @@ class A: def method(self): pass - def test(self): self.method \ No newline at end of file + def test(self): self.method() \ No newline at end of file