diff --git a/idea/src/org/jetbrains/kotlin/idea/KotlinQuickDocumentationProvider.kt b/idea/src/org/jetbrains/kotlin/idea/KotlinQuickDocumentationProvider.kt index 02859d07ce4..d72286a7071 100644 --- a/idea/src/org/jetbrains/kotlin/idea/KotlinQuickDocumentationProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/KotlinQuickDocumentationProvider.kt @@ -20,7 +20,8 @@ import com.google.common.html.HtmlEscapers import com.intellij.codeInsight.documentation.DocumentationManagerUtil import com.intellij.codeInsight.javadoc.JavaDocInfoGeneratorFactory import com.intellij.lang.documentation.AbstractDocumentationProvider -import com.intellij.lang.documentation.DocumentationMarkup +import com.intellij.lang.documentation.DocumentationMarkup.DEFINITION_END +import com.intellij.lang.documentation.DocumentationMarkup.DEFINITION_START import com.intellij.lang.java.JavaDocumentationProvider import com.intellij.openapi.diagnostic.Logger import com.intellij.psi.PsiElement @@ -35,10 +36,11 @@ import org.jetbrains.kotlin.idea.caches.resolve.resolveToDescriptorIfAny import org.jetbrains.kotlin.idea.codeInsight.DescriptorToSourceUtilsIde import org.jetbrains.kotlin.idea.core.completion.DeclarationLookupObject import org.jetbrains.kotlin.idea.decompiler.navigation.SourceNavigationHelper -import org.jetbrains.kotlin.idea.kdoc.KDocRenderer -import org.jetbrains.kotlin.idea.kdoc.findKDoc -import org.jetbrains.kotlin.idea.kdoc.isBoringBuiltinClass -import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink +import org.jetbrains.kotlin.idea.kdoc.* +import org.jetbrains.kotlin.idea.kdoc.KDocRenderer.appendKDocContent +import org.jetbrains.kotlin.idea.kdoc.KDocRenderer.appendKDocSection +import org.jetbrains.kotlin.idea.kdoc.KDocRenderer.renderKDocContent +import org.jetbrains.kotlin.idea.kdoc.KDocTemplate.DescriptionBodyTemplate import org.jetbrains.kotlin.idea.references.mainReference import org.jetbrains.kotlin.idea.resolve.frontendService import org.jetbrains.kotlin.js.resolve.diagnostics.findPsi @@ -164,37 +166,52 @@ class KotlinQuickDocumentationProvider : AbstractDocumentationProvider() { classifierNamePolicy = HtmlClassifierNamePolicy(ClassifierNamePolicy.SHORT) valueParametersHandler = WrapValueParameterHandler(valueParametersHandler) renderCompanionObjectName = true + withDefinedIn = false } private fun renderEnumSpecialFunction(element: KtClass, functionDescriptor: FunctionDescriptor, quickNavigation: Boolean): String { - var renderedDecl = DESCRIPTOR_RENDERER.render(functionDescriptor) + val renderedDeclaration = DESCRIPTOR_RENDERER.render(functionDescriptor) - if (quickNavigation) return renderedDecl + if (quickNavigation) return renderedDeclaration - val declarationDescriptor = element.resolveToDescriptorIfAny() - val enumDescriptor = declarationDescriptor?.getSuperClassNotAny() ?: return renderedDecl + val kdoc = run { + val declarationDescriptor = element.resolveToDescriptorIfAny() + val enumDescriptor = declarationDescriptor?.getSuperClassNotAny() ?: return@run null - val enumDeclaration = - DescriptorToSourceUtilsIde.getAnyDeclaration(element.project, enumDescriptor) as? KtDeclaration ?: return renderedDecl + val enumDeclaration = + DescriptorToSourceUtilsIde.getAnyDeclaration(element.project, enumDescriptor) as? KtDeclaration ?: return@run null - val enumSource = SourceNavigationHelper.getNavigationElement(enumDeclaration) - val functionName = functionDescriptor.fqNameSafe.shortName().asString() - val kdoc = enumSource.findDescendantOfType { - it.getChildrenOfType().any { it.findTagByName(functionName) != null } - } - - if (kdoc != null) { - val renderedComment = KDocRenderer.renderKDoc(kdoc.getDefaultSection()) - if (renderedComment.startsWith("

")) { - renderedDecl += renderedComment - } else { - renderedDecl = "$renderedDecl
$renderedComment" + val enumSource = SourceNavigationHelper.getNavigationElement(enumDeclaration) + val functionName = functionDescriptor.fqNameSafe.shortName().asString() + return@run enumSource.findDescendantOfType { + it.getChildrenOfType().any { it.findTagByName(functionName) != null } } } - return renderedDecl + val section = kdoc?.getDefaultSection() + + return buildString { + insert(KDocTemplate()) { + definition { + renderDefinition(functionDescriptor, DESCRIPTOR_RENDERER) + } + if (section != null) { + description { + insert(DescriptionBodyTemplate.Kotlin()) { + content { + appendKDocContent(section) + } + sections { + appendKDocSection(section) + } + } + } + } + } + } } + private fun renderEnum(element: KtClass, originalElement: PsiElement?, quickNavigation: Boolean): String? { val referenceExpression = originalElement?.getNonStrictParentOfType() if (referenceExpression != null) { @@ -238,10 +255,12 @@ class KotlinQuickDocumentationProvider : AbstractDocumentationProvider() { val ordinal = element.containingClassOrObject?.getBody()?.run { getChildrenOfType().indexOf(element) } return buildString { - append(renderKotlinDeclaration(element, quickNavigation)) - ordinal?.let { - wrapTag("b") { - append("Enum constant ordinal: $ordinal") + insert(buildKotlinDeclaration(element, quickNavigation)) { + definition { + it.inherit() + ordinal?.let { + append("Enum constant ordinal: $ordinal") + } } } } @@ -270,16 +289,24 @@ class KotlinQuickDocumentationProvider : AbstractDocumentationProvider() { return null } - private fun renderKotlinDeclaration(declaration: KtExpression, quickNavigation: Boolean): String { + private fun renderKotlinDeclaration(declaration: KtExpression, quickNavigation: Boolean) = buildString { + insert(buildKotlinDeclaration(declaration, quickNavigation)) {} + } + + private fun buildKotlinDeclaration(declaration: KtExpression, quickNavigation: Boolean): KDocTemplate { val context = declaration.analyze(BodyResolveMode.PARTIAL) val declarationDescriptor = context[BindingContext.DECLARATION_TO_DESCRIPTOR, declaration] if (declarationDescriptor == null) { LOG.info("Failed to find descriptor for declaration " + declaration.getElementTextWithContext()) - return "No documentation available" + return KDocTemplate.NoDocTemplate().apply { + error { + append("No documentation available") + } + } } - return renderKotlin(context, declarationDescriptor, quickNavigation, declaration) + return buildKotlin(context, declarationDescriptor, quickNavigation, declaration) } private fun renderKotlinImplicitLambdaParameter(element: KtReferenceExpression, quickNavigation: Boolean): String? { @@ -293,7 +320,16 @@ class KotlinQuickDocumentationProvider : AbstractDocumentationProvider() { declarationDescriptor: DeclarationDescriptor, quickNavigation: Boolean, ktElement: KtElement - ): String { + ) = buildString { + insert(buildKotlin(context, declarationDescriptor, quickNavigation, ktElement)) {} + } + + private fun buildKotlin( + context: BindingContext, + declarationDescriptor: DeclarationDescriptor, + quickNavigation: Boolean, + ktElement: KtElement + ): KDocTemplate { @Suppress("NAME_SHADOWING") var declarationDescriptor = declarationDescriptor if (declarationDescriptor is ValueParameterDescriptor) { @@ -303,65 +339,82 @@ class KotlinQuickDocumentationProvider : AbstractDocumentationProvider() { } } - var declaration = DESCRIPTOR_RENDERER.withOptions { - withDefinedIn = !DescriptorUtils.isLocal(declarationDescriptor) - }.render(declarationDescriptor) - - - var renderedHtml: String - - - renderedHtml = "${DocumentationMarkup.DEFINITION_START}$declaration${DocumentationMarkup.DEFINITION_END}" - val deprecationProvider = ktElement.getResolutionFacade().frontendService() - renderedHtml += renderDeprecationInfo(declarationDescriptor, deprecationProvider) - if (!quickNavigation) { - val comment = declarationDescriptor.findKDoc { DescriptorToSourceUtilsIde.getAnyDeclaration(ktElement.project, it) } - if (comment != null) { - val renderedComment = KDocRenderer.renderKDoc(comment) - renderedHtml += "${DocumentationMarkup.CONTENT_START}$renderedComment" - } else { - if (declarationDescriptor is CallableDescriptor) { // If we couldn't find KDoc, try to find javadoc in one of super's - val psi = declarationDescriptor.findPsi() as? KtFunction - if (psi != null) { - val lightElement = LightClassUtil.getLightClassMethod(psi) // Light method for super's scan in javadoc info gen - val javaDocInfoGenerator = JavaDocInfoGeneratorFactory.create(psi.project, lightElement) - val builder = StringBuilder() - if (javaDocInfoGenerator.generateDocInfoCore(builder, false)) { - val renderedJava = builder.toString() - renderedHtml += renderedJava.removeRange( - renderedJava.indexOf(DocumentationMarkup.DEFINITION_START), - renderedJava.indexOf(DocumentationMarkup.DEFINITION_END) - ) // Cut off light method signature + return KDocTemplate().apply { + definition { + renderDefinition(declarationDescriptor, DESCRIPTOR_RENDERER) + renderDeprecationInfo(declarationDescriptor, deprecationProvider) + } + if (!quickNavigation) { + description { + val comment = declarationDescriptor.findKDoc { DescriptorToSourceUtilsIde.getAnyDeclaration(ktElement.project, it) } + if (comment != null) { + insert(DescriptionBodyTemplate.Kotlin()) { + content { + appendKDocContent(comment) + } + sections { + if (comment is KDocSection) appendKDocSection(comment) + } + } + } else if (declarationDescriptor is CallableDescriptor) { // If we couldn't find KDoc, try to find javadoc in one of super's + insert(DescriptionBodyTemplate.FromJava()) { + body = extractJavaDescription(declarationDescriptor) } } } } } - - return renderedHtml } - private fun renderDeprecationInfo( + + private fun StringBuilder.renderDefinition(descriptor: DeclarationDescriptor, renderer: DescriptorRenderer) { + val containingDeclaration = descriptor.containingDeclaration + if (containingDeclaration != null) { + val fqName = containingDeclaration.fqNameSafe + if (!fqName.isRoot) { + DocumentationManagerUtil.createHyperlink(this, fqName.asString(), fqName.asString(), false) + } + } + + append(renderer.render(descriptor)) + } + + private fun extractJavaDescription(declarationDescriptor: DeclarationDescriptor): String { + val psi = declarationDescriptor.findPsi() as? KtFunction ?: return "" + val lightElement = + LightClassUtil.getLightClassMethod(psi) // Light method for super's scan in javadoc info gen + val javaDocInfoGenerator = JavaDocInfoGeneratorFactory.create(psi.project, lightElement) + val builder = StringBuilder() + if (javaDocInfoGenerator.generateDocInfoCore(builder, false)) { + val renderedJava = builder.toString() + return renderedJava.removeRange( + renderedJava.indexOf(DEFINITION_START), + renderedJava.indexOf(DEFINITION_END) + ) // Cut off light method signature + } + return "" + } + + private fun StringBuilder.renderDeprecationInfo( declarationDescriptor: DeclarationDescriptor, deprecationResolver: DeprecationResolver - ): String { - val deprecation = deprecationResolver.getDeprecations(declarationDescriptor).firstOrNull() ?: return "" + ) { + val deprecation = deprecationResolver.getDeprecations(declarationDescriptor).firstOrNull() ?: return - return buildString { - wrapTag("DL") { - deprecation.message?.let { message -> - wrapTag("DT") { wrapTag("b") { append("Deprecated:") } } - wrapTag("DD") { - append(message.htmlEscape()) - } + + wrapTag("DL") { + deprecation.message?.let { message -> + wrapTag("DT") { wrapTag("b") { append("Deprecated:") } } + wrapTag("DD") { + append(message.htmlEscape()) } - deprecation.deprecatedByAnnotationReplaceWithExpression()?.let { replaceWith -> - wrapTag("DT") { wrapTag("b") { append("Replace with:") } } - wrapTag("DD") { - wrapTag("code") { append(replaceWith.htmlEscape()) } - } + } + deprecation.deprecatedByAnnotationReplaceWithExpression()?.let { replaceWith -> + wrapTag("DT") { wrapTag("b") { append("Replace with:") } } + wrapTag("DD") { + wrapTag("code") { append(replaceWith.htmlEscape()) } } } } diff --git a/idea/src/org/jetbrains/kotlin/idea/kdoc/KDocRenderer.kt b/idea/src/org/jetbrains/kotlin/idea/kdoc/KDocRenderer.kt index 7ed9dd64f5b..ade72571d25 100644 --- a/idea/src/org/jetbrains/kotlin/idea/kdoc/KDocRenderer.kt +++ b/idea/src/org/jetbrains/kotlin/idea/kdoc/KDocRenderer.kt @@ -38,16 +38,26 @@ import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType object KDocRenderer { fun renderKDoc(docComment: KDocTag): String { return if (docComment is KDocSection) { - renderKDocSection(docComment) + renderKDocContent(docComment) + renderKDocSection(docComment) } else { - markdownToHtml(docComment.getContent(), allowSingleParagraph = true) + renderKDocContent(docComment) } } + fun renderKDocContent(docComment: KDocTag): String { + return markdownToHtml(docComment.getContent(), allowSingleParagraph = true) + } + + fun StringBuilder.appendKDocContent(docComment: KDocTag) { + append(renderKDocContent(docComment)) + } + + fun renderKDocSection(section: KDocSection): String = buildString { - append(markdownToHtml(section.getContent(), allowSingleParagraph = true)) - append(CONTENT_END) - append(SECTIONS_START) + appendKDocSection(section) + } + + fun StringBuilder.appendKDocSection(section: KDocSection) { renderTag(section.findTagByName("receiver"), "Receiver", this) val paramTags = section.findTagsByName("param").filter { it.getSubjectName() != null } renderTagList(paramTags, "Parameters", this) @@ -65,7 +75,6 @@ object KDocRenderer { val sampleTags = section.findTagsByName("sample").filter { it.getSubjectLink() != null } renderSamplesList(sampleTags, this) - append(SECTIONS_END) } private fun KDocLink.createHyperlink(to: StringBuilder) { diff --git a/idea/src/org/jetbrains/kotlin/idea/kdoc/KDocTemplate.kt b/idea/src/org/jetbrains/kotlin/idea/kdoc/KDocTemplate.kt new file mode 100644 index 00000000000..be029e652d8 --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/kdoc/KDocTemplate.kt @@ -0,0 +1,56 @@ +/* + * Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license + * that can be found in the license/LICENSE.txt file. + */ + +package org.jetbrains.kotlin.idea.kdoc + +import com.intellij.lang.documentation.DocumentationMarkup.* + +open class KDocTemplate : Template { + val definition = Placeholder() + + val description = Placeholder() + + override fun StringBuilder.apply() { + append(DEFINITION_START) + insert(definition) + append(DEFINITION_END) + + insert(description) + } + + sealed class DescriptionBodyTemplate : Template { + class Kotlin : DescriptionBodyTemplate() { + val content = Placeholder() + val sections = Placeholder() + override fun StringBuilder.apply() { + append(CONTENT_START) + insert(content) + append(CONTENT_END) + + append(SECTIONS_START) + insert(sections) + append(SECTIONS_END) + } + } + + class FromJava : DescriptionBodyTemplate() { + override fun StringBuilder.apply() { + append(body) + } + + lateinit var body: String + } + } + + class NoDocTemplate : KDocTemplate() { + + val error = Placeholder() + + override fun StringBuilder.apply() { + insert(error) + } + } +} +