Cleanup VariantsProcessor and CompletionVariantsProcessor

This commit is contained in:
Semyon Proshev
2017-08-18 16:03:21 +03:00
parent 2efdd023ea
commit 98d1343e4c
5 changed files with 96 additions and 75 deletions
@@ -465,9 +465,9 @@ public class PyFileImpl extends PsiFileBase implements PyFile, PyExpression {
@NotNull
public Iterable<PyElement> iterateNames() {
final List<PyElement> result = new ArrayList<>();
VariantsProcessor processor = new VariantsProcessor(this) {
final VariantsProcessor processor = new VariantsProcessor(this) {
@Override
protected void addElement(String name, PsiElement element) {
protected void addElement(@NotNull String name, @NotNull PsiElement element) {
element = PyUtil.turnDirIntoInit(element);
if (element instanceof PyElement) {
result.add((PyElement)element);
@@ -35,6 +35,7 @@ import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.impl.PyPsiUtils;
import com.jetbrains.python.psi.types.PyCallableParameter;
import com.jetbrains.python.psi.types.TypeEvalContext;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
@@ -44,24 +45,35 @@ import java.util.*;
* @author yole
*/
public class CompletionVariantsProcessor extends VariantsProcessor {
@NotNull
private final Map<String, LookupElement> myVariants = new HashMap<>();
private boolean mySuppressParentheses = false;
private final boolean mySuppressParentheses;
public CompletionVariantsProcessor(PsiElement context) {
super(context);
mySuppressParentheses = false;
}
public CompletionVariantsProcessor(PsiElement context,
@Nullable Condition<PsiElement> nodeFilter,
@Nullable Condition<String> nameFilter) {
super(context, nodeFilter, nameFilter);
mySuppressParentheses = false;
}
public void suppressParentheses() {
mySuppressParentheses = true;
public CompletionVariantsProcessor(PsiElement context,
@Nullable Condition<PsiElement> nodeFilter,
@Nullable Condition<String> nameFilter,
boolean plainNamesOnly,
boolean suppressParentheses) {
super(context, nodeFilter, nameFilter, plainNamesOnly);
mySuppressParentheses = suppressParentheses;
}
private LookupElementBuilder setupItem(LookupElementBuilder item) {
@NotNull
private LookupElementBuilder setupItem(@NotNull LookupElementBuilder item) {
final PsiElement element = item.getPsiElement();
if (!myPlainNamesOnly) {
if (!mySuppressParentheses &&
@@ -115,9 +127,9 @@ public class CompletionVariantsProcessor extends VariantsProcessor {
return item;
}
private static boolean isSingleArgDecoratorCall(PsiElement elementInCall, PyFunction callee) {
private static boolean isSingleArgDecoratorCall(@Nullable PsiElement elementInCall, @NotNull PyFunction callee) {
// special case hack to avoid the need of patching generator3.py
PyClass containingClass = callee.getContainingClass();
final PyClass containingClass = callee.getContainingClass();
if (containingClass != null && PyNames.PROPERTY.equals(containingClass.getName()) &&
PyBuiltinCache.getInstance(elementInCall).isBuiltin(containingClass)) {
return true;
@@ -126,24 +138,26 @@ public class CompletionVariantsProcessor extends VariantsProcessor {
if (callee.getParameterList().getParameters().length > 1) {
return false;
}
PyDecorator decorator = PsiTreeUtil.getParentOfType(elementInCall, PyDecorator.class);
final PyDecorator decorator = PsiTreeUtil.getParentOfType(elementInCall, PyDecorator.class);
if (decorator == null) {
return false;
}
return PsiTreeUtil.isAncestor(decorator.getCallee(), elementInCall, false);
}
@NotNull
public LookupElement[] getResult() {
final Collection<LookupElement> variants = myVariants.values();
return variants.toArray(new LookupElement[variants.size()]);
}
@NotNull
public List<LookupElement> getResultList() {
return new ArrayList<>(myVariants.values());
}
@Override
protected void addElement(String name, PsiElement element) {
protected void addElement(@NotNull String name, @NotNull PsiElement element) {
if (PyUtil.isClassPrivateName(name) && !PyUtil.inSameFile(element, myContext)) {
return;
}
@@ -151,11 +165,10 @@ public class CompletionVariantsProcessor extends VariantsProcessor {
}
@Override
protected void addImportedElement(String referencedName, PyElement expr) {
Icon icon = expr.getIcon(0);
protected void addImportedElement(@NotNull String name, @NotNull PyElement element) {
Icon icon = element.getIcon(0);
// things like PyTargetExpression cannot have a general icon, but here we only have variables
if (icon == null) icon = PlatformIcons.VARIABLE_ICON;
LookupElementBuilder lookupItem = setupItem(LookupElementBuilder.createWithSmartPointer(referencedName, expr).withIcon(icon));
myVariants.put(referencedName, lookupItem);
myVariants.put(name, setupItem(LookupElementBuilder.createWithSmartPointer(name, element).withIcon(icon)));
}
}
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -23,77 +23,80 @@ import com.intellij.psi.PsiInvalidElementAccessException;
import com.intellij.psi.PsiNamedElement;
import com.intellij.psi.ResolveState;
import com.intellij.psi.scope.PsiScopeProcessor;
import com.jetbrains.python.psi.*;
import com.intellij.psi.util.QualifiedName;
import com.jetbrains.python.psi.*;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
public abstract class VariantsProcessor implements PsiScopeProcessor {
protected final PsiElement myContext;
protected Condition<PsiElement> myNodeFilter;
protected Condition<String> myNameFilter;
protected boolean myPlainNamesOnly = false; // if true, add insert handlers to known things like functions
private List<String> myAllowedNames;
private final List<String> mySeenNames = new ArrayList<>();
protected final PsiElement myContext;
@Nullable
protected final Condition<PsiElement> myNodeFilter;
@Nullable
protected final Condition<String> myNameFilter;
protected final boolean myPlainNamesOnly; // if true, add insert handlers to known things like functions
@Nullable
private Set<String> myAllowedNames;
@NotNull
private final Set<String> mySeenNames = new HashSet<>();
public VariantsProcessor(PsiElement context) {
// empty
myContext = context;
this(context, null, null, false);
}
public VariantsProcessor(PsiElement context, @Nullable final Condition<PsiElement> nodeFilter, @Nullable final Condition<String> nameFilter) {
public VariantsProcessor(PsiElement context, @Nullable Condition<PsiElement> nodeFilter, @Nullable Condition<String> nameFilter) {
this(context, nodeFilter, nameFilter, false);
}
public VariantsProcessor(PsiElement context,
@Nullable Condition<PsiElement> nodeFilter,
@Nullable Condition<String> nameFilter,
boolean plainNamesOnly) {
myContext = context;
myNodeFilter = nodeFilter;
myNameFilter = nameFilter;
}
public boolean isPlainNamesOnly() {
return myPlainNamesOnly;
}
public void setPlainNamesOnly(boolean plainNamesOnly) {
myPlainNamesOnly = plainNamesOnly;
}
@Override
public boolean execute(@NotNull PsiElement element, @NotNull ResolveState substitutor) {
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 = PyUtil.getElementNameWithoutExtension(psiNamedElement);
if (name != null && nameIsAcceptable(name)) {
addElement(name, psiNamedElement);
final PsiNamedElement namedElement = (PsiNamedElement)element;
final String name = PyUtil.getElementNameWithoutExtension(namedElement);
if (nameIsAcceptable(name)) {
addElement(name, namedElement);
}
}
else if (element instanceof PyReferenceExpression) {
PyReferenceExpression expr = (PyReferenceExpression)element;
String referencedName = expr.getReferencedName();
if (nameIsAcceptable(referencedName)) {
addElement(referencedName, expr);
final PyReferenceExpression referenceExpression = (PyReferenceExpression)element;
final String name = referenceExpression.getReferencedName();
if (nameIsAcceptable(name)) {
addElement(name, referenceExpression);
}
}
else if (element instanceof PyImportedNameDefiner) {
boolean handledAsImported = false;
if (element instanceof PyImportElement) {
final PyImportElement importElement = (PyImportElement)element;
handledAsImported = handleImportElement(importElement);
}
if (! handledAsImported) {
if (!(element instanceof PyImportElement) || !handleImportElement((PyImportElement)element)) {
final PyImportedNameDefiner definer = (PyImportedNameDefiner)element;
for (PyElement expr : definer.iterateNames()) {
if (expr != null && expr != myContext) { // NOTE: maybe rather have SingleIterables skip nulls outright?
if (!expr.isValid()) {
throw new PsiInvalidElementAccessException(expr, "Definer: " + definer);
}
String referencedName = expr instanceof PyFile ? FileUtil.getNameWithoutExtension(((PyFile)expr).getName()) : expr.getName();
if (referencedName != null && nameIsAcceptable(referencedName)) {
addImportedElement(referencedName, expr);
final String name = expr instanceof PyFile ? FileUtil.getNameWithoutExtension(((PyFile)expr).getName()) : expr.getName();
if (nameIsAcceptable(name)) {
addImportedElement(name, expr);
}
}
}
@@ -103,11 +106,11 @@ public abstract class VariantsProcessor implements PsiScopeProcessor {
return true;
}
protected boolean handleImportElement(PyImportElement importElement) {
private boolean handleImportElement(@NotNull PyImportElement importElement) {
final QualifiedName qName = importElement.getImportedQName();
if (qName != null && qName.getComponentCount() == 1) {
String name = importElement.getAsName() != null ? importElement.getAsName() : qName.getLastComponent();
if (name != null && nameIsAcceptable(name)) {
final String name = importElement.getAsName() != null ? importElement.getAsName() : qName.getLastComponent();
if (nameIsAcceptable(name)) {
final PsiElement resolved = importElement.resolve();
if (resolved instanceof PsiNamedElement) {
addElement(name, resolved);
@@ -118,15 +121,16 @@ public abstract class VariantsProcessor implements PsiScopeProcessor {
return false;
}
protected void addElement(String name, PsiElement psiNamedElement) {
protected void addElement(@NotNull String name, @NotNull PsiElement element) {
mySeenNames.add(name);
}
protected void addImportedElement(String referencedName, PyElement expr) {
addElement(referencedName, expr);
protected void addImportedElement(@NotNull String name, @NotNull PyElement element) {
addElement(name, element);
}
private boolean nameIsAcceptable(String name) {
@Contract("null -> false")
private boolean nameIsAcceptable(@Nullable String name) {
if (name == null) {
return false;
}
@@ -152,7 +156,12 @@ public abstract class VariantsProcessor implements PsiScopeProcessor {
public void handleEvent(@NotNull Event event, Object associated) {
}
public void setAllowedNames(List<String> namesFilter) {
myAllowedNames = namesFilter;
public void setAllowedNames(@Nullable Collection<String> allowedNames) {
if (allowedNames == null) {
myAllowedNames = null;
}
else {
myAllowedNames = new HashSet<>(allowedNames);
}
}
}
@@ -720,11 +720,8 @@ public class PyClassTypeImpl extends UserDataHolderBase implements PyClassType {
boolean withinOurClass = containingClass == getPyClass() || isInSuperCall(expressionHook);
final CompletionVariantsProcessor processor = new CompletionVariantsProcessor(
expressionHook, new FilterNotInstance(myClass), null
expressionHook, new FilterNotInstance(myClass), null, false, suppressParentheses
);
if (suppressParentheses) {
processor.suppressParentheses();
}
myClass.processClassLevelDeclarations(processor);
// We are here because of completion (see call stack), so we use code complete here
@@ -1,5 +1,5 @@
/*
* Copyright 2000-2014 JetBrains s.r.o.
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@ import com.intellij.codeInsight.lookup.LookupElement;
import com.intellij.codeInsight.lookup.LookupElementBuilder;
import com.intellij.openapi.extensions.Extensions;
import com.intellij.openapi.roots.FileIndexFacade;
import com.intellij.openapi.util.Condition;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.io.FileUtil;
@@ -367,14 +368,15 @@ public class PyModuleType implements PyType { // Modules don't descend from obje
private static CompletionVariantsProcessor createCompletionVariantsProcessor(PsiElement location,
boolean suppressParentheses,
PointInImport point) {
final CompletionVariantsProcessor processor = new CompletionVariantsProcessor(location,
psiElement -> !(psiElement instanceof PyImportElement) ||
PsiTreeUtil.getParentOfType(psiElement, PyImportStatementBase.class) instanceof PyFromImportStatement, null);
if (suppressParentheses) {
processor.suppressParentheses();
}
processor.setPlainNamesOnly(point == PointInImport.AS_NAME); // no parens after imported function names
return processor;
final Condition<PsiElement> nodeFilter =
psiElement -> !(psiElement instanceof PyImportElement) ||
PsiTreeUtil.getParentOfType(psiElement, PyImportStatementBase.class) instanceof PyFromImportStatement;
return new CompletionVariantsProcessor(location,
nodeFilter,
null,
point == PointInImport.AS_NAME, // no parens after imported function names
suppressParentheses);
}
@NotNull