KT-3729 Quick documentation support

#KT-3729 Fixed
This commit is contained in:
Nikolay Krasko
2013-07-04 20:48:26 +04:00
parent c8f79e1966
commit 141c544775
11 changed files with 247 additions and 16 deletions
+10
View File
@@ -0,0 +1,10 @@
<component name="ProjectDictionaryState">
<dictionary name="Nikolay.Krasko">
<words>
<w>kdoc</w>
<w>redeclarations</w>
<w>subclassed</w>
<w>subgraph</w>
</words>
</dictionary>
</component>
@@ -60,5 +60,6 @@ public interface KDocTokens {
KDocToken MARKDOWN_ESCAPED_CHAR = new KDocToken("KDOC_MARKDOWN_ESCAPED_CHAR");
TokenSet CONTENT_TOKENS = TokenSet.create(START, END, LEADING_ASTERISK, TEXT, WIKI_LINK_OPEN, WIKI_LINK_CLOSE, MARKDOWN_ESCAPED_CHAR);
TokenSet KDOC_HIGHLIGHT_TOKENS = TokenSet.create(START, END, LEADING_ASTERISK, TEXT, WIKI_LINK_OPEN, WIKI_LINK_CLOSE, MARKDOWN_ESCAPED_CHAR);
TokenSet CONTENT_TOKENS = TokenSet.create(TEXT, TAG_NAME, WIKI_LINK_OPEN, WIKI_LINK_CLOSE, MARKDOWN_ESCAPED_CHAR);
}
@@ -31,18 +31,19 @@ import org.jetbrains.jet.completion.AbstractJavaCompletionTest;
import org.jetbrains.jet.completion.AbstractJavaWithLibCompletionTest;
import org.jetbrains.jet.completion.AbstractJetJSCompletionTest;
import org.jetbrains.jet.completion.AbstractKeywordCompletionTest;
import org.jetbrains.jet.editor.quickDoc.AbstractJetQuickDocProviderTest;
import org.jetbrains.jet.jvm.compiler.*;
import org.jetbrains.jet.lang.resolve.lazy.AbstractLazyResolveDescriptorRendererTest;
import org.jetbrains.jet.lang.resolve.lazy.AbstractLazyResolveNamespaceComparingTest;
import org.jetbrains.jet.lang.resolve.lazy.AbstractLazyResolveTest;
import org.jetbrains.jet.modules.xml.AbstractModuleXmlParserTest;
import org.jetbrains.jet.plugin.codeInsight.unwrap.AbstractUnwrapRemoveTest;
import org.jetbrains.jet.plugin.intentions.AbstractCodeTransformationTest;
import org.jetbrains.jet.plugin.codeInsight.moveUpDown.AbstractCodeMoverTest;
import org.jetbrains.jet.plugin.codeInsight.surroundWith.AbstractSurroundWithTest;
import org.jetbrains.jet.plugin.codeInsight.unwrap.AbstractUnwrapRemoveTest;
import org.jetbrains.jet.plugin.folding.AbstractKotlinFoldingTest;
import org.jetbrains.jet.plugin.hierarchy.AbstractHierarchyTest;
import org.jetbrains.jet.plugin.highlighter.AbstractDeprecatedHighlightingTest;
import org.jetbrains.jet.plugin.intentions.AbstractCodeTransformationTest;
import org.jetbrains.jet.plugin.navigation.JetAbstractGotoSuperTest;
import org.jetbrains.jet.plugin.quickfix.AbstractQuickFixMultiFileTest;
import org.jetbrains.jet.plugin.quickfix.AbstractQuickFixTest;
@@ -387,6 +388,13 @@ public class GenerateTests {
testModel("idea/testData/codeInsight/unwrapAndRemove/removeFinally", "doTestFinallyRemover"),
testModel("idea/testData/codeInsight/unwrapAndRemove/unwrapLambda", "doTestLambdaUnwrapper")
);
generateTest(
"idea/tests/",
"JetQuickDocProviderTestGenerated",
AbstractJetQuickDocProviderTest.class,
testModel("idea/testData/editor/quickDoc", "doTest")
);
}
private static SimpleTestClassModel testModel(@NotNull String rootPath) {
@@ -16,18 +16,23 @@
package org.jetbrains.jet.plugin;
import com.google.common.base.Predicate;
import com.intellij.lang.documentation.AbstractDocumentationProvider;
import com.intellij.lang.java.JavaDocumentationProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiWhiteSpace;
import com.intellij.psi.impl.compiled.ClsClassImpl;
import com.intellij.psi.impl.compiled.ClsFileImpl;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.kdoc.lexer.KDocTokens;
import org.jetbrains.jet.kdoc.psi.api.KDoc;
import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor;
import org.jetbrains.jet.lang.psi.JetDeclaration;
import org.jetbrains.jet.lang.psi.JetFile;
import org.jetbrains.jet.lang.psi.JetReferenceExpression;
import org.jetbrains.jet.lang.psi.*;
import org.jetbrains.jet.lang.resolve.BindingContext;
import org.jetbrains.jet.lang.resolve.BindingContextUtils;
import org.jetbrains.jet.plugin.libraries.JetDecompiledData;
@@ -38,14 +43,18 @@ import org.jetbrains.jet.renderer.DescriptorRenderer;
import java.util.Collection;
public class JetQuickDocumentationProvider extends AbstractDocumentationProvider {
private static final Predicate<PsiElement> SKIP_WHITESPACE_AND_EMPTY_NAMESPACE = new Predicate<PsiElement>() {
@Override
public boolean apply(PsiElement input) {
// Skip empty namespace because there can be comments before it
// Skip whitespaces
return (input instanceof JetNamespaceHeader && input.getChildren().length == 0) || input instanceof PsiWhiteSpace;
}
};
private static String getText(PsiElement element, PsiElement originalElement, boolean mergeKotlinAndJava) {
JetReferenceExpression ref;
if (originalElement instanceof JetReferenceExpression) {
ref = (JetReferenceExpression) originalElement;
}
else {
ref = PsiTreeUtil.getParentOfType(originalElement, JetReferenceExpression.class);
}
JetReferenceExpression ref = PsiTreeUtil.getParentOfType(originalElement, JetReferenceExpression.class, false);
PsiElement declarationPsiElement = PsiTreeUtil.getParentOfType(originalElement, JetDeclaration.class);
if (ref != null || declarationPsiElement != null) {
BindingContext bindingContext = WholeProjectAnalyzerFacade.analyzeProjectWithCacheOnAFile(
@@ -64,6 +73,7 @@ public class JetQuickDocumentationProvider extends AbstractDocumentationProvider
if (declarationPsiElement != null) {
DeclarationDescriptor declarationDescriptor = bindingContext.get(BindingContext.DECLARATION_TO_DESCRIPTOR,
declarationPsiElement);
if (declarationDescriptor != null) {
return render(declarationDescriptor, bindingContext, element, originalElement, mergeKotlinAndJava);
}
@@ -73,16 +83,22 @@ public class JetQuickDocumentationProvider extends AbstractDocumentationProvider
return null;
}
private static String render(@NotNull DeclarationDescriptor declarationDescriptor, @NotNull BindingContext bindingContext,
private static String render(
@NotNull DeclarationDescriptor declarationDescriptor, @NotNull BindingContext bindingContext,
PsiElement element, PsiElement originalElement, boolean mergeKotlinAndJava) {
String renderedDecl = DescriptorRenderer.HTML.render(declarationDescriptor);
if (isKotlinDeclaration(declarationDescriptor, bindingContext, originalElement.getProject())) {
KDoc comment = findElementKDoc(element);
if (comment != null) {
renderedDecl = renderedDecl + "<br/>" + kDocToHtml(comment);
}
return renderedDecl;
}
else if (mergeKotlinAndJava) {
String originalInfo = new JavaDocumentationProvider().getQuickNavigateInfo(element, originalElement);
if (originalInfo != null) {
return renderedDecl + "\nJava declaration:\n" + originalInfo;
return renderedDecl + "<br/>Java declaration:<br/>" + originalInfo;
}
return renderedDecl;
}
@@ -113,4 +129,49 @@ public class JetQuickDocumentationProvider extends AbstractDocumentationProvider
public String generateDoc(PsiElement element, PsiElement originalElement) {
return getText(element, originalElement, false);
}
private static String getKDocContent(@NotNull KDoc kDoc) {
StringBuilder builder = new StringBuilder();
boolean contentStarted = false;
boolean afterAsterisk = false;
for (PsiElement element : kDoc.getChildren()) {
IElementType type = element.getNode().getElementType();
if (KDocTokens.CONTENT_TOKENS.contains(type)) {
contentStarted = true;
builder.append(afterAsterisk ? StringUtil.trimLeading(element.getText()) : element.getText());
afterAsterisk = false;
}
if (type == KDocTokens.LEADING_ASTERISK || type == KDocTokens.START) {
afterAsterisk = true;
}
if (contentStarted && element instanceof PsiWhiteSpace) {
builder.append(StringUtil.repeat("\n", StringUtil.countNewLines(element.getText())));
}
}
return builder.toString();
}
@Nullable
private static KDoc findElementKDoc(@NotNull PsiElement element) {
PsiElement navigateElement = (element instanceof JetElement) ? element : element.getNavigationElement();
PsiElement comment = JetPsiUtil.skipSiblingsBackwardByPredicate(navigateElement, SKIP_WHITESPACE_AND_EMPTY_NAMESPACE);
return comment instanceof KDoc ? (KDoc) comment : null;
}
private static String kDocToHtml(@NotNull KDoc comment) {
// TODO: Parse and show markdown comments as html
String content = getKDocContent(comment);
String htmlContent = StringUtil.replace(content, "\n", "<br/>")
.replaceAll("(@param)\\s+(\\w+)", "@param - <i>$2</i>")
.replaceAll("(@\\w+)", "<b>$1</b>");
return "<p>" + htmlContent + "</p>";
}
}
@@ -85,7 +85,7 @@ public class JetHighlighter extends SyntaxHighlighterBase {
keys1.put(JetTokens.BLOCK_COMMENT, JetHighlightingColors.BLOCK_COMMENT);
keys1.put(JetTokens.DOC_COMMENT, JetHighlightingColors.DOC_COMMENT);
fillMap(keys1, KDocTokens.CONTENT_TOKENS, JetHighlightingColors.DOC_COMMENT);
fillMap(keys1, KDocTokens.KDOC_HIGHLIGHT_TOKENS, JetHighlightingColors.DOC_COMMENT);
keys1.put(KDocTokens.TAG_NAME, JetHighlightingColors.KDOC_TAG);
keys2.put(KDocTokens.TAG_NAME, JetHighlightingColors.DOC_COMMENT);
@@ -0,0 +1,5 @@
fun test() {
listOf(1, 2, 4).<caret>filter { it > 0 }
}
// INFO: <b>public</b> <b>fun</b> &lt;T> jet.Collection&lt;T&gt;.filter(predicate: (T) &rarr; jet.Boolean): jet.List&lt;T&gt; <i>defined in</i> kotlin<br/><p>Returns a list containing all elements which match the given *predicate*<br/></p>
@@ -0,0 +1,6 @@
/**
* Usefull comment
*/
class <caret>Some
// INFO: <b>internal</b> <b>final</b> <b>class</b> Some <i>defined in</i> root package<br/><p>Usefull comment<br/></p>
@@ -0,0 +1,15 @@
package test
/**
*
*
* Test function
*
* @param first - Some
* @param second - Other
*/
fun <caret>testFun(first: String, second: Int) = 12
// INFO: <b>internal</b> <b>fun</b> testFun(first: jet.String, second: jet.Int): jet.Int <i>defined in</i> test<br/><p>Test function<br/><br/><br/><b>@param</b> - <i>first</i> - Some<br/><b>@param</b> - <i>second</i> - Other<br/></p>
@@ -0,0 +1,15 @@
/**
Some documentation
@param a - Some int
* @param b: String
*/
fun testMethod(a: Int, b: String) {
}
fun test() {
<caret>testMethod(1, "value")
}
// INFO: <b>internal</b> <b>fun</b> testMethod(a: jet.Int, b: jet.String): jet.Unit <i>defined in</i> root package<br/><p>Some documentation<br/><br/><b>@param</b> - <i>a</i> - Some int<br/><b>@param</b> - <i>b</i>: String<br/></p>
@@ -0,0 +1,51 @@
/*
* Copyright 2010-2013 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.jet.editor.quickDoc;
import com.intellij.codeInsight.documentation.DocumentationManager;
import com.intellij.codeInsight.navigation.CtrlMouseHandler;
import com.intellij.psi.PsiElement;
import com.intellij.testFramework.LightProjectDescriptor;
import com.intellij.testFramework.fixtures.LightCodeInsightFixtureTestCase;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.jet.InTextDirectivesUtils;
import org.jetbrains.jet.plugin.ProjectDescriptorWithStdlibSources;
import java.util.List;
public abstract class AbstractJetQuickDocProviderTest extends LightCodeInsightFixtureTestCase {
public void doTest(@NotNull String path) throws Exception {
myFixture.configureByFile(path);
PsiElement element = myFixture.getFile().findElementAt(myFixture.getEditor().getCaretModel().getOffset());
assertNotNull("Can't find element at caret in file: " + path, element);
DocumentationManager documentationManager = DocumentationManager.getInstance(myFixture.getProject());
PsiElement targetElement = documentationManager.findTargetElement(myFixture.getEditor(), myFixture.getFile());
List<String> directives = InTextDirectivesUtils.findLinesWithPrefixesRemoved(myFixture.getFile().getText(), "INFO:");
assertTrue("Documentation to check should be added to test file with // INFO: directive " + path, 1 == directives.size());
assertEquals(directives.get(0), CtrlMouseHandler.getInfo(targetElement, element));
}
@NotNull
@Override
protected LightProjectDescriptor getProjectDescriptor() {
return ProjectDescriptorWithStdlibSources.INSTANCE;
}
}
@@ -0,0 +1,59 @@
/*
* Copyright 2010-2013 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.jet.editor.quickDoc;
import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestSuite;
import java.io.File;
import java.util.regex.Pattern;
import org.jetbrains.jet.JetTestUtils;
import org.jetbrains.jet.test.InnerTestClasses;
import org.jetbrains.jet.test.TestMetadata;
import org.jetbrains.jet.editor.quickDoc.AbstractJetQuickDocProviderTest;
/** This class is generated by {@link org.jetbrains.jet.generators.tests.GenerateTests}. DO NOT MODIFY MANUALLY */
@SuppressWarnings("all")
@TestMetadata("idea/testData/editor/quickDoc")
public class JetQuickDocProviderTestGenerated extends AbstractJetQuickDocProviderTest {
public void testAllFilesPresentInQuickDoc() throws Exception {
JetTestUtils.assertAllTestsPresentByMetadata(this.getClass(), "org.jetbrains.jet.generators.tests.GenerateTests", new File("idea/testData/editor/quickDoc"), Pattern.compile("^(.+)\\.kt$"), true);
}
@TestMetadata("MethodFromStdLib.kt")
public void testMethodFromStdLib() throws Exception {
doTest("idea/testData/editor/quickDoc/MethodFromStdLib.kt");
}
@TestMetadata("OnClassDeclarationWithNoPackage.kt")
public void testOnClassDeclarationWithNoPackage() throws Exception {
doTest("idea/testData/editor/quickDoc/OnClassDeclarationWithNoPackage.kt");
}
@TestMetadata("OnFunctionDeclarationWithPackage.kt")
public void testOnFunctionDeclarationWithPackage() throws Exception {
doTest("idea/testData/editor/quickDoc/OnFunctionDeclarationWithPackage.kt");
}
@TestMetadata("OnMethodUsage.kt")
public void testOnMethodUsage() throws Exception {
doTest("idea/testData/editor/quickDoc/OnMethodUsage.kt");
}
}