diff --git a/.idea/dictionaries/valentin.xml b/.idea/dictionaries/valentin.xml index 1fa06691aa3..cf661a2ee51 100644 --- a/.idea/dictionaries/valentin.xml +++ b/.idea/dictionaries/valentin.xml @@ -3,6 +3,7 @@ decapitalize delegator + elipsis funs immediates initializers diff --git a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtQualifiedExpression.kt b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtQualifiedExpression.kt index eac4010be27..49ed617cbe8 100644 --- a/compiler/frontend/src/org/jetbrains/kotlin/psi/KtQualifiedExpression.kt +++ b/compiler/frontend/src/org/jetbrains/kotlin/psi/KtQualifiedExpression.kt @@ -17,11 +17,12 @@ package org.jetbrains.kotlin.psi import com.intellij.lang.ASTNode -import org.jetbrains.kotlin.lexer.KtToken +import org.jetbrains.kotlin.lexer.KtSingleValueToken import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.psi.psiUtil.getElementTextWithContext import org.jetbrains.kotlin.psi.psiUtil.siblings import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull +import java.lang.AssertionError interface KtQualifiedExpression : KtExpression { val receiverExpression: KtExpression @@ -33,8 +34,8 @@ interface KtQualifiedExpression : KtExpression { val operationTokenNode: ASTNode get() = node.findChildByType(KtTokens.OPERATIONS)!! - val operationSign: KtToken - get() = operationTokenNode.elementType as KtToken + val operationSign: KtSingleValueToken + get() = operationTokenNode.elementType as KtSingleValueToken private fun KtQualifiedExpression.getExpression(afterOperation: Boolean): KtExpression? { return operationTokenNode.psi?.siblings(afterOperation, false)?.firstIsInstanceOrNull() diff --git a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt index cb0cc9d5d80..36543f895c6 100755 --- a/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt +++ b/generators/src/org/jetbrains/kotlin/generators/tests/GenerateTests.kt @@ -514,6 +514,10 @@ fun main(args: Array) { model("joinLines") } + testClass() { + model("codeInsight/breadcrumbs") + } + testClass() { model("intentions", pattern = "^([\\w\\-_]+)\\.kt$") } diff --git a/idea/src/META-INF/plugin.xml b/idea/src/META-INF/plugin.xml index f18ac78e0d7..b77fe0a4797 100644 --- a/idea/src/META-INF/plugin.xml +++ b/idea/src/META-INF/plugin.xml @@ -525,6 +525,8 @@ + + diff --git a/idea/src/org/jetbrains/kotlin/idea/codeInsight/KotlinBreadcrumbsInfoProvider.kt b/idea/src/org/jetbrains/kotlin/idea/codeInsight/KotlinBreadcrumbsInfoProvider.kt new file mode 100644 index 00000000000..b18e7dbf34a --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/codeInsight/KotlinBreadcrumbsInfoProvider.kt @@ -0,0 +1,462 @@ +/* + * 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.codeInsight + +import com.intellij.psi.ElementDescriptionUtil +import com.intellij.psi.PsiElement +import com.intellij.refactoring.util.RefactoringDescriptionLocation +import com.intellij.usageView.UsageViewShortNameLocation +import com.intellij.xml.breadcrumbs.BreadcrumbsInfoProvider +import org.jetbrains.kotlin.KtNodeTypes +import org.jetbrains.kotlin.idea.KotlinLanguage +import org.jetbrains.kotlin.idea.intentions.loopToCallChain.unwrapIfLabeled +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getCallNameExpression +import org.jetbrains.kotlin.psi.psiUtil.getQualifiedExpressionForSelector +import org.jetbrains.kotlin.renderer.render +import org.jetbrains.kotlin.resolve.calls.callUtil.getValueArgumentsInParentheses +import kotlin.reflect.KClass + +class KotlinBreadcrumbsInfoProvider : BreadcrumbsInfoProvider() { + private abstract class ElementHandler(val type: KClass) { + abstract fun elementInfo(element: TElement): String + abstract fun elementTooltip(element: TElement): String + + open fun accepts(element: TElement): Boolean = true + } + + private val handlers = listOf>( + LambdaHandler, + AnonymousObjectHandler, + AnonymousFunctionHandler, + PropertyAccessorHandler, + DeclarationHandler, + IfThenHandler, + ElseHandler, + TryHandler, + CatchHandler, + FinallyHandler, + WhileHandler, + DoWhileHandler, + WhenHandler, + WhenEntryHandler, + ForHandler + ) + + private object LambdaHandler : ElementHandler(KtFunctionLiteral::class) { + override fun elementInfo(element: KtFunctionLiteral): String { + val lambdaExpression = element.parent as KtLambdaExpression + val unwrapped = lambdaExpression.unwrapIfLabeled() + val label = lambdaExpression.labelText() + val lambdaText = "$label{$ellipsis}" + + val parent = unwrapped.parent + when (parent) { + is KtLambdaArgument -> { + val callExpression = parent.parent as? KtCallExpression + val callName = callExpression?.getCallNameExpression()?.getReferencedName() + if (callName != null) { + val receiverText = callExpression.getQualifiedExpressionForSelector()?.let { + it.receiverExpression.text.orEllipsis(TextKind.INFO) + it.operationSign.value + } ?: "" + + return buildString { + append(receiverText) + append(callName) + + if (callExpression.valueArgumentList != null) { + appendCallArguments(callExpression) + } + else { + if (label.isNotEmpty()) append(" ") + } + append(lambdaText) + } + } + } + + is KtProperty -> { + val name = parent.nameAsName + if (unwrapped == parent.initializer && name != null) { + val valOrVar = if (parent.isVar) "var" else "val" + return "$valOrVar ${name.render()} = $lambdaText" + } + } + } + + return lambdaText + } + + private fun StringBuilder.appendCallArguments(callExpression: KtCallExpression) { + var argumentText = "($ellipsis)" + val arguments = callExpression.getValueArgumentsInParentheses() + when (arguments.size) { + 0 -> argumentText = "()" + 1 -> { + val argument = arguments.single() + val argumentExpression = argument.getArgumentExpression() + if (!argument.isNamed() && argument.getSpreadElement() == null && argumentExpression != null) { + argumentText = "(" + argumentExpression.shortText(TextKind.INFO) + ")" + } + } + } + append(argumentText) + append(" ") + } + + //TODO + override fun elementTooltip(element: KtFunctionLiteral): String { + return ElementDescriptionUtil.getElementDescription(element, RefactoringDescriptionLocation.WITH_PARENT) + } + } + + private object AnonymousObjectHandler : ElementHandler(KtObjectDeclaration::class) { + override fun accepts(element: KtObjectDeclaration) = element.isObjectLiteral() + + override fun elementInfo(element: KtObjectDeclaration) = element.buildText(TextKind.INFO) + override fun elementTooltip(element: KtObjectDeclaration) = element.buildText(TextKind.TOOLTIP) + + private fun KtObjectDeclaration.buildText(kind: TextKind): String { + return buildString { + append("object") + + val superTypeEntries = getSuperTypeListEntries() + if (superTypeEntries.isNotEmpty()) { + append(" : ") + + if (kind == TextKind.INFO) { + val entry = superTypeEntries.first() + entry.typeReference?.text?.truncateStart(kind)?.let { append(it) } + if (superTypeEntries.size > 1) { + if (!endsWith(ellipsis)) { + append(",$ellipsis") + } + } + } + else { + append(superTypeEntries.joinToString(separator = ", ") { it.typeReference?.text ?: "" }.truncateEnd(kind)) + } + } + } + } + } + + private object AnonymousFunctionHandler : ElementHandler(KtNamedFunction::class) { + override fun accepts(element: KtNamedFunction) = element.name == null + + override fun elementInfo(element: KtNamedFunction) = element.buildText(TextKind.INFO) + override fun elementTooltip(element: KtNamedFunction) = element.buildText(TextKind.TOOLTIP) + + private fun KtNamedFunction.buildText(kind: TextKind): String { + return "fun(" + + valueParameters.joinToString(separator = ", ") { if (kind == TextKind.INFO) it.name ?: "" else it.text }.truncateEnd(kind) + + ")" + } + } + + private object PropertyAccessorHandler : ElementHandler(KtPropertyAccessor::class) { + override fun elementInfo(element: KtPropertyAccessor): String { + return element.property.name + "." + (if (element.isGetter) "get" else "set") + } + + override fun elementTooltip(element: KtPropertyAccessor): String { + return DeclarationHandler.elementTooltip(element) + } + } + + private object DeclarationHandler : ElementHandler(KtDeclaration::class) { + override fun accepts(element: KtDeclaration): Boolean { + if (element is KtProperty) { + return element.parent is KtFile || element.parent is KtClassBody // do not show local variables + } + return true + } + + override fun elementInfo(element: KtDeclaration): String { + when { + element is KtProperty -> { + return (if (element.isVar) "var " else "val ") + element.nameAsName?.render() + } + + element is KtObjectDeclaration && element.isCompanion() -> { + return buildString { + append("companion object") + element.nameIdentifier?.let { append(" "); append(it.text) } + } + } + + else -> { + val description = ElementDescriptionUtil.getElementDescription(element, UsageViewShortNameLocation.INSTANCE) + val suffix = if (element is KtFunction) "()" else null + return if (suffix != null) description + suffix else description + } + } + + } + + override fun elementTooltip(element: KtDeclaration): String { + return ElementDescriptionUtil.getElementDescription(element, RefactoringDescriptionLocation.WITH_PARENT) + } + } + + private abstract class ConstructWithExpressionHandler( + private val constructName: String, + type: KClass + ) : ElementHandler(type) { + + protected abstract fun extractExpression(element: TElement): KtExpression? + protected abstract fun labelOwner(element: TElement): KtExpression? + + override fun elementInfo(element: TElement) = element.buildText(TextKind.INFO) + override fun elementTooltip(element: TElement) = element.buildText(TextKind.TOOLTIP) + + protected fun TElement.buildText(kind: TextKind): String { + return buildString { + append(labelOwner(this@buildText)?.labelText() ?: "") + append(constructName) + val expression = extractExpression(this@buildText) + if (expression != null) { + append(" (") + append(expression.shortText(kind)) + append(")") + } + } + } + } + + private object IfThenHandler : ConstructWithExpressionHandler("if", KtContainerNode::class) { + override fun accepts(element: KtContainerNode): Boolean { + return element.node.elementType == KtNodeTypes.THEN + } + + override fun extractExpression(element: KtContainerNode): KtExpression? { + return (element.parent as KtIfExpression).condition + } + + override fun labelOwner(element: KtContainerNode) = null + + override fun elementInfo(element: KtContainerNode): String { + return elseIfPrefix(element) + super.elementInfo(element) + } + + override fun elementTooltip(element: KtContainerNode): String { + return elseIfPrefix(element) + super.elementTooltip(element) + } + + private fun elseIfPrefix(then: KtContainerNode): String { + return if ((then.parent as KtIfExpression).isElseIf()) "if $ellipsis else " else "" + } + } + + private object ElseHandler : ElementHandler(KtContainerNode::class) { + override fun accepts(element: KtContainerNode): Boolean { + return element.node.elementType == KtNodeTypes.ELSE + && (element.parent as KtIfExpression).`else` !is KtIfExpression // filter out "else if" + } + + override fun elementInfo(element: KtContainerNode): String { + val ifExpression = element.parent as KtIfExpression + val then = ifExpression.thenNode + val ifInfo = if (ifExpression.isElseIf() || then == null) "if" else IfThenHandler.elementInfo(then) + return "$ifInfo $ellipsis else" + } + + override fun elementTooltip(element: KtContainerNode): String { + val ifExpression = element.parent as KtIfExpression + val thenNode = ifExpression.thenNode ?: return "else" + return "else (of '" + IfThenHandler.elementTooltip(thenNode) + "')" //TODO + } + + private val KtIfExpression.thenNode: KtContainerNode? + get() = children.firstOrNull { it.node.elementType == KtNodeTypes.THEN } as KtContainerNode? + } + + private object TryHandler : ElementHandler(KtBlockExpression::class) { + override fun accepts(element: KtBlockExpression) = element.parent is KtTryExpression + + override fun elementInfo(element: KtBlockExpression) = "try" + override fun elementTooltip(element: KtBlockExpression) = "try" + } + + private object CatchHandler : ElementHandler(KtCatchClause::class) { + override fun elementInfo(element: KtCatchClause): String { + val text = element.catchParameter?.typeReference?.text ?: "" + return "catch ($text)" + } + + override fun elementTooltip(element: KtCatchClause): String { + return elementInfo(element) + } + } + + private object FinallyHandler : ElementHandler(KtFinallySection::class) { + override fun elementInfo(element: KtFinallySection) = "finally" + override fun elementTooltip(element: KtFinallySection) = "finally" + } + + private object WhileHandler : ConstructWithExpressionHandler("while", KtContainerNode::class) { + override fun accepts(element: KtContainerNode) = element.bodyOwner() is KtWhileExpression + override fun extractExpression(element: KtContainerNode) = (element.bodyOwner() as KtWhileExpression).condition + override fun labelOwner(element: KtContainerNode) = element.bodyOwner() + } + + private object DoWhileHandler : ConstructWithExpressionHandler("do $ellipsis while", KtContainerNode::class) { + override fun accepts(element: KtContainerNode) = element.bodyOwner() is KtDoWhileExpression + override fun extractExpression(element: KtContainerNode) = (element.bodyOwner() as KtDoWhileExpression).condition + override fun labelOwner(element: KtContainerNode) = element.bodyOwner() + } + + private object WhenHandler : ConstructWithExpressionHandler("when", KtWhenExpression::class) { + override fun extractExpression(element: KtWhenExpression) = element.subjectExpression + override fun labelOwner(element: KtWhenExpression) = null + } + + private object WhenEntryHandler : ElementHandler(KtExpression::class) { + override fun accepts(element: KtExpression) = element.parent is KtWhenEntry + + override fun elementInfo(element: KtExpression) = element.buildText(TextKind.INFO) + override fun elementTooltip(element: KtExpression) = element.buildText(TextKind.TOOLTIP) + + private fun KtExpression.buildText(kind: TextKind): String { + with (parent as KtWhenEntry) { + if (isElse) { + return "else ->" + } + else { + val condition = conditions.firstOrNull() ?: return "->" + val firstConditionText = condition.buildText(kind) + + if (conditions.size == 1) { + return firstConditionText + " ->" + } + else { + //TODO: show all conditions for tooltip + return (if (firstConditionText.endsWith(ellipsis)) firstConditionText else firstConditionText + ",$ellipsis") + " ->" + } + } + } + } + + private fun KtWhenCondition.buildText(kind: TextKind): String { + return when (this) { + is KtWhenConditionIsPattern -> { + (if (isNegated) "!is" else "is") + " " + (typeReference?.text?.truncateEnd(kind) ?: "") + } + + is KtWhenConditionInRange -> { + (if (isNegated) "!in" else "in") + " " + (rangeExpression?.text?.truncateEnd(kind) ?: "") + } + + is KtWhenConditionWithExpression -> { + expression?.text?.truncateStart(kind) ?: "" + } + + else -> error("Unknown when entry condition type: ${this}") + } + } + } + + private object ForHandler : ElementHandler(KtContainerNode::class) { + override fun accepts(element: KtContainerNode) = element.bodyOwner() is KtForExpression + + override fun elementInfo(element: KtContainerNode) = element.buildText(TextKind.INFO) + override fun elementTooltip(element: KtContainerNode) = element.buildText(TextKind.TOOLTIP) + + private fun KtContainerNode.buildText(kind: TextKind): String { + with (bodyOwner() as KtForExpression) { + val parameterText = loopParameter?.nameAsName?.render() ?: destructuringParameter?.text ?: return "for" + val collectionText = loopRange?.text ?: "" + val text = (parameterText + " in " + collectionText).truncateEnd(kind) + return labelText() + "for($text)" + } + } + } + + @Suppress("UNCHECKED_CAST") + private fun handler(e: PsiElement): ElementHandler? { + if (e !is KtElement) return null + val handler = handlers.firstOrNull { it.type.java.isInstance(e) && (it as ElementHandler).accepts(e) } + return handler as ElementHandler? + } + + override fun getLanguages() = arrayOf(KotlinLanguage.INSTANCE) + + override fun acceptElement(e: PsiElement) = handler(e) != null + + override fun getElementInfo(e: PsiElement): String { + return handler(e)!!.elementInfo(e as KtElement) + } + + override fun getElementTooltip(e: PsiElement): String { + return handler(e)!!.elementTooltip(e as KtElement) + } + + override fun getParent(e: PsiElement): PsiElement? { + val node = e.node ?: return null + when (node.elementType) { + KtNodeTypes.PROPERTY_ACCESSOR -> + return e.parent.parent + + else -> + return e.parent + } + } + + private companion object { + enum class TextKind(val maxTextLength: Int) { + INFO(16), TOOLTIP(100) + } + + fun KtExpression.shortText(kind: TextKind): String { + return if (this is KtNameReferenceExpression) text else text.truncateEnd(kind) + } + + //TODO: line breaks + + fun String.orEllipsis(kind: TextKind): String { + return if (length <= kind.maxTextLength) this else ellipsis + } + + fun String.truncateEnd(kind: TextKind): String { + val maxLength = kind.maxTextLength + return if (length > maxLength) substring(0, maxLength - ellipsis.length) + ellipsis else this + } + + fun String.truncateStart(kind: TextKind): String { + val maxLength = kind.maxTextLength + return if (length > maxLength) ellipsis + substring(length - maxLength - 1) else this + } + + val ellipsis = "${Typography.ellipsis}" + + fun KtIfExpression.isElseIf() = parent.node.elementType == KtNodeTypes.ELSE + + fun KtContainerNode.bodyOwner(): KtExpression? { + return if (node.elementType == KtNodeTypes.BODY) parent as KtExpression else null + } + + fun KtExpression.labelText(): String { + var result = "" + var current = parent + while (current is KtLabeledExpression) { + result = current.getLabelName() + "@ " + result + current = current.parent + } + return result + } + } +} diff --git a/idea/src/org/jetbrains/kotlin/idea/findUsages/KotlinElementDescriptionProvider.kt b/idea/src/org/jetbrains/kotlin/idea/findUsages/KotlinElementDescriptionProvider.kt index 2b03d6e0b80..3ad7cd0fc01 100644 --- a/idea/src/org/jetbrains/kotlin/idea/findUsages/KotlinElementDescriptionProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/findUsages/KotlinElementDescriptionProvider.kt @@ -52,7 +52,7 @@ class KotlinElementDescriptionProvider : ElementDescriptionProvider { fun elementKind() = when (targetElement) { is KtClass -> if (targetElement.isInterface()) "interface" else "class" - is KtObjectDeclaration -> "object" + is KtObjectDeclaration -> if (targetElement.isCompanion()) "companion object" else "object" is KtNamedFunction -> "function" is KtPropertyAccessor -> (if (targetElement.isGetter) "getter" else "setter") + " for property " is KtFunctionLiteral -> "lambda" diff --git a/idea/testData/codeInsight/breadcrumbs/AnonymousObjects.kt b/idea/testData/codeInsight/breadcrumbs/AnonymousObjects.kt new file mode 100644 index 00000000000..5d5a0e9a928 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/AnonymousObjects.kt @@ -0,0 +1,13 @@ +val v = foo(object : java.lang.Runnable { + val v2 = object : A, B { + fun f() { + object : java.io.Serializable, XXX { + var o = object { + override fun equals(other: Any?): Boolean { + + } + } + } + } + } +}) diff --git a/idea/testData/codeInsight/breadcrumbs/AnonymousObjects.txt b/idea/testData/codeInsight/breadcrumbs/AnonymousObjects.txt new file mode 100644 index 00000000000..3a6f29eba4a --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/AnonymousObjects.txt @@ -0,0 +1,20 @@ +Crumbs: + val v + object : …ava.lang.Runnable + val v2 + object : A,… + f() + object : …a.io.Serializable,… + var o + object + equals() +Tooltips: + property v + object : java.lang.Runnable + property v.<no name provided>.v2 + object : A, B + function v.<no name provided>.v2.<no name provided>.f() + object : java.io.Serializable, XXX + property v.<no name provided>.v2.<no name provided>.f.<no name provided>.o + object + function v.<no name provided>.v2.<no name provided>.f.<no name provided>.o.<no name provided>.equals(Any?) \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/Declarations.kt b/idea/testData/codeInsight/breadcrumbs/Declarations.kt new file mode 100644 index 00000000000..83ea96b314d --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/Declarations.kt @@ -0,0 +1,17 @@ +class Outer { + companion object { + class SomeClass : java.io.Serializable { + companion object CompanionName { + private object SomeObject { + fun String.someFun(p: Int, b: Boolean): String { + fun localFun() { + val v = doIt(fun (x: Int, y: Char) { + + }) + } + } + } + } + } + } +} diff --git a/idea/testData/codeInsight/breadcrumbs/Declarations.txt b/idea/testData/codeInsight/breadcrumbs/Declarations.txt new file mode 100644 index 00000000000..9555b599f11 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/Declarations.txt @@ -0,0 +1,18 @@ +Crumbs: + Outer + companion object + SomeClass + companion object CompanionName + SomeObject + someFun() + localFun() + fun(x, y) +Tooltips: + class Outer + companion object Outer.Companion + class Outer.Companion.SomeClass + companion object Outer.Companion.SomeClass.CompanionName + object Outer.Companion.SomeClass.CompanionName.SomeObject + function Outer.Companion.SomeClass.CompanionName.SomeObject.someFun(Int, Boolean) on String + function Outer.Companion.SomeClass.CompanionName.SomeObject.someFun.localFun() + fun(x: Int, y: Char) \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/For.kt b/idea/testData/codeInsight/breadcrumbs/For.kt new file mode 100644 index 00000000000..b0d93da1273 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/For.kt @@ -0,0 +1,9 @@ +fun foo() { + for (i in 1..10) { + for (j: Int in collection.filter(predicate)) { + for ((x, y) in entries) { + + } + } + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/For.txt b/idea/testData/codeInsight/breadcrumbs/For.txt new file mode 100644 index 00000000000..85162a99b71 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/For.txt @@ -0,0 +1,10 @@ +Crumbs: + foo() + for(i in 1..10) + for(j in collection…) + for((x, y) in entri…) +Tooltips: + function foo() + for(i in 1..10) + for(j in collection.filter(predicate)) + for((x, y) in entries) \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/If.kt b/idea/testData/codeInsight/breadcrumbs/If.kt new file mode 100644 index 00000000000..51e5469d8aa --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/If.kt @@ -0,0 +1,34 @@ +fun foo() { + if (x) { + if (x.y) { + if (y) { + + } + else { + if (a) { + + } + else if (b) { + + } + else if (c) { + if (q) { + + } + else if (qq) { + + } + else if (qqq) { + + } + else { + if (p) return + } + } + else { + + } + } + } + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/If.txt b/idea/testData/codeInsight/breadcrumbs/If.txt new file mode 100644 index 00000000000..f32e1c79a20 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/If.txt @@ -0,0 +1,16 @@ +Crumbs: + foo() + if (x) + if (x.y) + if (y) … else + if … else if (c) + if … else + if (p) +Tooltips: + function foo() + if (x) + if (x.y) + else (of 'if (y)') + if … else if (c) + else (of 'if … else if (qqq)') + if (p) \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/LabeledStatements.kt b/idea/testData/codeInsight/breadcrumbs/LabeledStatements.kt new file mode 100644 index 00000000000..374c43c1db3 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/LabeledStatements.kt @@ -0,0 +1,14 @@ +fun foo() { + OuterLoop@ + for (i in 1..10) { + for (j in 1..10) { + Label1@ Label2@ + while(true) { + DoWhile@ + do { + + } while (x) + } + } + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/LabeledStatements.txt b/idea/testData/codeInsight/breadcrumbs/LabeledStatements.txt new file mode 100644 index 00000000000..4e54b2c79f5 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/LabeledStatements.txt @@ -0,0 +1,12 @@ +Crumbs: + foo() + OuterLoop@ for(i in 1..10) + for(j in 1..10) + Label1@ Label2@ while (true) + DoWhile@ do … while (x) +Tooltips: + function foo() + OuterLoop@ for(i in 1..10) + for(j in 1..10) + Label1@ Label2@ while (true) + DoWhile@ do … while (x) \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/Lambdas.kt b/idea/testData/codeInsight/breadcrumbs/Lambdas.kt new file mode 100644 index 00000000000..e46755b8666 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/Lambdas.kt @@ -0,0 +1,17 @@ +fun foo() { + buildString { + with(xxx) { + with(f()) Label@ { + g()?.let Label1@ { + listOf(1, 2, 3, 4, 5).filterTo(collection) { item -> + doIt() { + val v = Label2@ { p: Int -> + x(1, Label3@ { Something().apply { } }) + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/Lambdas.txt b/idea/testData/codeInsight/breadcrumbs/Lambdas.txt new file mode 100644 index 00000000000..dc2f945a6f7 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/Lambdas.txt @@ -0,0 +1,22 @@ +Crumbs: + foo() + buildString{…} + with(xxx) {…} + with(f()) Label@ {…} + g()?.let Label1@ {…} + ….filterTo(collection) {…} + doIt() {…} + val v = Label2@ {…} + Label3@ {…} + Something().apply{…} +Tooltips: + function foo() + lambda foo.<anonymous>() on StringBuilder + lambda foo.<anonymous>.<anonymous>() on ??? + lambda foo.<anonymous>.<anonymous>.<anonymous>() + lambda foo.<anonymous>.<anonymous>.<anonymous>.<anonymous>() + lambda foo.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>(???) + lambda foo.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>() + lambda foo.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>(Int) + lambda foo.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>() + lambda foo.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>() \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/OnIfCondition.kt b/idea/testData/codeInsight/breadcrumbs/OnIfCondition.kt new file mode 100644 index 00000000000..71c6b3c688b --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/OnIfCondition.kt @@ -0,0 +1,5 @@ +fun foo() { + if (x) { + + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/OnIfCondition.txt b/idea/testData/codeInsight/breadcrumbs/OnIfCondition.txt new file mode 100644 index 00000000000..a13b6b94d9c --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/OnIfCondition.txt @@ -0,0 +1,4 @@ +Crumbs: + foo() +Tooltips: + function foo() diff --git a/idea/testData/codeInsight/breadcrumbs/OnWhenEntryCondition.kt b/idea/testData/codeInsight/breadcrumbs/OnWhenEntryCondition.kt new file mode 100644 index 00000000000..e17ff7b4d31 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/OnWhenEntryCondition.kt @@ -0,0 +1,7 @@ +fun foo() { + when (x) { + is Foo -> { + + } + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/OnWhenEntryCondition.txt b/idea/testData/codeInsight/breadcrumbs/OnWhenEntryCondition.txt new file mode 100644 index 00000000000..ff203e5612b --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/OnWhenEntryCondition.txt @@ -0,0 +1,6 @@ +Crumbs: + foo() + when (x) +Tooltips: + function foo() + when (x) diff --git a/idea/testData/codeInsight/breadcrumbs/PropertyAccessor.kt b/idea/testData/codeInsight/breadcrumbs/PropertyAccessor.kt new file mode 100644 index 00000000000..fe8fd983a23 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/PropertyAccessor.kt @@ -0,0 +1,5 @@ +var Int.prop: String + get() = TODO() + set(value) { + + } \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/PropertyAccessor.txt b/idea/testData/codeInsight/breadcrumbs/PropertyAccessor.txt new file mode 100644 index 00000000000..afd1aa12107 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/PropertyAccessor.txt @@ -0,0 +1,4 @@ +Crumbs: + prop.set +Tooltips: + setter for property prop \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/Try.kt b/idea/testData/codeInsight/breadcrumbs/Try.kt new file mode 100644 index 00000000000..ce5f7e40416 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/Try.kt @@ -0,0 +1,18 @@ +fun foo() { + try { + try { + + } + finally { + try { + + } + catch (e: RuntimeException) { + + } + } + } + catch(e: Exception) { + + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/Try.txt b/idea/testData/codeInsight/breadcrumbs/Try.txt new file mode 100644 index 00000000000..c513003e08e --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/Try.txt @@ -0,0 +1,10 @@ +Crumbs: + foo() + try + finally + catch (RuntimeException) +Tooltips: + function foo() + try + finally + catch (RuntimeException) \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/When.kt b/idea/testData/codeInsight/breadcrumbs/When.kt new file mode 100644 index 00000000000..40414a5ebeb --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/When.kt @@ -0,0 +1,39 @@ +fun foo() { + when { + else -> { + when (x) { + x.isSomethingLong() -> { + when (y) { + y.isSomething() -> { + when (z) { + is VeryVeryVeryVeryLongName -> { + when (xxx) { + SomeEnum.SomeLongValue -> { + when (yyy) { + !in Int.MIN_VALUE..Int.MAX_VALUE -> { + when (zzz) { + is A, is B -> { + when (x1) { + A.VeryVeryVeryLongName, A.ShortName -> { + when (y1) { + is VeryVeryVeryVeryLongClassName, is ShortName -> { + + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/When.txt b/idea/testData/codeInsight/breadcrumbs/When.txt new file mode 100644 index 00000000000..0015ae3d4e2 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/When.txt @@ -0,0 +1,40 @@ +Crumbs: + foo() + when + else -> + when (x) + …isSomethingLong() -> + when (y) + y.isSomething() -> + when (z) + is VeryVeryVeryVer… -> + when (xxx) + …num.SomeLongValue -> + when (yyy) + in Int.MIN_VALUE..… -> + when (zzz) + is A,… -> + when (x1) + …yVeryVeryLongName,… -> + when (y1) + is VeryVeryVeryVer… -> +Tooltips: + function foo() + when + else -> + when (x) + x.isSomethingLong() -> + when (y) + y.isSomething() -> + when (z) + is VeryVeryVeryVeryLongName -> + when (xxx) + SomeEnum.SomeLongValue -> + when (yyy) + in Int.MIN_VALUE..Int.MAX_VALUE -> + when (zzz) + is A,… -> + when (x1) + A.VeryVeryVeryLongName,… -> + when (y1) + is VeryVeryVeryVeryLongClassName,… -> \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/While.kt b/idea/testData/codeInsight/breadcrumbs/While.kt new file mode 100644 index 00000000000..67c66907d43 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/While.kt @@ -0,0 +1,11 @@ +fun foo() { + while (true) { + while (p) { + while (x > 0) { + do { + + } while (true) + } + } + } +} \ No newline at end of file diff --git a/idea/testData/codeInsight/breadcrumbs/While.txt b/idea/testData/codeInsight/breadcrumbs/While.txt new file mode 100644 index 00000000000..c8a6207b869 --- /dev/null +++ b/idea/testData/codeInsight/breadcrumbs/While.txt @@ -0,0 +1,12 @@ +Crumbs: + foo() + while (true) + while (p) + while (x > 0) + do … while (true) +Tooltips: + function foo() + while (true) + while (p) + while (x > 0) + do … while (true) \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractBreadcrumbsTest.kt b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractBreadcrumbsTest.kt new file mode 100644 index 00000000000..075c413c079 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractBreadcrumbsTest.kt @@ -0,0 +1,46 @@ +/* + * 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.codeInsight + +import com.intellij.testFramework.LightProjectDescriptor +import org.jetbrains.kotlin.idea.test.KotlinLightPlatformCodeInsightFixtureTestCase +import org.jetbrains.kotlin.idea.test.KotlinWithJdkAndRuntimeLightProjectDescriptor +import org.jetbrains.kotlin.idea.test.PluginTestCaseBase +import org.jetbrains.kotlin.test.KotlinTestUtils +import java.io.File + +abstract class AbstractBreadcrumbsTest : KotlinLightPlatformCodeInsightFixtureTestCase() { + override fun getProjectDescriptor(): LightProjectDescriptor? = KotlinWithJdkAndRuntimeLightProjectDescriptor.INSTANCE + + override fun getTestDataPath() = PluginTestCaseBase.getTestDataPathBase() + "/codeInsight/breadcrumbs" + + protected open fun doTest(path: String) { + assert(path.endsWith(".kt")) { path } + myFixture.configureByFile(path) + + val element = myFixture.file.findElementAt(myFixture.caretOffset)!! + val provider = KotlinBreadcrumbsInfoProvider() + val elements = generateSequence(element) { provider.getParent(it) } + .filter { provider.acceptElement(it) } + .toList() + .asReversed() + val crumbs = elements.joinToString(separator = "\n") { " " + provider.getElementInfo(it) } + val tooltips = elements.joinToString(separator = "\n") { " " + provider.getElementTooltip(it) } + val resultText = "Crumbs:\n$crumbs\nTooltips:\n$tooltips" + KotlinTestUtils.assertEqualsToFile(File(testDataPath + "/" + File(path).nameWithoutExtension + ".txt"), resultText) + } +} \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/BreadcrumbsTestGenerated.java b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/BreadcrumbsTestGenerated.java new file mode 100644 index 00000000000..efd076255a1 --- /dev/null +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/BreadcrumbsTestGenerated.java @@ -0,0 +1,109 @@ +/* + * 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.codeInsight; + +import com.intellij.testFramework.TestDataPath; +import org.jetbrains.kotlin.test.JUnit3RunnerWithInners; +import org.jetbrains.kotlin.test.KotlinTestUtils; +import org.jetbrains.kotlin.test.TestMetadata; +import org.junit.runner.RunWith; + +import java.io.File; +import java.util.regex.Pattern; + +/** This class is generated by {@link org.jetbrains.kotlin.generators.tests.TestsPackage}. DO NOT MODIFY MANUALLY */ +@SuppressWarnings("all") +@TestMetadata("idea/testData/codeInsight/breadcrumbs") +@TestDataPath("$PROJECT_ROOT") +@RunWith(JUnit3RunnerWithInners.class) +public class BreadcrumbsTestGenerated extends AbstractBreadcrumbsTest { + public void testAllFilesPresentInBreadcrumbs() throws Exception { + KotlinTestUtils.assertAllTestsPresentByMetadata(this.getClass(), new File("idea/testData/codeInsight/breadcrumbs"), Pattern.compile("^(.+)\\.kt$"), true); + } + + @TestMetadata("AnonymousObjects.kt") + public void testAnonymousObjects() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/breadcrumbs/AnonymousObjects.kt"); + doTest(fileName); + } + + @TestMetadata("Declarations.kt") + public void testDeclarations() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/breadcrumbs/Declarations.kt"); + doTest(fileName); + } + + @TestMetadata("For.kt") + public void testFor() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/breadcrumbs/For.kt"); + doTest(fileName); + } + + @TestMetadata("If.kt") + public void testIf() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/breadcrumbs/If.kt"); + doTest(fileName); + } + + @TestMetadata("LabeledStatements.kt") + public void testLabeledStatements() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/breadcrumbs/LabeledStatements.kt"); + doTest(fileName); + } + + @TestMetadata("Lambdas.kt") + public void testLambdas() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/breadcrumbs/Lambdas.kt"); + doTest(fileName); + } + + @TestMetadata("OnIfCondition.kt") + public void testOnIfCondition() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/breadcrumbs/OnIfCondition.kt"); + doTest(fileName); + } + + @TestMetadata("OnWhenEntryCondition.kt") + public void testOnWhenEntryCondition() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/breadcrumbs/OnWhenEntryCondition.kt"); + doTest(fileName); + } + + @TestMetadata("PropertyAccessor.kt") + public void testPropertyAccessor() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/breadcrumbs/PropertyAccessor.kt"); + doTest(fileName); + } + + @TestMetadata("Try.kt") + public void testTry() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/breadcrumbs/Try.kt"); + doTest(fileName); + } + + @TestMetadata("When.kt") + public void testWhen() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/breadcrumbs/When.kt"); + doTest(fileName); + } + + @TestMetadata("While.kt") + public void testWhile() throws Exception { + String fileName = KotlinTestUtils.navigationMetadata("idea/testData/codeInsight/breadcrumbs/While.kt"); + doTest(fileName); + } +}