mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-16870 Use TRIMMED_DUMMY_IDENTIFIER to complete references in docstrings
Otherwise if caret is in the middle of reference, standard dummy identifier will break docstring format and existing references used for completion will be lost. For example previously line "param (int) : foo" became "paraIntellijIdeaRulezzz m (int) : foo" and Google code style docstring parser wasn't able to reparse it and extract necessary parameter reference. To use alternative dummy identifier I moved populating completion variants from DocString*Reference classes to PyDocstringCompletionContributor.
This commit is contained in:
+38
-18
@@ -22,9 +22,13 @@ import com.intellij.openapi.module.Module;
|
||||
import com.intellij.openapi.module.ModuleUtilCore;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.PsiReference;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.util.ProcessingContext;
|
||||
import com.jetbrains.python.documentation.docstrings.DocStringParameterReference;
|
||||
import com.jetbrains.python.documentation.docstrings.DocStringTypeReference;
|
||||
import com.jetbrains.python.psi.PyDocStringOwner;
|
||||
import com.jetbrains.python.psi.PyNamedParameter;
|
||||
import com.jetbrains.python.psi.PyStringLiteralExpression;
|
||||
import com.jetbrains.python.refactoring.PyRefactoringUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -47,31 +51,41 @@ public class PyDocstringCompletionContributor extends CompletionContributor {
|
||||
|
||||
private static class IdentifierCompletionProvider extends CompletionProvider<CompletionParameters> {
|
||||
|
||||
private IdentifierCompletionProvider() {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void addCompletions(@NotNull CompletionParameters parameters,
|
||||
ProcessingContext context,
|
||||
@NotNull CompletionResultSet result) {
|
||||
if (parameters.isAutoPopup()) return;
|
||||
final PsiElement element = parameters.getOriginalPosition();
|
||||
if (element == null) return;
|
||||
final PsiFile file = element.getContainingFile();
|
||||
// Parameter references are filled with DocStringParameterReference#getVariants
|
||||
if (file.findReferenceAt(parameters.getOffset()) != null) return;
|
||||
final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(element, PyDocStringOwner.class);
|
||||
final Module module = ModuleUtilCore.findModuleForPsiElement(element);
|
||||
if (module != null) {
|
||||
result = result.withPrefixMatcher(getPrefix(parameters.getOffset(), file));
|
||||
final Collection<String> identifiers = PyRefactoringUtil.collectUsedNames(docStringOwner);
|
||||
for (String identifier : identifiers)
|
||||
result.addElement(LookupElementBuilder.create(identifier).withAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE));
|
||||
final PsiReference reference = file.findReferenceAt(parameters.getOffset());
|
||||
if (reference == null) {
|
||||
if (parameters.isAutoPopup()) return;
|
||||
final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(element, PyDocStringOwner.class);
|
||||
final Module module = ModuleUtilCore.findModuleForPsiElement(element);
|
||||
if (module != null) {
|
||||
result = result.withPrefixMatcher(getPrefix(parameters.getOffset(), file));
|
||||
final Collection<String> identifiers = PyRefactoringUtil.collectUsedNames(docStringOwner);
|
||||
for (String identifier : identifiers) {
|
||||
result.addElement(LookupElementBuilder.create(identifier).withAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE));
|
||||
}
|
||||
|
||||
|
||||
final Collection<String> fileIdentifiers = PyRefactoringUtil.collectUsedNames(parameters.getOriginalFile());
|
||||
for (String identifier : fileIdentifiers)
|
||||
result.addElement(LookupElementBuilder.create(identifier).withAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE));
|
||||
final Collection<String> fileIdentifiers = PyRefactoringUtil.collectUsedNames(parameters.getOriginalFile());
|
||||
for (String identifier : fileIdentifiers) {
|
||||
result.addElement(LookupElementBuilder.create(identifier).withAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE));
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (reference instanceof DocStringParameterReference) {
|
||||
for (PyNamedParameter param : ((DocStringParameterReference)reference).collectParameterVariants()) {
|
||||
result.addElement(LookupElementBuilder.createWithIcon(param));
|
||||
}
|
||||
}
|
||||
else if (reference instanceof DocStringTypeReference) {
|
||||
for (Object variant : ((DocStringTypeReference)reference).collectTypeVariants()) {
|
||||
result.addElement(LookupElementBuilder.create(variant));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -90,7 +104,13 @@ public class PyDocstringCompletionContributor extends CompletionContributor {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean invokeAutoPopup(@NotNull PsiElement position, char typeChar) {
|
||||
return false;
|
||||
public void beforeCompletion(@NotNull CompletionInitializationContext context) {
|
||||
// With standard dummy identifier inserted, docstring might become malformed
|
||||
// e.g. "@param para<caret>m" -> "@param paraIntellijIdeaRulezzz m"
|
||||
// and param is no longer parameter, but type reference now
|
||||
final PsiReference ref = context.getFile().findReferenceAt(context.getCaret().getOffset());
|
||||
if (ref instanceof DocStringParameterReference || ref instanceof DocStringTypeReference) {
|
||||
context.setDummyIdentifier(CompletionInitializationContext.DUMMY_IDENTIFIER_TRIMMED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+10
-3
@@ -34,6 +34,7 @@ import com.jetbrains.python.psi.types.TypeEvalContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -121,9 +122,15 @@ public class DocStringParameterReference extends PsiReferenceBase<PyStringLitera
|
||||
@NotNull
|
||||
@Override
|
||||
public Object[] getVariants() {
|
||||
// see PyDocstringCompletionContributor
|
||||
return ArrayUtil.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<PyNamedParameter> collectParameterVariants() {
|
||||
PyDocStringOwner owner = PsiTreeUtil.getParentOfType(getElement(), PyDocStringOwner.class);
|
||||
if (owner instanceof PyFunction) {
|
||||
List <PyNamedParameter> result = Lists.newArrayList();
|
||||
List<PyNamedParameter> result = Lists.newArrayList();
|
||||
final List<PyNamedParameter> namedParameters = ParamHelper.collectNamedParameters(((PyFunction)owner).getParameterList());
|
||||
Set<String> usedParameters = new HashSet<String>();
|
||||
PyStringLiteralExpression expression = PsiTreeUtil.getParentOfType(getElement(), PyStringLiteralExpression.class, false);
|
||||
@@ -139,9 +146,9 @@ public class DocStringParameterReference extends PsiReferenceBase<PyStringLitera
|
||||
result.add(param);
|
||||
}
|
||||
|
||||
return ArrayUtil.toObjectArray(result);
|
||||
return result;
|
||||
}
|
||||
return ArrayUtil.EMPTY_OBJECT_ARRAY;
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
public ReferenceType getType() {
|
||||
|
||||
+11
-4
@@ -20,6 +20,7 @@ import com.intellij.codeInsight.completion.CompletionUtil;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.util.QualifiedName;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.IncorrectOperationException;
|
||||
import com.jetbrains.python.PyNames;
|
||||
import com.jetbrains.python.psi.*;
|
||||
@@ -121,9 +122,16 @@ public class DocStringTypeReference extends PsiPolyVariantReferenceBase<PsiEleme
|
||||
@NotNull
|
||||
@Override
|
||||
public Object[] getVariants() {
|
||||
// see PyDocstringCompletionContributor
|
||||
return ArrayUtil.EMPTY_OBJECT_ARRAY;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public List<Object> collectTypeVariants() {
|
||||
final PsiFile file = myElement.getContainingFile();
|
||||
final ArrayList<Object> variants = Lists.<Object>newArrayList("str", "int", "basestring", "bool", "buffer", "bytearray", "complex", "dict",
|
||||
"tuple", "enumerate", "file", "float", "frozenset", "list", "long", "set", "object");
|
||||
final ArrayList<Object>
|
||||
variants = Lists.<Object>newArrayList("str", "int", "basestring", "bool", "buffer", "bytearray", "complex", "dict",
|
||||
"tuple", "enumerate", "file", "float", "frozenset", "list", "long", "set", "object");
|
||||
if (file instanceof PyFile) {
|
||||
variants.addAll(((PyFile)file).getTopLevelClasses());
|
||||
final List<PyFromImportStatement> fromImports = ((PyFile)file).getFromImports();
|
||||
@@ -139,7 +147,6 @@ public class DocStringTypeReference extends PsiPolyVariantReferenceBase<PsiEleme
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return variants.toArray();
|
||||
return variants;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
def f(param1, param2):
|
||||
"""
|
||||
Args:
|
||||
par<caret>am1: description
|
||||
"""
|
||||
@@ -0,0 +1,4 @@
|
||||
def f(param1, param2):
|
||||
"""
|
||||
:param par<caret>am1: description
|
||||
"""
|
||||
@@ -438,6 +438,30 @@ public class PythonCompletionTest extends PyTestCase {
|
||||
});
|
||||
}
|
||||
|
||||
// PY-16870
|
||||
public void testOverrideParamNameInGoogleDocstring() {
|
||||
runWithDocStringFormat(DocStringFormat.GOOGLE, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final List<String> variants = doTestByFile();
|
||||
assertNotNull(variants);
|
||||
assertSameElements(variants, "param2");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// PY-16870
|
||||
public void testOverrideParamNameInRestDocstring() {
|
||||
runWithDocStringFormat(DocStringFormat.REST, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final List<String> variants = doTestByFile();
|
||||
assertNotNull(variants);
|
||||
assertSameElements(variants, "param2");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void testPep328Completion() { // PY-3409
|
||||
myFixture.copyDirectoryToProject("pep328", "pep328");
|
||||
myFixture.configureByFile("pep328/package/subpackage1/moduleX.py");
|
||||
|
||||
Reference in New Issue
Block a user