diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasActualMarker.kt b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasActualMarker.kt index f0de41025bf..c6000042107 100644 --- a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasActualMarker.kt +++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasActualMarker.kt @@ -16,7 +16,6 @@ package org.jetbrains.kotlin.idea.highlighter.markers -import com.intellij.codeInsight.daemon.impl.PsiElementListNavigator import com.intellij.ide.util.DefaultPsiElementCellRenderer import com.intellij.psi.PsiElement import org.jetbrains.kotlin.analyzer.ModuleInfo @@ -64,19 +63,26 @@ fun getPlatformActualTooltip(declaration: KtDeclaration): String? { } } -fun navigateToPlatformActual(e: MouseEvent?, declaration: KtDeclaration) { - val actualDeclarations = - declaration.actualsForExpected() + declaration.findMarkerBoundDeclarations().flatMap { it.actualsForExpected() } - if (actualDeclarations.isEmpty()) return +fun KtDeclaration.allNavigatableActualDeclarations(): Set = + actualsForExpected() + findMarkerBoundDeclarations().flatMap { it.actualsForExpected() } - val renderer = object : DefaultPsiElementCellRenderer() { - override fun getContainerText(element: PsiElement?, name: String?) = "" +class ActualExpectedPsiElementCellRenderer : DefaultPsiElementCellRenderer() { + override fun getContainerText(element: PsiElement?, name: String?) = "" +} + +fun KtDeclaration.navigateToActualTitle() = "Choose actual for $name" + +fun KtDeclaration.navigateToActualUsagesTitle() = "Actuals for $name" + +fun buildNavigateToActualDeclarationsPopup(e: MouseEvent?, element: PsiElement?): NavigationPopupDescriptor? { + return element?.markerDeclaration?.let { + val navigatableActualDeclarations = it.allNavigatableActualDeclarations() + if (navigatableActualDeclarations.isEmpty()) return null + return NavigationPopupDescriptor( + navigatableActualDeclarations, + it.navigateToActualTitle(), + it.navigateToActualUsagesTitle(), + ActualExpectedPsiElementCellRenderer() + ) } - PsiElementListNavigator.openTargets( - e, - actualDeclarations.toTypedArray(), - "Choose actual for ${declaration.name}", - "Actuals for ${declaration.name}", - renderer - ) } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasExpectedMarker.kt b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasExpectedMarker.kt index 7351d752f76..ff9d7dac6f9 100644 --- a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasExpectedMarker.kt +++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/HasExpectedMarker.kt @@ -16,8 +16,6 @@ package org.jetbrains.kotlin.idea.highlighter.markers -import com.intellij.codeInsight.daemon.impl.PsiElementListNavigator -import com.intellij.ide.util.DefaultPsiElementCellRenderer import com.intellij.psi.PsiElement import org.jetbrains.kotlin.descriptors.MemberDescriptor import org.jetbrains.kotlin.idea.caches.project.implementedDescriptors @@ -38,20 +36,22 @@ fun getExpectedDeclarationTooltip(declaration: KtDeclaration): String? { return "Has declaration in common module" } -fun navigateToExpectedDeclaration(e: MouseEvent?, declaration: KtDeclaration) { - val expectedDeclarations = - listOfNotNull(declaration.expectedDeclarationIfAny()) + - declaration.findMarkerBoundDeclarations().mapNotNull { it.expectedDeclarationIfAny() } - if (expectedDeclarations.isEmpty()) return +fun KtDeclaration.allNavigatableExpectedDeclarations(): List = + listOfNotNull(expectedDeclarationIfAny()) + findMarkerBoundDeclarations().mapNotNull { it.expectedDeclarationIfAny() } - val renderer = object : DefaultPsiElementCellRenderer() { - override fun getContainerText(element: PsiElement?, name: String?) = "" +fun KtDeclaration.navigateToExpectedTitle() = "Choose expected for $name" + +fun KtDeclaration.navigateToExpectedUsagesTitle() = "Expected for $name" + +fun buildNavigateToExpectedDeclarationsPopup(e: MouseEvent?, element: PsiElement?): NavigationPopupDescriptor? { + return element?.markerDeclaration?.let { + val navigatableExpectedDeclarations = it.allNavigatableExpectedDeclarations() + if (navigatableExpectedDeclarations.isEmpty()) return null + return NavigationPopupDescriptor( + navigatableExpectedDeclarations, + it.navigateToExpectedTitle(), + it.navigateToExpectedUsagesTitle(), + ActualExpectedPsiElementCellRenderer() + ) } - PsiElementListNavigator.openTargets( - e, - expectedDeclarations.toTypedArray(), - "Choose expected for ${declaration.name}", - "Expected for ${declaration.name}", - renderer - ) } \ No newline at end of file diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt index cfa1e5e385c..5a607520807 100644 --- a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt +++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt @@ -198,28 +198,52 @@ private val OVERRIDDEN_PROPERTY = object : MarkerType( } } -private val PsiElement.markerDeclaration +val PsiElement.markerDeclaration get() = (this as? KtDeclaration) ?: (parent as? KtDeclaration) -private val PLATFORM_ACTUAL = MarkerType( +private val PLATFORM_ACTUAL = object : MarkerType( "PLATFORM_ACTUAL", { element -> element?.markerDeclaration?.let { getPlatformActualTooltip(it) } }, object : LineMarkerNavigator() { override fun browse(e: MouseEvent?, element: PsiElement?) { - element?.markerDeclaration?.let { navigateToPlatformActual(e, it) } + buildNavigateToActualDeclarationsPopup(e, element)?.showPopup(e) + } + }) { + override fun getNavigationHandler(): GutterIconNavigationHandler { + val superHandler = super.getNavigationHandler() + return object : GutterIconNavigationHandler, TestableLineMarkerNavigator { + override fun navigate(e: MouseEvent?, elt: PsiElement?) { + superHandler.navigate(e, elt) + } + + override fun getTargetsPopupDescriptor(element: PsiElement?): NavigationPopupDescriptor? { + return buildNavigateToActualDeclarationsPopup(null, element) + } } } -) +} -private val EXPECTED_DECLARATION = MarkerType( +private val EXPECTED_DECLARATION = object : MarkerType( "EXPECTED_DECLARATION", { element -> element?.markerDeclaration?.let { getExpectedDeclarationTooltip(it) } }, object : LineMarkerNavigator() { override fun browse(e: MouseEvent?, element: PsiElement?) { - element?.markerDeclaration?.let { navigateToExpectedDeclaration(e, it) } + buildNavigateToExpectedDeclarationsPopup(e, element)?.showPopup(e) + } + }) { + override fun getNavigationHandler(): GutterIconNavigationHandler { + val superHandler = super.getNavigationHandler() + return object : GutterIconNavigationHandler, TestableLineMarkerNavigator { + override fun navigate(e: MouseEvent?, elt: PsiElement?) { + superHandler.navigate(e, elt) + } + + override fun getTargetsPopupDescriptor(element: PsiElement?): NavigationPopupDescriptor? { + return buildNavigateToExpectedDeclarationsPopup(null, element) + } } } -) +} private fun isImplementsAndNotOverrides( descriptor: CallableMemberDescriptor, diff --git a/idea/testData/multiModuleLineMarker/actualConstructorWithProperties/jvm/jvm.kt b/idea/testData/multiModuleLineMarker/actualConstructorWithProperties/jvm/jvm.kt index b7e353ffa64..463d99ff1d1 100644 --- a/idea/testData/multiModuleLineMarker/actualConstructorWithProperties/jvm/jvm.kt +++ b/idea/testData/multiModuleLineMarker/actualConstructorWithProperties/jvm/jvm.kt @@ -1 +1,11 @@ -actual class WithConstructor actual constructor(actual val x: Int, actual val s: String) +actual class WithConstructor actual constructor(actual val x: Int, actual val s: String) + +/* +LINEMARKER: Has declaration in common module +TARGETS: +common.kt +expect class <1>WithConstructor(x: Int, s: String) { + val <3>x: Int + + val <2>s: String +*/ diff --git a/idea/testData/multiModuleLineMarker/actualEnumEntriesInOneLine/jvm/jvm.kt b/idea/testData/multiModuleLineMarker/actualEnumEntriesInOneLine/jvm/jvm.kt index ca49aaa4b60..0c6e2c14140 100644 --- a/idea/testData/multiModuleLineMarker/actualEnumEntriesInOneLine/jvm/jvm.kt +++ b/idea/testData/multiModuleLineMarker/actualEnumEntriesInOneLine/jvm/jvm.kt @@ -1,3 +1,10 @@ package test -actual enum class Enum { A, B, C } \ No newline at end of file +actual enum class Enum { A, B, C } + +/* +LINEMARKER: Has declaration in common module +TARGETS: +common.kt +expect enum class <4>Enum { <1>A, <2>B, <3>C } +*/ diff --git a/idea/testData/multiModuleLineMarker/expectEnumEntriesInOneLine/common/common.kt b/idea/testData/multiModuleLineMarker/expectEnumEntriesInOneLine/common/common.kt index 1cd143e045b..065dd8f0523 100644 --- a/idea/testData/multiModuleLineMarker/expectEnumEntriesInOneLine/common/common.kt +++ b/idea/testData/multiModuleLineMarker/expectEnumEntriesInOneLine/common/common.kt @@ -1,3 +1,10 @@ package test -expect enum class Enum { A, B, C, D } \ No newline at end of file +expect enum class Enum { A, B, C, D } + +/* +LINEMARKER: Has actuals in JVM +TARGETS: +jvm.kt +actual enum class <5>Enum { <1>A, <2>B, <3>C, <4>D } +*/ diff --git a/idea/testData/multiModuleLineMarker/suspendImplInPlatformModules/common/common.kt b/idea/testData/multiModuleLineMarker/suspendImplInPlatformModules/common/common.kt index 2103943d8e5..dd5b060371c 100644 --- a/idea/testData/multiModuleLineMarker/suspendImplInPlatformModules/common/common.kt +++ b/idea/testData/multiModuleLineMarker/suspendImplInPlatformModules/common/common.kt @@ -1,3 +1,12 @@ interface I { suspend fun foo(s: String) -} \ No newline at end of file +} + +/* +LINEMARKER: Is implemented in
    KJs
    KJvm +TARGETS: +js.kt + suspend override fun <1>foo(s: String) { + jvm.kt + suspend override fun <2>foo(s: String) { +*/ diff --git a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleHighlightingTest.kt b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleHighlightingTest.kt index 8af9176dde4..4e9c40a9592 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleHighlightingTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleHighlightingTest.kt @@ -16,16 +16,30 @@ package org.jetbrains.kotlin.idea.caches.resolve +import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl import com.intellij.psi.PsiFile +import org.jetbrains.kotlin.idea.codeInsight.AbstractLineMarkersTest import org.jetbrains.kotlin.idea.multiplatform.setupMppProjectFromDirStructure import org.jetbrains.kotlin.idea.stubs.AbstractMultiHighlightingTest import org.jetbrains.kotlin.idea.test.PluginTestCaseBase import org.jetbrains.kotlin.idea.test.allJavaFiles import org.jetbrains.kotlin.idea.test.allKotlinFiles +import org.jetbrains.kotlin.psi.KtFile import java.io.File abstract class AbstractMultiModuleHighlightingTest : AbstractMultiHighlightingTest() { + protected open fun checkLineMarkersInProject( + findFiles: () -> List = { project.allKotlinFiles().excludeByDirective() } + ) { + checkFiles(findFiles) { + checkHighlighting(myEditor, true, false) + + val markers = DaemonCodeAnalyzerImpl.getLineMarkers(getDocument(file), project) + AbstractLineMarkersTest.assertNavigationElements(project, myFile as KtFile, markers) + } + } + protected open fun checkHighlightingInProject( findFiles: () -> List = { project.allKotlinFiles().excludeByDirective() } ) { diff --git a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleLineMarkerTest.kt b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleLineMarkerTest.kt index 4e4cc334bf0..7ffafa5d6a8 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleLineMarkerTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/caches/resolve/AbstractMultiModuleLineMarkerTest.kt @@ -32,6 +32,6 @@ abstract class AbstractMultiModuleLineMarkerTest : AbstractMultiModuleHighlighti protected fun doTest(path: String) { setupMppProjectFromDirStructure(File(path)) - checkHighlightingInProject() + checkLineMarkersInProject() } } \ No newline at end of file diff --git a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractLineMarkersTest.kt b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractLineMarkersTest.kt index 8f566226319..5eda3ce7b8b 100644 --- a/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractLineMarkersTest.kt +++ b/idea/tests/org/jetbrains/kotlin/idea/codeInsight/AbstractLineMarkersTest.kt @@ -9,6 +9,7 @@ import com.intellij.codeInsight.daemon.DaemonCodeAnalyzerSettings import com.intellij.codeInsight.daemon.LineMarkerInfo import com.intellij.codeInsight.daemon.impl.DaemonCodeAnalyzerImpl import com.intellij.openapi.editor.Document +import com.intellij.openapi.project.Project import com.intellij.openapi.util.io.FileUtil import com.intellij.psi.PsiDocumentManager import com.intellij.rt.execution.junit.FileComparisonFailure @@ -93,7 +94,7 @@ abstract class AbstractLineMarkersTest : KotlinLightCodeInsightFixtureTestCase() val markers = doAndCheckHighlighting(document, data, File(path)) - assertNavigationElements(markers) + assertNavigationElements(myFixture.project, myFixture.file as KtFile, markers) additionalCheck() } catch (exc: Exception) { throw RuntimeException(exc) @@ -104,45 +105,53 @@ abstract class AbstractLineMarkersTest : KotlinLightCodeInsightFixtureTestCase() } - private fun assertNavigationElements(markers: List>) { - val navigationDataComments = KotlinTestUtils.getLastCommentsInFile( - myFixture.file as KtFile, KotlinTestUtils.CommentType.BLOCK_COMMENT, false) - if (navigationDataComments.isEmpty()) return - - for (navigationComment in navigationDataComments) { - val description = getLineMarkerDescription(navigationComment) - val navigateMarker = markers.find { it.lineMarkerTooltip?.startsWith(description) == true }!! - - TestCase.assertNotNull( - String.format("Can't find marker for navigation check with description \"%s\"", description), - navigateMarker) - - val handler = navigateMarker.navigationHandler - if (handler is TestableLineMarkerNavigator) { - val navigateElements = handler.getTargetsPopupDescriptor(navigateMarker.element)?.targets?.sortedBy { it.renderAsGotoImplementation() } - val actualNavigationData = NavigationTestUtils.getNavigateElementsText(myFixture.project, navigateElements) - - UsefulTestCase.assertSameLines(getExpectedNavigationText(navigationComment), actualNavigationData) - } - else { - Assert.fail("Only SuperDeclarationMarkerNavigationHandler are supported in navigate check") - } - } - } - companion object { - private val LINE_MARKER_PREFIX = "LINEMARKER:" - private val TARGETS_PREFIX = "TARGETS" + @Suppress("SpellCheckingInspection") + private const val LINE_MARKER_PREFIX = "LINEMARKER:" + private const val TARGETS_PREFIX = "TARGETS" + + fun assertNavigationElements(project: Project, file: KtFile, markers: List>) { + val navigationDataComments = KotlinTestUtils.getLastCommentsInFile( + file, KotlinTestUtils.CommentType.BLOCK_COMMENT, false + ) + if (navigationDataComments.isEmpty()) return + + for (navigationComment in navigationDataComments) { + val description = getLineMarkerDescription(navigationComment) + val navigateMarker = markers.find { it.lineMarkerTooltip?.startsWith(description) == true }!! + + TestCase.assertNotNull( + String.format("Can't find marker for navigation check with description \"%s\"", description), + navigateMarker + ) + + val handler = navigateMarker.navigationHandler + if (handler is TestableLineMarkerNavigator) { + val navigateElements = handler.getTargetsPopupDescriptor(navigateMarker.element)?.targets?.sortedBy { + it.renderAsGotoImplementation() + } + val actualNavigationData = NavigationTestUtils.getNavigateElementsText(project, navigateElements) + + UsefulTestCase.assertSameLines(getExpectedNavigationText(navigationComment), actualNavigationData) + } else { + Assert.fail("Only TestableLineMarkerNavigator are supported in navigate check") + } + } + } private fun getLineMarkerDescription(navigationComment: String): String { val firstLineEnd = navigationComment.indexOf("\n") - TestCase.assertTrue("The first line in block comment must contain description of marker for navigation check", firstLineEnd != -1) + TestCase.assertTrue( + "The first line in block comment must contain description of marker for navigation check", firstLineEnd != -1 + ) var navigationMarkerText = navigationComment.substring(0, firstLineEnd) - TestCase.assertTrue(String.format("Add %s directive in first line of comment", LINE_MARKER_PREFIX), - navigationMarkerText.startsWith(LINE_MARKER_PREFIX)) + TestCase.assertTrue( + String.format("Add %s directive in first line of comment", LINE_MARKER_PREFIX), + navigationMarkerText.startsWith(LINE_MARKER_PREFIX) + ) navigationMarkerText = navigationMarkerText.substring(LINE_MARKER_PREFIX.length) @@ -155,8 +164,9 @@ abstract class AbstractLineMarkersTest : KotlinLightCodeInsightFixtureTestCase() var expectedNavigationText = navigationComment.substring(firstLineEnd + 1) TestCase.assertTrue( - String.format("Marker %s is expected before navigation data", TARGETS_PREFIX), - expectedNavigationText.startsWith(TARGETS_PREFIX)) + String.format("Marker %s is expected before navigation data", TARGETS_PREFIX), + expectedNavigationText.startsWith(TARGETS_PREFIX) + ) expectedNavigationText = expectedNavigationText.substring(expectedNavigationText.indexOf("\n") + 1)