mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
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:
@@ -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 <b>maybe_add_numbers</b>(a: Optional[<a href="psi_element://#typename#int">int</a>],<br> b: Optional[<a href="psi_element://#typename#int">int</a>],<br> c: Optional[<a href="psi_element://#typename#int">int</a>],<br> d: Optional[<a href="psi_element://#typename#int">int</a>]) -> 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 <b>f</b>(x1: Optional[T], x2: Union[T, Tuple[Any, Any]]) -> None</pre></div></body></html>
|
||||
<html><body><div class='definition'><pre><a href="psi_element://#module#OptionalAndUnionTypesContainingTypeVars">OptionalAndUnionTypesContainingTypeVars</a><br>def <b>f</b>(x1: Optional[T],<br> x2: Union[T, Tuple[Any, Any]]) -> None</pre></div></body></html>
|
||||
@@ -1 +1 @@
|
||||
<html><body><div class='definition'><pre><a href="psi_element://#module#OptionalParameterType">OptionalParameterType</a><br>def <b>open</b>(encoding: Optional[<a href="psi_element://#typename#str">str</a>], errors: Optional[<a href="psi_element://#typename#str">str</a>]) -> None</pre></div></body></html>
|
||||
<html><body><div class='definition'><pre><a href="psi_element://#module#OptionalParameterType">OptionalParameterType</a><br>def <b>open</b>(encoding: Optional[<a href="psi_element://#typename#str">str</a>],<br> errors: Optional[<a href="psi_element://#typename#str">str</a>]) -> None</pre></div></body></html>
|
||||
@@ -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 <b>x</b>(self: <a href="psi_element://#typename#A">A</a>, v: Any) -> None</pre></div><div class='content'>Deletes 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 <b>x</b>(self: <a href="psi_element://#typename#A">A</a>,<br> v: Any) -> None</pre></div><div class='content'>Deletes 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 <b>m</b>(self: <a href="psi_element://#typename#C">C</a>, x: Any) -> 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 <b>m</b>(self: <a href="psi_element://#typename#C">C</a>,<br> x: Any) -> 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 @@
|
||||
<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 <b>x</b>(self: <a href="psi_element://#typename#A">A</a>, v: Any) -> None</pre></div><div class='content'>Sets 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 <b>x</b>(self: <a href="psi_element://#typename#A">A</a>,<br> v: Any) -> None</pre></div><div class='content'>Sets 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 <b>_set</b>(self: <a href="psi_element://#typename#C">C</a>, x: Any) -> 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 <b>_set</b>(self: <a href="psi_element://#typename#C">C</a>,<br> x: Any) -> 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 <b>_set</b>(self: <a href="psi_element://#typename#C">C</a>, x: Any) -> 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 <b>_set</b>(self: <a href="psi_element://#typename#C">C</a>,<br> x: Any) -> 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 @@
|
||||
<html><body><div class='definition'><pre>Property <b>x</b> of <a href="psi_element://#typename#PropOldSetter.A">PropOldSetter.A</a><br>def <b>__getX</b>(self: <a href="psi_element://#typename#A">A</a>, x: Any) -> None</pre></div><div class='content'>Doc of 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 <b>__getX</b>(self: <a href="psi_element://#typename#A">A</a>,<br> x: Any) -> None</pre></div><div class='content'>Doc of 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 <b>count</b>(self: <a href="psi_element://#typename#list">list</a>, object: _T) -> <a href="psi_element://#typename#int">int</a></pre></div><div class='content'>L.count(value) -> integer -- return number of occurrences of 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 <b>count</b>(self: <a href="psi_element://#typename#list">list</a>,<br> object: _T) -> <a href="psi_element://#typename#int">int</a></pre></div><div class='content'>L.count(value) -> integer -- return number of occurrences of 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 <b>func01234567890123456789012345678901234567890123456789012345678901234567890123456789</b>()<br> -> <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 <b>func</b>(first: Any,<br> second0123456789012345678901234567890123456789012345678901234567890123456789: Any)<br> -> <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 <b>func</b>()<br> -> Tuple[<a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#int">int</a>, <a href="psi_element://#typename#int">int</a>, <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 @@
|
||||
<html><body><div class='definition'><pre><a href="psi_element://#module#TypeVars">TypeVars</a><br>def <b>f</b>(p1: T1, p2: T2, p3: T3) -> None</pre></div></body></html>
|
||||
<html><body><div class='definition'><pre><a href="psi_element://#module#TypeVars">TypeVars</a><br>def <b>f</b>(p1: T1,<br> p2: T2,<br> p3: T3) -> 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");
|
||||
|
||||
Reference in New Issue
Block a user