From 29a93d914550ec54b7bd81a9c624b502bb3f01f0 Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Tue, 15 Sep 2015 00:13:29 +0300 Subject: [PATCH] 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. --- .../PyDocstringCompletionContributor.java | 56 +++++++++++++------ .../DocStringParameterReference.java | 13 ++++- .../docstrings/DocStringTypeReference.java | 15 +++-- .../overrideParamNameInGoogleDocstring.py | 5 ++ .../overrideParamNameInRestDocstring.py | 4 ++ .../python/PythonCompletionTest.java | 24 ++++++++ 6 files changed, 92 insertions(+), 25 deletions(-) create mode 100644 python/testData/completion/overrideParamNameInGoogleDocstring.py create mode 100644 python/testData/completion/overrideParamNameInRestDocstring.py diff --git a/python/src/com/jetbrains/python/codeInsight/completion/PyDocstringCompletionContributor.java b/python/src/com/jetbrains/python/codeInsight/completion/PyDocstringCompletionContributor.java index 9bbbe561f331..594c64754b7c 100644 --- a/python/src/com/jetbrains/python/codeInsight/completion/PyDocstringCompletionContributor.java +++ b/python/src/com/jetbrains/python/codeInsight/completion/PyDocstringCompletionContributor.java @@ -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 { - 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 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 identifiers = PyRefactoringUtil.collectUsedNames(docStringOwner); + for (String identifier : identifiers) { + result.addElement(LookupElementBuilder.create(identifier).withAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE)); + } - - final Collection fileIdentifiers = PyRefactoringUtil.collectUsedNames(parameters.getOriginalFile()); - for (String identifier : fileIdentifiers) - result.addElement(LookupElementBuilder.create(identifier).withAutoCompletionPolicy(AutoCompletionPolicy.NEVER_AUTOCOMPLETE)); + final Collection 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 param" -> "@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); + } } } diff --git a/python/src/com/jetbrains/python/documentation/docstrings/DocStringParameterReference.java b/python/src/com/jetbrains/python/documentation/docstrings/DocStringParameterReference.java index c16df248b0d8..42395ef5e111 100644 --- a/python/src/com/jetbrains/python/documentation/docstrings/DocStringParameterReference.java +++ b/python/src/com/jetbrains/python/documentation/docstrings/DocStringParameterReference.java @@ -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 collectParameterVariants() { PyDocStringOwner owner = PsiTreeUtil.getParentOfType(getElement(), PyDocStringOwner.class); if (owner instanceof PyFunction) { - List result = Lists.newArrayList(); + List result = Lists.newArrayList(); final List namedParameters = ParamHelper.collectNamedParameters(((PyFunction)owner).getParameterList()); Set usedParameters = new HashSet(); PyStringLiteralExpression expression = PsiTreeUtil.getParentOfType(getElement(), PyStringLiteralExpression.class, false); @@ -139,9 +146,9 @@ public class DocStringParameterReference extends PsiReferenceBase collectTypeVariants() { final PsiFile file = myElement.getContainingFile(); - final ArrayList variants = Lists.newArrayList("str", "int", "basestring", "bool", "buffer", "bytearray", "complex", "dict", - "tuple", "enumerate", "file", "float", "frozenset", "list", "long", "set", "object"); + final ArrayList + variants = Lists.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 fromImports = ((PyFile)file).getFromImports(); @@ -139,7 +147,6 @@ public class DocStringTypeReference extends PsiPolyVariantReferenceBaseam1: description + """ \ No newline at end of file diff --git a/python/testData/completion/overrideParamNameInRestDocstring.py b/python/testData/completion/overrideParamNameInRestDocstring.py new file mode 100644 index 000000000000..660fea531c0d --- /dev/null +++ b/python/testData/completion/overrideParamNameInRestDocstring.py @@ -0,0 +1,4 @@ +def f(param1, param2): + """ + :param param1: description + """ \ No newline at end of file diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java index bd867033dd94..ef19da7f5ccb 100644 --- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java +++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java @@ -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 variants = doTestByFile(); + assertNotNull(variants); + assertSameElements(variants, "param2"); + } + }); + } + + // PY-16870 + public void testOverrideParamNameInRestDocstring() { + runWithDocStringFormat(DocStringFormat.REST, new Runnable() { + @Override + public void run() { + final List variants = doTestByFile(); + assertNotNull(variants); + assertSameElements(variants, "param2"); + } + }); + } + public void testPep328Completion() { // PY-3409 myFixture.copyDirectoryToProject("pep328", "pep328"); myFixture.configureByFile("pep328/package/subpackage1/moduleX.py");