diff --git a/.idea/dictionaries/Nikolay_Krasko.xml b/.idea/dictionaries/Nikolay_Krasko.xml new file mode 100644 index 00000000000..f882fb4f9b5 --- /dev/null +++ b/.idea/dictionaries/Nikolay_Krasko.xml @@ -0,0 +1,10 @@ + + + + kdoc + redeclarations + subclassed + subgraph + + + \ No newline at end of file diff --git a/compiler/frontend/src/org/jetbrains/jet/kdoc/lexer/KDocTokens.java b/compiler/frontend/src/org/jetbrains/jet/kdoc/lexer/KDocTokens.java index da637335958..fbcf0102867 100644 --- a/compiler/frontend/src/org/jetbrains/jet/kdoc/lexer/KDocTokens.java +++ b/compiler/frontend/src/org/jetbrains/jet/kdoc/lexer/KDocTokens.java @@ -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); } diff --git a/generators/org/jetbrains/jet/generators/tests/GenerateTests.java b/generators/org/jetbrains/jet/generators/tests/GenerateTests.java index 4bced467dec..d4c30962b2f 100644 --- a/generators/org/jetbrains/jet/generators/tests/GenerateTests.java +++ b/generators/org/jetbrains/jet/generators/tests/GenerateTests.java @@ -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) { diff --git a/idea/src/org/jetbrains/jet/plugin/JetQuickDocumentationProvider.java b/idea/src/org/jetbrains/jet/plugin/JetQuickDocumentationProvider.java index b99933f42fe..95ef6e1f1da 100644 --- a/idea/src/org/jetbrains/jet/plugin/JetQuickDocumentationProvider.java +++ b/idea/src/org/jetbrains/jet/plugin/JetQuickDocumentationProvider.java @@ -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 SKIP_WHITESPACE_AND_EMPTY_NAMESPACE = new Predicate() { + @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 + "
" + 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 + "
Java declaration:
" + 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", "
") + .replaceAll("(@param)\\s+(\\w+)", "@param - $2") + .replaceAll("(@\\w+)", "$1"); + + return "

" + htmlContent + "

"; + } } diff --git a/idea/src/org/jetbrains/jet/plugin/highlighter/JetHighlighter.java b/idea/src/org/jetbrains/jet/plugin/highlighter/JetHighlighter.java index 8987e291e12..f9bb1e3a895 100644 --- a/idea/src/org/jetbrains/jet/plugin/highlighter/JetHighlighter.java +++ b/idea/src/org/jetbrains/jet/plugin/highlighter/JetHighlighter.java @@ -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); diff --git a/idea/testData/editor/quickDoc/MethodFromStdLib.kt b/idea/testData/editor/quickDoc/MethodFromStdLib.kt new file mode 100644 index 00000000000..3cd05d8b360 --- /dev/null +++ b/idea/testData/editor/quickDoc/MethodFromStdLib.kt @@ -0,0 +1,5 @@ +fun test() { + listOf(1, 2, 4).filter { it > 0 } +} + +// INFO: public fun <T> jet.Collection<T>.filter(predicate: (T) → jet.Boolean): jet.List<T> defined in kotlin

Returns a list containing all elements which match the given *predicate*

\ No newline at end of file diff --git a/idea/testData/editor/quickDoc/OnClassDeclarationWithNoPackage.kt b/idea/testData/editor/quickDoc/OnClassDeclarationWithNoPackage.kt new file mode 100644 index 00000000000..2364066228c --- /dev/null +++ b/idea/testData/editor/quickDoc/OnClassDeclarationWithNoPackage.kt @@ -0,0 +1,6 @@ +/** + * Usefull comment + */ +class Some + +// INFO: internal final class Some defined in root package

Usefull comment

\ No newline at end of file diff --git a/idea/testData/editor/quickDoc/OnFunctionDeclarationWithPackage.kt b/idea/testData/editor/quickDoc/OnFunctionDeclarationWithPackage.kt new file mode 100644 index 00000000000..eaa85a06877 --- /dev/null +++ b/idea/testData/editor/quickDoc/OnFunctionDeclarationWithPackage.kt @@ -0,0 +1,15 @@ +package test + +/** + + * + * + * Test function + + * + * @param first - Some + * @param second - Other + */ +fun testFun(first: String, second: Int) = 12 + +// INFO: internal fun testFun(first: jet.String, second: jet.Int): jet.Int defined in test

Test function


@param - first - Some
@param - second - Other

\ No newline at end of file diff --git a/idea/testData/editor/quickDoc/OnMethodUsage.kt b/idea/testData/editor/quickDoc/OnMethodUsage.kt new file mode 100644 index 00000000000..529988e3239 --- /dev/null +++ b/idea/testData/editor/quickDoc/OnMethodUsage.kt @@ -0,0 +1,15 @@ +/** + Some documentation + + @param a - Some int + * @param b: String + */ +fun testMethod(a: Int, b: String) { + +} + +fun test() { + testMethod(1, "value") +} + +// INFO: internal fun testMethod(a: jet.Int, b: jet.String): jet.Unit defined in root package

Some documentation

@param - a - Some int
@param - b: String

\ No newline at end of file diff --git a/idea/tests/org/jetbrains/jet/editor/quickDoc/AbstractJetQuickDocProviderTest.java b/idea/tests/org/jetbrains/jet/editor/quickDoc/AbstractJetQuickDocProviderTest.java new file mode 100644 index 00000000000..fa21406adde --- /dev/null +++ b/idea/tests/org/jetbrains/jet/editor/quickDoc/AbstractJetQuickDocProviderTest.java @@ -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 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; + } +} diff --git a/idea/tests/org/jetbrains/jet/editor/quickDoc/JetQuickDocProviderTestGenerated.java b/idea/tests/org/jetbrains/jet/editor/quickDoc/JetQuickDocProviderTestGenerated.java new file mode 100644 index 00000000000..e0c6c8f15e0 --- /dev/null +++ b/idea/tests/org/jetbrains/jet/editor/quickDoc/JetQuickDocProviderTestGenerated.java @@ -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"); + } + +}