KT-12852 Support breadcrumbs for Kotlin

#KT-12852 Fixed
This commit is contained in:
Valentin Kipyatkov
2016-09-08 00:28:56 +03:00
parent dae4521c68
commit fcce1e3838
32 changed files with 992 additions and 4 deletions
+1
View File
@@ -3,6 +3,7 @@
<words>
<w>decapitalize</w>
<w>delegator</w>
<w>elipsis</w>
<w>funs</w>
<w>immediates</w>
<w>initializers</w>
@@ -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<KtExpression>()
@@ -514,6 +514,10 @@ fun main(args: Array<String>) {
model("joinLines")
}
testClass<AbstractBreadcrumbsTest>() {
model("codeInsight/breadcrumbs")
}
testClass<AbstractIntentionTest>() {
model("intentions", pattern = "^([\\w\\-_]+)\\.kt$")
}
+2
View File
@@ -525,6 +525,8 @@
<copyPastePostProcessor implementation="org.jetbrains.kotlin.idea.conversion.copy.ConvertTextJavaCopyPasteProcessor"/>
<copyPastePostProcessor implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinCopyPasteReferenceProcessor"/>
<breadcrumbsInfoProvider implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinBreadcrumbsInfoProvider"/>
<lang.documentationProvider language="JAVA" implementationClass="org.jetbrains.kotlin.idea.KotlinQuickDocumentationProvider" order="first"/>
<documentationProvider implementation="org.jetbrains.kotlin.idea.KotlinQuickDocumentationProvider"/>
<configurationType implementation="org.jetbrains.kotlin.idea.run.JetRunConfigurationType"/>
@@ -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<TElement : KtElement>(val type: KClass<TElement>) {
abstract fun elementInfo(element: TElement): String
abstract fun elementTooltip(element: TElement): String
open fun accepts(element: TElement): Boolean = true
}
private val handlers = listOf<ElementHandler<*>>(
LambdaHandler,
AnonymousObjectHandler,
AnonymousFunctionHandler,
PropertyAccessorHandler,
DeclarationHandler,
IfThenHandler,
ElseHandler,
TryHandler,
CatchHandler,
FinallyHandler,
WhileHandler,
DoWhileHandler,
WhenHandler,
WhenEntryHandler,
ForHandler
)
private object LambdaHandler : ElementHandler<KtFunctionLiteral>(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>(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>(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>(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>(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<TElement : KtElement>(
private val constructName: String,
type: KClass<TElement>
) : ElementHandler<TElement>(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<KtContainerNode>("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>(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>(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>(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>(KtFinallySection::class) {
override fun elementInfo(element: KtFinallySection) = "finally"
override fun elementTooltip(element: KtFinallySection) = "finally"
}
private object WhileHandler : ConstructWithExpressionHandler<KtContainerNode>("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<KtContainerNode>("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<KtWhenExpression>("when", KtWhenExpression::class) {
override fun extractExpression(element: KtWhenExpression) = element.subjectExpression
override fun labelOwner(element: KtWhenExpression) = null
}
private object WhenEntryHandler : ElementHandler<KtExpression>(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>(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<in KtElement>? {
if (e !is KtElement) return null
val handler = handlers.firstOrNull { it.type.java.isInstance(e) && (it as ElementHandler<in KtElement>).accepts(e) }
return handler as ElementHandler<in KtElement>?
}
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
}
}
}
@@ -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"
@@ -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 {
<caret>
}
}
}
}
}
})
@@ -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 <b><code>v</code></b>
object : java.lang.Runnable
property <b><code>v.&lt;no name provided&gt;.v2</code></b>
object : A, B
function <b><code>v.&lt;no name provided&gt;.v2.&lt;no name provided&gt;.f()</code></b>
object : java.io.Serializable, XXX
property <b><code>v.&lt;no name provided&gt;.v2.&lt;no name provided&gt;.f.&lt;no name provided&gt;.o</code></b>
object
function <b><code>v.&lt;no name provided&gt;.v2.&lt;no name provided&gt;.f.&lt;no name provided&gt;.o.&lt;no name provided&gt;.equals(Any?)</code></b>
+17
View File
@@ -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) {
<caret>
})
}
}
}
}
}
}
}
+18
View File
@@ -0,0 +1,18 @@
Crumbs:
Outer
companion object
SomeClass
companion object CompanionName
SomeObject
someFun()
localFun()
fun(x, y)
Tooltips:
class <b><code>Outer</code></b>
companion object <b><code>Outer.Companion</code></b>
class <b><code>Outer.Companion.SomeClass</code></b>
companion object <b><code>Outer.Companion.SomeClass.CompanionName</code></b>
object <b><code>Outer.Companion.SomeClass.CompanionName.SomeObject</code></b>
function <b><code>Outer.Companion.SomeClass.CompanionName.SomeObject.someFun(Int, Boolean) on String</code></b>
function <b><code>Outer.Companion.SomeClass.CompanionName.SomeObject.someFun.localFun()</code></b>
fun(x: Int, y: Char)
+9
View File
@@ -0,0 +1,9 @@
fun foo() {
for (i in 1..10) {
for (j: Int in collection.filter(predicate)) {
for ((x, y) in entries) {
<caret>
}
}
}
}
+10
View File
@@ -0,0 +1,10 @@
Crumbs:
foo()
for(i in 1..10)
for(j in collection…)
for((x, y) in entri…)
Tooltips:
function <b><code>foo()</code></b>
for(i in 1..10)
for(j in collection.filter(predicate))
for((x, y) in entries)
+34
View File
@@ -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) <caret>return
}
}
else {
}
}
}
}
}
+16
View File
@@ -0,0 +1,16 @@
Crumbs:
foo()
if (x)
if (x.y)
if (y) … else
if … else if (c)
if … else
if (p)
Tooltips:
function <b><code>foo()</code></b>
if (x)
if (x.y)
else (of 'if (y)')
if … else if (c)
else (of 'if … else if (qqq)')
if (p)
@@ -0,0 +1,14 @@
fun foo() {
OuterLoop@
for (i in 1..10) {
for (j in 1..10) {
Label1@ Label2@
while(true) {
DoWhile@
do {
<caret>
} while (x)
}
}
}
}
@@ -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 <b><code>foo()</code></b>
OuterLoop@ for(i in 1..10)
for(j in 1..10)
Label1@ Label2@ while (true)
DoWhile@ do … while (x)
+17
View File
@@ -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 { <caret> } })
}
}
}
}
}
}
}
}
+22
View File
@@ -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 <b><code>foo()</code></b>
lambda <b><code>foo.&lt;anonymous&gt;() on StringBuilder</code></b>
lambda <b><code>foo.&lt;anonymous&gt;.&lt;anonymous&gt;() on ???</code></b>
lambda <b><code>foo.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;()</code></b>
lambda <b><code>foo.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;()</code></b>
lambda <b><code>foo.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;(???)</code></b>
lambda <b><code>foo.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;()</code></b>
lambda <b><code>foo.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;(Int)</code></b>
lambda <b><code>foo.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;()</code></b>
lambda <b><code>foo.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;.&lt;anonymous&gt;()</code></b>
@@ -0,0 +1,5 @@
fun foo() {
if (<caret>x) {
}
}
@@ -0,0 +1,4 @@
Crumbs:
foo()
Tooltips:
function <b><code>foo()</code></b>
@@ -0,0 +1,7 @@
fun foo() {
when (x) {
is <caret>Foo -> {
}
}
}
@@ -0,0 +1,6 @@
Crumbs:
foo()
when (x)
Tooltips:
function <b><code>foo()</code></b>
when (x)
@@ -0,0 +1,5 @@
var Int.prop: String
get() = TODO()
set(value) {
<caret>
}
@@ -0,0 +1,4 @@
Crumbs:
prop.set
Tooltips:
setter for property <b><code>prop</code></b>
+18
View File
@@ -0,0 +1,18 @@
fun foo() {
try {
try {
}
finally {
try {
}
catch (e: RuntimeException) {
<caret>
}
}
}
catch(e: Exception) {
}
}
+10
View File
@@ -0,0 +1,10 @@
Crumbs:
foo()
try
finally
catch (RuntimeException)
Tooltips:
function <b><code>foo()</code></b>
try
finally
catch (RuntimeException)
+39
View File
@@ -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 -> {
<caret>
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
}
+40
View File
@@ -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 <b><code>foo()</code></b>
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,… ->
+11
View File
@@ -0,0 +1,11 @@
fun foo() {
while (true) {
while (p) {
while (x > 0) {
do {
<caret>
} while (true)
}
}
}
}
+12
View File
@@ -0,0 +1,12 @@
Crumbs:
foo()
while (true)
while (p)
while (x > 0)
do … while (true)
Tooltips:
function <b><code>foo()</code></b>
while (true)
while (p)
while (x > 0)
do … while (true)
@@ -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)
}
}
@@ -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);
}
}