KT-12852 Support breadcrumbs for Kotlin
#KT-12852 Fixed
This commit is contained in:
Generated
+1
@@ -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$")
|
||||
}
|
||||
|
||||
@@ -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.<no name provided>.v2</code></b>
|
||||
object : A, B
|
||||
function <b><code>v.<no name provided>.v2.<no name provided>.f()</code></b>
|
||||
object : java.io.Serializable, XXX
|
||||
property <b><code>v.<no name provided>.v2.<no name provided>.f.<no name provided>.o</code></b>
|
||||
object
|
||||
function <b><code>v.<no name provided>.v2.<no name provided>.f.<no name provided>.o.<no name provided>.equals(Any?)</code></b>
|
||||
@@ -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>
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
@@ -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
@@ -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
@@ -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
@@ -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)
|
||||
@@ -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> } })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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.<anonymous>() on StringBuilder</code></b>
|
||||
lambda <b><code>foo.<anonymous>.<anonymous>() on ???</code></b>
|
||||
lambda <b><code>foo.<anonymous>.<anonymous>.<anonymous>()</code></b>
|
||||
lambda <b><code>foo.<anonymous>.<anonymous>.<anonymous>.<anonymous>()</code></b>
|
||||
lambda <b><code>foo.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>(???)</code></b>
|
||||
lambda <b><code>foo.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>()</code></b>
|
||||
lambda <b><code>foo.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>(Int)</code></b>
|
||||
lambda <b><code>foo.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>()</code></b>
|
||||
lambda <b><code>foo.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>.<anonymous>()</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
@@ -0,0 +1,18 @@
|
||||
fun foo() {
|
||||
try {
|
||||
try {
|
||||
|
||||
}
|
||||
finally {
|
||||
try {
|
||||
|
||||
}
|
||||
catch (e: RuntimeException) {
|
||||
<caret>
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(e: Exception) {
|
||||
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
Crumbs:
|
||||
foo()
|
||||
try
|
||||
finally
|
||||
catch (RuntimeException)
|
||||
Tooltips:
|
||||
function <b><code>foo()</code></b>
|
||||
try
|
||||
finally
|
||||
catch (RuntimeException)
|
||||
+39
@@ -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
@@ -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
@@ -0,0 +1,11 @@
|
||||
fun foo() {
|
||||
while (true) {
|
||||
while (p) {
|
||||
while (x > 0) {
|
||||
do {
|
||||
<caret>
|
||||
} while (true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
+12
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user