Presentation: Do not use resolve to compute element description

Invoking analysis on EDT may degrade editor performance
This commit is contained in:
Alexey Sedunov
2017-04-11 19:07:40 +03:00
parent 8df502de84
commit aa0f0ef02d
4 changed files with 78 additions and 23 deletions
@@ -29,23 +29,72 @@ import com.intellij.usageView.UsageViewShortNameLocation
import com.intellij.usageView.UsageViewTypeLocation
import org.jetbrains.kotlin.asJava.classes.KtLightClassForFacade
import org.jetbrains.kotlin.asJava.unwrapped
import org.jetbrains.kotlin.descriptors.FunctionDescriptor
import org.jetbrains.kotlin.idea.KotlinLanguage
import org.jetbrains.kotlin.idea.refactoring.rename.RenameJavaSyntheticPropertyHandler
import org.jetbrains.kotlin.idea.refactoring.rename.RenameKotlinPropertyProcessor
import org.jetbrains.kotlin.idea.search.usagesSearch.descriptor
import org.jetbrains.kotlin.idea.util.string.collapseSpaces
import org.jetbrains.kotlin.name.FqNameUnsafe
import org.jetbrains.kotlin.psi.*
import org.jetbrains.kotlin.renderer.DescriptorRenderer
import org.jetbrains.kotlin.resolve.DescriptorUtils
import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
import org.jetbrains.kotlin.psi.psiUtil.getStrictParentOfType
class KotlinElementDescriptionProvider : ElementDescriptionProvider {
companion object {
val REFACTORING_RENDERER = DescriptorRenderer.ONLY_NAMES_WITH_SHORT_TYPES.withOptions {
withoutReturnType = true
renderConstructorKeyword = false
}
private tailrec fun KtNamedDeclaration.parentForFqName(): KtNamedDeclaration? {
val parent = getStrictParentOfType<KtNamedDeclaration>() ?: return null
if (parent is KtProperty && parent.isLocal) return parent.parentForFqName()
return parent
}
private fun KtNamedDeclaration.fqName(): FqNameUnsafe {
val internalSegments = generateSequence(this) { it.parentForFqName() }
.filterIsInstance<KtNamedDeclaration>()
.map { it.name ?: "<no name provided>" }
.toList()
.asReversed()
val packageSegments = containingKtFile.packageFqName.pathSegments()
return FqNameUnsafe((packageSegments + internalSegments).joinToString("."))
}
private fun KtTypeReference.renderShort(): String {
return accept(
object : KtVisitor<String, Unit>() {
private val visitor get() = this
override fun visitTypeReference(typeReference: KtTypeReference, data: Unit): String {
val typeText = typeReference.typeElement?.accept(this, data) ?: "???"
return if (typeReference.hasParentheses()) "($typeText)" else typeText
}
override fun visitDynamicType(type: KtDynamicType, data: Unit) = type.text
override fun visitFunctionType(type: KtFunctionType, data: Unit): String {
return buildString {
type.receiverTypeReference?.let { append(it.accept(visitor, data)).append('.') }
type.parameters.joinTo(this, prefix = "(", postfix = ")") { it.accept(visitor, data) }
append(" -> ")
append(type.returnTypeReference?.accept(visitor, data) ?: "???")
}
}
override fun visitNullableType(nullableType: KtNullableType, data: Unit): String {
val innerTypeText = nullableType.innerType?.accept(this, data) ?: return "???"
return "$innerTypeText?"
}
override fun visitSelfType(type: KtSelfType, data: Unit) = type.text
override fun visitUserType(type: KtUserType, data: Unit): String {
return buildString {
append(type.referencedName ?: "???")
val arguments = type.typeArgumentsAsTypes
if (arguments.isNotEmpty()) {
arguments.joinTo(this, prefix = "<", postfix = ">") { it.accept(visitor, data) }
}
}
}
},
Unit
)
}
override fun getElementDescription(element: PsiElement, location: ElementDescriptionLocation): String? {
@@ -85,26 +134,32 @@ class KotlinElementDescriptionProvider : ElementDescriptionProvider {
is UsageViewShortNameLocation, is UsageViewLongNameLocation -> namedElement.name
is RefactoringDescriptionLocation -> {
val kind = elementKind() ?: return null
val descriptor = (namedElement as KtDeclaration).descriptor ?: return null
if (namedElement !is KtNamedDeclaration) return null
val renderFqName = location.includeParent() &&
namedElement !is KtTypeParameter &&
namedElement !is KtParameter &&
namedElement !is KtConstructor<*>
val desc = when (descriptor) {
is FunctionDescriptor -> {
val baseText = REFACTORING_RENDERER.render(descriptor)
val parentFqName = if (renderFqName) descriptor.containingDeclaration.fqNameSafe else null
val desc = when (namedElement) {
is KtFunction -> {
val baseText = buildString {
append(namedElement.name ?: "")
namedElement.valueParameters.joinTo(this, prefix = "(", postfix = ")") {
(if (it.isVarArg) "vararg " else "") + (it.typeReference?.renderShort() ?: "")
}
namedElement.receiverTypeReference?.let { append(" on ").append(it.renderShort()) }
}
val parentFqName = if (renderFqName) namedElement.fqName().parent() else null
if (parentFqName?.isRoot ?: true) baseText else "${parentFqName!!.asString()}.$baseText"
}
else -> if (renderFqName) DescriptorUtils.getFqName(descriptor).asString() else descriptor.name.asString()
else -> (if (renderFqName) namedElement.fqName().asString() else namedElement.name) ?: ""
}
"$kind ${CommonRefactoringUtil.htmlEmphasize(desc)}"
}
is HighlightUsagesDescriptionLocation -> {
val kind = elementKind() ?: return null
val descriptor = (namedElement as KtDeclaration).descriptor ?: return null
"$kind ${descriptor.name.asString()}"
if (namedElement !is KtNamedDeclaration) return null
"$kind ${namedElement.name}"
}
else -> null
}
+3 -3
View File
@@ -11,11 +11,11 @@ Crumbs:
Something().apply{…}
Tooltips:
function <b><code>foo()</code></b>
lambda <b><code>foo.&lt;anonymous&gt;() on StringBuilder /* = StringBuilder */</code></b>
lambda <b><code>foo.&lt;anonymous&gt;.&lt;anonymous&gt;() on ???</code></b>
lambda <b><code>foo.&lt;anonymous&gt;()</code></b>
lambda <b><code>foo.&lt;anonymous&gt;.&lt;anonymous&gt;()</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;()</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>
@@ -1,5 +1,5 @@
// WITH_RUNTIME
// SHOULD_FAIL_WITH: 'foo' in class B will require class instance, 'foo' in function test() will require class instance, 'foo' in function test() will require class instance, 'foo' in lambda &lt;anonymous&gt;() on A.Companion will require class instance
// SHOULD_FAIL_WITH: 'foo' in class B will require class instance, 'foo' in function test() will require class instance, 'foo' in function test() will require class instance, 'foo' in lambda &lt;anonymous&gt;() will require class instance
class A {
companion object {
class B {
@@ -1,5 +1,5 @@
// WITH_RUNTIME
// SHOULD_FAIL_WITH: 'foo' in class B will require class instance, 'foo' in function test() will require class instance, 'foo' in function test() will require class instance, 'foo' in lambda &lt;anonymous&gt;() on A.Companion will require class instance
// SHOULD_FAIL_WITH: 'foo' in class B will require class instance, 'foo' in function test() will require class instance, 'foo' in function test() will require class instance, 'foo' in lambda &lt;anonymous&gt;() will require class instance
class A {
companion object {
class B {