mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Use smart PSI pointers in Python LookupElements to avoid invalid elements in completion (PY-17801)
During completion while the user is typing the PSI structure of the current file may change significantly. The main reason is that Python doesn't have any braces around syntactic blocks, only indents, and some syntactically incorrect lines of partially typed code may break the block structure thus invalidating the completion results.
This commit is contained in:
@@ -1,12 +1,9 @@
|
||||
package org.jetbrains.plugins.ipnb.psi;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.codeInsight.completion.CompletionUtil;
|
||||
import com.intellij.codeInsight.lookup.LookupElement;
|
||||
import com.intellij.lang.annotation.HighlightSeverity;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.ResolveResult;
|
||||
import com.jetbrains.python.psi.PyQualifiedExpression;
|
||||
@@ -48,18 +45,7 @@ public class IpnbPyReference extends PyReferenceImpl {
|
||||
if (psiFile == null) continue;
|
||||
final CompletionVariantsProcessor processor = new CompletionVariantsProcessor(myElement);
|
||||
PyResolveUtil.scopeCrawlUp(processor, psiFile, null, null);
|
||||
|
||||
for (LookupElement e : processor.getResultList()) {
|
||||
final Object o = e.getObject();
|
||||
if (o instanceof PsiElement) {
|
||||
final PsiElement original = CompletionUtil.getOriginalElement((PsiElement)o);
|
||||
if (original == null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
variants.add(e);
|
||||
}
|
||||
|
||||
variants.addAll(getOriginalElements(processor));
|
||||
}
|
||||
}
|
||||
return variants.toArray();
|
||||
|
||||
@@ -75,14 +75,14 @@ public class PyDunderAllReference extends PsiReferenceBase<PyStringLiteralExpres
|
||||
final String name = ((PsiNamedElement)element).getName();
|
||||
if (name != null && PyUtil.getInitialUnderscores(name) == 0 && !seenNames.contains(name)) {
|
||||
seenNames.add(name);
|
||||
result.add(LookupElementBuilder.create((PsiNamedElement) element).withIcon(element.getIcon(0)));
|
||||
result.add(LookupElementBuilder.createWithSmartPointer(name, element).withIcon(element.getIcon(0)));
|
||||
}
|
||||
}
|
||||
else if (element instanceof PyImportElement) {
|
||||
final String visibleName = ((PyImportElement)element).getVisibleName();
|
||||
if (visibleName != null && !seenNames.contains(visibleName)) {
|
||||
seenNames.add(visibleName);
|
||||
result.add(LookupElementBuilder.create(element, visibleName));
|
||||
result.add(LookupElementBuilder.createWithSmartPointer(visibleName, element));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -23,6 +23,7 @@ import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.jetbrains.python.psi.PyClass;
|
||||
import com.jetbrains.python.psi.PyFunction;
|
||||
import com.jetbrains.python.psi.PyUtil;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
@@ -41,8 +42,8 @@ public class PyClassInsertHandler implements InsertHandler<LookupElement> {
|
||||
final int offset = context.getTailOffset();
|
||||
document.insertString(offset, "()");
|
||||
|
||||
PyClass pyClass = (PyClass) item.getObject();
|
||||
PyFunction init = pyClass.findInitOrNew(true, null);
|
||||
PyClass pyClass = PyUtil.as(item.getPsiElement(), PyClass.class);
|
||||
PyFunction init = pyClass != null ? pyClass.findInitOrNew(true, null) : null;
|
||||
if (init != null && PyFunctionInsertHandler.hasParams(context, init)) {
|
||||
editor.getCaretModel().moveToOffset(offset+1);
|
||||
AutoPopupController.getInstance(context.getProject()).autoPopupParameterInfo(context.getEditor(), init);
|
||||
|
||||
+9
-6
@@ -126,9 +126,12 @@ public class PyClassNameCompletionContributor extends CompletionContributor {
|
||||
for (final String elementName : CompletionUtil.sortMatching(resultSet.getPrefixMatcher(), keys)) {
|
||||
for (T element : StubIndex.getElements(key, elementName, project, scope, elementClass)) {
|
||||
if (condition.value(element)) {
|
||||
resultSet.addElement(LookupElementBuilder.createWithIcon(element)
|
||||
.withTailText(" " + ((NavigationItem)element).getPresentation().getLocationString(), true)
|
||||
.withInsertHandler(insertHandler));
|
||||
final String name = element.getName();
|
||||
if (name != null) {
|
||||
resultSet.addElement(LookupElementBuilder.createWithSmartPointer(name, element).withIcon(element.getIcon(0))
|
||||
.withTailText(" " + ((NavigationItem)element).getPresentation().getLocationString(), true)
|
||||
.withInsertHandler(insertHandler));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,7 +145,7 @@ public class PyClassNameCompletionContributor extends CompletionContributor {
|
||||
|
||||
|
||||
private static final InsertHandler<LookupElement> FUNCTION_INSERT_HANDLER = new PyFunctionInsertHandler() {
|
||||
public void handleInsert(final InsertionContext context, final LookupElement item) {
|
||||
public void handleInsert(@NotNull final InsertionContext context, @NotNull final LookupElement item) {
|
||||
int tailOffset = context.getTailOffset()-1;
|
||||
super.handleInsert(context, item); // adds parentheses, modifies tail offset
|
||||
context.commitDocument();
|
||||
@@ -172,14 +175,14 @@ public class PyClassNameCompletionContributor extends CompletionContributor {
|
||||
manager.commitDocument(document);
|
||||
}
|
||||
final PsiReference ref = context.getFile().findReferenceAt(tailOffset);
|
||||
if (ref == null || ref.resolve() == item.getObject()) {
|
||||
if (ref == null || ref.resolve() == item.getPsiElement()) {
|
||||
// no import statement needed
|
||||
return;
|
||||
}
|
||||
new WriteCommandAction(context.getProject(), context.getFile()) {
|
||||
@Override
|
||||
protected void run(@NotNull Result result) throws Throwable {
|
||||
AddImportHelper.addImport((PsiNamedElement)item.getObject(), context.getFile(), (PyElement)ref.getElement());
|
||||
AddImportHelper.addImport(PyUtil.as(item.getPsiElement(), PsiNamedElement.class), context.getFile(), (PyElement)ref.getElement());
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
+15
-6
@@ -23,8 +23,11 @@ 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.PyUtil;
|
||||
import com.jetbrains.python.psi.impl.PyCallExpressionHelper;
|
||||
import com.jetbrains.python.psi.resolve.PyResolveContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
/**
|
||||
* @author yole
|
||||
@@ -33,23 +36,24 @@ public class PyFunctionInsertHandler extends ParenthesesInsertHandler<LookupElem
|
||||
public static PyFunctionInsertHandler INSTANCE = new PyFunctionInsertHandler();
|
||||
|
||||
@Override
|
||||
public void handleInsert(InsertionContext context, LookupElement item) {
|
||||
public void handleInsert(@NotNull InsertionContext context, @NotNull LookupElement item) {
|
||||
super.handleInsert(context, item);
|
||||
if (hasParams(context, item)) {
|
||||
AutoPopupController.getInstance(context.getProject()).autoPopupParameterInfo(context.getEditor(), (PyFunction) item.getObject());
|
||||
AutoPopupController.getInstance(context.getProject()).autoPopupParameterInfo(context.getEditor(), getFunction(item));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean placeCaretInsideParentheses(InsertionContext context, LookupElement item) {
|
||||
protected boolean placeCaretInsideParentheses(@NotNull InsertionContext context, @NotNull LookupElement item) {
|
||||
return hasParams(context, item);
|
||||
}
|
||||
|
||||
private static boolean hasParams(InsertionContext context, LookupElement item) {
|
||||
return hasParams(context, (PyFunction) item.getObject());
|
||||
private static boolean hasParams(@NotNull InsertionContext context, @NotNull LookupElement item) {
|
||||
final PyFunction function = getFunction(item);
|
||||
return function != null && hasParams(context, function);
|
||||
}
|
||||
|
||||
public static boolean hasParams(InsertionContext context, PyFunction function) {
|
||||
public static boolean hasParams(@NotNull InsertionContext context, @NotNull PyFunction function) {
|
||||
final PsiElement element = context.getFile().findElementAt(context.getStartOffset());
|
||||
PyReferenceExpression refExpr = PsiTreeUtil.getParentOfType(element, PyReferenceExpression.class);
|
||||
int implicitArgsCount = refExpr != null
|
||||
@@ -57,4 +61,9 @@ public class PyFunctionInsertHandler extends ParenthesesInsertHandler<LookupElem
|
||||
: 0;
|
||||
return function.getParameterList().getParameters().length > implicitArgsCount;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static PyFunction getFunction(@NotNull LookupElement item) {
|
||||
return PyUtil.as(item.getPsiElement(), PyFunction.class);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -47,7 +47,7 @@ public class PySuperClassAttributesCompletionContributor extends CompletionContr
|
||||
return;
|
||||
}
|
||||
for (PyTargetExpression expr : getSuperClassAttributes(containingClass)) {
|
||||
result.addElement(LookupElementBuilder.create(expr, expr.getName() + " = "));
|
||||
result.addElement(LookupElementBuilder.createWithSmartPointer(expr.getName() + " = ", expr));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+4
-1
@@ -71,7 +71,10 @@ public class PyIterableVariableMacro extends Macro {
|
||||
final PsiElement element = context.getPsiElementAtStartOffset();
|
||||
if (element != null) {
|
||||
for (PsiNamedElement iterableElement : getIterableElements(element)) {
|
||||
results.add(LookupElementBuilder.create(iterableElement));
|
||||
final String name = iterableElement.getName();
|
||||
if (name != null) {
|
||||
results.add(LookupElementBuilder.createWithSmartPointer(name, iterableElement));
|
||||
}
|
||||
}
|
||||
}
|
||||
return results.toArray(new LookupElement[results.size()]);
|
||||
|
||||
@@ -24,4 +24,9 @@ public class PyBoundFunction extends PyFunctionImpl {
|
||||
public PyBoundFunction(PyFunction function) {
|
||||
super(function.getNode());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isPhysical() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,8 +146,9 @@ public class PyImportReference extends PyReferenceImpl {
|
||||
}
|
||||
else if (item instanceof LookupElement) {
|
||||
LookupElement lookupElement = (LookupElement) item;
|
||||
if (lookupElement.getObject() instanceof PsiElement) {
|
||||
itemElement = (PsiElement) lookupElement.getObject();
|
||||
final PsiElement element = lookupElement.getPsiElement();
|
||||
if (element != null) {
|
||||
itemElement = element;
|
||||
}
|
||||
}
|
||||
return !(itemElement instanceof PsiFile); // TODO deeper check?
|
||||
|
||||
@@ -282,8 +282,8 @@ public class PyQualifiedReference extends PyReferenceImpl {
|
||||
if (name != null && name.endsWith(CompletionUtil.DUMMY_IDENTIFIER_TRIMMED)) {
|
||||
continue;
|
||||
}
|
||||
if (ex instanceof PsiNamedElement && qualifierType instanceof PyClassType) {
|
||||
variants.add(LookupElementBuilder.create((PsiNamedElement)ex)
|
||||
if (ex instanceof PsiNamedElement && qualifierType instanceof PyClassType && name != null) {
|
||||
variants.add(LookupElementBuilder.createWithSmartPointer(name, ex)
|
||||
.withTypeText(qualifierType.getName())
|
||||
.withIcon(PlatformIcons.FIELD_ICON));
|
||||
}
|
||||
|
||||
@@ -597,19 +597,26 @@ public class PyReferenceImpl implements PsiReferenceEx, PsiPolyVariantReference
|
||||
}
|
||||
}
|
||||
|
||||
// Throw away fake elements used for completion internally
|
||||
for (LookupElement e : processor.getResultList()) {
|
||||
final Object o = e.getObject();
|
||||
if (o instanceof PsiElement) {
|
||||
final PsiElement original = CompletionUtil.getOriginalElement((PsiElement)o);
|
||||
ret.addAll(getOriginalElements(processor));
|
||||
return ret.toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Throws away fake elements used for completion internally.
|
||||
*/
|
||||
protected List<LookupElement> getOriginalElements(@NotNull CompletionVariantsProcessor processor) {
|
||||
final List<LookupElement> ret = Lists.newArrayList();
|
||||
for (LookupElement item : processor.getResultList()) {
|
||||
final PsiElement e = item.getPsiElement();
|
||||
if (e != null) {
|
||||
final PsiElement original = CompletionUtil.getOriginalElement(e);
|
||||
if (original == null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
ret.add(e);
|
||||
ret.add(item);
|
||||
}
|
||||
|
||||
return ret.toArray();
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -22,6 +22,7 @@ import com.intellij.openapi.util.Condition;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.intellij.util.Function;
|
||||
import com.intellij.util.PlatformIcons;
|
||||
import com.jetbrains.python.PyNames;
|
||||
@@ -31,7 +32,6 @@ import com.jetbrains.python.codeInsight.controlflow.ScopeOwner;
|
||||
import com.jetbrains.python.codeInsight.dataflow.scope.ScopeUtil;
|
||||
import com.jetbrains.python.psi.*;
|
||||
import com.jetbrains.python.psi.impl.PyBuiltinCache;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -59,17 +59,17 @@ public class CompletionVariantsProcessor extends VariantsProcessor {
|
||||
mySuppressParentheses = true;
|
||||
}
|
||||
|
||||
protected LookupElementBuilder setupItem(LookupElementBuilder item) {
|
||||
final Object object = item.getObject();
|
||||
private LookupElementBuilder setupItem(LookupElementBuilder item) {
|
||||
final PsiElement element = item.getPsiElement();
|
||||
if (!myPlainNamesOnly) {
|
||||
if (!mySuppressParentheses &&
|
||||
object instanceof PyFunction && ((PyFunction)object).getProperty() == null &&
|
||||
!PyUtil.hasCustomDecorators((PyFunction)object) &&
|
||||
!isSingleArgDecoratorCall(myContext, (PyFunction)object)) {
|
||||
final Project project = ((PyFunction)object).getProject();
|
||||
element instanceof PyFunction && ((PyFunction)element).getProperty() == null &&
|
||||
!PyUtil.hasCustomDecorators((PyFunction)element) &&
|
||||
!isSingleArgDecoratorCall(myContext, (PyFunction)element)) {
|
||||
final Project project = element.getProject();
|
||||
item = item.withInsertHandler(PyFunctionInsertHandler.INSTANCE);
|
||||
final TypeEvalContext context = TypeEvalContext.codeCompletion(project, myContext != null ? myContext.getContainingFile() : null);
|
||||
final List<PyParameter> parameters = PyUtil.getParameters((PyFunction)object, context);
|
||||
final List<PyParameter> parameters = PyUtil.getParameters((PyFunction)element, context);
|
||||
final String params = StringUtil.join(parameters, new Function<PyParameter, String>() {
|
||||
@Override
|
||||
public String fun(PyParameter pyParameter) {
|
||||
@@ -78,13 +78,12 @@ public class CompletionVariantsProcessor extends VariantsProcessor {
|
||||
}, ", ");
|
||||
item = item.withTailText("(" + params + ")");
|
||||
}
|
||||
else if (object instanceof PyClass) {
|
||||
else if (element instanceof PyClass) {
|
||||
item = item.withInsertHandler(PyClassInsertHandler.INSTANCE);
|
||||
}
|
||||
}
|
||||
String source = null;
|
||||
if (object instanceof PsiElement) {
|
||||
final PsiElement element = (PsiElement)object;
|
||||
if (element != null) {
|
||||
PyClass cls = null;
|
||||
|
||||
if (element instanceof PyFunction) {
|
||||
@@ -158,14 +157,14 @@ public class CompletionVariantsProcessor extends VariantsProcessor {
|
||||
if (PyUtil.isClassPrivateName(name) && !PyUtil.inSameFile(element, myContext)) {
|
||||
return;
|
||||
}
|
||||
myVariants.put(name, setupItem(LookupElementBuilder.create(element, name).withIcon(element.getIcon(0))));
|
||||
myVariants.put(name, setupItem(LookupElementBuilder.createWithSmartPointer(name, element).withIcon(element.getIcon(0))));
|
||||
}
|
||||
|
||||
protected void addImportedElement(String referencedName, NameDefiner definer, PyElement expr) {
|
||||
Icon icon = expr.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.create(expr, referencedName).withIcon(icon));
|
||||
LookupElementBuilder lookupItem = setupItem(LookupElementBuilder.createWithSmartPointer(referencedName, expr).withIcon(icon));
|
||||
myVariants.put(referencedName, lookupItem);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user