Merge pull request #595 from JetBrains/rr/yole/render-markdown
render Markdown and support link navigation in KDoc comments
This commit is contained in:
Generated
+1
@@ -45,6 +45,7 @@
|
|||||||
<element id="directory" name="jps">
|
<element id="directory" name="jps">
|
||||||
<element id="artifact" artifact-name="KotlinJpsPlugin" />
|
<element id="artifact" artifact-name="KotlinJpsPlugin" />
|
||||||
</element>
|
</element>
|
||||||
|
<element id="library" level="project" name="markdown" />
|
||||||
</element>
|
</element>
|
||||||
<element id="directory" name="kotlinc">
|
<element id="directory" name="kotlinc">
|
||||||
<element id="dir-copy" path="$PROJECT_DIR$/dist/kotlinc" />
|
<element id="dir-copy" path="$PROJECT_DIR$/dist/kotlinc" />
|
||||||
|
|||||||
Generated
+9
@@ -0,0 +1,9 @@
|
|||||||
|
<component name="libraryTable">
|
||||||
|
<library name="markdown">
|
||||||
|
<CLASSES>
|
||||||
|
<root url="jar://$PROJECT_DIR$/dependencies/markdown.jar!/" />
|
||||||
|
</CLASSES>
|
||||||
|
<JAVADOC />
|
||||||
|
<SOURCES />
|
||||||
|
</library>
|
||||||
|
</component>
|
||||||
+2
-1
@@ -45,5 +45,6 @@
|
|||||||
<orderEntry type="module" module-name="util" />
|
<orderEntry type="module" module-name="util" />
|
||||||
<orderEntry type="module" module-name="android-compiler-plugin" scope="TEST" />
|
<orderEntry type="module" module-name="android-compiler-plugin" scope="TEST" />
|
||||||
<orderEntry type="module" module-name="android-idea-plugin" scope="TEST" />
|
<orderEntry type="module" module-name="android-idea-plugin" scope="TEST" />
|
||||||
|
<orderEntry type="library" name="markdown" level="project" />
|
||||||
</component>
|
</component>
|
||||||
</module>
|
</module>
|
||||||
@@ -16,39 +16,42 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea;
|
package org.jetbrains.kotlin.idea;
|
||||||
|
|
||||||
import com.google.common.base.Predicate;
|
|
||||||
import com.intellij.lang.documentation.AbstractDocumentationProvider;
|
import com.intellij.lang.documentation.AbstractDocumentationProvider;
|
||||||
import com.intellij.lang.java.JavaDocumentationProvider;
|
import com.intellij.lang.java.JavaDocumentationProvider;
|
||||||
import com.intellij.openapi.diagnostic.Logger;
|
import com.intellij.openapi.diagnostic.Logger;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
import com.intellij.openapi.util.text.StringUtil;
|
||||||
import com.intellij.psi.PsiElement;
|
import com.intellij.psi.PsiElement;
|
||||||
import com.intellij.psi.PsiWhiteSpace;
|
import com.intellij.psi.PsiManager;
|
||||||
import com.intellij.psi.util.PsiTreeUtil;
|
import com.intellij.psi.util.PsiTreeUtil;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.kotlin.asJava.KotlinLightMethod;
|
import org.jetbrains.kotlin.asJava.KotlinLightMethod;
|
||||||
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptor;
|
||||||
|
import org.jetbrains.kotlin.descriptors.DeclarationDescriptorWithSource;
|
||||||
|
import org.jetbrains.kotlin.descriptors.SourceElement;
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.KotlinCacheService;
|
||||||
|
import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade;
|
||||||
import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage;
|
import org.jetbrains.kotlin.idea.caches.resolve.ResolvePackage;
|
||||||
import org.jetbrains.kotlin.idea.kdoc.KDocFinder;
|
import org.jetbrains.kotlin.idea.kdoc.KDocFinder;
|
||||||
import org.jetbrains.kotlin.idea.kdoc.KDocRenderer;
|
import org.jetbrains.kotlin.idea.kdoc.KDocRenderer;
|
||||||
|
import org.jetbrains.kotlin.idea.kdoc.KdocPackage;
|
||||||
|
import org.jetbrains.kotlin.idea.project.ResolveSessionForBodies;
|
||||||
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag;
|
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag;
|
||||||
import org.jetbrains.kotlin.psi.JetDeclaration;
|
import org.jetbrains.kotlin.psi.JetDeclaration;
|
||||||
import org.jetbrains.kotlin.psi.JetPackageDirective;
|
import org.jetbrains.kotlin.psi.JetElement;
|
||||||
import org.jetbrains.kotlin.psi.JetPsiUtil;
|
import org.jetbrains.kotlin.psi.JetPsiUtil;
|
||||||
import org.jetbrains.kotlin.psi.JetReferenceExpression;
|
import org.jetbrains.kotlin.psi.JetReferenceExpression;
|
||||||
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
|
import org.jetbrains.kotlin.renderer.DescriptorRenderer;
|
||||||
import org.jetbrains.kotlin.resolve.BindingContext;
|
import org.jetbrains.kotlin.resolve.BindingContext;
|
||||||
|
import org.jetbrains.kotlin.resolve.lazy.BodyResolveMode;
|
||||||
|
import org.jetbrains.kotlin.resolve.source.PsiSourceElement;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
public class JetQuickDocumentationProvider extends AbstractDocumentationProvider {
|
public class JetQuickDocumentationProvider extends AbstractDocumentationProvider {
|
||||||
private static final Logger LOG = Logger.getInstance(JetQuickDocumentationProvider.class);
|
private static final Logger LOG = Logger.getInstance(JetQuickDocumentationProvider.class);
|
||||||
|
|
||||||
private static final Predicate<PsiElement> SKIP_WHITESPACE_AND_EMPTY_PACKAGE = new Predicate<PsiElement>() {
|
|
||||||
@Override
|
|
||||||
public boolean apply(PsiElement input) {
|
|
||||||
// Skip empty package because there can be comments before it
|
|
||||||
// Skip whitespaces
|
|
||||||
return (input instanceof JetPackageDirective && input.getChildren().length == 0) || input instanceof PsiWhiteSpace;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getQuickNavigateInfo(PsiElement element, PsiElement originalElement) {
|
public String getQuickNavigateInfo(PsiElement element, PsiElement originalElement) {
|
||||||
return getText(element, originalElement, true);
|
return getText(element, originalElement, true);
|
||||||
@@ -118,4 +121,33 @@ public class JetQuickDocumentationProvider extends AbstractDocumentationProvider
|
|||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PsiElement getDocumentationElementForLink(PsiManager psiManager, String link, PsiElement context) {
|
||||||
|
if (!(context instanceof JetElement)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
JetElement jetElement = (JetElement) context;
|
||||||
|
Project project = psiManager.getProject();
|
||||||
|
KotlinCacheService cacheService = KotlinCacheService.getInstance(project);
|
||||||
|
ResolveSessionForBodies session = cacheService.getLazyResolveSession(jetElement);
|
||||||
|
ResolutionFacade facade = cacheService.getResolutionFacade(Collections.singletonList(jetElement));
|
||||||
|
BindingContext bindingContext = facade.analyze(jetElement, BodyResolveMode.FULL);
|
||||||
|
DeclarationDescriptor contextDescriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR, context);
|
||||||
|
if (contextDescriptor == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
Collection<DeclarationDescriptor> descriptors =
|
||||||
|
KdocPackage.resolveKDocLink(session, contextDescriptor, null, StringUtil.split(link, ","));
|
||||||
|
if (!descriptors.isEmpty()) {
|
||||||
|
DeclarationDescriptor target = descriptors.iterator().next();
|
||||||
|
if (target instanceof DeclarationDescriptorWithSource) {
|
||||||
|
SourceElement source = ((DeclarationDescriptorWithSource) target).getSource();
|
||||||
|
if (source instanceof PsiSourceElement) {
|
||||||
|
return ((PsiSourceElement) source).getPsi();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,9 +16,15 @@
|
|||||||
|
|
||||||
package org.jetbrains.kotlin.idea.kdoc
|
package org.jetbrains.kotlin.idea.kdoc
|
||||||
|
|
||||||
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
|
import com.intellij.codeInsight.documentation.DocumentationManagerUtil
|
||||||
import com.intellij.openapi.util.text.StringUtil
|
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.parser.MarkdownParser
|
||||||
|
import org.intellij.markdown.parser.dialects.commonmark.CommonMarkMarkerProcessor
|
||||||
import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
|
import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection
|
||||||
|
import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag
|
||||||
|
|
||||||
object KDocRenderer {
|
object KDocRenderer {
|
||||||
fun renderKDoc(docComment: KDocTag): String {
|
fun renderKDoc(docComment: KDocTag): String {
|
||||||
@@ -26,6 +32,7 @@ object KDocRenderer {
|
|||||||
val result = StringBuilder("<p>")
|
val result = StringBuilder("<p>")
|
||||||
result.append(markdownToHtml(content))
|
result.append(markdownToHtml(content))
|
||||||
if (docComment is KDocSection) {
|
if (docComment is KDocSection) {
|
||||||
|
result.append("\n")
|
||||||
val paramTags = docComment.findTagsByName("param").filter { it.getSubjectName() != null }
|
val paramTags = docComment.findTagsByName("param").filter { it.getSubjectName() != null }
|
||||||
renderTagList(paramTags, "Parameters", result)
|
renderTagList(paramTags, "Parameters", result)
|
||||||
|
|
||||||
@@ -37,11 +44,28 @@ object KDocRenderer {
|
|||||||
|
|
||||||
renderTag(docComment.findTagByName("author"), "Author", result)
|
renderTag(docComment.findTagByName("author"), "Author", result)
|
||||||
renderTag(docComment.findTagByName("since"), "Since", result)
|
renderTag(docComment.findTagByName("since"), "Since", result)
|
||||||
|
|
||||||
|
renderSeeAlso(docComment, result)
|
||||||
}
|
}
|
||||||
result.append("</p>")
|
result.append("</p>")
|
||||||
return result.toString()
|
return result.toString()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun renderSeeAlso(docComment: KDocSection, to: StringBuilder) {
|
||||||
|
val seeTags = docComment.findTagsByName("see")
|
||||||
|
if (seeTags.isEmpty()) return
|
||||||
|
to.append("<DD><DL>")
|
||||||
|
to.append("<DT><b>").append("See Also:").append("</b>")
|
||||||
|
to.append("<DD>")
|
||||||
|
seeTags.forEachIndexed { index, tag ->
|
||||||
|
DocumentationManagerUtil.createHyperlink(to, tag.getSubjectName(), tag.getSubjectName(), false)
|
||||||
|
if (index < seeTags.size() - 1) {
|
||||||
|
to.append(", ")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
to.append("</DD></DL></DD>");
|
||||||
|
}
|
||||||
|
|
||||||
private fun renderTagList(tags: List<KDocTag>, title: String, to: StringBuilder) {
|
private fun renderTagList(tags: List<KDocTag>, title: String, to: StringBuilder) {
|
||||||
if (tags.isEmpty()) {
|
if (tags.isEmpty()) {
|
||||||
return
|
return
|
||||||
@@ -50,19 +74,166 @@ object KDocRenderer {
|
|||||||
tags.forEach {
|
tags.forEach {
|
||||||
to.append("<dd><code>${it.getSubjectName()}</code> - ${markdownToHtml(it.getContent().trimLeading())}</dd>")
|
to.append("<dd><code>${it.getSubjectName()}</code> - ${markdownToHtml(it.getContent().trimLeading())}</dd>")
|
||||||
}
|
}
|
||||||
to.append("</dl>")
|
to.append("</dl>\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renderTag(tag: KDocTag?, title: String, to: StringBuilder) {
|
private fun renderTag(tag: KDocTag?, title: String, to: StringBuilder) {
|
||||||
if (tag != null) {
|
if (tag != null) {
|
||||||
to.append("<dl><dt><b>${title}:</b></dt>")
|
to.append("<dl><dt><b>${title}:</b></dt>")
|
||||||
to.append("<dd>${markdownToHtml(tag.getContent())}</dd>")
|
to.append("<dd>${markdownToHtml(tag.getContent())}</dd>")
|
||||||
to.append("</dl>")
|
to.append("</dl>\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun markdownToHtml(markdown: String): String {
|
fun markdownToHtml(markdown: String): String {
|
||||||
// TODO Integrate a real Markdown parser
|
val markdownTree = MarkdownParser(CommonMarkMarkerProcessor.Factory).buildMarkdownTreeFromString(markdown)
|
||||||
return StringUtil.replace(markdown, "\n", "<br/>");
|
val markdownNode = MarkdownNode(markdownTree, null, markdown)
|
||||||
|
|
||||||
|
// Avoid wrapping the entire converted contents in a <p> tag if it's just a single paragraph
|
||||||
|
val maybeSingleParagraph = markdownNode.children.filter { it.type == MarkdownElementTypes.PARAGRAPH }.singleOrNull()
|
||||||
|
if (maybeSingleParagraph != null) {
|
||||||
|
return maybeSingleParagraph.children.map { it.toHtml() }.join("")
|
||||||
|
} else {
|
||||||
|
return markdownNode.toHtml()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class MarkdownNode(val node: ASTNode, val parent: MarkdownNode?, val markdown: String) {
|
||||||
|
val children: List<MarkdownNode> = node.children.map { MarkdownNode(it, this, markdown) }
|
||||||
|
val endOffset: Int get() = node.endOffset
|
||||||
|
val startOffset: Int get() = node.startOffset
|
||||||
|
val type: IElementType get() = node.type
|
||||||
|
val text: String get() = markdown.substring(startOffset, endOffset)
|
||||||
|
fun child(type: IElementType): MarkdownNode? = children.firstOrNull { it.type == type }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun MarkdownNode.visit(action: (MarkdownNode, () -> Unit) -> Unit) {
|
||||||
|
action(this) {
|
||||||
|
for (child in children) {
|
||||||
|
child.visit(action)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun MarkdownNode.toHtml(): String {
|
||||||
|
val sb = StringBuilder()
|
||||||
|
visit {(node, processChildren) ->
|
||||||
|
val nodeType = node.type
|
||||||
|
val nodeText = node.text
|
||||||
|
when (nodeType) {
|
||||||
|
MarkdownElementTypes.UNORDERED_LIST -> {
|
||||||
|
sb.appendln("<ul>")
|
||||||
|
processChildren()
|
||||||
|
sb.appendln("</ul>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.ORDERED_LIST -> {
|
||||||
|
sb.appendln("<ol>")
|
||||||
|
processChildren()
|
||||||
|
sb.appendln("</ol>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.LIST_ITEM -> {
|
||||||
|
sb.append("<li>")
|
||||||
|
processChildren()
|
||||||
|
sb.appendln("</li>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.EMPH -> {
|
||||||
|
sb.append("<em>")
|
||||||
|
processChildren()
|
||||||
|
sb.append("</em>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.STRONG -> {
|
||||||
|
sb.append("<strong>")
|
||||||
|
processChildren()
|
||||||
|
sb.append("</strong>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.ATX_1 -> {
|
||||||
|
sb.append("<h1>")
|
||||||
|
processChildren()
|
||||||
|
sb.append("</h1>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.ATX_2 -> {
|
||||||
|
sb.append("<h2>")
|
||||||
|
processChildren()
|
||||||
|
sb.append("</h2>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.ATX_3 -> {
|
||||||
|
sb.append("<h3>")
|
||||||
|
processChildren()
|
||||||
|
sb.append("</h3>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.ATX_4 -> {
|
||||||
|
sb.append("<h4>")
|
||||||
|
processChildren()
|
||||||
|
sb.append("</h4>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.ATX_5 -> {
|
||||||
|
sb.append("<h5>")
|
||||||
|
processChildren()
|
||||||
|
sb.append("</h5>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.ATX_6 -> {
|
||||||
|
sb.append("<h6>")
|
||||||
|
processChildren()
|
||||||
|
sb.append("</h6>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.BLOCK_QUOTE -> {
|
||||||
|
sb.append("<blockquote>")
|
||||||
|
processChildren()
|
||||||
|
sb.append("</blockquote>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.PARAGRAPH -> {
|
||||||
|
sb.append("<p>")
|
||||||
|
processChildren()
|
||||||
|
sb.appendln("</p>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.CODE_SPAN -> {
|
||||||
|
sb.append("<code>")
|
||||||
|
processChildren()
|
||||||
|
sb.append("</code>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.CODE_BLOCK -> {
|
||||||
|
sb.append("<pre><code>")
|
||||||
|
processChildren()
|
||||||
|
sb.append("</code><pre>")
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.SHORT_REFERENCE_LINK,
|
||||||
|
MarkdownElementTypes.FULL_REFERENCE_LINK -> {
|
||||||
|
val label = node.child(MarkdownElementTypes.LINK_LABEL)?.child(MarkdownTokenTypes.TEXT)?.text
|
||||||
|
if (label != null) {
|
||||||
|
val linkText = node.child(MarkdownElementTypes.LINK_TEXT)?.child(MarkdownTokenTypes.TEXT)?.text ?: label
|
||||||
|
DocumentationManagerUtil.createHyperlink(sb, label, linkText, true)
|
||||||
|
} else {
|
||||||
|
sb.append(node.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MarkdownElementTypes.INLINE_LINK -> {
|
||||||
|
val label = node.child(MarkdownElementTypes.LINK_TEXT)?.child(MarkdownTokenTypes.TEXT)?.text
|
||||||
|
val destination = node.child(MarkdownElementTypes.LINK_DESTINATION)?.text
|
||||||
|
if (label != null && destination != null) {
|
||||||
|
sb.append("a href=\"${destination}\">${label.htmlEscape()}</a>")
|
||||||
|
} else {
|
||||||
|
sb.append(node.text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MarkdownTokenTypes.TEXT,
|
||||||
|
MarkdownTokenTypes.WHITE_SPACE,
|
||||||
|
MarkdownTokenTypes.COLON,
|
||||||
|
MarkdownTokenTypes.DOUBLE_QUOTE,
|
||||||
|
MarkdownTokenTypes.LPAREN,
|
||||||
|
MarkdownTokenTypes.RPAREN,
|
||||||
|
MarkdownTokenTypes.LBRACKET,
|
||||||
|
MarkdownTokenTypes.RBRACKET -> {
|
||||||
|
sb.append(nodeText)
|
||||||
|
}
|
||||||
|
MarkdownTokenTypes.GT -> sb.append(">")
|
||||||
|
MarkdownTokenTypes.LT -> sb.append("<")
|
||||||
|
else -> {
|
||||||
|
processChildren()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sb.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun String.htmlEscape(): String = replace("&", "&").replace("<", "<").replace(">", ">")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,4 +2,4 @@ fun ktTest() {
|
|||||||
Test.<caret>foo("SomeTest")
|
Test.<caret>foo("SomeTest")
|
||||||
}
|
}
|
||||||
|
|
||||||
// INFO: <b>public</b> <b>open</b> <b>fun</b> foo(param: String!): Array<(out) Any!>!<br/>Java declaration:<br/>Test...
|
//INFO: <b>public</b> <b>open</b> <b>fun</b> foo(param: String!): Array<(out) Any!>!<br/>Java declaration:<br/>Test...
|
||||||
|
|||||||
@@ -6,4 +6,4 @@ class KotlinClassUsedFromJava {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// INFO: [light_idea_test_case] testing...
|
//INFO: [light_idea_test_case] testing.TestingPackage...
|
||||||
|
|||||||
@@ -2,4 +2,5 @@ fun test() {
|
|||||||
listOf(1, 2, 4).<caret>filter { it > 0 }
|
listOf(1, 2, 4).<caret>filter { it > 0 }
|
||||||
}
|
}
|
||||||
|
|
||||||
// INFO: inline <b>public</b> <b>fun</b> <T> Iterable<T>.filter(predicate: (T) → Boolean): List<T><br/><p>Returns a list containing all elements matching the given [predicate]</p>
|
//INFO: inline <b>public</b> <b>fun</b> <T> Iterable<T>.filter(predicate: (T) → Boolean): List<T><br/><p>Returns a list containing all elements matching the given <a href="psi_element://predicate">predicate</a>
|
||||||
|
//INFO: </p>
|
||||||
|
|||||||
@@ -3,4 +3,5 @@
|
|||||||
*/
|
*/
|
||||||
class <caret>Some
|
class <caret>Some
|
||||||
|
|
||||||
// INFO: <b>internal</b> <b>final</b> <b>class</b> Some<br/><p>Usefull comment</p>
|
//INFO: <b>internal</b> <b>final</b> <b>class</b> Some<br/><p>Usefull comment
|
||||||
|
//INFO: </p>
|
||||||
|
|||||||
@@ -12,4 +12,6 @@ package test
|
|||||||
*/
|
*/
|
||||||
fun <caret>testFun(first: String, second: Int) = 12
|
fun <caret>testFun(first: String, second: Int) = 12
|
||||||
|
|
||||||
// INFO: <b>internal</b> <b>fun</b> testFun(first: String, second: Int): Int<br/><p>Test function<br/><br/><br/><dl><dt><b>Parameters:</b></dt><dd><code>first</code> - Some</dd><dd><code>second</code> - Other</dd></dl></p>
|
//INFO: <b>internal</b> <b>fun</b> testFun(first: String, second: Int): Int<br/><p>Test function
|
||||||
|
//INFO: <dl><dt><b>Parameters:</b></dt><dd><code>first</code> - Some</dd><dd><code>second</code> - Other</dd></dl>
|
||||||
|
//INFO: </p>
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ fun test() {
|
|||||||
D().f<caret>oo()
|
D().f<caret>oo()
|
||||||
}
|
}
|
||||||
|
|
||||||
// INFO: <b>internal</b> <b>open</b> <b>fun</b> foo(): Int<br/><p>This method returns zero.</p>
|
//INFO: <b>internal</b> <b>open</b> <b>fun</b> foo(): Int<br/><p>This method returns zero.
|
||||||
|
//INFO: </p>
|
||||||
|
|||||||
@@ -14,4 +14,5 @@ fun test() {
|
|||||||
D().f<caret>oo
|
D().f<caret>oo
|
||||||
}
|
}
|
||||||
|
|
||||||
// INFO: <b>internal</b> <b>open</b> <b>val</b> foo: Int<br/><p>This property returns zero.</p>
|
//INFO: <b>internal</b> <b>open</b> <b>val</b> foo: Int<br/><p>This property returns zero.
|
||||||
|
//INFO: </p>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
Some documentation
|
Some documentation
|
||||||
|
|
||||||
* @param a Some int
|
* @param a Some int
|
||||||
* @param b String
|
* @param b String
|
||||||
@@ -12,4 +12,6 @@ fun test() {
|
|||||||
<caret>testMethod(1, "value")
|
<caret>testMethod(1, "value")
|
||||||
}
|
}
|
||||||
|
|
||||||
// INFO: <b>internal</b> <b>fun</b> testMethod(a: Int, b: String): Unit<br/><p>Some documentation<br/><br/><dl><dt><b>Parameters:</b></dt><dd><code>a</code> - Some int</dd><dd><code>b</code> - String</dd></dl></p>
|
//INFO: <b>internal</b> <b>fun</b> testMethod(a: Int, b: String): Unit<br/><p>Some documentation
|
||||||
|
//INFO: <dl><dt><b>Parameters:</b></dt><dd><code>a</code> - Some int</dd><dd><code>b</code> - String</dd></dl>
|
||||||
|
//INFO: </p>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
Some documentation
|
Some documentation
|
||||||
|
|
||||||
* @param[a] Some int
|
* @param[a] Some int
|
||||||
* @param[b] String
|
* @param[b] String
|
||||||
@@ -12,4 +12,6 @@ fun test() {
|
|||||||
<caret>testMethod(1, "value")
|
<caret>testMethod(1, "value")
|
||||||
}
|
}
|
||||||
|
|
||||||
// INFO: <b>internal</b> <b>fun</b> testMethod(a: Int, b: String): Unit<br/><p>Some documentation<br/><br/><dl><dt><b>Parameters:</b></dt><dd><code>a</code> - Some int</dd><dd><code>b</code> - String</dd></dl></p>
|
//INFO: <b>internal</b> <b>fun</b> testMethod(a: Int, b: String): Unit<br/><p>Some documentation
|
||||||
|
//INFO: <dl><dt><b>Parameters:</b></dt><dd><code>a</code> - Some int</dd><dd><code>b</code> - String</dd></dl>
|
||||||
|
//INFO: </p>
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
/**
|
||||||
|
* Some documentation. **Bold** *underline* `code` foo: bar (baz) [quux] <xyzzy>
|
||||||
|
*
|
||||||
|
* [Kotlin](http://www.kotlinlang.org)
|
||||||
|
*
|
||||||
|
* [C]
|
||||||
|
*
|
||||||
|
* [See this class][C]
|
||||||
|
*/
|
||||||
|
fun testMethod() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class C {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
<caret>testMethod(1, "value")
|
||||||
|
}
|
||||||
|
|
||||||
|
//INFO: <b>internal</b> <b>fun</b> testMethod(): Unit<br/><p><p>Some documentation. <strong>Bold</strong> <em>underline</em> <code>code</code> foo: bar (baz) <a href="psi_element://quux">quux</a> </p>
|
||||||
|
//INFO: <p>a href="http://www.kotlinlang.org">Kotlin</a></p>
|
||||||
|
//INFO: <p><a href="psi_element://C">C</a></p>
|
||||||
|
//INFO: <p><a href="psi_element://C">See this class</a></p>
|
||||||
|
//INFO:
|
||||||
|
//INFO: </p>
|
||||||
@@ -14,4 +14,8 @@ fun test() {
|
|||||||
<caret>testMethod(1, "value")
|
<caret>testMethod(1, "value")
|
||||||
}
|
}
|
||||||
|
|
||||||
// INFO: <b>internal</b> <b>fun</b> testMethod(a: Int, b: String): Unit<br/><p>Some documentation<br/><br/><dl><dt><b>Parameters:</b></dt><dd><code>a</code> - Some int</dd><dd><code>b</code> - String</dd></dl><dl><dt><b>Returns:</b></dt><dd>Return value</dd></dl><dl><dt><b>Throws:</b></dt><dd><code>IllegalArgumentException</code> - if the weather is bad</dd></dl></p>
|
//INFO: <b>internal</b> <b>fun</b> testMethod(a: Int, b: String): Unit<br/><p>Some documentation
|
||||||
|
//INFO: <dl><dt><b>Parameters:</b></dt><dd><code>a</code> - Some int</dd><dd><code>b</code> - String</dd></dl>
|
||||||
|
//INFO: <dl><dt><b>Returns:</b></dt><dd>Return value</dd></dl>
|
||||||
|
//INFO: <dl><dt><b>Throws:</b></dt><dd><code>IllegalArgumentException</code> - if the weather is bad</dd></dl>
|
||||||
|
//INFO: </p>
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
/**
|
||||||
|
* @see C
|
||||||
|
* @see D
|
||||||
|
*/
|
||||||
|
fun testMethod() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class C {
|
||||||
|
}
|
||||||
|
|
||||||
|
class D {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun test() {
|
||||||
|
<caret>testMethod(1, "value")
|
||||||
|
}
|
||||||
|
|
||||||
|
//INFO: <b>internal</b> <b>fun</b> testMethod(): Unit<br/><p>
|
||||||
|
//INFO: <DD><DL><DT><b>See Also:</b><DD><a href="psi_element://C"><code>C</code></a>, <a href="psi_element://D"><code>D</code></a></DD></DL></DD></p>
|
||||||
@@ -8,4 +8,5 @@ class Testing {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// INFO: <b>internal</b> <b>fun</b> foo(bar: Int): Unit<br/><p>KDoc foo</p>
|
//INFO: <b>internal</b> <b>fun</b> foo(bar: Int): Unit<br/><p>KDoc foo
|
||||||
|
//INFO: </p>
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
class C {
|
||||||
|
}
|
||||||
|
|
||||||
|
fun fo<caret>o() {
|
||||||
|
}
|
||||||
+19
-15
@@ -28,7 +28,6 @@ import org.jetbrains.kotlin.idea.JetLightCodeInsightFixtureTestCase;
|
|||||||
import org.jetbrains.kotlin.idea.ProjectDescriptorWithStdlibSources;
|
import org.jetbrains.kotlin.idea.ProjectDescriptorWithStdlibSources;
|
||||||
import org.jetbrains.kotlin.test.InTextDirectivesUtils;
|
import org.jetbrains.kotlin.test.InTextDirectivesUtils;
|
||||||
import org.jetbrains.kotlin.test.util.UtilPackage;
|
import org.jetbrains.kotlin.test.util.UtilPackage;
|
||||||
import org.junit.Assert;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -44,6 +43,12 @@ public abstract class AbstractJetQuickDocProviderTest extends JetLightCodeInsigh
|
|||||||
PsiElement targetElement = documentationManager.findTargetElement(myFixture.getEditor(), myFixture.getFile());
|
PsiElement targetElement = documentationManager.findTargetElement(myFixture.getEditor(), myFixture.getFile());
|
||||||
|
|
||||||
String info = CtrlMouseHandler.getInfo(targetElement, element);
|
String info = CtrlMouseHandler.getInfo(targetElement, element);
|
||||||
|
if (info != null) {
|
||||||
|
info = StringUtil.convertLineSeparators(info);
|
||||||
|
}
|
||||||
|
if (info != null && !info.endsWith("\n")) {
|
||||||
|
info += "\n";
|
||||||
|
}
|
||||||
|
|
||||||
File testDataFile = new File(path);
|
File testDataFile = new File(path);
|
||||||
String textData = FileUtil.loadFile(testDataFile, true);
|
String textData = FileUtil.loadFile(testDataFile, true);
|
||||||
@@ -56,14 +61,15 @@ public abstract class AbstractJetQuickDocProviderTest extends JetLightCodeInsigh
|
|||||||
textData + "\n\n//INFO: " + info,
|
textData + "\n\n//INFO: " + info,
|
||||||
testDataFile.getAbsolutePath());
|
testDataFile.getAbsolutePath());
|
||||||
}
|
}
|
||||||
else if (directives.size() == 1) {
|
else {
|
||||||
assertNotNull(info);
|
StringBuilder expectedInfoBuilder = new StringBuilder();
|
||||||
|
for (String directive : directives) {
|
||||||
|
expectedInfoBuilder.append(directive).append("\n");
|
||||||
|
}
|
||||||
|
String expectedInfo = expectedInfoBuilder.toString();
|
||||||
|
|
||||||
String expectedInfo = directives.get(0);
|
if (expectedInfo.endsWith("...\n")) {
|
||||||
|
if (!info.startsWith(StringUtil.trimEnd(expectedInfo, "...\n"))) {
|
||||||
// We can avoid testing for too long comments with \n character by placing '...' in test data
|
|
||||||
if (expectedInfo.endsWith("...")) {
|
|
||||||
if (!info.startsWith(StringUtil.trimEnd(expectedInfo, "..."))) {
|
|
||||||
wrapToFileComparisonFailure(info, path, textData);
|
wrapToFileComparisonFailure(info, path, textData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -71,18 +77,16 @@ public abstract class AbstractJetQuickDocProviderTest extends JetLightCodeInsigh
|
|||||||
wrapToFileComparisonFailure(info, path, textData);
|
wrapToFileComparisonFailure(info, path, textData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else {
|
|
||||||
Assert.fail("Too many '// INFO:' directives in file " + path);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void wrapToFileComparisonFailure(String info, String filePath, String fileData) {
|
private static void wrapToFileComparisonFailure(String info, String filePath, String fileData) {
|
||||||
int newLineIndex = info.indexOf('\n');
|
List<String> infoLines = StringUtil.split(info, "\n");
|
||||||
if (newLineIndex != -1) {
|
StringBuilder infoBuilder = new StringBuilder();
|
||||||
info = info.substring(0, newLineIndex) + "...";
|
for (String line : infoLines) {
|
||||||
|
infoBuilder.append("//INFO: ").append(line).append("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
String correctedFileText = fileData.replaceFirst("//\\s?INFO: .*", "// INFO: " + info);
|
String correctedFileText = fileData.replaceAll("//\\s?INFO: .*\n?", "") + infoBuilder.toString();
|
||||||
throw new FileComparisonFailure("Unexpected info", fileData, correctedFileText, new File(filePath).getAbsolutePath());
|
throw new FileComparisonFailure("Unexpected info", fileData, correctedFileText, new File(filePath).getAbsolutePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+12
@@ -120,12 +120,24 @@ public class JetQuickDocProviderTestGenerated extends AbstractJetQuickDocProvide
|
|||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("OnMethodUsageWithMarkdown.kt")
|
||||||
|
public void testOnMethodUsageWithMarkdown() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/quickDoc/OnMethodUsageWithMarkdown.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("OnMethodUsageWithReturnAndThrows.kt")
|
@TestMetadata("OnMethodUsageWithReturnAndThrows.kt")
|
||||||
public void testOnMethodUsageWithReturnAndThrows() throws Exception {
|
public void testOnMethodUsageWithReturnAndThrows() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/quickDoc/OnMethodUsageWithReturnAndThrows.kt");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/quickDoc/OnMethodUsageWithReturnAndThrows.kt");
|
||||||
doTest(fileName);
|
doTest(fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@TestMetadata("OnMethodUsageWithSee.kt")
|
||||||
|
public void testOnMethodUsageWithSee() throws Exception {
|
||||||
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/quickDoc/OnMethodUsageWithSee.kt");
|
||||||
|
doTest(fileName);
|
||||||
|
}
|
||||||
|
|
||||||
@TestMetadata("TopLevelMethodFromJava.java")
|
@TestMetadata("TopLevelMethodFromJava.java")
|
||||||
public void testTopLevelMethodFromJava() throws Exception {
|
public void testTopLevelMethodFromJava() throws Exception {
|
||||||
String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/quickDoc/TopLevelMethodFromJava.java");
|
String fileName = JetTestUtils.navigationMetadata("idea/testData/editor/quickDoc/TopLevelMethodFromJava.java");
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2010-2015 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.editor.quickDoc
|
||||||
|
|
||||||
|
import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase
|
||||||
|
import org.jetbrains.kotlin.idea.JetQuickDocumentationProvider
|
||||||
|
import org.jetbrains.kotlin.idea.PluginTestCaseBase
|
||||||
|
import org.jetbrains.kotlin.psi.JetClass
|
||||||
|
import org.jetbrains.kotlin.psi.JetFunction
|
||||||
|
import org.jetbrains.kotlin.psi.psiUtil.getParentOfType
|
||||||
|
import org.junit.Assert
|
||||||
|
|
||||||
|
public class QuickDocNavigationTest() : LightPlatformCodeInsightFixtureTestCase() {
|
||||||
|
override fun getTestDataPath(): String {
|
||||||
|
return PluginTestCaseBase.getTestDataPathBase() + "/kdoc/navigate/"
|
||||||
|
}
|
||||||
|
|
||||||
|
public fun testSimple() {
|
||||||
|
myFixture.configureByFile(getTestName(true) + ".kt")
|
||||||
|
val source = myFixture.getElementAtCaret().getParentOfType<JetFunction>(false)
|
||||||
|
val target = JetQuickDocumentationProvider().getDocumentationElementForLink(
|
||||||
|
myFixture.getPsiManager(), "C", source);
|
||||||
|
Assert.assertTrue(target is JetClass)
|
||||||
|
Assert.assertEquals("C", (target as JetClass).getName())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -220,6 +220,10 @@
|
|||||||
</exec>
|
</exec>
|
||||||
</then>
|
</then>
|
||||||
</if>
|
</if>
|
||||||
|
|
||||||
|
<!-- Markdown parser -->
|
||||||
|
<get src="https://teamcity.jetbrains.com/guestAuth/repository/download/IntelliJMarkdownParser_Build/.lastFinished/markdown_jar/markdown.jar"
|
||||||
|
dest="dependencies/markdown.jar" usetimestamp="true"/>
|
||||||
</target>
|
</target>
|
||||||
|
|
||||||
<macrodef name="get_android_sdk">
|
<macrodef name="get_android_sdk">
|
||||||
|
|||||||
Reference in New Issue
Block a user