ALL = ImmutableList.of(PLAIN, EPYDOC, SPHINX);
+
+ private DocStringFormat() {
+ }
+}
diff --git a/python/src/com/jetbrains/python/documentation/EpydocString.java b/python/src/com/jetbrains/python/documentation/EpydocString.java
index df76825894ca..883f209364b1 100644
--- a/python/src/com/jetbrains/python/documentation/EpydocString.java
+++ b/python/src/com/jetbrains/python/documentation/EpydocString.java
@@ -1,5 +1,7 @@
package com.jetbrains.python.documentation;
+import com.intellij.openapi.util.text.StringUtil;
+import com.intellij.xml.util.XmlTagUtilBase;
import org.jetbrains.annotations.Nullable;
/**
@@ -10,6 +12,13 @@ public class EpydocString extends StructuredDocString {
super(docstringText, "@");
}
+ @Override
+ public String getDescription() {
+ final String html = inlineMarkupToHTML(myDescription);
+ assert html != null;
+ return html;
+ }
+
@Override
@Nullable
public String getReturnType() {
@@ -34,34 +43,150 @@ public class EpydocString extends StructuredDocString {
if (value == null) {
value = getTagValue("param", "**" + paramName);
}
- return value;
+ return inlineMarkupToHTML(value);
}
@Nullable
public static String removeInlineMarkup(String s) {
+ return convertInlineMarkup(s, false);
+ }
+
+ @Nullable
+ private static String convertInlineMarkup(String s, boolean toHTML) {
if (s == null) return null;
- StringBuilder resultBuilder = new StringBuilder();
- int pos = 0;
- while(true) {
- int bracePos = s.indexOf('{', pos);
- if (bracePos < 1) break;
- char prevChar = s.charAt(bracePos-1);
- if (prevChar >= 'A' && prevChar <= 'Z') {
- resultBuilder.append(s.substring(pos, bracePos-1));
- int rbracePos = s.indexOf('}', bracePos);
- if (rbracePos < 0) {
+ MarkupConverter converter = toHTML ? new HTMLConverter() : new MarkupConverter();
+ converter.appendWithMarkup(s);
+ return converter.result();
+ }
+
+ private static class MarkupConverter {
+ protected final StringBuilder myResult = new StringBuilder();
+
+ public void appendWithMarkup(String s) {
+ int pos = 0;
+ while(true) {
+ int bracePos = s.indexOf('{', pos);
+ if (bracePos < 1) break;
+ char prevChar = s.charAt(bracePos-1);
+ if (prevChar >= 'A' && prevChar <= 'Z') {
+ appendText(s.substring(pos, bracePos - 1));
+ int rbracePos = findMatchingEndBrace(s, bracePos);
+ if (rbracePos < 0) {
+ pos = bracePos + 1;
+ break;
+ }
+ final String inlineMarkupContent = s.substring(bracePos + 1, rbracePos);
+ appendMarkup(prevChar, inlineMarkupContent);
+ pos = rbracePos + 1;
+ }
+ else {
+ appendText(s.substring(pos, bracePos + 1));
pos = bracePos+1;
- break;
}
- resultBuilder.append(s.substring(bracePos+1, rbracePos));
- pos = rbracePos+1;
}
- else {
- resultBuilder.append(s.substring(pos, bracePos+1));
- pos = bracePos+1;
+ appendText(s.substring(pos));
+ }
+
+ protected void appendText(String text) {
+ myResult.append(text);
+ }
+
+ protected void appendMarkup(char markupChar, String markupContent) {
+ myResult.append(markupContent);
+ }
+
+ public String result() {
+ return myResult.toString();
+ }
+ }
+
+ private static class HTMLConverter extends MarkupConverter {
+ @Override
+ protected void appendText(String text) {
+ myResult.append(joinLines(XmlTagUtilBase.escapeString(text, false), true));
+ }
+
+ @Override
+ protected void appendMarkup(char markupChar, String markupContent) {
+ if (markupChar == 'U') {
+ appendLink(markupContent);
+ return;
+ }
+ switch (markupChar) {
+ case 'I':
+ appendTagPair(markupContent, "i");
+ break;
+ case 'B':
+ appendTagPair(markupContent, "b");
+ break;
+ case 'C':
+ appendTagPair(markupContent, "code");
+ break;
+ default:
+ myResult.append(StringUtil.escapeXml(markupContent));
+ break;
}
}
- resultBuilder.append(s.substring(pos));
- return resultBuilder.toString();
+
+ private void appendTagPair(String markupContent, final String tagName) {
+ myResult.append("<").append(tagName).append(">");
+ appendWithMarkup(markupContent);
+ myResult.append("").append(tagName).append(">");
+ }
+
+ private void appendLink(String markupContent) {
+ String linkText = StringUtil.escapeXml(markupContent);
+ String linkUrl = linkText;
+ int pos = markupContent.indexOf('<');
+ if (pos >= 0 && markupContent.endsWith(">")) {
+ linkText = StringUtil.escapeXml(markupContent.substring(0, pos).trim());
+ linkUrl = joinLines(StringUtil.escapeXml(markupContent.substring(pos + 1, markupContent.length() - 1)), false);
+ }
+ myResult.append("").append(linkText).append("");
+ }
+
+ }
+
+ private static int findMatchingEndBrace(String s, int bracePos) {
+ int braceCount = 1;
+ for(int pos=bracePos+1; pos < s.length(); pos++) {
+ char c = s.charAt(pos);
+ if (c == '{') braceCount++;
+ else if (c == '}') {
+ braceCount--;
+ if (braceCount == 0) return pos;
+ }
+ }
+ return -1;
+ }
+
+ private static String joinLines(String s, boolean addSpace) {
+ while(true) {
+ int lineBreakStart = s.indexOf('\n');
+ if (lineBreakStart < 0) break;
+ int lineBreakEnd = lineBreakStart+1;
+ int blankLines = 0;
+ while(lineBreakEnd < s.length() && (s.charAt(lineBreakEnd) == ' ' || s.charAt(lineBreakEnd) == '\n')) {
+ if (s.charAt(lineBreakEnd) == '\n') blankLines++;
+ lineBreakEnd++;
+ }
+ if (addSpace) {
+ String separator = blankLines > 0 ? "" : " ";
+ s = s.substring(0, lineBreakStart) + separator + s.substring(lineBreakEnd);
+ }
+ else {
+ s = s.substring(0, lineBreakStart) + s.substring(lineBreakEnd);
+ }
+ }
+ return s;
+ }
+
+ @Nullable
+ public static String inlineMarkupToHTML(String s) {
+ return convertInlineMarkup(s, true);
}
}
diff --git a/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java b/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java
new file mode 100644
index 000000000000..7da47bb9f95a
--- /dev/null
+++ b/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java
@@ -0,0 +1,36 @@
+package com.jetbrains.python.documentation;
+
+import com.intellij.openapi.components.*;
+import com.intellij.openapi.project.Project;
+import com.intellij.util.xmlb.XmlSerializerUtil;
+
+/**
+ * @author yole
+ */
+@State(name = "PyDocumentationSettings",
+ storages = {
+ @Storage(id = "default", file = "$PROJECT_FILE$"),
+ @Storage(id = "dir", file = "$PROJECT_CONFIG_DIR$/other.xml", scheme = StorageScheme.DIRECTORY_BASED)
+ }
+)
+public class PyDocumentationSettings implements PersistentStateComponent {
+ public String myDocStringFormat = DocStringFormat.PLAIN;
+
+ public boolean isEpydocFormat() {
+ return DocStringFormat.EPYDOC.equals(myDocStringFormat);
+ }
+
+ public static PyDocumentationSettings getInstance(Project project) {
+ return ServiceManager.getService(project, PyDocumentationSettings.class);
+ }
+
+ @Override
+ public PyDocumentationSettings getState() {
+ return this;
+ }
+
+ @Override
+ public void loadState(PyDocumentationSettings state) {
+ XmlSerializerUtil.copyBean(state, this);
+ }
+}
diff --git a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java
index 2add356575c6..9d44c6349c0d 100644
--- a/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java
+++ b/python/src/com/jetbrains/python/documentation/PythonDocumentationProvider.java
@@ -45,10 +45,7 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.LinkedList;
-import java.util.List;
+import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -181,8 +178,14 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider impl
return cat;
}
- private static @NotNull ChainIterable combUpDocString(Project project, @NotNull String docstring) {
- ChainIterable cat = new ChainIterable();
+ private static @NotNull List combUpDocString(Project project, @NotNull String docstring) {
+ PyDocumentationSettings documentationSettings = PyDocumentationSettings.getInstance(project);
+ List result = new ArrayList();
+ if (documentationSettings.isEpydocFormat()) {
+ final EpydocString epydocString = new EpydocString(docstring);
+ result.add(formatStructuredDocString(epydocString));
+ return result;
+ }
// detect common indentation
String[] lines = LineTokenizer.tokenize(docstring, false);
Pattern spaces_pat = Pattern.compile("^\\s+");
@@ -217,7 +220,7 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider impl
for (String line : lines) {
if (is_first && spaces_pat.matcher(line).matches()) continue; // ignore all initial whitespace
if (is_first) is_first = false;
- else cat.add(BR);
+ else result.add(BR);
int leadingTabs = 0;
while (leadingTabs < line.length() && line.charAt(leadingTabs) == '\t') {
leadingTabs++;
@@ -225,9 +228,22 @@ public class PythonDocumentationProvider extends QuickDocumentationProvider impl
if (leadingTabs > 0) {
line = StringUtil.repeatSymbol(' ', tabSize * leadingTabs) + line.substring(leadingTabs);
}
- cat.add(combUp(line));
+ result.add(combUp(line));
}
- return cat;
+ return result;
+ }
+
+ private static String formatStructuredDocString(StructuredDocString docString) {
+ StringBuilder result = new StringBuilder(docString.getDescription());
+ final List parameters = docString.getParameters();
+ if (parameters.size() > 0) {
+ result.append("
Parameters:
");
+ for (String parameter : parameters) {
+ result.append("").append(parameter).append(": ").append(docString.getParamDescription(parameter)).append("
");
+ }
+ }
+ return result.toString();
+
}
// provides ctrl+Q doc
diff --git a/python/src/com/jetbrains/python/documentation/StructuredDocString.java b/python/src/com/jetbrains/python/documentation/StructuredDocString.java
index 002f8f23da22..36ca2f3c7bbd 100644
--- a/python/src/com/jetbrains/python/documentation/StructuredDocString.java
+++ b/python/src/com/jetbrains/python/documentation/StructuredDocString.java
@@ -1,16 +1,20 @@
package com.jetbrains.python.documentation;
+import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.intellij.openapi.util.text.LineTokenizer;
import com.intellij.openapi.util.text.StringUtil;
import org.jetbrains.annotations.Nullable;
+import java.util.List;
import java.util.Map;
/**
* @author yole
*/
public abstract class StructuredDocString {
+ protected final String myDescription;
+ protected final List myParameters = Lists.newArrayList();
protected final Map mySimpleTagValues = Maps.newHashMap();
protected final Map> myArgTagValues = Maps.newHashMap();
@@ -24,13 +28,22 @@ public abstract class StructuredDocString {
protected StructuredDocString(String docstringText, String tagPrefix) {
final String[] lines = LineTokenizer.tokenize(docstringText, false);
int i = 0;
+ StringBuilder descBuilder = new StringBuilder();
while (i < lines.length) {
String line = lines[i].trim();
if (line.startsWith(tagPrefix)) {
i = parseTag(lines, i, tagPrefix);
}
+ else {
+ descBuilder.append(line).append("\n");
+ }
i++;
}
+ myDescription = descBuilder.toString();
+ }
+
+ public String getDescription() {
+ return myDescription;
}
private int parseTag(String[] lines, int index, String tagPrefix) {
@@ -57,6 +70,9 @@ public abstract class StructuredDocString {
myArgTagValues.put(tagName, argValues);
}
argValues.put(arg, value);
+ if (tagName.equals("param")) {
+ myParameters.add(arg);
+ }
}
return index;
}
@@ -72,6 +88,10 @@ public abstract class StructuredDocString {
return argValues == null ? null : argValues.get(argName);
}
+ public List getParameters() {
+ return myParameters;
+ }
+
@Nullable
public abstract String getReturnType();
diff --git a/python/src/com/jetbrains/python/testing/TestRunnerConfigurable.form b/python/src/com/jetbrains/python/testing/PyIntegratedToolsConfigurable.form
similarity index 56%
rename from python/src/com/jetbrains/python/testing/TestRunnerConfigurable.form
rename to python/src/com/jetbrains/python/testing/PyIntegratedToolsConfigurable.form
index af8eb74a9225..73365d9acdfd 100644
--- a/python/src/com/jetbrains/python/testing/TestRunnerConfigurable.form
+++ b/python/src/com/jetbrains/python/testing/PyIntegratedToolsConfigurable.form
@@ -1,6 +1,6 @@
-