PY-30103 Add wrapping for long function signatures

Similarly to how it's done for Java and other languages parameters are
wrapped and aligned always if there are more than one of them. Also, the
need to wrap the return type annotation is detected naively by checking
whether the length of the last line containing it exceeds 80 characters.
This commit is contained in:
Mikhail Golubev
2018-06-09 15:59:38 +03:00
parent 5988a48236
commit 9e118e01a9
22 changed files with 125 additions and 34 deletions
@@ -35,7 +35,7 @@ class DocumentationBuilderKit {
static final TagWrapper TagCode = new TagWrapper("code");
static final TagWrapper TagSpan = new TagWrapper("span");
final static @NonNls String BR = "<br>";
public final static @NonNls String BR = "<br>";
@NotNull
static final Function<String, String> ESCAPE_ONLY = StringUtil::escapeXml;
@@ -54,7 +54,6 @@ import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.regex.Pattern;
import static com.jetbrains.python.documentation.DocumentationBuilderKit.*;
@@ -343,7 +342,7 @@ public class PyDocumentationBuilder {
}
}
myBody.add(PythonDocumentationProvider.describeDecorators(pyFunction, WRAP_IN_ITALIC, ESCAPE_AND_SAVE_NEW_LINES_AND_SPACES, BR, BR));
myBody.add(PythonDocumentationProvider.describeFunction(pyFunction, WRAP_IN_BOLD, ESCAPE_AND_SAVE_NEW_LINES_AND_SPACES, myContext));
myBody.add(PythonDocumentationProvider.describeFunction(pyFunction, myContext, false));
if (docStringExpression == null && !isProperty) {
addInheritedDocString(pyFunction, pyClass);
}
@@ -22,6 +22,7 @@ import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.QualifiedName;
import com.intellij.util.io.HttpRequests;
import com.intellij.xml.CommonXmlStrings;
import com.jetbrains.python.PyNames;
import com.jetbrains.python.PyTokenTypes;
import com.jetbrains.python.PythonDialectsTokenSetProvider;
@@ -64,6 +65,8 @@ import static com.jetbrains.python.documentation.DocumentationBuilderKit.*;
*/
public class PythonDocumentationProvider extends AbstractDocumentationProvider implements ExternalDocumentationProvider {
private static final int RETURN_TYPE_WRAPPING_THRESHOLD = 80;
// provides ctrl+hover info
@Override
@Nullable
@@ -92,7 +95,7 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
result
.add(describeDecorators(function, Function.identity(), TO_ONE_LINE_AND_ESCAPE, ", ", "\n"))
.add(describeFunction(function, Function.identity(), ESCAPE_ONLY, context));
.add(describeFunction(function, context, true));
final String docStringSummary = getDocStringSummary(function);
if (docStringSummary != null) {
@@ -139,15 +142,13 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
}
@NotNull
static ChainIterable<String> describeFunction(@NotNull PyFunction function,
@NotNull Function<String, String> escapedNameMapper,
@NotNull Function<String, String> escaper,
@NotNull TypeEvalContext context) {
static ChainIterable<String> describeFunction(@NotNull PyFunction function, @NotNull TypeEvalContext context, boolean forTooltip) {
final ChainIterable<String> result = describeFunctionWithTypes(function, escaper, escapedNameMapper, context);
final ChainIterable<String> result = new ChainIterable<>(describeFunctionWithTypes(function, context, forTooltip));
if (!PyiUtil.isOverload(function, context)) {
final List<PyFunction> overloads = PyiUtil.getOverloads(function, context);
final Function<String, String> escaper = forTooltip ? Function.identity() : PythonDocumentationProvider::saveSpaces;
if (!overloads.isEmpty()) {
result.addItem(escaper.apply("\nPossible types:\n"));
boolean first = true;
@@ -202,30 +203,57 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
}
@NotNull
private static ChainIterable<String> describeFunctionWithTypes(@NotNull PyFunction function,
@NotNull Function<String, String> escaper,
@NotNull Function<String, String> escapedNameMapper,
@NotNull TypeEvalContext context) {
final ChainIterable<String> result = new ChainIterable<>();
private static String describeFunctionWithTypes(@NotNull PyFunction function,
@NotNull TypeEvalContext context,
boolean forTooltip) {
final StringBuilder result = new StringBuilder();
// TODO wrapping of long signatures
if (function.isAsync()) {
result.addItem(ESCAPE_AND_SAVE_NEW_LINES_AND_SPACES.apply("async "));
result.append(saveSpaces("async "));
}
result.addItem(ESCAPE_AND_SAVE_NEW_LINES_AND_SPACES.apply("def "))
.addItem(escapedNameMapper.apply(function.getName()))
.addItem("(");
result.append(saveSpaces("def "));
final String name = StringUtil.notNullize(function.getName(), PyNames.UNNAMED_ELEMENT);
int firstParamOffset = result.length() + name.length();
int lastLineOffset = 0;
if (forTooltip) {
result.append(escaped(name));
}
else {
appendWithTags(result, escaped(name), "b");
}
result.append("(");
firstParamOffset++;
boolean first = true;
for (PyCallableParameter parameter : function.getParameters(context)) {
if (!first) {
result.addItem(ESCAPE_AND_SAVE_NEW_LINES_AND_SPACES.apply(", "));
result.append(",");
if (forTooltip) {
result.append(CommonXmlStrings.NBSP);
}
else {
result.append(BR);
lastLineOffset = result.length();
// alignment
StringUtil.repeatSymbol(result, ' ', firstParamOffset);
}
}
result.addItem(parameter.getName()).addItem(ESCAPE_AND_SAVE_NEW_LINES_AND_SPACES.apply(": "));
describeTypeWithLinks(parameter.getType(context), context, function, result);
result.append(escaped(StringUtil.notNullize(parameter.getName(), PyNames.UNNAMED_ELEMENT)))
.append(saveSpaces(": "));
result.append(formatTypeWithLinks(parameter.getType(context), function, context));
first = false;
}
result.addItem(ESCAPE_AND_SAVE_NEW_LINES_AND_SPACES.apply(") -> "));
describeTypeWithLinks(context.getReturnType(function), context, function, result);
return result;
result.append(")");
final int wrappingOffset = result.length();
result.append(saveSpaces(" -> "))
.append(formatTypeWithLinks(context.getReturnType(function), function, context));
if (!forTooltip && StringUtil.stripHtml(result.substring(lastLineOffset), false).length() > RETURN_TYPE_WRAPPING_THRESHOLD) {
result.insert(wrappingOffset, saveSpaces("\n "));
}
return result.toString();
}
@Nullable
@@ -681,4 +709,32 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
}
return super.getCustomDocumentationElement(editor, file, contextElement);
}
private static void appendWithTags(@NotNull StringBuilder result, @NotNull String escapedContent, @NotNull String... tags) {
for (String tag : tags) {
result.append("<").append(tag).append(">");
}
result.append(escapedContent);
for (int i = tags.length - 1; i >= 0; i--) {
result.append("</").append(tags[i]).append(">");
}
}
@NotNull
private static String escaped(@NotNull String unescaped) {
return StringUtil.escapeXml(unescaped);
}
@NotNull
private static String saveSpaces(@NotNull String text) {
return ESCAPE_AND_SAVE_NEW_LINES_AND_SPACES.apply(text);
}
@NotNull
private static String formatTypeWithLinks(@Nullable PyType type, @NotNull PsiElement anchor, @NotNull TypeEvalContext context) {
final ChainIterable<String> holder = new ChainIterable<>();
describeTypeWithLinks(type, context, anchor, holder);
return holder.toString();
}
}
@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#FunctionWrapping">FunctionWrapping</a><br>def&nbsp;<b>maybe_add_numbers</b>(a:&nbsp;Optional[<a href="psi_element://#typename#int">int</a>],<br> b:&nbsp;Optional[<a href="psi_element://#typename#int">int</a>],<br> c:&nbsp;Optional[<a href="psi_element://#typename#int">int</a>],<br> d:&nbsp;Optional[<a href="psi_element://#typename#int">int</a>])&nbsp;-&gt;&nbsp;Optional[<a href="psi_element://#typename#int">int</a>]</pre></div></body></html>
@@ -0,0 +1,4 @@
from typing import Optional
def maybe<the_ref>_add_numbers(a: Optional[int], b: Optional[int], c: Optional[int],
d: Optional[int]) -> Optional[int]:
pass
@@ -1 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#OptionalAndUnionTypesContainingTypeVars">OptionalAndUnionTypesContainingTypeVars</a><br>def&nbsp;<b>f</b>(x1:&nbsp;Optional[T],&nbsp;x2:&nbsp;Union[T,&nbsp;Tuple[Any,&nbsp;Any]])&nbsp;-&gt;&nbsp;None</pre></div></body></html>
<html><body><div class='definition'><pre><a href="psi_element://#module#OptionalAndUnionTypesContainingTypeVars">OptionalAndUnionTypesContainingTypeVars</a><br>def&nbsp;<b>f</b>(x1:&nbsp;Optional[T],<br> x2:&nbsp;Union[T,&nbsp;Tuple[Any,&nbsp;Any]])&nbsp;-&gt;&nbsp;None</pre></div></body></html>
@@ -1 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#OptionalParameterType">OptionalParameterType</a><br>def&nbsp;<b>open</b>(encoding:&nbsp;Optional[<a href="psi_element://#typename#str">str</a>],&nbsp;errors:&nbsp;Optional[<a href="psi_element://#typename#str">str</a>])&nbsp;-&gt;&nbsp;None</pre></div></body></html>
<html><body><div class='definition'><pre><a href="psi_element://#module#OptionalParameterType">OptionalParameterType</a><br>def&nbsp;<b>open</b>(encoding:&nbsp;Optional[<a href="psi_element://#typename#str">str</a>],<br> errors:&nbsp;Optional[<a href="psi_element://#typename#str">str</a>])&nbsp;-&gt;&nbsp;None</pre></div></body></html>
+1 -1
View File
@@ -1 +1 @@
<html><body><div class='definition'><pre>Property <b>x</b> of <a href="psi_element://#typename#PropNewDeleter.A">PropNewDeleter.A</a><br>@<i>x.deleter</i><br>def&nbsp;<b>x</b>(self:&nbsp;<a href="psi_element://#typename#A">A</a>,&nbsp;v:&nbsp;Any)&nbsp;-&gt;&nbsp;None</pre></div><div class='content'>Deletes&nbsp;X</div><table class='sections'><tr><td valign='top' class='section'><p>Accessor kind:</td><td valign='top'>Deleter</td></table></body></html>
<html><body><div class='definition'><pre>Property <b>x</b> of <a href="psi_element://#typename#PropNewDeleter.A">PropNewDeleter.A</a><br>@<i>x.deleter</i><br>def&nbsp;<b>x</b>(self:&nbsp;<a href="psi_element://#typename#A">A</a>,<br> v:&nbsp;Any)&nbsp;-&gt;&nbsp;None</pre></div><div class='content'>Deletes&nbsp;X</div><table class='sections'><tr><td valign='top' class='section'><p>Accessor kind:</td><td valign='top'>Deleter</td></table></body></html>
@@ -1 +1 @@
<html><body><div class='definition'><pre>Property <b>m</b> of <a href="psi_element://#typename#PropNewDocstringOfGetter.C">PropNewDocstringOfGetter.C</a><br>@<i>m.setter</i><br>def&nbsp;<b>m</b>(self:&nbsp;<a href="psi_element://#typename#C">C</a>,&nbsp;x:&nbsp;Any)&nbsp;-&gt;&nbsp;None</pre></div><div class='content'>Foo</div><table class='sections'><tr><td valign='top' class='section'><p>Documentation is copied from:</td><td valign='top'>property getter</td><tr><td valign='top' class='section'><p>Accessor kind:</td><td valign='top'>Setter</td></table></body></html>
<html><body><div class='definition'><pre>Property <b>m</b> of <a href="psi_element://#typename#PropNewDocstringOfGetter.C">PropNewDocstringOfGetter.C</a><br>@<i>m.setter</i><br>def&nbsp;<b>m</b>(self:&nbsp;<a href="psi_element://#typename#C">C</a>,<br> x:&nbsp;Any)&nbsp;-&gt;&nbsp;None</pre></div><div class='content'>Foo</div><table class='sections'><tr><td valign='top' class='section'><p>Documentation is copied from:</td><td valign='top'>property getter</td><tr><td valign='top' class='section'><p>Accessor kind:</td><td valign='top'>Setter</td></table></body></html>
+1 -1
View File
@@ -1 +1 @@
<html><body><div class='definition'><pre>Property <b>x</b> of <a href="psi_element://#typename#PropNewSetter.A">PropNewSetter.A</a><br>@<i>x.setter</i><br>def&nbsp;<b>x</b>(self:&nbsp;<a href="psi_element://#typename#A">A</a>,&nbsp;v:&nbsp;Any)&nbsp;-&gt;&nbsp;None</pre></div><div class='content'>Sets&nbsp;X</div><table class='sections'><tr><td valign='top' class='section'><p>Accessor kind:</td><td valign='top'>Setter</td></table></body></html>
<html><body><div class='definition'><pre>Property <b>x</b> of <a href="psi_element://#typename#PropNewSetter.A">PropNewSetter.A</a><br>@<i>x.setter</i><br>def&nbsp;<b>x</b>(self:&nbsp;<a href="psi_element://#typename#A">A</a>,<br> v:&nbsp;Any)&nbsp;-&gt;&nbsp;None</pre></div><div class='content'>Sets&nbsp;X</div><table class='sections'><tr><td valign='top' class='section'><p>Accessor kind:</td><td valign='top'>Setter</td></table></body></html>
@@ -1 +1 @@
<html><body><div class='definition'><pre>Property <b>attr</b> of <a href="psi_element://#typename#PropOldDocParamOfPropertyCall.C">PropOldDocParamOfPropertyCall.C</a><br>def&nbsp;<b>_set</b>(self:&nbsp;<a href="psi_element://#typename#C">C</a>,&nbsp;x:&nbsp;Any)&nbsp;-&gt;&nbsp;None</pre></div><div class='content'>Docstring</div><table class='sections'><tr><td valign='top' class='section'><p>Accessor kind:</td><td valign='top'>Setter</td></table></body></html>
<html><body><div class='definition'><pre>Property <b>attr</b> of <a href="psi_element://#typename#PropOldDocParamOfPropertyCall.C">PropOldDocParamOfPropertyCall.C</a><br>def&nbsp;<b>_set</b>(self:&nbsp;<a href="psi_element://#typename#C">C</a>,<br> x:&nbsp;Any)&nbsp;-&gt;&nbsp;None</pre></div><div class='content'>Docstring</div><table class='sections'><tr><td valign='top' class='section'><p>Accessor kind:</td><td valign='top'>Setter</td></table></body></html>
@@ -1 +1 @@
<html><body><div class='definition'><pre>Property <b>attr</b> of <a href="psi_element://#typename#PropOldDocstringOfGetter.C">PropOldDocstringOfGetter.C</a><br>def&nbsp;<b>_set</b>(self:&nbsp;<a href="psi_element://#typename#C">C</a>,&nbsp;x:&nbsp;Any)&nbsp;-&gt;&nbsp;None</pre></div><div class='content'>Getter.</div><table class='sections'><tr><td valign='top' class='section'><p>Documentation is copied from:</td><td valign='top'>property getter</td><tr><td valign='top' class='section'><p>Accessor kind:</td><td valign='top'>Setter</td></table></body></html>
<html><body><div class='definition'><pre>Property <b>attr</b> of <a href="psi_element://#typename#PropOldDocstringOfGetter.C">PropOldDocstringOfGetter.C</a><br>def&nbsp;<b>_set</b>(self:&nbsp;<a href="psi_element://#typename#C">C</a>,<br> x:&nbsp;Any)&nbsp;-&gt;&nbsp;None</pre></div><div class='content'>Getter.</div><table class='sections'><tr><td valign='top' class='section'><p>Documentation is copied from:</td><td valign='top'>property getter</td><tr><td valign='top' class='section'><p>Accessor kind:</td><td valign='top'>Setter</td></table></body></html>
+1 -1
View File
@@ -1 +1 @@
<html><body><div class='definition'><pre>Property <b>x</b> of <a href="psi_element://#typename#PropOldSetter.A">PropOldSetter.A</a><br>def&nbsp;<b>__getX</b>(self:&nbsp;<a href="psi_element://#typename#A">A</a>,&nbsp;x:&nbsp;Any)&nbsp;-&gt;&nbsp;None</pre></div><div class='content'>Doc&nbsp;of&nbsp;getter</div><table class='sections'><tr><td valign='top' class='section'><p>Accessor kind:</td><td valign='top'>Setter</td></table></body></html>
<html><body><div class='definition'><pre>Property <b>x</b> of <a href="psi_element://#typename#PropOldSetter.A">PropOldSetter.A</a><br>def&nbsp;<b>__getX</b>(self:&nbsp;<a href="psi_element://#typename#A">A</a>,<br> x:&nbsp;Any)&nbsp;-&gt;&nbsp;None</pre></div><div class='content'>Doc&nbsp;of&nbsp;getter</div><table class='sections'><tr><td valign='top' class='section'><p>Accessor kind:</td><td valign='top'>Setter</td></table></body></html>
@@ -1 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#typename#list">list</a><br>def&nbsp;<b>count</b>(self:&nbsp;<a href="psi_element://#typename#list">list</a>,&nbsp;object:&nbsp;_T)&nbsp;-&gt;&nbsp;<a href="psi_element://#typename#int">int</a></pre></div><div class='content'>L.count(value)&nbsp;-&gt;&nbsp;integer&nbsp;--&nbsp;return&nbsp;number&nbsp;of&nbsp;occurrences&nbsp;of&nbsp;value</div><table class='sections'><tr><td valign='top' class='section'><p>Assigned to:</td><td valign='top'><code>c1</code></td></table></body></html>
<html><body><div class='definition'><pre><a href="psi_element://#typename#list">list</a><br>def&nbsp;<b>count</b>(self:&nbsp;<a href="psi_element://#typename#list">list</a>,<br> object:&nbsp;_T)&nbsp;-&gt;&nbsp;<a href="psi_element://#typename#int">int</a></pre></div><div class='content'>L.count(value)&nbsp;-&gt;&nbsp;integer&nbsp;--&nbsp;return&nbsp;number&nbsp;of&nbsp;occurrences&nbsp;of&nbsp;value</div><table class='sections'><tr><td valign='top' class='section'><p>Assigned to:</td><td valign='top'><code>c1</code></td></table></body></html>
@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#ReturnTypeWrappedBecauseOfFunctionName">ReturnTypeWrappedBecauseOfFunctionName</a><br>def&nbsp;<b>func01234567890123456789012345678901234567890123456789012345678901234567890123456789</b>()<br>&nbsp;&nbsp;-&gt;&nbsp;<a href="psi_element://#typename#int">int</a></pre></div></body></html>
@@ -0,0 +1,2 @@
def fu<the_ref>nc01234567890123456789012345678901234567890123456789012345678901234567890123456789() -> int:
pass
@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#ReturnTypeWrappedBecauseOfParameters">ReturnTypeWrappedBecauseOfParameters</a><br>def&nbsp;<b>func</b>(first:&nbsp;Any,<br> second0123456789012345678901234567890123456789012345678901234567890123456789:&nbsp;Any)<br>&nbsp;&nbsp;-&gt;&nbsp;<a href="psi_element://#typename#int">int</a></pre></div></body></html>
@@ -0,0 +1,2 @@
def fu<the_ref>nc(first, second0123456789012345678901234567890123456789012345678901234567890123456789) -> int:
pass
@@ -0,0 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#ReturnTypeWrappedBecauseTooLongItself">ReturnTypeWrappedBecauseTooLongItself</a><br>def&nbsp;<b>func</b>()<br>&nbsp;&nbsp;-&gt;&nbsp;Tuple[<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#int">int</a>]</pre></div></body></html>
@@ -0,0 +1,4 @@
from typing import Tuple
def fu<the_ref>nc() -> Tuple[int, int, int, int, int, int, int, int, int, int, int, int, int, int, int, int]:
pass
+1 -1
View File
@@ -1 +1 @@
<html><body><div class='definition'><pre><a href="psi_element://#module#TypeVars">TypeVars</a><br>def&nbsp;<b>f</b>(p1:&nbsp;T1,&nbsp;p2:&nbsp;T2,&nbsp;p3:&nbsp;T3)&nbsp;-&gt;&nbsp;None</pre></div></body></html>
<html><body><div class='definition'><pre><a href="psi_element://#module#TypeVars">TypeVars</a><br>def&nbsp;<b>f</b>(p1:&nbsp;T1,<br> p2:&nbsp;T2,<br> p3:&nbsp;T3)&nbsp;-&gt;&nbsp;None</pre></div></body></html>
@@ -455,6 +455,26 @@ public class PyQuickDocTest extends LightMarkedTestCase {
runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly);
}
// PY-30103
public void testFunctionWrapping() {
runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly);
}
// PY-30103
public void testReturnTypeWrappedBecauseTooLongItself() {
runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly);
}
// PY-30103
public void testReturnTypeWrappedBecauseOfParameters() {
runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly);
}
// PY-30103
public void testReturnTypeWrappedBecauseOfFunctionName() {
runWithLanguageLevel(LanguageLevel.PYTHON35, this::checkHTMLOnly);
}
public void testPackage() {
myFixture.copyDirectoryToProject(getTestName(false), "");
final VirtualFile file = myFixture.findFileInTempDir("pkg/__init__.py");