diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml
index 80632ecc2b1b..b579d19fe6ce 100644
--- a/python/src/META-INF/python-plugin-common.xml
+++ b/python/src/META-INF/python-plugin-common.xml
@@ -287,6 +287,7 @@
+
diff --git a/python/src/com/jetbrains/python/documentation/DocStringParameterReference.java b/python/src/com/jetbrains/python/documentation/DocStringParameterReference.java
new file mode 100644
index 000000000000..43d118936e3a
--- /dev/null
+++ b/python/src/com/jetbrains/python/documentation/DocStringParameterReference.java
@@ -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 {
+ 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 namedParameters = ParamHelper.collectNamedParameters(((PyFunction)owner).getParameterList());
+ return ArrayUtil.toObjectArray(namedParameters);
+ }
+ return ArrayUtil.EMPTY_OBJECT_ARRAY;
+ }
+}
diff --git a/python/src/com/jetbrains/python/documentation/DocStringReferenceContributor.java b/python/src/com/jetbrains/python/documentation/DocStringReferenceContributor.java
new file mode 100644
index 000000000000..ba7ddcf73431
--- /dev/null
+++ b/python/src/com/jetbrains/python/documentation/DocStringReferenceContributor.java
@@ -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());
+ }
+}
diff --git a/python/src/com/jetbrains/python/documentation/DocStringReferenceProvider.java b/python/src/com/jetbrains/python/documentation/DocStringReferenceProvider.java
new file mode 100644
index 000000000000..a03f1f03534a
--- /dev/null
+++ b/python/src/com/jetbrains/python/documentation/DocStringReferenceProvider.java
@@ -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 allParamTags = new ArrayList();
+ 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 result = new ArrayList();
+ 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();
+ }
+}
diff --git a/python/testData/completion/epydocParamTag.after.py b/python/testData/completion/epydocParamTag.after.py
new file mode 100644
index 000000000000..7a9bf5aa1dfa
--- /dev/null
+++ b/python/testData/completion/epydocParamTag.after.py
@@ -0,0 +1,2 @@
+def foo(bar):
+ """ @param bar """
\ No newline at end of file
diff --git a/python/testData/completion/epydocParamTag.py b/python/testData/completion/epydocParamTag.py
new file mode 100644
index 000000000000..6323111c8540
--- /dev/null
+++ b/python/testData/completion/epydocParamTag.py
@@ -0,0 +1,2 @@
+def foo(bar):
+ """ @param """
\ No newline at end of file
diff --git a/python/testData/refactoring/rename/epydocRenameParameter.py b/python/testData/refactoring/rename/epydocRenameParameter.py
new file mode 100644
index 000000000000..1c62996851ab
--- /dev/null
+++ b/python/testData/refactoring/rename/epydocRenameParameter.py
@@ -0,0 +1,3 @@
+def func(foo):
+ """ @param foo: The parameter value."""
+ pass
diff --git a/python/testData/refactoring/rename/epydocRenameParameter_after.py b/python/testData/refactoring/rename/epydocRenameParameter_after.py
new file mode 100644
index 000000000000..8872fb68c263
--- /dev/null
+++ b/python/testData/refactoring/rename/epydocRenameParameter_after.py
@@ -0,0 +1,3 @@
+def func(bar):
+ """ @param bar: The parameter value."""
+ pass
diff --git a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java
index 2b26c0f8f833..7e371c6d0a1e 100644
--- a/python/testSrc/com/jetbrains/python/PythonCompletionTest.java
+++ b/python/testSrc/com/jetbrains/python/PythonCompletionTest.java
@@ -320,4 +320,8 @@ public class PythonCompletionTest extends PyLightFixtureTestCase {
public void testReexportModules() { // PY-2385
doMultiFileTest();
}
+
+ public void testEpydocParamTag() {
+ doTest();
+ }
}
diff --git a/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java b/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java
index 1e13a18edf70..5bc613b1b812 100644
--- a/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java
+++ b/python/testSrc/com/jetbrains/python/refactoring/PyRenameTest.java
@@ -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");
}