Implement Kotlin sub-classes tooltip to handle MPP correctly

Before this commit, Java tooltip was in use which could not
work correctly with expect / actual classes
So #KT-21011 Fixed
This commit is contained in:
Mikhail Glukhikh
2018-06-09 13:29:13 +03:00
parent 90b6f369e0
commit cbcd35e2f5
13 changed files with 114 additions and 6 deletions
+1
View File
@@ -477,6 +477,7 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
<codeInsight.template.postfixTemplateProvider language="kotlin"
implementationClass="org.jetbrains.kotlin.idea.codeInsight.postfix.KtPostfixTemplateProvider"/>
<codeInsight.linkHandler prefix="#kotlinClass/" handlerClass="org.jetbrains.kotlin.idea.highlighter.markers.KotlinClassTooltipLinkHandler"/>
<typeDeclarationProvider implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinTypeDeclarationProvider"/>
+2
View File
@@ -478,6 +478,8 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
implementationClass="org.jetbrains.kotlin.idea.codeInsight.postfix.KtPostfixTemplateProvider"/>
<codeInsight.linkHandler prefix="#kotlinClass/" handlerClass="org.jetbrains.kotlin.idea.highlighter.markers.KotlinClassTooltipLinkHandler"/>
<typeDeclarationProvider implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinTypeDeclarationProvider"/>
<completion.contributor language="kotlin"
+2
View File
@@ -478,6 +478,8 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
implementationClass="org.jetbrains.kotlin.idea.codeInsight.postfix.KtPostfixTemplateProvider"/>
<codeInsight.linkHandler prefix="#kotlinClass/" handlerClass="org.jetbrains.kotlin.idea.highlighter.markers.KotlinClassTooltipLinkHandler"/>
<typeDeclarationProvider implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinTypeDeclarationProvider"/>
<completion.contributor language="kotlin"
+2
View File
@@ -479,6 +479,8 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
implementationClass="org.jetbrains.kotlin.idea.codeInsight.postfix.KtPostfixTemplateProvider"/>
<codeInsight.linkHandler prefix="#kotlinClass/" handlerClass="org.jetbrains.kotlin.idea.highlighter.markers.KotlinClassTooltipLinkHandler"/>
<typeDeclarationProvider implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinTypeDeclarationProvider"/>
<completion.contributor language="kotlin"
+2
View File
@@ -478,6 +478,8 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
implementationClass="org.jetbrains.kotlin.idea.codeInsight.postfix.KtPostfixTemplateProvider"/>
<codeInsight.linkHandler prefix="#kotlinClass/" handlerClass="org.jetbrains.kotlin.idea.highlighter.markers.KotlinClassTooltipLinkHandler"/>
<typeDeclarationProvider implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinTypeDeclarationProvider"/>
<completion.contributor language="kotlin"
+2
View File
@@ -478,6 +478,8 @@ The Kotlin plugin provides language support in IntelliJ IDEA and Android Studio.
implementationClass="org.jetbrains.kotlin.idea.codeInsight.postfix.KtPostfixTemplateProvider"/>
<codeInsight.linkHandler prefix="#kotlinClass/" handlerClass="org.jetbrains.kotlin.idea.highlighter.markers.KotlinClassTooltipLinkHandler"/>
<typeDeclarationProvider implementation="org.jetbrains.kotlin.idea.codeInsight.KotlinTypeDeclarationProvider"/>
<completion.contributor language="kotlin"
@@ -0,0 +1,36 @@
/*
* Copyright 2010-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license
* that can be found in the license/LICENSE.txt file.
*/
package org.jetbrains.kotlin.idea.highlighter.markers
import com.intellij.codeInsight.highlighting.TooltipLinkHandler
import com.intellij.codeInsight.navigation.NavigationUtil
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.module.ModuleManager
import com.intellij.psi.JavaPsiFacade
import com.intellij.psi.search.GlobalSearchScope
import org.jetbrains.kotlin.idea.stubindex.KotlinFullClassNameIndex
class KotlinClassTooltipLinkHandler : TooltipLinkHandler() {
override fun handleLink(refSuffix: String, editor: Editor): Boolean {
val project = editor.project ?: return false
val javaPsiFacade = JavaPsiFacade.getInstance(project)
val qualifiedName = refSuffix.substringAfterLast(":")
val scope = if (qualifiedName.length == refSuffix.length) {
GlobalSearchScope.allScope(project)
} else {
val moduleName = refSuffix.substringBeforeLast(":")
val module = ModuleManager.getInstance(project).findModuleByName(moduleName)
module?.let { GlobalSearchScope.moduleScope(it) } ?: GlobalSearchScope.allScope(project)
}
// Non-JVM classes cannot be found with Java PSI Facade
val aClassElement =
KotlinFullClassNameIndex.getInstance().get(qualifiedName, project, scope).firstOrNull()
?: javaPsiFacade.findClass(qualifiedName, scope)
?: return false
NavigationUtil.activateFileWithPsiElement(aClassElement)
return true
}
}
@@ -144,7 +144,7 @@ interface TestableLineMarkerNavigator {
private val SUBCLASSED_CLASS = MarkerType(
"SUBCLASSED_CLASS",
{ getPsiClass(it)?.let { MarkerType.getSubclassedClassTooltip(it) } },
{ getPsiClass(it)?.let(::getSubclassedClassTooltip) },
object : LineMarkerNavigator() {
override fun browse(e: MouseEvent?, element: PsiElement?) {
getPsiClass(element)?.let {
@@ -144,7 +144,7 @@ interface TestableLineMarkerNavigator {
private val SUBCLASSED_CLASS = MarkerType(
"SUBCLASSED_CLASS",
{ getPsiClass(it)?.let { MarkerType.getSubclassedClassTooltip(it) } },
{ getPsiClass(it)?.let(::getSubclassedClassTooltip) },
object : LineMarkerNavigator() {
override fun browse(e: MouseEvent?, element: PsiElement?) {
getPsiClass(element)?.let { MarkerType.navigateToSubclassedClass(e, it, DeclarationByModuleRenderer()) }
@@ -21,23 +21,31 @@ import com.intellij.codeInsight.daemon.impl.GutterIconTooltipHelper
import com.intellij.codeInsight.navigation.ListBackgroundUpdaterTask
import com.intellij.ide.util.MethodCellRenderer
import com.intellij.ide.util.PsiElementListCellRenderer
import com.intellij.openapi.actionSystem.ActionManager
import com.intellij.openapi.actionSystem.IdeActions
import com.intellij.openapi.keymap.KeymapUtil
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.progress.ProgressManager
import com.intellij.openapi.project.DumbService
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiModifier
import com.intellij.psi.*
import com.intellij.psi.presentation.java.ClassPresentationUtil
import com.intellij.psi.search.PsiElementProcessor
import com.intellij.psi.search.PsiElementProcessorAdapter
import com.intellij.psi.search.searches.ClassInheritorsSearch
import com.intellij.psi.search.searches.FunctionalExpressionSearch
import com.intellij.util.CommonProcessors
import gnu.trove.THashSet
import org.jetbrains.kotlin.asJava.classes.KtLightClass
import org.jetbrains.kotlin.asJava.elements.KtLightMethod
import org.jetbrains.kotlin.asJava.elements.isTraitFakeOverride
import org.jetbrains.kotlin.idea.presentation.DeclarationByModuleRenderer
import org.jetbrains.kotlin.idea.search.declarationsSearch.forEachDeclaredMemberOverride
import org.jetbrains.kotlin.idea.search.declarationsSearch.forEachOverridingMethod
import org.jetbrains.kotlin.idea.search.declarationsSearch.toPossiblyFakeLightMethods
import org.jetbrains.kotlin.idea.util.application.runReadAction
import org.jetbrains.kotlin.idea.util.isExpectDeclaration
import org.jetbrains.kotlin.idea.util.projectStructure.module
import org.jetbrains.kotlin.psi.psiUtil.hasActualModifier
import java.awt.event.MouseEvent
import java.util.*
import javax.swing.JComponent
@@ -66,6 +74,45 @@ internal fun <T> getOverriddenDeclarations(mappingToJava: MutableMap<PsiElement,
return overridden
}
// Module-specific version of MarkerType.getSubclassedClassTooltip
fun getSubclassedClassTooltip(klass: PsiClass): String? {
val processor = PsiElementProcessor.CollectElementsWithLimit(5, THashSet<PsiClass>())
ClassInheritorsSearch.search(klass).forEach(PsiElementProcessorAdapter(processor))
if (processor.isOverflow) {
return DaemonBundle.message(if (klass.isInterface) "interface.is.implemented.too.many" else "class.is.subclassed.too.many")
}
val subclasses = processor.toArray(PsiClass.EMPTY_ARRAY)
if (subclasses.isEmpty()) {
val functionalImplementations = PsiElementProcessor.CollectElementsWithLimit(2, THashSet<PsiFunctionalExpression>())
FunctionalExpressionSearch.search(klass).forEach(PsiElementProcessorAdapter(functionalImplementations))
return if (functionalImplementations.collection.isNotEmpty()) "Has functional implementations" else null
}
val start = DaemonBundle.message(if (klass.isInterface) "interface.is.implemented.by.header" else "class.is.subclassed.by.header")
val shortcuts = ActionManager.getInstance().getAction(IdeActions.ACTION_GOTO_IMPLEMENTATION).shortcutSet.shortcuts
val shortcut = shortcuts.firstOrNull()
var postfix = "<br><div style='margin-top: 5px'><font size='2'>Click"
if (shortcut != null) postfix += " or press " + KeymapUtil.getShortcutText(shortcut)
postfix += " to navigate</font></div>"
val renderer = DeclarationByModuleRenderer()
val comparator = renderer.comparator
return subclasses.toList().sortedWith(comparator).joinToString(
prefix = "<html><body>$start", postfix = "$postfix</body</html>", separator = "<br>"
) {
val moduleNameRequired = if (it is KtLightClass) {
val origin = it.kotlinOrigin
origin?.hasActualModifier() == true || origin?.isExpectDeclaration() == true
} else false
val moduleName = it.module?.name
val elementText = renderer.getElementText(it) + (moduleName?.takeIf { moduleNameRequired }?.let { " [$it]" } ?: "")
val refText = (moduleName?.let { "$it:" } ?: "") + ClassPresentationUtil.getNameForClass(it, /* qualified = */ true)
"&nbsp;&nbsp;&nbsp;&nbsp;<a href=\"#kotlinClass/$refText\">$elementText</a>"
}
}
fun getOverriddenMethodTooltip(method: PsiMethod): String? {
val processor = PsiElementProcessor.CollectElementsWithLimit<PsiMethod>(5)
method.forEachOverridingMethod(processor = PsiElementProcessorAdapter(processor)::process)
@@ -0,0 +1,6 @@
open class <lineMarker descr="<html><body>Is subclassed by<br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#kotlinClass/testModule_Common:ExpectedChild">ExpectedChild [testModule_Common]</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#kotlinClass/testModule_JVM:ExpectedChild">ExpectedChild [testModule_JVM]</a><br>&nbsp;&nbsp;&nbsp;&nbsp;<a href="#kotlinClass/testModule_Common:SimpleChild">SimpleChild</a><br><div style='margin-top: 5px'><font size='2'>Click or press Ctrl+Alt+B to navigate</font></div></body</html>">SimpleParent</lineMarker>
expect class <lineMarker descr="Has actuals in JVM">ExpectedChild</lineMarker> : SimpleParent
class SimpleChild : SimpleParent()
@@ -0,0 +1,3 @@
// !CHECK_HIGHLIGHTING
actual class ExpectedChild : SimpleParent()
@@ -25,6 +25,11 @@ public class MultiModuleLineMarkerTestGenerated extends AbstractMultiModuleLineM
KotlinTestUtils.runTest(this::doTest, TargetBackend.ANY, testDataFilePath);
}
@TestMetadata("actualDerived")
public void testActualDerived() throws Exception {
runTest("idea/testData/multiModuleLineMarker/actualDerived/");
}
@TestMetadata("actualEnumEntries")
public void testActualEnumEntries() throws Exception {
runTest("idea/testData/multiModuleLineMarker/actualEnumEntries/");