Merge branch 'master' of git.labs.intellij.net:idea/ultimate

This commit is contained in:
Ekaterina Tuzova
2011-04-07 18:20:29 +04:00
10 changed files with 394 additions and 113 deletions
@@ -278,6 +278,8 @@
<applicationService serviceInterface="com.jetbrains.python.documentation.PythonDocumentationMap"
serviceImplementation="com.jetbrains.python.documentation.PythonDocumentationMap"/>
<applicationConfigurable implementation="com.jetbrains.python.documentation.PythonDocumentationConfigurable"/>
<projectService serviceInterface="com.jetbrains.python.documentation.PyDocumentationSettings"
serviceImplementation="com.jetbrains.python.documentation.PyDocumentationSettings"/>
<!-- Django -->
<codeInsight.lineMarkerProvider language="Python" implementationClass="com.jetbrains.django.annotator.ViewMethodLineMarkerProvider"/>
@@ -0,0 +1,92 @@
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.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;
import javax.swing.*;
import java.util.List;
/**
* User: catherine
*/
public class PyIntegratedToolsConfigurable implements Configurable, NonDefaultProjectConfigurable {
private JPanel myMainPanel;
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() {
return "Python Integrated Tools";
}
@Override
public Icon getIcon() {
return null;
}
@Override
public String getHelpTopic() {
return "test_runner_configuration";
}
@Override
public JComponent createComponent() {
List<String> configurations = TestRunnerService.getInstance(myProject).getConfigurations();
myModel = new PythonTestConfigurationsModel(configurations, TestRunnerService.getInstance(myProject).getProjectConfiguration(),
myProject);
updateConfigurations();
return myMainPanel;
}
private void updateConfigurations() {
myTestRunnerComboBox.setModel(myModel);
}
@Override
public boolean isModified() {
if (myTestRunnerComboBox.getSelectedItem() != myModel.getProjectConfiguration()) {
return true;
}
if (!Comparing.equal(myDocstringFormatComboBox.getSelectedItem(), myDocumentationSettings.myDocStringFormat)) {
return true;
}
return false;
}
@Override
public void apply() throws ConfigurationException {
myModel.apply();
myDocumentationSettings.myDocStringFormat = (String) myDocstringFormatComboBox.getSelectedItem();
}
@Override
public void reset() {
myTestRunnerComboBox.setSelectedItem(myModel.getProjectConfiguration());
myTestRunnerComboBox.repaint();
myModel.reset();
myDocstringFormatComboBox.setSelectedItem(myDocumentationSettings.myDocStringFormat);
}
@Override
public void disposeUIResources() {
}
}
@@ -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<String> ALL = ImmutableList.of(PLAIN, EPYDOC, SPHINX);
private DocStringFormat() {
}
}
@@ -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("<a href=\"");
if (!linkUrl.matches("[a-z]+:.+")) {
myResult.append("http://");
}
myResult.append(linkUrl).append("\">").append(linkText).append("</a>");
}
}
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 ? "<p>" : " ";
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);
}
}
@@ -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<PyDocumentationSettings> {
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);
}
}
@@ -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<String> combUpDocString(Project project, @NotNull String docstring) {
ChainIterable<String> cat = new ChainIterable<String>();
private static @NotNull List<String> combUpDocString(Project project, @NotNull String docstring) {
PyDocumentationSettings documentationSettings = PyDocumentationSettings.getInstance(project);
List<String> result = new ArrayList<String>();
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<String> parameters = docString.getParameters();
if (parameters.size() > 0) {
result.append("<br><b>Parameters:</b><br>");
for (String parameter : parameters) {
result.append("<b>").append(parameter).append("</b>: ").append(docString.getParamDescription(parameter)).append("<br>");
}
}
return result.toString();
}
// provides ctrl+Q doc
@@ -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<String> myParameters = Lists.newArrayList();
protected final Map<String, String> mySimpleTagValues = Maps.newHashMap();
protected final Map<String, Map<String, String>> 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<String> getParameters() {
return myParameters;
}
@Nullable
public abstract String getReturnType();
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.testing.TestRunnerConfigurable">
<grid id="27dc6" binding="myMainPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.configuration.PyIntegratedToolsConfigurable">
<grid id="27dc6" binding="myMainPanel" layout-manager="GridLayoutManager" row-count="3" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
<margin top="0" left="0" bottom="0" right="0"/>
<constraints>
<xy x="20" y="20" width="547" height="302"/>
@@ -8,7 +8,7 @@
<properties/>
<border type="none"/>
<children>
<component id="68c44" class="javax.swing.JComboBox" binding="myComboBox">
<component id="68c44" class="javax.swing.JComboBox" binding="myTestRunnerComboBox">
<constraints>
<grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
@@ -19,14 +19,31 @@
<grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<text value="Default test configuration"/>
<text value="Default test runner:"/>
</properties>
</component>
<vspacer id="fc19e">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
<grid row="2" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
</constraints>
</vspacer>
<component id="88016" class="com.intellij.ui.components.JBLabel">
<constraints>
<grid row="1" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="0" fill="3" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<labelFor value="e486b"/>
<text value="Docstring format:"/>
</properties>
</component>
<component id="e486b" class="javax.swing.JComboBox" binding="myDocstringFormatComboBox" default-binding="true">
<constraints>
<grid row="1" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
</constraints>
<properties>
<model/>
</properties>
</component>
</children>
</grid>
</form>
@@ -1,78 +0,0 @@
package com.jetbrains.python.testing;
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 org.jetbrains.annotations.Nls;
import javax.swing.*;
import java.util.List;
/**
* User: catherine
*/
public class TestRunnerConfigurable implements Configurable, NonDefaultProjectConfigurable {
private JPanel myMainPanel;
private JComboBox myComboBox;
private PythonTestConfigurationsModel myModel;
private Project myProject;
public TestRunnerConfigurable(Project project) {
myProject = project;
}
@Nls
@Override
public String getDisplayName() {
return "Test Runner";
}
@Override
public Icon getIcon() {
return null;
}
@Override
public String getHelpTopic() {
return "test_runner_configuration";
}
@Override
public JComponent createComponent() {
List<String> configurations = TestRunnerService.getInstance(myProject).getConfigurations();
myModel = new PythonTestConfigurationsModel(configurations, TestRunnerService.getInstance(myProject).getProjectConfiguration(),
myProject);
updateConfigurations();
return myMainPanel;
}
private void updateConfigurations() {
myComboBox.setModel(myModel);
}
@Override
public boolean isModified() {
if (myComboBox.getSelectedItem() != myModel.getProjectConfiguration()) {
return true;
}
return false;
}
@Override
public void apply() throws ConfigurationException {
myModel.apply();
}
@Override
public void reset() {
myComboBox.setSelectedItem(myModel.getProjectConfiguration());
myComboBox.repaint();
myModel.reset();
}
@Override
public void disposeUIResources() {
//To change body of implemented methods use File | Settings | File Templates.
}
}
@@ -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,8 +52,38 @@ public class EpydocStringTest extends TestCase {
" @param *args: arguments passed to function\n" +
" @param **kwargs: keyword arguments passed to C{function}\n" +
" \"\"\"");
final List<String> 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"));
}
public void testInlineMarkupToHTML() {
assertEquals("can contain <i>inline markup</i> and <b>bold text</b>", EpydocString.inlineMarkupToHTML("can contain I{inline markup} and B{bold text}"));
}
public void testCodeToHTML() {
assertEquals("<code>my_dict={1:2, 3:4}</code>", EpydocString.inlineMarkupToHTML("C{my_dict={1:2, 3:4}}"));
}
public void testUrlToHTML() {
assertEquals("<a href=\"http://www.python.org\">www.python.org</a>", EpydocString.inlineMarkupToHTML("U{www.python.org}"));
assertEquals("<a href=\"http://www.python.org\">www.python.org</a>", EpydocString.inlineMarkupToHTML("U{www.python.org}"));
assertEquals("<a href=\"http://epydoc.sourceforge.net\">The epydoc homepage</a>",
EpydocString.inlineMarkupToHTML("U{The epydoc homepage<http://\n" +
" epydoc.sourceforge.net>}"));
}
public void testNestedInlineMarkup() {
assertEquals("<i><b>Inline markup</b> may be nested; and it may span</i> multiple lines.",
EpydocString.inlineMarkupToHTML("I{B{Inline markup} may be nested; and\n" +
" it may span} multiple lines."));
}
public void testParagraph() {
assertEquals("foo<p>bar", EpydocString.inlineMarkupToHTML("foo\n\nbar"));
}
}