when completing method calls, auto-insert parentheses and show parameter info (PY-437)

This commit is contained in:
Dmitry Jemerov
2010-03-29 20:28:45 +04:00
parent dfc9f20231
commit a8279cd413
9 changed files with 117 additions and 57 deletions
@@ -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();
}
@@ -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<LookupElement> {
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;
}
}
@@ -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<PyFunction.Flag> 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<PyFunction.Flag> 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) {
@@ -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
@@ -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
@@ -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<String, LookupElement> myVariants = new HashMap<String, LookupElement>();
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);
}
@@ -116,7 +116,7 @@ public class PyClassType implements PyType {
public Object[] getCompletionVariants(final PyReferenceExpression referenceExpression, ProcessingContext context) {
Set<String> 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<Object> ret = new ArrayList<Object>();
for(PyClassMembersProvider provider: Extensions.getExtensions(PyClassMembersProvider.EP_NAME)) {
@@ -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<Object> result = new ArrayList<Object>();
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()) {
@@ -1,3 +1,3 @@
class A:
def method(self): pass
def test(self): self.method
def test(self): self.method()