KT-3729 Quick documentation support
#KT-3729 Fixed
This commit is contained in:
Generated
+10
@@ -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> <T> jet.Collection<T>.filter(predicate: (T) → jet.Boolean): jet.List<T> <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");
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user