Navigation from header to implementation and vice versa #KT-15204 Fixed

This commit is contained in:
Mikhail Glukhikh
2016-12-13 20:01:39 +03:00
parent 2277239ab3
commit 295360ad3e
4 changed files with 212 additions and 4 deletions
@@ -131,7 +131,7 @@ class HeaderImplDeclarationChecker(val moduleToCheck: ModuleDescriptor? = null)
}
}
private fun CallableMemberDescriptor.findNamesakesFromTheSameModule(): Collection<CallableMemberDescriptor> {
fun CallableMemberDescriptor.findNamesakesFromTheSameModule(): Collection<CallableMemberDescriptor> {
val packageFqName = (containingDeclaration as? PackageFragmentDescriptor)?.fqName ?: return emptyList()
val myModule = moduleToCheck ?: module
val scope = myModule.getPackage(packageFqName).memberScope
@@ -145,7 +145,7 @@ class HeaderImplDeclarationChecker(val moduleToCheck: ModuleDescriptor? = null)
}
}
private fun ClassifierDescriptor.findClassifiersFromTheSameModule(): Collection<ClassifierDescriptor> {
fun ClassifierDescriptor.findClassifiersFromTheSameModule(): Collection<ClassifierDescriptor> {
val myModule = moduleToCheck ?: module
// TODO: support nested classes
return myModule.getPackage(fqNameSafe.parent()).memberScope
@@ -0,0 +1,65 @@
/*
* 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.highlighter.markers
import com.intellij.psi.NavigatablePsiElement
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
import org.jetbrains.kotlin.idea.core.toDescriptor
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.checkers.HeaderImplDeclarationChecker
fun ModuleDescriptor.commonModuleOrNull() = allDependencyModules.filter {
it.platformKind == PlatformKind.DEFAULT && it.sourceKind == sourceKind
}.firstOrNull()
fun ModuleDescriptor.hasDeclarationOf(descriptor: MemberDescriptor) = declarationOf(descriptor) != null
private fun ModuleDescriptor.declarationOf(descriptor: MemberDescriptor) =
with (HeaderImplDeclarationChecker(this)) {
when (descriptor) {
is CallableMemberDescriptor ->
descriptor.findNamesakesFromTheSameModule().filter { it.isHeader }.firstOrNull()
is ClassDescriptor ->
descriptor.findClassifiersFromTheSameModule().filter { it is ClassDescriptor && it.isHeader }.firstOrNull()
else ->
null
}
}
fun getHeaderDeclarationTooltip(declaration: KtDeclaration): String? {
val descriptor = declaration.toDescriptor() as? MemberDescriptor ?: return null
val platformModuleDescriptor = declaration.containingKtFile.findModuleDescriptor()
val commonModuleDescriptor = platformModuleDescriptor.commonModuleOrNull() ?: return null
if (!commonModuleDescriptor.hasDeclarationOf(descriptor)) return null
return "Has declaration in common module"
}
fun navigateToHeaderDeclaration(declaration: KtDeclaration) {
val descriptor = declaration.toDescriptor() as? MemberDescriptor ?: return
val platformModuleDescriptor = declaration.containingKtFile.findModuleDescriptor()
val commonModuleDescriptor = platformModuleDescriptor.commonModuleOrNull() ?: return
val headerDeclaration = DescriptorToSourceUtils.descriptorToDeclaration(
commonModuleDescriptor.declarationOf(descriptor) ?: return
) as? NavigatablePsiElement ?: return
headerDeclaration.navigate(false)
}
@@ -0,0 +1,71 @@
/*
* 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.highlighter.markers
import com.intellij.codeInsight.daemon.impl.PsiElementListNavigator
import com.intellij.ide.util.DefaultPsiElementCellRenderer
import com.intellij.psi.NavigatablePsiElement
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
import org.jetbrains.kotlin.idea.core.toDescriptor
import org.jetbrains.kotlin.psi.KtDeclaration
import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils
import org.jetbrains.kotlin.resolve.checkers.HeaderImplDeclarationChecker
import java.awt.event.MouseEvent
fun ModuleDescriptor.hasImplementationsOf(descriptor: MemberDescriptor) =
implementationsOf(descriptor).isNotEmpty()
private fun ModuleDescriptor.implementationsOf(descriptor: MemberDescriptor) =
with (HeaderImplDeclarationChecker(this)) {
when (descriptor) {
is CallableMemberDescriptor -> descriptor.findNamesakesFromTheSameModule().filter { it.isImpl }
is ClassDescriptor -> descriptor.findClassifiersFromTheSameModule().filter { it is ClassDescriptor && it.isImpl }
else -> emptyList()
}
}
fun getPlatformImplementationTooltip(declaration: KtDeclaration): String? {
val descriptor = declaration.toDescriptor() as? MemberDescriptor ?: return null
val commonModuleDescriptor = declaration.containingKtFile.findModuleDescriptor()
val platformModulesWithImplementation = commonModuleDescriptor.allImplementingModules.filter {
it.hasImplementationsOf(descriptor)
}
if (platformModulesWithImplementation.isEmpty()) return null
return platformModulesWithImplementation.joinToString(prefix = "Has implementations in ") {
it.platformKind.name
}
}
fun navigateToPlatformImplementation(e: MouseEvent?, declaration: KtDeclaration) {
val descriptor = declaration.toDescriptor() as? MemberDescriptor ?: return
val commonModuleDescriptor = declaration.containingKtFile.findModuleDescriptor()
val implementations = commonModuleDescriptor.allImplementingModules.flatMap {
it.implementationsOf(descriptor)
}.mapNotNull { DescriptorToSourceUtils.descriptorToDeclaration(it) as? NavigatablePsiElement }
if (implementations.isEmpty()) return
val renderer = DefaultPsiElementCellRenderer()
PsiElementListNavigator.openTargets(e,
implementations.toTypedArray(),
"Choose implementation of ${declaration.name}",
"Implementations of ${declaration.name}",
renderer)
}
@@ -31,8 +31,9 @@ import com.intellij.psi.PsiNameIdentifierOwner
import com.intellij.psi.search.searches.ClassInheritorsSearch
import org.jetbrains.kotlin.asJava.LightClassUtil
import org.jetbrains.kotlin.asJava.toLightClass
import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor
import org.jetbrains.kotlin.descriptors.Modality
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor
import org.jetbrains.kotlin.idea.core.toDescriptor
import org.jetbrains.kotlin.idea.util.ProjectRootsUtil
import org.jetbrains.kotlin.lexer.KtTokens
import org.jetbrains.kotlin.psi.*
@@ -78,6 +79,15 @@ class KotlinLineMarkerProvider : LineMarkerProvider {
}
}
}
if (element is KtNamedDeclaration) {
if (element.hasModifier(KtTokens.HEADER_KEYWORD)) {
collectImplementationMarkers(element, result)
}
else if (element.hasModifier(KtTokens.IMPL_KEYWORD)) {
collectHeaderMarkers(element, result)
}
}
}
collectOverriddenFunctions(functions, result)
@@ -114,6 +124,26 @@ private val OVERRIDDEN_PROPERTY = MarkerType(
}
})
private val PLATFORM_IMPLEMENTATION = MarkerType(
"PLATFORM_IMPLEMENTATION",
{ it?.let { getPlatformImplementationTooltip(it.parent as KtDeclaration) } },
object : LineMarkerNavigator() {
override fun browse(e: MouseEvent?, element: PsiElement?) {
element?.let { navigateToPlatformImplementation(e, it.parent as KtDeclaration) }
}
}
)
private val HEADER_DECLARATION = MarkerType(
"HEADER_DECLARATION",
{ it?.let { getHeaderDeclarationTooltip(it.parent as KtDeclaration) } },
object : LineMarkerNavigator() {
override fun browse(e: MouseEvent?, element: PsiElement?) {
element?.let { navigateToHeaderDeclaration(it.parent as KtDeclaration) }
}
}
)
private fun isImplementsAndNotOverrides(descriptor: CallableMemberDescriptor, overriddenMembers: Collection<CallableMemberDescriptor>): Boolean {
return descriptor.modality != Modality.ABSTRACT && overriddenMembers.all { it.modality == Modality.ABSTRACT }
}
@@ -196,6 +226,48 @@ private fun collectOverriddenPropertyAccessors(properties: Collection<KtNamedDec
}
}
private fun collectImplementationMarkers(declaration: KtNamedDeclaration,
result: MutableCollection<LineMarkerInfo<*>>) {
val descriptor = declaration.toDescriptor() as? MemberDescriptor ?: return
val commonModuleDescriptor = declaration.containingKtFile.findModuleDescriptor()
if (commonModuleDescriptor.allImplementingModules.none { it.hasImplementationsOf(descriptor) }) return
val anchor = declaration.nameIdentifier ?: declaration
result.add(LineMarkerInfo(
anchor,
anchor.textRange,
IMPLEMENTED_MARK,
Pass.UPDATE_OVERRIDDEN_MARKERS,
PLATFORM_IMPLEMENTATION.tooltip,
PLATFORM_IMPLEMENTATION.navigationHandler,
GutterIconRenderer.Alignment.RIGHT
))
}
private fun collectHeaderMarkers(declaration: KtNamedDeclaration,
result: MutableCollection<LineMarkerInfo<*>>) {
val descriptor = declaration.toDescriptor() as? MemberDescriptor ?: return
val platformModuleDescriptor = declaration.containingKtFile.findModuleDescriptor()
val commonModuleDescriptor = platformModuleDescriptor.commonModuleOrNull() ?: return
if (!commonModuleDescriptor.hasDeclarationOf(descriptor)) return
val anchor = declaration.nameIdentifier ?: declaration
result.add(LineMarkerInfo(
anchor,
anchor.textRange,
IMPLEMENTING_MARK,
Pass.UPDATE_OVERRIDDEN_MARKERS,
HEADER_DECLARATION.tooltip,
HEADER_DECLARATION.navigationHandler,
GutterIconRenderer.Alignment.RIGHT
))
}
fun KtNamedDeclaration.getAccessorLightMethods(): LightClassUtil.PropertyAccessorsPsiMethods {
return when (this) {
is KtProperty -> LightClassUtil.getLightClassPropertyMethods(this)