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:
Mikhail Golubev
2015-09-15 11:18:26 +03:00
parent 4a73b1fa66
commit 29a93d9145
6 changed files with 92 additions and 25 deletions
@@ -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);
}
}
}
@@ -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() {
@@ -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");