diff --git a/idea/src/org/jetbrains/jet/plugin/highlighter/JetLineMarkerProvider.java b/idea/src/org/jetbrains/jet/plugin/highlighter/JetLineMarkerProvider.java index 27f41f6f6f5..a6e84447bdf 100644 --- a/idea/src/org/jetbrains/jet/plugin/highlighter/JetLineMarkerProvider.java +++ b/idea/src/org/jetbrains/jet/plugin/highlighter/JetLineMarkerProvider.java @@ -17,6 +17,8 @@ package org.jetbrains.jet.plugin.highlighter; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import com.intellij.codeHighlighting.Pass; import com.intellij.codeInsight.daemon.GutterIconNavigationHandler; import com.intellij.codeInsight.daemon.LineMarkerInfo; @@ -26,27 +28,32 @@ import com.intellij.codeInsight.daemon.impl.MarkerType; import com.intellij.codeInsight.hint.HintUtil; import com.intellij.codeInsight.navigation.NavigationUtil; import com.intellij.icons.AllIcons; +import com.intellij.openapi.editor.markup.GutterIconRenderer; +import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.ui.popup.JBPopup; import com.intellij.openapi.ui.popup.JBPopupFactory; +import com.intellij.openapi.util.Pair; +import com.intellij.psi.CommonClassNames; import com.intellij.psi.PsiClass; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.search.searches.AllOverridingMethodsSearch; import com.intellij.psi.search.searches.ClassInheritorsSearch; import com.intellij.psi.util.PsiUtilCore; import com.intellij.ui.awt.RelativePoint; import com.intellij.util.Function; import com.intellij.util.NullableFunction; +import com.intellij.util.Processor; import com.intellij.util.PsiNavigateUtil; +import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jet.asJava.LightClassUtil; import org.jetbrains.jet.lang.descriptors.CallableMemberDescriptor; import org.jetbrains.jet.lang.descriptors.DeclarationDescriptor; import org.jetbrains.jet.lang.descriptors.Modality; -import org.jetbrains.jet.lang.psi.JetClass; -import org.jetbrains.jet.lang.psi.JetFile; -import org.jetbrains.jet.lang.psi.JetNamedFunction; -import org.jetbrains.jet.lang.psi.JetProperty; +import org.jetbrains.jet.lang.psi.*; import org.jetbrains.jet.lang.resolve.BindingContext; import org.jetbrains.jet.lang.resolve.BindingContextUtils; import org.jetbrains.jet.lexer.JetTokens; @@ -58,6 +65,7 @@ import javax.swing.*; import java.awt.event.MouseEvent; import java.util.Collection; import java.util.List; +import java.util.Map; import java.util.Set; public class JetLineMarkerProvider implements LineMarkerProvider { @@ -86,6 +94,26 @@ public class JetLineMarkerProvider implements LineMarkerProvider { } ); + private static final MarkerType OVERRIDDEN_FUNCTION = new MarkerType( + new NullableFunction() { + @Override + public String fun(@Nullable PsiElement element) { + PsiMethod psiMethod = getPsiMethod(element); + return psiMethod != null ? MarkerType.getOverriddenMethodTooltip(psiMethod) : null; + } + }, + + new LineMarkerNavigator() { + @Override + public void browse(@Nullable MouseEvent e, @Nullable PsiElement element) { + PsiMethod psiMethod = getPsiMethod(element); + if (psiMethod != null) { + MarkerType.navigateToOverriddenMethod(e, psiMethod); + } + } + } + ); + @Nullable private static PsiClass getPsiClass(@Nullable PsiElement element) { if (element == null) return null; @@ -101,13 +129,20 @@ public class JetLineMarkerProvider implements LineMarkerProvider { return LightClassUtil.getPsiClass((JetClass) element); } + @Nullable + private static PsiMethod getPsiMethod(@Nullable PsiElement element) { + if (element == null) return null; + if (element instanceof PsiMethod) return (PsiMethod) element; + if (element.getParent() instanceof JetNamedFunction) return LightClassUtil.getLightClassMethod((JetNamedFunction) element.getParent()); + return null; + } + @Override public LineMarkerInfo getLineMarkerInfo(@NotNull PsiElement element) { - JetFile file = (JetFile)element.getContainingFile(); if (file == null) return null; - if (!(element instanceof JetNamedFunction || element instanceof JetProperty)) return null; + if (!(element instanceof JetNamedFunction || element instanceof JetProperty)) return null; BindingContext bindingContext = WholeProjectAnalyzerFacade.analyzeProjectWithCacheOnAFile(file).getBindingContext(); @@ -236,15 +271,23 @@ public class JetLineMarkerProvider implements LineMarkerProvider { @Override public void collectSlowLineMarkers(@NotNull List elements, @NotNull Collection result) { - if (elements.isEmpty() || DumbService.getInstance(elements.get(0).getProject()).isDumb()) { - return; + return; } + + Set functions = Sets.newHashSet(); + for (PsiElement element : elements) { if (element instanceof JetClass) { - collectInheritingClasses((JetClass)element, result); + collectInheritingClasses((JetClass) element, result); + } + + if (element instanceof JetNamedFunction) { + functions.add((JetNamedFunction) element); } } + + collectOverridingAccessors(functions, result); } private static void collectInheritingClasses(JetClass element, Collection result) { @@ -267,4 +310,85 @@ public class JetLineMarkerProvider implements LineMarkerProvider { SUBCLASSED_CLASS.getTooltip(), SUBCLASSED_CLASS.getNavigationHandler())); } } + + private static boolean isOverridableHeuristic(JetNamedDeclaration declaration) { + PsiElement parent = declaration.getParent(); + if (parent instanceof JetFile) return false; + if (parent instanceof JetClassBody && parent.getParent() instanceof JetClass) { + // Not all open or abstract declarations are actually overridable, but this is heuristic + return declaration.hasModifier(JetTokens.ABSTRACT_KEYWORD) || + declaration.hasModifier(JetTokens.OPEN_KEYWORD) || + ((JetClass) parent.getParent()).isTrait(); + } + + return false; + } + + private static boolean isImplemented(JetNamedDeclaration declaration) { + if (declaration.hasModifier(JetTokens.ABSTRACT_KEYWORD)) return true; + + PsiElement parent = declaration.getParent(); + if (parent instanceof JetClass) { + return ((JetClass) parent).isTrait(); + } + + return false; + } + + private static void collectOverridingAccessors(Collection functions, Collection result) { + final Map mappingToJava = Maps.newHashMap(); + for (JetNamedFunction function : functions) { + if (isOverridableHeuristic(function)) { + PsiMethod method = LightClassUtil.getLightClassMethod(function); + if (method != null) { + mappingToJava.put(method, function); + } + } + } + + Set classes = new THashSet(); + for (PsiMethod method : mappingToJava.keySet()) { + ProgressManager.checkCanceled(); + PsiClass parentClass = method.getContainingClass(); + if (parentClass != null && !CommonClassNames.JAVA_LANG_OBJECT.equals(parentClass.getQualifiedName())) { + classes.add(parentClass); + } + } + + final Set overridden = Sets.newHashSet(); + for (PsiClass aClass : classes) { + AllOverridingMethodsSearch.search(aClass).forEach(new Processor>() { + @Override + public boolean process(Pair pair) { + ProgressManager.checkCanceled(); + + PsiMethod superMethod = pair.getFirst(); + + JetNamedFunction function = mappingToJava.get(superMethod); + if (function != null) { + mappingToJava.remove(superMethod); + overridden.add(function); + } + + return !mappingToJava.isEmpty(); + } + }); + } + + for (JetNamedFunction function : overridden) { + ProgressManager.checkCanceled(); + + PsiElement anchor = function.getNameIdentifier(); + if (anchor == null) anchor = function; + + LineMarkerInfo info = new LineMarkerInfo( + anchor, anchor.getTextOffset(), + isImplemented(function) ? IMPLEMENTED_MARK : OVERRIDDEN_MARK, + Pass.UPDATE_OVERRIDEN_MARKERS, + OVERRIDDEN_FUNCTION.getTooltip(), OVERRIDDEN_FUNCTION.getNavigationHandler(), + GutterIconRenderer.Alignment.RIGHT); + + result.add(info); + } + } } diff --git a/idea/testData/codeInsight/lineMarker/Class.kt b/idea/testData/codeInsight/lineMarker/Class.kt index 5a55b08ccbb..e1c9fa06162 100644 --- a/idea/testData/codeInsight/lineMarker/Class.kt +++ b/idea/testData/codeInsight/lineMarker/Class.kt @@ -1,5 +1,5 @@ open class A { - open fun a(){ + open fun a(){ } } diff --git a/idea/testData/codeInsight/lineMarker/Trait.kt b/idea/testData/codeInsight/lineMarker/Trait.kt index 61c1b6272a7..ac35ff5d317 100644 --- a/idea/testData/codeInsight/lineMarker/Trait.kt +++ b/idea/testData/codeInsight/lineMarker/Trait.kt @@ -1,5 +1,5 @@ trait A { - fun a(){ + fun a(){ } }