Fix for KT-11032 Samples not rendered in Quick Doc

Now samples included as code blocks into quick doc
This commit is contained in:
Simon Ogorodnik
2016-11-15 12:57:47 +03:00
parent 94770f8447
commit 2824c8d4cd
4 changed files with 135 additions and 1 deletions
@@ -17,14 +17,22 @@
package org.jetbrains.kotlin.idea.kdoc
import com.intellij.codeInsight.documentation.DocumentationManagerUtil
import com.intellij.psi.PsiElement
import org.intellij.markdown.IElementType
import org.intellij.markdown.MarkdownElementTypes
import org.intellij.markdown.MarkdownTokenTypes
import org.intellij.markdown.ast.ASTNode
import org.intellij.markdown.flavours.commonmark.CommonMarkFlavourDescriptor
import org.intellij.markdown.parser.MarkdownParser
import org.jetbrains.kotlin.idea.references.mainReference
import org.jetbrains.kotlin.idea.util.wrapTag
import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink
import org.jetbrains.kotlin.kdoc.psi.impl.KDocName
import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
import org.jetbrains.kotlin.psi.KtBlockExpression
import org.jetbrains.kotlin.psi.KtDeclarationWithBody
import org.jetbrains.kotlin.psi.psiUtil.getChildrenOfType
object KDocRenderer {
fun renderKDoc(docComment: KDocTag): String {
@@ -47,10 +55,67 @@ object KDocRenderer {
renderTag(docComment.findTagByName("since"), "Since", result)
renderSeeAlso(docComment, result)
val sampleTags = docComment.findTagsByName("sample").filter { it.getSubjectLink() != null }
renderSamplesList(sampleTags, result)
}
return result.toString()
}
private fun KDocLink.createHyperlink(to: StringBuilder) {
DocumentationManagerUtil.createHyperlink(to, getLinkText(), getLinkText(), false)
}
private fun KDocLink.getTargetElement(): PsiElement? {
return this.getChildrenOfType<KDocName>().last().mainReference.resolve()
}
private fun PsiElement.extractExampleText() = when (this) {
is KtDeclarationWithBody -> {
val bodyExpression = bodyExpression
when (bodyExpression) {
is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}")
else -> bodyExpression!!.text
}
}
else -> text
}
private fun trimCommonIndent(text: String): String {
fun String.leadingIndent() = indexOfFirst { !it.isWhitespace() }
val lines = text.split('\n')
val minIndent = lines.filter { it.trim().isNotEmpty() }.map(String::leadingIndent).min() ?: 0
return lines.map { it.drop(minIndent) }.joinToString("\n")
}
private fun renderSamplesList(sampleTags: List<KDocTag>, to: StringBuilder) {
if (sampleTags.isEmpty()) return
to.apply {
wrapTag("dl") {
append("<dt><b>Samples:</b></dt>")
sampleTags.forEach {
wrapTag("dd") {
it.getSubjectLink()?.let { subjectLink ->
subjectLink.createHyperlink(to)
val target = subjectLink.getTargetElement()
wrapTag("pre") {
wrapTag("code") {
if (target == null)
to.append("// Unresolved")
else {
to.append(trimCommonIndent(target.extractExampleText()))
}
}
}
}
}
}
}
}
}
private fun renderSeeAlso(docComment: KDocSection, to: StringBuilder) {
val seeTags = docComment.findTagsByName("see")
if (seeTags.isEmpty()) return
@@ -99,7 +164,8 @@ object KDocRenderer {
val maybeSingleParagraph = markdownNode.children.filter { it.type != MarkdownTokenTypes.EOL }.singleOrNull()
if (maybeSingleParagraph != null && !allowSingleParagraph) {
return maybeSingleParagraph.children.joinToString("") { it.toHtml() }
} else {
}
else {
return markdownNode.toHtml()
}
}
@@ -0,0 +1,34 @@
/*
* Copyright 2010-2016 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 org.jetbrains.kotlin.idea.util
inline fun StringBuilder.wrap(prefix: String, postfix: String, crossinline body: () -> Unit) {
append(prefix)
body()
append(postfix)
}
inline fun StringBuilder.wrapTag(tag: String, crossinline body: () -> Unit) {
wrap("<$tag>", "</$tag>", body)
}
inline fun StringBuilder.wrapTag(tag: String, params: String, crossinline body: () -> Unit) {
wrap("<$tag $params>", "</$tag>", body)
}
+28
View File
@@ -0,0 +1,28 @@
package magic
object Samples {
fun sampleMagic() {
castTextSpell("[asd] [dse] [asz]")
}
}
fun sampleScroll() {
val reader = Scroll("[asd] [dse] [asz]").reader()
castTextSpell(reader.readAll())
}
/**
* @sample Samples.sampleMagic
* @sample sampleScroll
*/
fun <caret>castTextSpell(spell: String) {
throw SecurityException("Magic prohibited outside Hogwarts")
}
//INFO: <b>public</b> <b>fun</b> castTextSpell(spell: String): Unit <i>defined in</i> magic<br/>
//INFO: <dl><dt><b>Samples:</b></dt><dd><a href="psi_element://Samples.sampleMagic"><code>Samples.sampleMagic</code></a><pre><code>
//INFO: castTextSpell("[asd] [dse] [asz]")
//INFO: </code></pre></dd><dd><a href="psi_element://sampleScroll"><code>sampleScroll</code></a><pre><code>
//INFO: val reader = Scroll("[asd] [dse] [asz]").reader()
//INFO: castTextSpell(reader.readAll())
//INFO: </code></pre></dd></dl>
@@ -204,6 +204,12 @@ public class QuickDocProviderTestGenerated extends AbstractQuickDocProviderTest
doTest(fileName);
}
@TestMetadata("Samples.kt")
public void testSamples() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/quickDoc/Samples.kt");
doTest(fileName);
}
@TestMetadata("TopLevelMethodFromJava.java")
public void testTopLevelMethodFromJava() throws Exception {
String fileName = KotlinTestUtils.navigationMetadata("idea/testData/editor/quickDoc/TopLevelMethodFromJava.java");