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:
@@ -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"/>
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
+36
@@ -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 {
|
||||
|
||||
+1
-1
@@ -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)
|
||||
" <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> <a href="#kotlinClass/testModule_Common:ExpectedChild">ExpectedChild [testModule_Common]</a><br> <a href="#kotlinClass/testModule_JVM:ExpectedChild">ExpectedChild [testModule_JVM]</a><br> <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()
|
||||
Generated
+5
@@ -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/");
|
||||
|
||||
Reference in New Issue
Block a user