Move all documentation links processing to separate class

This commit is contained in:
Semyon Proshev
2017-09-22 15:52:15 +03:00
parent 2c536c6461
commit 924ed716d6
7 changed files with 139 additions and 99 deletions
@@ -16,7 +16,6 @@
package com.jetbrains.python.documentation;
import com.google.common.collect.Lists;
import com.intellij.codeInsight.documentation.DocumentationManagerProtocol;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.xml.CommonXmlStrings;
@@ -51,6 +50,9 @@ class DocumentationBuilderKit {
@NotNull
static final Function<String, String> WRAP_IN_ITALIC = s -> "<i>" + s + "</i>";
@NotNull
static final Function<String, String> WRAP_IN_CODE = s -> "<code>" + s + "</code>";
private DocumentationBuilderKit() {
}
@@ -101,19 +103,4 @@ class DocumentationBuilderKit {
}
}
static class LinkWrapper implements Function<Iterable<String>, Iterable<String>> {
private final String myLink;
LinkWrapper(String link) {
myLink = link;
}
public Iterable<String> apply(Iterable<String> contents) {
return new ChainIterable<String>()
.addItem("<a href=\"").addItem(DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL).addItem(myLink).addItem("\">")
.add(contents).addItem("</a>")
;
}
}
}
@@ -43,7 +43,10 @@ import com.jetbrains.python.psi.resolve.PyResolveContext;
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
import com.jetbrains.python.psi.resolve.QualifiedResolveResult;
import com.jetbrains.python.psi.resolve.RootVisitor;
import com.jetbrains.python.psi.types.*;
import com.jetbrains.python.psi.types.PyClassType;
import com.jetbrains.python.psi.types.PyDynamicallyEvaluatedType;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.pyi.PyiUtil;
import com.jetbrains.python.toolbox.ChainIterable;
import com.jetbrains.python.toolbox.Maybe;
@@ -384,10 +387,8 @@ public class PyDocumentationBuilder {
if (inheritedDoc.length() > 1) {
myEpilog.addItem(BR).addItem(BR);
final String ancestorName = ancestor.getName();
final String marker =
(pyClass == ancestor) ? PythonDocumentationProvider.LINK_TYPE_CLASS : PythonDocumentationProvider.LINK_TYPE_PARENT;
final String ancestorLink =
$().addWith(new LinkWrapper(marker + ancestorName), $(ancestorName)).toString();
pyClass == ancestor ? PyDocumentationLink.toContainingClass(ancestorName) : PyDocumentationLink.toAncestorOfContainingClass(ancestorName);
if (isFromClass) {
myEpilog.addItem(PyBundle.message("QDOC.copied.from.class.$0", ancestorLink));
}
@@ -487,13 +488,7 @@ public class PyDocumentationBuilder {
final String description = typeAndDescr.second;
if (type != null) {
final PyType pyType = PyTypeParser.getTypeByName(parameter, type, context);
if (pyType instanceof PyClassType) {
myBody.addItem(": ").addWith(new LinkWrapper(PythonDocumentationProvider.LINK_TYPE_PARAM), $(pyType.getName()));
}
else {
myBody.addItem(": ").addItem(type);
}
myBody.addItem(": ").addItem(PyDocumentationLink.toParameterPossibleClass(type, parameter, context));
}
if (description != null) {
@@ -523,8 +518,11 @@ public class PyDocumentationBuilder {
assert cls != null;
final String type = PyUtil.isInstanceAttribute((PyExpression)myElement) ? "Instance attribute " : "Class attribute ";
myProlog
.addItem(type).addWith(TagBold, $().addWith(TagCode, $(((PyTargetExpression)myElement).getName())))
.addItem(" of class ").addWith(PythonDocumentationProvider.LinkMyClass, $().addWith(TagCode, $(cls.getName()))).addItem(BR);
.addItem(type)
.addWith(TagBold, $().addWith(TagCode, $(((PyTargetExpression)myElement).getName())))
.addItem(" of class ")
.addItem(PyDocumentationLink.toContainingClass(WRAP_IN_CODE.apply(cls.getName())))
.addItem(BR);
final String docString = PyPsiUtils.strValue(getEffectiveDocStringExpression((PyTargetExpression)myElement));
if (docString != null) {
@@ -0,0 +1,116 @@
/*
* Copyright 2000-2017 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.jetbrains.python.documentation
import com.intellij.codeInsight.documentation.DocumentationManagerProtocol
import com.intellij.psi.PsiElement
import com.intellij.psi.util.PsiTreeUtil
import com.jetbrains.python.psi.PyClass
import com.jetbrains.python.psi.PyFunction
import com.jetbrains.python.psi.PyNamedParameter
import com.jetbrains.python.psi.types.PyClassType
import com.jetbrains.python.psi.types.PyTypeParser
import com.jetbrains.python.psi.types.TypeEvalContext
object PyDocumentationLink {
private const val LINK_TYPE_CLASS = "#class#"
private const val LINK_TYPE_PARENT = "#parent#"
private const val LINK_TYPE_PARAM = "#param#"
private const val LINK_TYPE_TYPENAME = "#typename#"
@JvmStatic
fun toContainingClass(content: String?): String {
return "<a href=\"${DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL}$LINK_TYPE_CLASS\">$content</a>"
}
@JvmStatic
fun toAncestorOfContainingClass(ancestor: String?): String {
return "<a href=\"${DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL}$LINK_TYPE_PARENT$ancestor\">$ancestor</a>"
}
@JvmStatic
fun toParameterPossibleClass(type: String, anchor: PsiElement, context: TypeEvalContext): String {
val pyType = PyTypeParser.getTypeByName(anchor, type, context)
return when (pyType) {
is PyClassType -> "<a href=\"${DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL}$LINK_TYPE_PARAM\">$type</a>"
else -> type
}
}
@JvmStatic
fun toPossibleClass(type: String, anchor: PsiElement, context: TypeEvalContext): String {
val pyType = PyTypeParser.getTypeByName(anchor, type, context)
return when (pyType) {
is PyClassType -> "<a href=\"${DocumentationManagerProtocol.PSI_ELEMENT_PROTOCOL}$LINK_TYPE_TYPENAME$type\">$type</a>"
else -> type
}
}
@JvmStatic
fun elementForLink(link: String, element: PsiElement, context: TypeEvalContext): PsiElement? {
return if (link == LINK_TYPE_CLASS) {
containingClass(element)
}
else if (link == LINK_TYPE_PARAM) {
parameterPossibleClass(element, context)
}
else if (link.startsWith(LINK_TYPE_PARENT)) {
ancestorOfContainingClass(element, link.substring(LINK_TYPE_PARENT.length), context)
}
else if (link.startsWith(LINK_TYPE_TYPENAME)) {
possibleClass(link.substring(LINK_TYPE_TYPENAME.length), element, context)
}
else {
null
}
}
@JvmStatic
private fun containingClass(element: PsiElement): PyClass? {
return when (element) {
is PyClass -> element
is PyFunction -> element.containingClass
else -> PsiTreeUtil.getParentOfType(element, PyClass::class.java)
}
}
@JvmStatic
private fun ancestorOfContainingClass(element: PsiElement, ancestor: String, context: TypeEvalContext): PyClass? {
val ancestors = containingClass(element)?.getAncestorClasses(context)
return ancestors?.find { it.name == ancestor }
}
@JvmStatic
private fun parameterPossibleClass(parameter: PsiElement, context: TypeEvalContext): PyClass? {
if (parameter is PyNamedParameter) {
val type = context.getType(parameter)
if (type is PyClassType) {
return type.pyClass
}
}
return null
}
@JvmStatic
private fun possibleClass(type: String, anchor: PsiElement, context: TypeEvalContext): PyClass? {
val pyType = PyTypeParser.getTypeByName(anchor, type, context)
return when (pyType) {
is PyClassType -> pyType.pyClass
else -> null
}
}
}
@@ -30,7 +30,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
import static com.jetbrains.python.documentation.DocumentationBuilderKit.$;
import static com.jetbrains.python.documentation.DocumentationBuilderKit.combUp;
/**
@@ -421,13 +420,8 @@ public class PyTypeModelBuilder {
@Override
protected void addType(String name) {
final PyType type = PyTypeParser.getTypeByName(myAnchor, name);
if (type instanceof PyClassType) {
myBody.addWith(new DocumentationBuilderKit.LinkWrapper(PythonDocumentationProvider.LINK_TYPE_TYPENAME + name), $(name));
}
else {
add(name);
}
final TypeEvalContext context = TypeEvalContext.userInitiated(myAnchor.getProject(), myAnchor.getContainingFile());
myBody.addItem(PyDocumentationLink.toPossibleClass(name, myAnchor, context));
}
}
@@ -44,15 +44,12 @@ import com.jetbrains.python.documentation.docstrings.DocStringUtil;
import com.jetbrains.python.psi.*;
import com.jetbrains.python.psi.impl.PyBuiltinCache;
import com.jetbrains.python.psi.resolve.QualifiedNameFinder;
import com.jetbrains.python.psi.types.PyClassType;
import com.jetbrains.python.psi.types.PyType;
import com.jetbrains.python.psi.types.PyTypeParser;
import com.jetbrains.python.psi.types.TypeEvalContext;
import com.jetbrains.python.toolbox.ChainIterable;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.HeadMethod;
import org.apache.commons.httpclient.params.HttpConnectionManagerParams;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jsoup.Jsoup;
@@ -75,11 +72,6 @@ import static com.jetbrains.python.documentation.DocumentationBuilderKit.*;
*/
public class PythonDocumentationProvider extends AbstractDocumentationProvider implements ExternalDocumentationProvider {
@NonNls static final String LINK_TYPE_CLASS = "#class#";
@NonNls static final String LINK_TYPE_PARENT = "#parent#";
@NonNls static final String LINK_TYPE_PARAM = "#param#";
@NonNls static final String LINK_TYPE_TYPENAME = "#typename#";
// provides ctrl+hover info
@Override
@Nullable
@@ -282,7 +274,7 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
final String name = cls.getName();
cat.addItem("class ");
if (allowHtml && linkOwnName) {
cat.addWith(LinkMyClass, $(name));
cat.addItem(PyDocumentationLink.toContainingClass(name));
}
else {
cat.addWith(nameWrapper, $(name));
@@ -303,7 +295,7 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
isNotFirst = true;
}
if (allowHtml) {
cat.addWith(new LinkWrapper(LINK_TYPE_PARENT + parentName), $(parentName));
cat.addItem(PyDocumentationLink.toAncestorOfContainingClass(parentName));
}
else {
cat.addItem(parentName);
@@ -344,30 +336,9 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
@Override
public PsiElement getDocumentationElementForLink(PsiManager psiManager, @NotNull String link, @NotNull PsiElement context) {
if (link.equals(LINK_TYPE_CLASS)) {
return inferContainingClassOf(context);
}
else if (link.equals(LINK_TYPE_PARAM)) {
return inferClassOfParameter(context);
}
else if (link.startsWith(LINK_TYPE_PARENT)) {
final PyClass cls = inferContainingClassOf(context);
if (cls != null) {
final String desiredName = link.substring(LINK_TYPE_PARENT.length());
for (PyClass parent : cls.getAncestorClasses(null)) {
final String parentName = parent.getName();
if (parentName != null && parentName.equals(desiredName)) return parent;
}
}
}
else if (link.startsWith(LINK_TYPE_TYPENAME)) {
final String typeName = link.substring(LINK_TYPE_TYPENAME.length());
final PyType type = PyTypeParser.getTypeByName(context, typeName);
if (type instanceof PyClassType) {
return ((PyClassType)type).getPyClass();
}
}
return null;
return PyDocumentationLink.elementForLink(link,
context,
TypeEvalContext.userInitiated(context.getProject(), context.getContainingFile()));
}
@Override
@@ -585,30 +556,4 @@ public class PythonDocumentationProvider extends AbstractDocumentationProvider i
}
return super.getCustomDocumentationElement(editor, file, contextElement);
}
@Nullable
private static PyClass inferContainingClassOf(PsiElement context) {
if (context instanceof PyClass) return (PyClass)context;
if (context instanceof PyFunction) {
return ((PyFunction)context).getContainingClass();
}
else {
return PsiTreeUtil.getParentOfType(context, PyClass.class);
}
}
@Nullable
private static PyClass inferClassOfParameter(@NotNull PsiElement context) {
if (context instanceof PyNamedParameter) {
final PyType type = TypeEvalContext.userInitiated(context.getProject(), context.getContainingFile()).getType(
(PyNamedParameter)context);
if (type instanceof PyClassType) {
return ((PyClassType)type).getPyClass();
}
}
return null;
}
public static final LinkWrapper LinkMyClass = new LinkWrapper(LINK_TYPE_CLASS);
// link item to containing class
}
@@ -1 +1 @@
<html><body><code><small>class <a href="psi_element://#class#">Foo</a>(<a href="psi_element://#parent#object">object</a>)</small><br><br>def <b>__init__</b>(self)</code><br><br><i>Documentation is missing.</i> The following is copied from class <code><a href="psi_element://#class#Foo">Foo</a></code>.<br><br><code>Doc&nbsp;of&nbsp;Foo.</code></body></html>
<html><body><code><small>class <a href="psi_element://#class#">Foo</a>(<a href="psi_element://#parent#object">object</a>)</small><br><br>def <b>__init__</b>(self)</code><br><br><i>Documentation is missing.</i> The following is copied from class <code><a href="psi_element://#class#">Foo</a></code>.<br><br><code>Doc&nbsp;of&nbsp;Foo.</code></body></html>
+1 -1
View File
@@ -1 +1 @@
<html><body><code>def <b>f</b>(p1:&nbsp;T1,&nbsp;p2:&nbsp;T2,&nbsp;p3:&nbsp;T3)<br>Inferred&nbsp;type:&nbsp;(p1:&nbsp;TypeVar('T1',&nbsp;<a href="psi_element://#typename#int">int</a>),&nbsp;p2:&nbsp;TypeVar('T2',&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#str">str</a>),&nbsp;p3:&nbsp;TypeVar('T3',&nbsp;List[<a href="psi_element://#typename#bool">bool</a>]))&nbsp;-&gt;&nbsp;None<br></code></body></html>
<html><body><code>def <b>f</b>(p1:&nbsp;T1,&nbsp;p2:&nbsp;T2,&nbsp;p3:&nbsp;T3)<br>Inferred&nbsp;type:&nbsp;(p1:&nbsp;TypeVar('T1',&nbsp;<a href="psi_element://#typename#int">int</a>),&nbsp;p2:&nbsp;TypeVar('T2',&nbsp;<a href="psi_element://#typename#int">int</a>,&nbsp;<a href="psi_element://#typename#str">str</a>),&nbsp;p3:&nbsp;TypeVar('T3',&nbsp;<a href="psi_element://#typename#List">List</a>[<a href="psi_element://#typename#bool">bool</a>]))&nbsp;-&gt;&nbsp;None<br></code></body></html>