From c1d2ba5c94f75bac34173fd2c170b59026d6733f Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 6 Apr 2011 19:10:03 +0200 Subject: [PATCH 1/4] converting epydoc markup to HTML --- .../python/documentation/EpydocString.java | 107 ++++++++++++++++-- .../jetbrains/python/EpydocStringTest.java | 23 ++++ 2 files changed, 121 insertions(+), 9 deletions(-) diff --git a/python/src/com/jetbrains/python/documentation/EpydocString.java b/python/src/com/jetbrains/python/documentation/EpydocString.java index df76825894ca..ecc2c93dd854 100644 --- a/python/src/com/jetbrains/python/documentation/EpydocString.java +++ b/python/src/com/jetbrains/python/documentation/EpydocString.java @@ -1,5 +1,6 @@ package com.jetbrains.python.documentation; +import com.intellij.openapi.util.text.StringUtil; import org.jetbrains.annotations.Nullable; /** @@ -34,34 +35,122 @@ 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(); + appendWithMarkup(s, resultBuilder, toHTML); + return resultBuilder.toString(); + } + + private static void appendWithMarkup(String s, StringBuilder resultBuilder, boolean toHTML) { 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); + resultBuilder.append(s.substring(pos, bracePos - 1)); + int rbracePos = findMatchingEndBrace(s, bracePos); if (rbracePos < 0) { - pos = bracePos+1; + pos = bracePos + 1; break; } - resultBuilder.append(s.substring(bracePos+1, rbracePos)); - pos = rbracePos+1; + final String inlineMarkupContent = s.substring(bracePos + 1, rbracePos); + if (toHTML) { + appendInlineMarkup(resultBuilder, prevChar, inlineMarkupContent); + } + else { + resultBuilder.append(inlineMarkupContent); + } + pos = rbracePos + 1; } else { - resultBuilder.append(s.substring(pos, bracePos+1)); + resultBuilder.append(StringUtil.escapeXml(joinLines(s.substring(pos, bracePos + 1), true))); pos = bracePos+1; } } - resultBuilder.append(s.substring(pos)); - return resultBuilder.toString(); + resultBuilder.append(StringUtil.escapeXml(joinLines(s.substring(pos), true))); + } + + 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 void appendInlineMarkup(StringBuilder resultBuilder, char markupChar, String markupContent) { + if (markupChar == 'U') { + appendLink(resultBuilder, markupContent); + return; + } + switch (markupChar) { + case 'I': + appendTagPair(resultBuilder, markupContent, "i"); + break; + case 'B': + appendTagPair(resultBuilder, markupContent, "b"); + break; + case 'C': + appendTagPair(resultBuilder, markupContent, "pre"); + break; + default: + resultBuilder.append(StringUtil.escapeXml(markupContent)); + break; + } + } + + private static void appendTagPair(StringBuilder resultBuilder, String markupContent, final String tagName) { + resultBuilder.append("<").append(tagName).append(">"); + appendWithMarkup(markupContent, resultBuilder, true); + resultBuilder.append(""); + } + + private static void appendLink(StringBuilder resultBuilder, 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 = StringUtil.escapeXml(joinLines(markupContent.substring(pos + 1, markupContent.length() - 1), false)); + } + resultBuilder.append("").append(linkText).append(""); + } + + private static String joinLines(String s, boolean addSpace) { + while(true) { + int lineBreakStart = s.indexOf('\n'); + if (lineBreakStart < 0) break; + int lineBreakEnd = lineBreakStart+1; + while(lineBreakEnd < s.length() && s.charAt(lineBreakEnd) == ' ') { + lineBreakEnd++; + } + s = s.substring(0, lineBreakStart) + (addSpace ? " " : "") + s.substring(lineBreakEnd); + } + return s; + } + + @Nullable + public static String inlineMarkupToHTML(String s) { + return convertInlineMarkup(s, true); } } diff --git a/python/testSrc/com/jetbrains/python/EpydocStringTest.java b/python/testSrc/com/jetbrains/python/EpydocStringTest.java index 24c5ca63b20a..0752c18cd011 100644 --- a/python/testSrc/com/jetbrains/python/EpydocStringTest.java +++ b/python/testSrc/com/jetbrains/python/EpydocStringTest.java @@ -54,4 +54,27 @@ public class EpydocStringTest extends TestCase { assertEquals("effective GID used to call the function.", docString.getParamDescription("egid")); assertEquals("arguments passed to function", docString.getParamDescription("args")); } + + public void testInlineMarkupToHTML() { + assertEquals("can contain inline markup and bold text", EpydocString.inlineMarkupToHTML("can contain I{inline markup} and B{bold text}")); + } + + public void testCodeToHTML() { + assertEquals("
my_dict={1:2, 3:4}
", EpydocString.inlineMarkupToHTML("C{my_dict={1:2, 3:4}}")); + } + + public void testUrlToHTML() { + assertEquals("www.python.org", EpydocString.inlineMarkupToHTML("U{www.python.org}")); + assertEquals("www.python.org", EpydocString.inlineMarkupToHTML("U{www.python.org}")); + assertEquals("The epydoc homepage", + EpydocString.inlineMarkupToHTML("U{The epydoc homepage}")); + } + + public void testNestedInlineMarkup() { + assertEquals("Inline markup may be nested; and it may span multiple lines.", + EpydocString.inlineMarkupToHTML("I{B{Inline markup} may be nested; and\n" + + " it may span} multiple lines.")); + + } } From cd16f5165e96a986476d9501153a9db23dc56db2 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 6 Apr 2011 19:16:08 +0200 Subject: [PATCH 2/4] "test runner" configurable -> "python integrated tools" --- .../PyIntegratedToolsConfigurable.java} | 10 ++++++---- ...gurable.form => PyIntegratedToolsConfigurable.form} | 4 ++-- 2 files changed, 8 insertions(+), 6 deletions(-) rename python/src/com/jetbrains/python/{testing/TestRunnerConfigurable.java => configuration/PyIntegratedToolsConfigurable.java} (82%) rename python/src/com/jetbrains/python/testing/{TestRunnerConfigurable.form => PyIntegratedToolsConfigurable.form} (91%) diff --git a/python/src/com/jetbrains/python/testing/TestRunnerConfigurable.java b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java similarity index 82% rename from python/src/com/jetbrains/python/testing/TestRunnerConfigurable.java rename to python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java index 4887637350e4..62b4095bef7e 100644 --- a/python/src/com/jetbrains/python/testing/TestRunnerConfigurable.java +++ b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java @@ -1,9 +1,11 @@ -package com.jetbrains.python.testing; +package com.jetbrains.python.configuration; import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.NonDefaultProjectConfigurable; import com.intellij.openapi.project.Project; +import com.jetbrains.python.testing.PythonTestConfigurationsModel; +import com.jetbrains.python.testing.TestRunnerService; import org.jetbrains.annotations.Nls; import javax.swing.*; @@ -12,19 +14,19 @@ import java.util.List; /** * User: catherine */ -public class TestRunnerConfigurable implements Configurable, NonDefaultProjectConfigurable { +public class PyIntegratedToolsConfigurable implements Configurable, NonDefaultProjectConfigurable { private JPanel myMainPanel; private JComboBox myComboBox; private PythonTestConfigurationsModel myModel; private Project myProject; - public TestRunnerConfigurable(Project project) { + public PyIntegratedToolsConfigurable(Project project) { myProject = project; } @Nls @Override public String getDisplayName() { - return "Test Runner"; + return "Python Integrated Tools"; } @Override diff --git a/python/src/com/jetbrains/python/testing/TestRunnerConfigurable.form b/python/src/com/jetbrains/python/testing/PyIntegratedToolsConfigurable.form similarity index 91% rename from python/src/com/jetbrains/python/testing/TestRunnerConfigurable.form rename to python/src/com/jetbrains/python/testing/PyIntegratedToolsConfigurable.form index af8eb74a9225..9ad59229134f 100644 --- a/python/src/com/jetbrains/python/testing/TestRunnerConfigurable.form +++ b/python/src/com/jetbrains/python/testing/PyIntegratedToolsConfigurable.form @@ -1,5 +1,5 @@ -
+ @@ -19,7 +19,7 @@ - + From 0e221ca2778c05b4bb368836a8e5a1dc5f2de096 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 6 Apr 2011 19:28:55 +0200 Subject: [PATCH 3/4] configure docstring format on "integrated tools" page --- python/src/META-INF/python-plugin-common.xml | 2 ++ .../PyIntegratedToolsConfigurable.java | 26 +++++++++++---- .../python/documentation/DocStringFormat.java | 19 +++++++++++ .../PyDocumentationSettings.java | 32 +++++++++++++++++++ .../PyIntegratedToolsConfigurable.form | 23 +++++++++++-- 5 files changed, 92 insertions(+), 10 deletions(-) create mode 100644 python/src/com/jetbrains/python/documentation/DocStringFormat.java create mode 100644 python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java diff --git a/python/src/META-INF/python-plugin-common.xml b/python/src/META-INF/python-plugin-common.xml index 14a43ebbff35..a2c645c32c76 100644 --- a/python/src/META-INF/python-plugin-common.xml +++ b/python/src/META-INF/python-plugin-common.xml @@ -278,6 +278,8 @@ + diff --git a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java index 62b4095bef7e..b9fc3e0375c3 100644 --- a/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java +++ b/python/src/com/jetbrains/python/configuration/PyIntegratedToolsConfigurable.java @@ -4,6 +4,10 @@ import com.intellij.openapi.options.Configurable; import com.intellij.openapi.options.ConfigurationException; import com.intellij.openapi.options.NonDefaultProjectConfigurable; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Comparing; +import com.intellij.ui.CollectionComboBoxModel; +import com.jetbrains.python.documentation.DocStringFormat; +import com.jetbrains.python.documentation.PyDocumentationSettings; import com.jetbrains.python.testing.PythonTestConfigurationsModel; import com.jetbrains.python.testing.TestRunnerService; import org.jetbrains.annotations.Nls; @@ -16,13 +20,18 @@ import java.util.List; */ public class PyIntegratedToolsConfigurable implements Configurable, NonDefaultProjectConfigurable { private JPanel myMainPanel; - private JComboBox myComboBox; + private JComboBox myTestRunnerComboBox; + private JComboBox myDocstringFormatComboBox; private PythonTestConfigurationsModel myModel; private Project myProject; + private final PyDocumentationSettings myDocumentationSettings; public PyIntegratedToolsConfigurable(Project project) { myProject = project; + myDocumentationSettings = PyDocumentationSettings.getInstance(project); + myDocstringFormatComboBox.setModel(new CollectionComboBoxModel(DocStringFormat.ALL, myDocumentationSettings.myDocStringFormat)); } + @Nls @Override public String getDisplayName() { @@ -49,13 +58,15 @@ public class PyIntegratedToolsConfigurable implements Configurable, NonDefaultPr } private void updateConfigurations() { - myComboBox.setModel(myModel); - + myTestRunnerComboBox.setModel(myModel); } @Override public boolean isModified() { - if (myComboBox.getSelectedItem() != myModel.getProjectConfiguration()) { + if (myTestRunnerComboBox.getSelectedItem() != myModel.getProjectConfiguration()) { + return true; + } + if (!Comparing.equal(myDocstringFormatComboBox.getSelectedItem(), myDocumentationSettings.myDocStringFormat)) { return true; } return false; @@ -64,17 +75,18 @@ public class PyIntegratedToolsConfigurable implements Configurable, NonDefaultPr @Override public void apply() throws ConfigurationException { myModel.apply(); + myDocumentationSettings.myDocStringFormat = (String) myDocstringFormatComboBox.getSelectedItem(); } @Override public void reset() { - myComboBox.setSelectedItem(myModel.getProjectConfiguration()); - myComboBox.repaint(); + myTestRunnerComboBox.setSelectedItem(myModel.getProjectConfiguration()); + myTestRunnerComboBox.repaint(); myModel.reset(); + myDocstringFormatComboBox.setSelectedItem(myDocumentationSettings.myDocStringFormat); } @Override public void disposeUIResources() { - //To change body of implemented methods use File | Settings | File Templates. } } diff --git a/python/src/com/jetbrains/python/documentation/DocStringFormat.java b/python/src/com/jetbrains/python/documentation/DocStringFormat.java new file mode 100644 index 000000000000..63cd229b195f --- /dev/null +++ b/python/src/com/jetbrains/python/documentation/DocStringFormat.java @@ -0,0 +1,19 @@ +package com.jetbrains.python.documentation; + +import com.google.common.collect.ImmutableList; + +import java.util.List; + +/** + * @author yole + */ +public class DocStringFormat { + public static final String PLAIN = "Plain"; + public static final String EPYDOC = "Epydoc"; + public static final String SPHINX = "Sphinx"; + + public static final List ALL = ImmutableList.of(PLAIN, EPYDOC, SPHINX); + + private DocStringFormat() { + } +} 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..db6bd1b3c3ea --- /dev/null +++ b/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java @@ -0,0 +1,32 @@ +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 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/testing/PyIntegratedToolsConfigurable.form b/python/src/com/jetbrains/python/testing/PyIntegratedToolsConfigurable.form index 9ad59229134f..73365d9acdfd 100644 --- a/python/src/com/jetbrains/python/testing/PyIntegratedToolsConfigurable.form +++ b/python/src/com/jetbrains/python/testing/PyIntegratedToolsConfigurable.form @@ -1,6 +1,6 @@ - + @@ -8,7 +8,7 @@ - + @@ -24,9 +24,26 @@ - + + + + + + + + + + + + + + + + + + From 2a79e8ea202baa36feca44a90d508c46f31a18f4 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 6 Apr 2011 21:15:55 +0200 Subject: [PATCH 4/4] work in progress on rendering epydoc docstrings to HTML --- .../python/documentation/EpydocString.java | 174 +++++++++++------- .../PyDocumentationSettings.java | 4 + .../PythonDocumentationProvider.java | 34 +++- .../documentation/StructuredDocString.java | 20 ++ .../jetbrains/python/EpydocStringTest.java | 15 +- 5 files changed, 166 insertions(+), 81 deletions(-) diff --git a/python/src/com/jetbrains/python/documentation/EpydocString.java b/python/src/com/jetbrains/python/documentation/EpydocString.java index ecc2c93dd854..883f209364b1 100644 --- a/python/src/com/jetbrains/python/documentation/EpydocString.java +++ b/python/src/com/jetbrains/python/documentation/EpydocString.java @@ -1,6 +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; /** @@ -11,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() { @@ -46,39 +54,101 @@ public class EpydocString extends StructuredDocString { @Nullable private static String convertInlineMarkup(String s, boolean toHTML) { if (s == null) return null; - StringBuilder resultBuilder = new StringBuilder(); - appendWithMarkup(s, resultBuilder, toHTML); - return resultBuilder.toString(); + MarkupConverter converter = toHTML ? new HTMLConverter() : new MarkupConverter(); + converter.appendWithMarkup(s); + return converter.result(); } - private static void appendWithMarkup(String s, StringBuilder resultBuilder, boolean toHTML) { - 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 = findMatchingEndBrace(s, bracePos); - if (rbracePos < 0) { - pos = bracePos + 1; - break; - } - final String inlineMarkupContent = s.substring(bracePos + 1, rbracePos); - if (toHTML) { - appendInlineMarkup(resultBuilder, prevChar, inlineMarkupContent); + 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 { - resultBuilder.append(inlineMarkupContent); + appendText(s.substring(pos, bracePos + 1)); + pos = bracePos+1; } - pos = rbracePos + 1; } - else { - resultBuilder.append(StringUtil.escapeXml(joinLines(s.substring(pos, bracePos + 1), true))); - 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(StringUtil.escapeXml(joinLines(s.substring(pos), true))); + + private void appendTagPair(String markupContent, final String tagName) { + myResult.append("<").append(tagName).append(">"); + appendWithMarkup(markupContent); + myResult.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) { @@ -94,57 +164,23 @@ public class EpydocString extends StructuredDocString { return -1; } - private static void appendInlineMarkup(StringBuilder resultBuilder, char markupChar, String markupContent) { - if (markupChar == 'U') { - appendLink(resultBuilder, markupContent); - return; - } - switch (markupChar) { - case 'I': - appendTagPair(resultBuilder, markupContent, "i"); - break; - case 'B': - appendTagPair(resultBuilder, markupContent, "b"); - break; - case 'C': - appendTagPair(resultBuilder, markupContent, "pre"); - break; - default: - resultBuilder.append(StringUtil.escapeXml(markupContent)); - break; - } - } - - private static void appendTagPair(StringBuilder resultBuilder, String markupContent, final String tagName) { - resultBuilder.append("<").append(tagName).append(">"); - appendWithMarkup(markupContent, resultBuilder, true); - resultBuilder.append(""); - } - - private static void appendLink(StringBuilder resultBuilder, 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 = StringUtil.escapeXml(joinLines(markupContent.substring(pos + 1, markupContent.length() - 1), false)); - } - resultBuilder.append("").append(linkText).append(""); - } - private static String joinLines(String s, boolean addSpace) { while(true) { int lineBreakStart = s.indexOf('\n'); if (lineBreakStart < 0) break; int lineBreakEnd = lineBreakStart+1; - while(lineBreakEnd < s.length() && s.charAt(lineBreakEnd) == ' ') { + int blankLines = 0; + while(lineBreakEnd < s.length() && (s.charAt(lineBreakEnd) == ' ' || s.charAt(lineBreakEnd) == '\n')) { + if (s.charAt(lineBreakEnd) == '\n') blankLines++; lineBreakEnd++; } - s = s.substring(0, lineBreakStart) + (addSpace ? " " : "") + s.substring(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; } diff --git a/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java b/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java index db6bd1b3c3ea..7da47bb9f95a 100644 --- a/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java +++ b/python/src/com/jetbrains/python/documentation/PyDocumentationSettings.java @@ -16,6 +16,10 @@ import com.intellij.util.xmlb.XmlSerializerUtil; 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); } 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/testSrc/com/jetbrains/python/EpydocStringTest.java b/python/testSrc/com/jetbrains/python/EpydocStringTest.java index 0752c18cd011..4d121b60af14 100644 --- a/python/testSrc/com/jetbrains/python/EpydocStringTest.java +++ b/python/testSrc/com/jetbrains/python/EpydocStringTest.java @@ -1,12 +1,14 @@ package com.jetbrains.python; +import com.intellij.testFramework.UsefulTestCase; import com.jetbrains.python.documentation.EpydocString; -import junit.framework.TestCase; + +import java.util.List; /** * @author yole */ -public class EpydocStringTest extends TestCase { +public class EpydocStringTest extends UsefulTestCase { public void testTagValue() { EpydocString docString = new EpydocString("@rtype: C{str}"); assertEquals("C{str}", docString.getTagValue("rtype")); @@ -50,6 +52,9 @@ public class EpydocStringTest extends TestCase { " @param *args: arguments passed to function\n" + " @param **kwargs: keyword arguments passed to C{function}\n" + " \"\"\""); + + final List params = docString.getParameters(); + assertOrderedEquals(params, "euid", "egid", "function", "*args", "**kwargs"); assertEquals("effective UID used to call the function.", docString.getParamDescription("euid")); assertEquals("effective GID used to call the function.", docString.getParamDescription("egid")); assertEquals("arguments passed to function", docString.getParamDescription("args")); @@ -60,7 +65,7 @@ public class EpydocStringTest extends TestCase { } public void testCodeToHTML() { - assertEquals("

my_dict={1:2, 3:4}
", EpydocString.inlineMarkupToHTML("C{my_dict={1:2, 3:4}}")); + assertEquals("my_dict={1:2, 3:4}", EpydocString.inlineMarkupToHTML("C{my_dict={1:2, 3:4}}")); } public void testUrlToHTML() { @@ -77,4 +82,8 @@ public class EpydocStringTest extends TestCase { " it may span} multiple lines.")); } + + public void testParagraph() { + assertEquals("foo

bar", EpydocString.inlineMarkupToHTML("foo\n\nbar")); + } }