diff --git a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties
index 0ac4c6666ce..868da4e6b67 100644
--- a/idea/src/org/jetbrains/jet/plugin/JetBundle.properties
+++ b/idea/src/org/jetbrains/jet/plugin/JetBundle.properties
@@ -202,3 +202,11 @@ add.name.to.argument.single=Add name to argument\: ''{0}''
add.name.to.argument.multiple=Add name to argument...
add.name.to.argument.action=Add name to argument...
add.name.to.parameter.name.chooser.title=Choose parameter name
+
+property.is.implemented.too.many=Has implementations
+property.is.overridden.too.many=Is overridden in subclasses
+property.is.implemented.header=Is implemented in
+property.is.overridden.header=Is overridden in
+
+navigation.title.overriding.property=Choose Implementation of {0}
+navigation.findUsages.title.overriding.property=Overriding properties of {0}
\ No newline at end of file
diff --git a/idea/src/org/jetbrains/jet/plugin/highlighter/JetLineMarkerProvider.java b/idea/src/org/jetbrains/jet/plugin/highlighter/JetLineMarkerProvider.java
index 25577f783f3..667bff4566a 100644
--- a/idea/src/org/jetbrains/jet/plugin/highlighter/JetLineMarkerProvider.java
+++ b/idea/src/org/jetbrains/jet/plugin/highlighter/JetLineMarkerProvider.java
@@ -16,18 +16,20 @@
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.google.common.collect.*;
import com.intellij.codeHighlighting.Pass;
import com.intellij.codeInsight.daemon.GutterIconNavigationHandler;
import com.intellij.codeInsight.daemon.LineMarkerInfo;
import com.intellij.codeInsight.daemon.LineMarkerProvider;
+import com.intellij.codeInsight.daemon.impl.GutterIconTooltipHelper;
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.hint.HintUtil;
import com.intellij.codeInsight.navigation.NavigationUtil;
import com.intellij.icons.AllIcons;
+import com.intellij.ide.util.DefaultPsiElementCellRenderer;
+import com.intellij.ide.util.PsiClassListCellRenderer;
import com.intellij.openapi.editor.markup.GutterIconRenderer;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.project.DumbService;
@@ -35,19 +37,17 @@ import com.intellij.openapi.ui.popup.JBPopup;
import com.intellij.openapi.ui.popup.JBPopupFactory;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.text.StringUtil;
-import com.intellij.psi.CommonClassNames;
-import com.intellij.psi.PsiClass;
-import com.intellij.psi.PsiElement;
-import com.intellij.psi.PsiMethod;
+import com.intellij.psi.*;
+import com.intellij.psi.search.PsiElementProcessor;
+import com.intellij.psi.search.PsiElementProcessorAdapter;
import com.intellij.psi.search.searches.AllOverridingMethodsSearch;
import com.intellij.psi.search.searches.ClassInheritorsSearch;
+import com.intellij.psi.search.searches.OverridingMethodsSearch;
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 com.intellij.util.*;
import gnu.trove.THashSet;
+import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jet.asJava.LightClassUtil;
@@ -58,16 +58,15 @@ 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;
+import org.jetbrains.jet.plugin.JetBundle;
import org.jetbrains.jet.plugin.codeInsight.JetFunctionPsiElementCellRenderer;
import org.jetbrains.jet.plugin.project.WholeProjectAnalyzerFacade;
+import org.jetbrains.jet.plugin.search.KotlinDefinitionsSearcher;
import org.jetbrains.jet.renderer.DescriptorRenderer;
import javax.swing.*;
import java.awt.event.MouseEvent;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
+import java.util.*;
public class JetLineMarkerProvider implements LineMarkerProvider {
public static final Icon OVERRIDING_MARK = AllIcons.Gutter.OverridingMethod;
@@ -115,6 +114,98 @@ public class JetLineMarkerProvider implements LineMarkerProvider {
}
);
+ private static final MarkerType OVERRIDDEN_PROPERTY = new MarkerType(
+ new NullableFunction() {
+ @Override
+ public String fun(@Nullable PsiElement element) {
+ if (element == null) return null;
+
+ assert element.getParent() instanceof JetProperty : "This tooltip provider should be placed only on identifies in properties";
+ JetProperty property = (JetProperty) element.getParent();
+
+ PsiElementProcessor.CollectElementsWithLimit processor = new PsiElementProcessor.CollectElementsWithLimit(5);
+ Processor consumer = new AdapterProcessor(
+ new CommonProcessors.UniqueProcessor(new PsiElementProcessorAdapter(processor)),
+ new Function() {
+ @Override
+ public PsiClass fun(PsiMethod method) {
+ return method.getContainingClass();
+ }
+ });
+
+ for (PsiMethod method : LightClassUtil.getLightClassPropertyMethods(property)) {
+ if (!processor.isOverflow()) {
+ OverridingMethodsSearch.search(method, true).forEach(consumer);
+ }
+ }
+
+ boolean isImplemented = isImplemented(property);
+ if (processor.isOverflow()) {
+ return isImplemented ?
+ JetBundle.message("property.is.implemented.too.many") :
+ JetBundle.message("property.is.overridden.too.many");
+ }
+
+ List collectedClasses = Lists.newArrayList(processor.getCollection());
+ if (collectedClasses.isEmpty()) return null;
+
+ Collections.sort(collectedClasses, new PsiClassListCellRenderer().getComparator());
+
+ String start = isImplemented ?
+ JetBundle.message("property.is.implemented.header") :
+ JetBundle.message("property.is.overridden.header");
+
+ @NonNls String pattern = " {0}";
+ return GutterIconTooltipHelper.composeText(collectedClasses, start, pattern);
+ }
+ },
+
+ new LineMarkerNavigator() {
+ @Override
+ public void browse(@Nullable MouseEvent e, @Nullable PsiElement element) {
+ if (element == null) return;
+
+ assert element.getParent() instanceof JetProperty : "This marker navigator should be placed only on identifies in properties";
+ JetProperty property = (JetProperty) element.getParent();
+
+ if (DumbService.isDumb(element.getProject())) {
+ DumbService.getInstance(element.getProject()).showDumbModeNotification("Navigation to overriding classes is not possible during index update");
+ return;
+ }
+
+ final LightClassUtil.PropertyAccessorsPsiMethods psiPropertyMethods =
+ LightClassUtil.getLightClassPropertyMethods((JetProperty) element.getParent());
+
+ final CommonProcessors.CollectUniquesProcessor elementProcessor = new CommonProcessors.CollectUniquesProcessor();
+ Runnable jetPsiMethodProcessor = new Runnable() {
+ @Override
+ public void run() {
+ KotlinDefinitionsSearcher.processPropertyImplementationsMethods(psiPropertyMethods, elementProcessor);
+ }
+ };
+
+ if (!ProgressManager.getInstance().runProcessWithProgressSynchronously(
+ jetPsiMethodProcessor,
+ MarkerType.SEARCHING_FOR_OVERRIDING_METHODS, true,
+ element.getProject(),
+ e != null ? (JComponent) e.getComponent() : null)) {
+ return;
+ }
+
+ DefaultPsiElementCellRenderer renderer = new DefaultPsiElementCellRenderer();
+ List elements = Ordering.from(renderer.getComparator()).sortedCopy(elementProcessor.getResults());
+
+ NavigatablePsiElement[] navigatableElements = Iterables.toArray(
+ Iterables.filter(elements, NavigatablePsiElement.class), NavigatablePsiElement.class);
+
+ PsiElementListNavigator.openTargets(e, navigatableElements,
+ JetBundle.message("navigation.title.overriding.property", property.getName()),
+ JetBundle.message("navigation.findUsages.title.overriding.property", property.getName()),
+ renderer);
+ }
+ }
+ );
+
@Nullable
private static PsiClass getPsiClass(@Nullable PsiElement element) {
if (element == null) return null;
@@ -274,6 +365,7 @@ public class JetLineMarkerProvider implements LineMarkerProvider {
}
Set functions = Sets.newHashSet();
+ Set properties = Sets.newHashSet();
for (PsiElement element : elements) {
if (element instanceof JetClass) {
@@ -283,9 +375,14 @@ public class JetLineMarkerProvider implements LineMarkerProvider {
if (element instanceof JetNamedFunction) {
functions.add((JetNamedFunction) element);
}
+
+ if (element instanceof JetProperty) {
+ properties.add((JetProperty) element);
+ }
}
collectOverridingAccessors(functions, result);
+ collectOverridingPropertiesAccessors(properties, result);
}
private static void collectInheritingClasses(JetClass element, Collection result) {
@@ -334,8 +431,38 @@ public class JetLineMarkerProvider implements LineMarkerProvider {
return false;
}
+ private static void collectOverridingPropertiesAccessors(Collection properties, Collection result) {
+ Map mappingToJava = Maps.newHashMap();
+ for (JetProperty property : properties) {
+ if (isOverridableHeuristic(property)) {
+ LightClassUtil.PropertyAccessorsPsiMethods accessorsPsiMethods = LightClassUtil.getLightClassPropertyMethods(property);
+ for (PsiMethod psiMethod : accessorsPsiMethods) {
+ mappingToJava.put(psiMethod, property);
+ }
+ }
+ }
+
+ Set classes = collectContainingClasses(mappingToJava.keySet());
+
+ for (JetProperty property : getOverriddenDeclarations(mappingToJava, classes)) {
+ ProgressManager.checkCanceled();
+
+ PsiElement anchor = property.getNameIdentifier();
+ if (anchor == null) anchor = property;
+
+ LineMarkerInfo info = new LineMarkerInfo(
+ anchor, anchor.getTextOffset(),
+ isImplemented(property) ? IMPLEMENTED_MARK : OVERRIDDEN_MARK,
+ Pass.UPDATE_OVERRIDEN_MARKERS,
+ OVERRIDDEN_PROPERTY.getTooltip(), OVERRIDDEN_PROPERTY.getNavigationHandler(),
+ GutterIconRenderer.Alignment.RIGHT);
+
+ result.add(info);
+ }
+ }
+
private static void collectOverridingAccessors(Collection functions, Collection result) {
- final Map mappingToJava = Maps.newHashMap();
+ Map mappingToJava = Maps.newHashMap();
for (JetNamedFunction function : functions) {
if (isOverridableHeuristic(function)) {
PsiMethod method = LightClassUtil.getLightClassMethod(function);
@@ -345,36 +472,9 @@ public class JetLineMarkerProvider implements LineMarkerProvider {
}
}
- 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);
- }
- }
+ Set classes = collectContainingClasses(mappingToJava.keySet());
- 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) {
+ for (JetNamedFunction function : getOverriddenDeclarations(mappingToJava, classes)) {
ProgressManager.checkCanceled();
PsiElement anchor = function.getNameIdentifier();
@@ -390,4 +490,40 @@ public class JetLineMarkerProvider implements LineMarkerProvider {
result.add(info);
}
}
+
+ private static Set collectContainingClasses(Collection methods) {
+ Set classes = new THashSet();
+ for (PsiMethod method : methods) {
+ ProgressManager.checkCanceled();
+ PsiClass parentClass = method.getContainingClass();
+ if (parentClass != null && !CommonClassNames.JAVA_LANG_OBJECT.equals(parentClass.getQualifiedName())) {
+ classes.add(parentClass);
+ }
+ }
+ return classes;
+ }
+
+ private static Set getOverriddenDeclarations(final Map mappingToJava, Set classes) {
+ 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();
+
+ T declaration = mappingToJava.get(superMethod);
+ if (declaration != null) {
+ mappingToJava.remove(superMethod);
+ overridden.add(declaration);
+ }
+
+ return !mappingToJava.isEmpty();
+ }
+ });
+ }
+
+ return overridden;
+ }
}
diff --git a/idea/src/org/jetbrains/jet/plugin/search/KotlinDefinitionsSearcher.java b/idea/src/org/jetbrains/jet/plugin/search/KotlinDefinitionsSearcher.java
index 89980b7611d..bbceb76d7ca 100644
--- a/idea/src/org/jetbrains/jet/plugin/search/KotlinDefinitionsSearcher.java
+++ b/idea/src/org/jetbrains/jet/plugin/search/KotlinDefinitionsSearcher.java
@@ -103,7 +103,7 @@ public class KotlinDefinitionsSearcher extends QueryExecutorBase consumer) {
+ public static void processPropertyImplementationsMethods(LightClassUtil.PropertyAccessorsPsiMethods accessors, @NotNull Processor consumer) {
for (PsiMethod method : accessors) {
PsiMethod[] implementations = MethodImplementationsSearch.getMethodImplementations(method);
for (PsiMethod implementation : implementations) {
diff --git a/idea/testData/codeInsight/lineMarker/PropertyOverride.kt b/idea/testData/codeInsight/lineMarker/PropertyOverride.kt
new file mode 100644
index 00000000000..1c3507c2823
--- /dev/null
+++ b/idea/testData/codeInsight/lineMarker/PropertyOverride.kt
@@ -0,0 +1,7 @@
+open class Base {
+ open var writable: Int = 12
+}
+
+class SubBase: Base() {
+ override var writable: Int = 42
+}
\ No newline at end of file
diff --git a/idea/tests/org/jetbrains/jet/plugin/codeInsight/OverrideImplementLineMarkerTest.java b/idea/tests/org/jetbrains/jet/plugin/codeInsight/OverrideImplementLineMarkerTest.java
index 1d5c0fe2bde..0e7db67e78d 100644
--- a/idea/tests/org/jetbrains/jet/plugin/codeInsight/OverrideImplementLineMarkerTest.java
+++ b/idea/tests/org/jetbrains/jet/plugin/codeInsight/OverrideImplementLineMarkerTest.java
@@ -46,6 +46,10 @@ public class OverrideImplementLineMarkerTest extends LightCodeInsightFixtureTest
doTest();
}
+ public void testPropertyOverride() throws Throwable {
+ doTest();
+ }
+
private void doTest() {
try {
myFixture.configureByFile(getTestName(false) + ".kt");