diff --git a/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt.172 b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt.172 new file mode 100644 index 00000000000..1e62f57f4bc --- /dev/null +++ b/idea/src/org/jetbrains/kotlin/idea/highlighter/markers/KotlinLineMarkerProvider.kt.172 @@ -0,0 +1,421 @@ +/* + * Copyright 2000-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.codeHighlighting.Pass +import com.intellij.codeInsight.daemon.* +import com.intellij.codeInsight.daemon.impl.LineMarkerNavigator +import com.intellij.codeInsight.daemon.impl.MarkerType +import com.intellij.codeInsight.daemon.impl.PsiElementListNavigator +import com.intellij.codeInsight.navigation.ListBackgroundUpdaterTask +import com.intellij.icons.AllIcons +import com.intellij.openapi.actionSystem.IdeActions +import com.intellij.openapi.editor.colors.CodeInsightColors +import com.intellij.openapi.editor.colors.EditorColorsManager +import com.intellij.openapi.editor.markup.GutterIconRenderer +import com.intellij.openapi.editor.markup.SeparatorPlacement +import com.intellij.openapi.progress.ProgressManager +import com.intellij.openapi.project.DumbService +import com.intellij.openapi.util.text.StringUtil +import com.intellij.psi.NavigatablePsiElement +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiMethod +import com.intellij.psi.PsiNameIdentifierOwner +import com.intellij.psi.search.searches.ClassInheritorsSearch +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.kotlin.asJava.LightClassUtil +import org.jetbrains.kotlin.asJava.toLightClass +import org.jetbrains.kotlin.descriptors.CallableMemberDescriptor +import org.jetbrains.kotlin.descriptors.MemberDescriptor +import org.jetbrains.kotlin.descriptors.Modality +import org.jetbrains.kotlin.idea.KotlinIcons +import org.jetbrains.kotlin.idea.caches.lightClasses.KtFakeLightClass +import org.jetbrains.kotlin.idea.caches.lightClasses.KtFakeLightMethod +import org.jetbrains.kotlin.idea.caches.project.implementedDescriptors +import org.jetbrains.kotlin.idea.caches.project.implementingDescriptors +import org.jetbrains.kotlin.idea.caches.resolve.findModuleDescriptor +import org.jetbrains.kotlin.idea.core.isInheritable +import org.jetbrains.kotlin.idea.core.isOverridable +import org.jetbrains.kotlin.idea.core.toDescriptor +import org.jetbrains.kotlin.idea.search.declarationsSearch.toPossiblyFakeLightMethods +import org.jetbrains.kotlin.idea.util.* +import org.jetbrains.kotlin.lexer.KtTokens +import org.jetbrains.kotlin.psi.* +import org.jetbrains.kotlin.psi.psiUtil.getPrevSiblingIgnoringWhitespaceAndComments +import java.awt.event.MouseEvent +import java.util.* +import javax.swing.Icon +import javax.swing.ListCellRenderer + +class KotlinLineMarkerProvider : LineMarkerProvider { + override fun getLineMarkerInfo(element: PsiElement): LineMarkerInfo? { + if (DaemonCodeAnalyzerSettings.getInstance().SHOW_METHOD_SEPARATORS) { + if (element.canHaveSeparator()) { + val prevSibling = element.getPrevSiblingIgnoringWhitespaceAndComments() + if (prevSibling.canHaveSeparator() && + (element.wantsSeparator() || prevSibling?.wantsSeparator() == true)) { + return createLineSeparatorByElement(element) + } + } + } + + return null + } + + private fun PsiElement?.canHaveSeparator() = + this is KtFunction || this is KtClassInitializer || (this is KtProperty && !isLocal) + + private fun PsiElement.wantsSeparator() = StringUtil.getLineBreakCount(text) > 0 + + private fun createLineSeparatorByElement(element: PsiElement): LineMarkerInfo { + val anchor = PsiTreeUtil.getDeepestFirst(element) + + val info = LineMarkerInfo(anchor, anchor.textRange, null, Pass.LINE_MARKERS, null, null, GutterIconRenderer.Alignment.RIGHT) + info.separatorColor = EditorColorsManager.getInstance().globalScheme.getColor(CodeInsightColors.METHOD_SEPARATORS_COLOR) + info.separatorPlacement = SeparatorPlacement.TOP + return info + } + + override fun collectSlowLineMarkers(elements: List, result: MutableCollection>) { + if (elements.isEmpty()) return + + val first = elements.first() + if (DumbService.getInstance(first.project).isDumb || !ProjectRootsUtil.isInProjectOrLibSource(first)) return + + val functions = HashSet() + val properties = HashSet() + + for (element in elements) { + ProgressManager.checkCanceled() + + when (element) { + is KtClass -> { + collectInheritedClassMarker(element, result) + collectHighlightingColorsMarkers(element, result) + } + is KtNamedFunction -> { + functions.add(element) + collectSuperDeclarationMarkers(element, result) + } + is KtProperty -> { + properties.add(element) + collectSuperDeclarationMarkers(element, result) + } + is KtParameter -> { + if (element.hasValOrVar()) { + properties.add(element) + collectSuperDeclarationMarkers(element, result) + } + } + } + } + + collectOverriddenFunctions(functions, result) + collectOverriddenPropertyAccessors(properties, result) + + for (element in elements) { + if (element !is KtNamedDeclaration) continue + + if (element.isExpectDeclaration()) { + collectActualMarkers(element, result) + } + else if (element.isEffectivelyActual()) { + collectExpectedMarkers(element, result) + } + } + } +} + +private val OVERRIDING_MARK: Icon = AllIcons.Gutter.OverridingMethod +private val IMPLEMENTING_MARK: Icon = AllIcons.Gutter.ImplementingMethod +private val OVERRIDDEN_MARK: Icon = AllIcons.Gutter.OverridenMethod +private val IMPLEMENTED_MARK: Icon = AllIcons.Gutter.ImplementedMethod + +data class NavigationPopupDescriptor( + val targets: Collection, + val title: String, + val findUsagesTitle: String, + val renderer: ListCellRenderer<*>, + val updater: ListBackgroundUpdaterTask? = null +) { + fun showPopup(e: MouseEvent?) { + PsiElementListNavigator.openTargets(e, targets.toTypedArray(), title, findUsagesTitle, renderer, updater) + } +} + +interface TestableLineMarkerNavigator { + fun getTargetsPopupDescriptor(element: PsiElement?): NavigationPopupDescriptor? +} + +private val SUBCLASSED_CLASS = MarkerType( + "SUBCLASSED_CLASS", + { getPsiClass(it)?.let { MarkerType.getSubclassedClassTooltip(it) } }, + object : LineMarkerNavigator() { + override fun browse(e: MouseEvent?, element: PsiElement?) { + getPsiClass(element)?.let { MarkerType.navigateToSubclassedClass(e, it) } + } + }) + +private val OVERRIDDEN_FUNCTION = object : MarkerType( + "OVERRIDDEN_FUNCTION", + { getPsiMethod(it)?.let(::getOverriddenMethodTooltip) }, + object : LineMarkerNavigator() { + override fun browse(e: MouseEvent?, element: PsiElement?) { + buildNavigateToOverriddenMethodPopup(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?) = buildNavigateToOverriddenMethodPopup(null, element) + } + } +} + +private val OVERRIDDEN_PROPERTY = object : MarkerType( + "OVERRIDDEN_PROPERTY", + { it?.let { getOverriddenPropertyTooltip(it.parent as KtNamedDeclaration) } }, + object : LineMarkerNavigator() { + override fun browse(e: MouseEvent?, element: PsiElement?) { + buildNavigateToPropertyOverriddenDeclarationsPopup(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?) = buildNavigateToPropertyOverriddenDeclarationsPopup(null, element) + } + } +} + +private val PsiElement.markerDeclaration + get() = (this as? KtDeclaration) ?: (parent as? KtDeclaration) + +private val PLATFORM_ACTUAL = MarkerType( + "PLATFORM_ACTUAL", + { it?.let { getPlatformActualTooltip(it.markerDeclaration) } }, + object : LineMarkerNavigator() { + override fun browse(e: MouseEvent?, element: PsiElement?) { + element?.let { navigateToPlatformActual(e, it.markerDeclaration) } + } + } +) + +private val EXPECTED_DECLARATION = MarkerType( + "EXPECTED_DECLARATION", + { it?.let { getExpectedDeclarationTooltip(it.markerDeclaration) } }, + object : LineMarkerNavigator() { + override fun browse(e: MouseEvent?, element: PsiElement?) { + element?.let { navigateToExpectedDeclaration(it.markerDeclaration) } + } + } +) + +private fun isImplementsAndNotOverrides(descriptor: CallableMemberDescriptor, overriddenMembers: Collection): Boolean { + return descriptor.modality != Modality.ABSTRACT && overriddenMembers.all { it.modality == Modality.ABSTRACT } +} + +private fun collectSuperDeclarationMarkers(declaration: KtDeclaration, result: MutableCollection>) { + assert(declaration is KtNamedFunction || declaration is KtProperty || declaration is KtParameter) + + if (!declaration.hasModifier(KtTokens.OVERRIDE_KEYWORD)) return + + val resolveWithParents = resolveDeclarationWithParents(declaration) + if (resolveWithParents.overriddenDescriptors.isEmpty()) return + + val implements = isImplementsAndNotOverrides(resolveWithParents.descriptor!!, resolveWithParents.overriddenDescriptors) + + val anchor = (declaration as? KtNamedDeclaration)?.nameIdentifier ?: declaration + + // NOTE: Don't store descriptors in line markers because line markers are not deleted while editing other files and this can prevent + // clearing the whole BindingTrace. + + val lineMarkerInfo = LineMarkerInfo( + anchor, + anchor.textRange, + if (implements) IMPLEMENTING_MARK else OVERRIDING_MARK, + Pass.LINE_MARKERS, + SuperDeclarationMarkerTooltip, + SuperDeclarationMarkerNavigationHandler(), + GutterIconRenderer.Alignment.RIGHT + ) + NavigateAction.setNavigateAction( + lineMarkerInfo, + if (declaration is KtNamedFunction) "Go to super method" else "Go to super property", + IdeActions.ACTION_GOTO_SUPER + ) + result.add(lineMarkerInfo) +} + +private fun collectInheritedClassMarker(element: KtClass, result: MutableCollection>) { + if (!element.isInheritable()) { + return + } + + val lightClass = element.toLightClass() ?: KtFakeLightClass(element) + + if (ClassInheritorsSearch.search(lightClass, false).findFirst() == null) return + + val anchor = element.nameIdentifier ?: element + + val lineMarkerInfo = LineMarkerInfo( + anchor, + anchor.textRange, + if (element.isInterface()) IMPLEMENTED_MARK else OVERRIDDEN_MARK, + Pass.LINE_MARKERS, + SUBCLASSED_CLASS.tooltip, + SUBCLASSED_CLASS.navigationHandler, + GutterIconRenderer.Alignment.RIGHT + ) + NavigateAction.setNavigateAction( + lineMarkerInfo, + if (element.isInterface()) "Go to implementations" else "Go to subclasses", + IdeActions.ACTION_GOTO_IMPLEMENTATION + ) + result.add(lineMarkerInfo) +} + +private fun collectOverriddenPropertyAccessors(properties: Collection, + result: MutableCollection>) { + val mappingToJava = HashMap() + for (property in properties) { + if (property.isOverridable()) { + property.toPossiblyFakeLightMethods().forEach { mappingToJava.put(it, property) } + mappingToJava[property] = property + } + } + + val classes = collectContainingClasses(mappingToJava.keys.filterIsInstance()) + + for (property in getOverriddenDeclarations(mappingToJava, classes)) { + ProgressManager.checkCanceled() + + val anchor = (property as? PsiNameIdentifierOwner)?.nameIdentifier ?: property + + val lineMarkerInfo = LineMarkerInfo( + anchor, + anchor.textRange, + if (isImplemented(property)) IMPLEMENTED_MARK else OVERRIDDEN_MARK, + Pass.LINE_MARKERS, + OVERRIDDEN_PROPERTY.tooltip, + OVERRIDDEN_PROPERTY.navigationHandler, + GutterIconRenderer.Alignment.RIGHT + ) + NavigateAction.setNavigateAction( + lineMarkerInfo, + "Go to overridden properties", + IdeActions.ACTION_GOTO_IMPLEMENTATION + ) + result.add(lineMarkerInfo) + } +} + +private val KtNamedDeclaration.expectOrActualAnchor + get() = + nameIdentifier + ?: (this as? KtConstructor<*>)?.let { + it.getConstructorKeyword() ?: it.getValueParameterList()?.leftParenthesis + } + ?: this + +private fun collectActualMarkers(declaration: KtNamedDeclaration, + result: MutableCollection>) { + + val descriptor = declaration.toDescriptor() as? MemberDescriptor ?: return + val commonModuleDescriptor = declaration.containingKtFile.findModuleDescriptor() + + if (commonModuleDescriptor.implementingDescriptors.none { it.hasActualsFor(descriptor) }) return + + val anchor = declaration.expectOrActualAnchor + + val lineMarkerInfo = LineMarkerInfo( + anchor, + anchor.textRange, + KotlinIcons.ACTUAL, + Pass.LINE_MARKERS, + PLATFORM_ACTUAL.tooltip, + PLATFORM_ACTUAL.navigationHandler, + GutterIconRenderer.Alignment.RIGHT + ) + NavigateAction.setNavigateAction( + lineMarkerInfo, + "Go to actual declarations", + null + ) + result.add(lineMarkerInfo) +} + +private fun collectExpectedMarkers(declaration: KtNamedDeclaration, + result: MutableCollection>) { + + val descriptor = declaration.toDescriptor() as? MemberDescriptor ?: return + val platformModuleDescriptor = declaration.containingKtFile.findModuleDescriptor() + if (!platformModuleDescriptor.implementedDescriptors.any { it.hasDeclarationOf(descriptor) }) return + + val anchor = declaration.expectOrActualAnchor + + val lineMarkerInfo = LineMarkerInfo( + anchor, + anchor.textRange, + KotlinIcons.EXPECT, + Pass.LINE_MARKERS, + EXPECTED_DECLARATION.tooltip, + EXPECTED_DECLARATION.navigationHandler, + GutterIconRenderer.Alignment.RIGHT + ) + NavigateAction.setNavigateAction( + lineMarkerInfo, + "Go to expected declaration", + null + ) + result.add(lineMarkerInfo) +} + +private fun collectOverriddenFunctions(functions: Collection, result: MutableCollection>) { + val mappingToJava = HashMap() + for (function in functions) { + if (function.isOverridable()) { + val method = LightClassUtil.getLightClassMethod(function) ?: KtFakeLightMethod.get(function) + if (method != null) { + mappingToJava.put(method, function) + } + mappingToJava.put(function, function) + } + } + + val classes = collectContainingClasses(mappingToJava.keys.filterIsInstance()) + + for (function in getOverriddenDeclarations(mappingToJava, classes)) { + ProgressManager.checkCanceled() + + val anchor = function.nameIdentifier ?: function + + val lineMarkerInfo = LineMarkerInfo( + anchor, + anchor.textRange, + if (isImplemented(function)) IMPLEMENTED_MARK else OVERRIDDEN_MARK, + Pass.LINE_MARKERS, OVERRIDDEN_FUNCTION.tooltip, + OVERRIDDEN_FUNCTION.navigationHandler, + GutterIconRenderer.Alignment.RIGHT + ) + NavigateAction.setNavigateAction( + lineMarkerInfo, + "Go to overridden methods", + IdeActions.ACTION_GOTO_IMPLEMENTATION + ) + result.add(lineMarkerInfo) + } +}