references to parameters in docstrings, with rename and completion support

This commit is contained in:
Dmitry Jemerov
2011-04-20 21:17:18 +02:00
parent 784c08f162
commit a04d5b85bc
10 changed files with 152 additions and 0 deletions
@@ -287,6 +287,7 @@
<applicationConfigurable implementation="com.jetbrains.python.documentation.PythonDocumentationConfigurable"/>
<projectService serviceInterface="com.jetbrains.python.documentation.PyDocumentationSettings"
serviceImplementation="com.jetbrains.python.documentation.PyDocumentationSettings"/>
<psi.referenceContributor implementation="com.jetbrains.python.documentation.DocStringReferenceContributor"/>
<!-- Django -->
<codeInsight.lineMarkerProvider language="Python" implementationClass="com.jetbrains.django.annotator.ViewMethodLineMarkerProvider"/>
@@ -0,0 +1,44 @@
package com.jetbrains.python.documentation;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReferenceBase;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.jetbrains.python.psi.PyDocStringOwner;
import com.jetbrains.python.psi.PyFunction;
import com.jetbrains.python.psi.PyNamedParameter;
import com.jetbrains.python.psi.impl.ParamHelper;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* @author yole
*/
public class DocStringParameterReference extends PsiReferenceBase<PsiElement> {
public DocStringParameterReference(PsiElement element, TextRange range) {
super(element, range);
}
@Override
public PsiElement resolve() {
PyDocStringOwner owner = PsiTreeUtil.getParentOfType(getElement(), PyDocStringOwner.class);
if (owner instanceof PyFunction) {
final String paramName = getCanonicalText();
return ((PyFunction) owner).getParameterList().getElementNamed(paramName);
}
return null;
}
@NotNull
@Override
public Object[] getVariants() {
PyDocStringOwner owner = PsiTreeUtil.getParentOfType(getElement(), PyDocStringOwner.class);
if (owner instanceof PyFunction) {
final List<PyNamedParameter> namedParameters = ParamHelper.collectNamedParameters(((PyFunction)owner).getParameterList());
return ArrayUtil.toObjectArray(namedParameters);
}
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}
}
@@ -0,0 +1,18 @@
package com.jetbrains.python.documentation;
import com.intellij.psi.PsiReferenceContributor;
import com.intellij.psi.PsiReferenceRegistrar;
import com.jetbrains.python.psi.PyStringLiteralExpression;
import static com.intellij.patterns.PlatformPatterns.psiElement;
/**
* @author yole
*/
public class DocStringReferenceContributor extends PsiReferenceContributor {
@Override
public void registerReferenceProviders(PsiReferenceRegistrar registrar) {
registrar.registerReferenceProvider(psiElement(PyStringLiteralExpression.class),
new DocStringReferenceProvider());
}
}
@@ -0,0 +1,71 @@
package com.jetbrains.python.documentation;
import com.google.common.base.CharMatcher;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.PsiReferenceProvider;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.ArrayUtil;
import com.intellij.util.ProcessingContext;
import com.jetbrains.python.psi.PyDocStringOwner;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* @author yole
*/
public class DocStringReferenceProvider extends PsiReferenceProvider {
private final String[] ALL_PARAM_TAGS;
public DocStringReferenceProvider() {
List<String> allParamTags = new ArrayList<String>();
for (String tag : EpydocString.PARAM_TAGS) {
allParamTags.add("@" + tag);
allParamTags.add(":" + tag);
}
ALL_PARAM_TAGS = ArrayUtil.toStringArray(allParamTags);
}
@NotNull
@Override
public PsiReference[] getReferencesByElement(@NotNull PsiElement element, @NotNull ProcessingContext context) {
final PyDocStringOwner docStringOwner = PsiTreeUtil.getParentOfType(element, PyDocStringOwner.class);
if (docStringOwner != null && element == docStringOwner.getDocStringExpression()) {
List<PsiReference> result = new ArrayList<PsiReference>();
String docString = element.getText();
int pos = 0;
while (pos < docString.length()) {
pos = findNextTag(docString, pos, ALL_PARAM_TAGS);
if (pos < 0) {
break;
}
pos = CharMatcher.anyOf(" \t*").negate().indexIn(docString, pos);
int endPos = CharMatcher.JAVA_LETTER_OR_DIGIT.negate().indexIn(docString, pos);
if (endPos < 0) {
endPos = docString.length();
}
result.add(new DocStringParameterReference(element, new TextRange(pos, endPos)));
pos = endPos;
}
return result.toArray(new PsiReference[result.size()]);
}
return PsiReference.EMPTY_ARRAY;
}
private static int findNextTag(String docString, int pos, String[] paramTags) {
int result = Integer.MAX_VALUE;
String foundTag = null;
for (String paramTag : paramTags) {
int tagPos = docString.indexOf(paramTag + " ", pos);
if (tagPos >= 0 && tagPos < result) {
foundTag = paramTag;
result = tagPos;
}
}
return foundTag == null ? -1 : result + foundTag.length();
}
}
@@ -0,0 +1,2 @@
def foo(bar):
""" @param bar """
@@ -0,0 +1,2 @@
def foo(bar):
""" @param <caret> """
@@ -0,0 +1,3 @@
def func(fo<caret>o):
""" @param foo: The parameter value."""
pass
@@ -0,0 +1,3 @@
def func(bar):
""" @param bar: The parameter value."""
pass
@@ -320,4 +320,8 @@ public class PythonCompletionTest extends PyLightFixtureTestCase {
public void testReexportModules() { // PY-2385
doMultiFileTest();
}
public void testEpydocParamTag() {
doTest();
}
}
@@ -62,6 +62,10 @@ public class PyRenameTest extends PyLightFixtureTestCase {
doTest("bar");
}
public void testEpydocRenameParameter() {
doTest("bar");
}
public void testClassNameConflict() { // PY-2390
doRenameConflictTest("Foo", "A class named 'Foo' is already defined in classNameConflict.py");
}