From d2f3765ab5d4f07939a7619c86682bc4bfacbe51 Mon Sep 17 00:00:00 2001 From: Herbert Poul Date: Thu, 23 Jun 2022 17:14:26 +0200 Subject: [PATCH] WEB-41595 call hierarchy: jump to caller position --- .../ide/hierarchy/call/DartCallChild.java | 22 +++++ .../call/DartCallHierarchyNodeDescriptor.java | 87 ++++++++++++++++++- .../call/DartCallHierarchyTreeStructure.java | 22 +++-- .../call/DartCalleeTreeStructure.java | 10 +-- .../call/DartCallerTreeStructure.java | 6 +- .../analysisServer/DartCallHierarchyTest.java | 5 +- 6 files changed, 132 insertions(+), 20 deletions(-) create mode 100644 Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallChild.java diff --git a/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallChild.java b/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallChild.java new file mode 100644 index 00000000000..72b73ac31b7 --- /dev/null +++ b/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallChild.java @@ -0,0 +1,22 @@ +// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.jetbrains.lang.dart.ide.hierarchy.call; + +import com.intellij.psi.PsiElement; + +public class DartCallChild { + private PsiElement element; + private PsiElement reference; + + public DartCallChild(PsiElement element, PsiElement reference) { + this.element = element; + this.reference = reference; + } + + public PsiElement getElement() { + return element; + } + + public PsiElement getReference() { + return reference; + } +} diff --git a/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallHierarchyNodeDescriptor.java b/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallHierarchyNodeDescriptor.java index 0fac820e8a7..81da4bf737d 100644 --- a/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallHierarchyNodeDescriptor.java +++ b/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallHierarchyNodeDescriptor.java @@ -1,26 +1,45 @@ // Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.jetbrains.lang.dart.ide.hierarchy.call; +import com.intellij.codeInsight.highlighting.HighlightManager; +import com.intellij.ide.IdeBundle; import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; import com.intellij.ide.util.treeView.NodeDescriptor; import com.intellij.navigation.ItemPresentation; +import com.intellij.openapi.editor.Editor; +import com.intellij.openapi.editor.colors.EditorColors; +import com.intellij.openapi.editor.markup.RangeHighlighter; import com.intellij.openapi.roots.ui.util.CompositeAppearance; import com.intellij.openapi.util.Comparing; +import com.intellij.openapi.util.TextRange; +import com.intellij.pom.Navigatable; import com.intellij.psi.NavigatablePsiElement; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; +import com.intellij.psi.PsiReference; +import com.intellij.psi.util.PsiEditorUtil; import com.intellij.psi.util.PsiTreeUtil; import com.jetbrains.lang.dart.DartComponentType; import com.jetbrains.lang.dart.psi.DartClass; import com.jetbrains.lang.dart.psi.DartMethodDeclaration; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -public class DartCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor { +import java.util.ArrayList; +import java.util.List; + +public class DartCallHierarchyNodeDescriptor extends HierarchyNodeDescriptor implements Navigatable { + + private final List myReferences = new ArrayList<>(); public DartCallHierarchyNodeDescriptor(final NodeDescriptor parentDescriptor, @NotNull final PsiElement element, final boolean isBase) { super(element.getProject(), parentDescriptor, element, isBase); } + public void addReference(PsiReference reference) { + myReferences.add(reference); + } + @Override public boolean update() { boolean changes = super.update(); @@ -47,6 +66,11 @@ public boolean update() { if (file != null) { myHighlightedText.getEnding().addText(" (" + file.getName() + ")", HierarchyNodeDescriptor.getPackageNameAttributes()); } + var myUsageCount = myReferences.size(); + if (myUsageCount > 1) { + myHighlightedText.getEnding().addText(IdeBundle.message("node.call.hierarchy.N.usages", myUsageCount), HierarchyNodeDescriptor.getUsageCountPrefixAttributes()); + } + } myName = myHighlightedText.getText(); if (!Comparing.equal(myHighlightedText, oldText)) { @@ -54,4 +78,65 @@ public boolean update() { } return changes; } + + private @Nullable Navigatable getNavigatable() { + if (!myReferences.isEmpty()) { + final var reference = myReferences.get(0); + if (reference instanceof Navigatable) { + return (Navigatable)reference; + } + } + final var ret = getPsiElement(); + if (ret instanceof Navigatable) { + return (Navigatable)ret; + } + return null; + } + + @Override + public void navigate(boolean requestFocus) { + final var nav = getNavigatable(); + if (nav == null) { + return; + } + nav.navigate(requestFocus); + + if (!(nav instanceof PsiElement)) { + return; + } + + Editor editor = PsiEditorUtil.findEditor((PsiElement) nav); + + if (editor != null) { + HighlightManager highlightManager = HighlightManager.getInstance(myProject); + List highlighters = new ArrayList<>(); + for (PsiReference psiReference : myReferences) { + PsiElement eachElement = psiReference.getElement(); + PsiElement eachMethodCall = eachElement.getParent(); + if (eachMethodCall != null) { + TextRange textRange = eachMethodCall.getTextRange(); + highlightManager.addRangeHighlight(editor, textRange.getStartOffset(), textRange.getEndOffset(), + EditorColors.SEARCH_RESULT_ATTRIBUTES, false, highlighters); + } + } + } + } + + @Override + public boolean canNavigate() { + final var nav = getNavigatable(); + if (nav != null) { + return nav.canNavigate(); + } + return false; + } + + @Override + public boolean canNavigateToSource() { + final var nav = getNavigatable(); + if (nav != null) { + return nav.canNavigateToSource(); + } + return false; + } } diff --git a/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallHierarchyTreeStructure.java b/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallHierarchyTreeStructure.java index c8f2cad7489..fde48ad9a6e 100644 --- a/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallHierarchyTreeStructure.java +++ b/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallHierarchyTreeStructure.java @@ -8,6 +8,7 @@ import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Condition; import com.intellij.psi.PsiElement; +import com.intellij.psi.PsiReference; import com.intellij.psi.search.GlobalSearchScope; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.ArrayUtil; @@ -35,7 +36,7 @@ protected static FindUsagesHandler createFindUsageHandler(@NotNull final PsiElem return new DartServerFindUsagesHandler(element); } - public static void collectDeclarations(@Nullable final PsiElement element, @NotNull final List results) { + public static void collectDeclarations(@Nullable final PsiElement element, @NotNull final List results) { if (element != null) { Condition isExecutable = object -> { if (object == null) return false; @@ -43,13 +44,13 @@ public static void collectDeclarations(@Nullable final PsiElement element, @NotN }; PsiElement ref = PsiTreeUtil.findFirstParent(element, isExecutable); if (ref != null) { - results.add(ref); + results.add(new DartCallChild(ref, element)); } } } @NotNull - protected abstract List getChildren(@NotNull PsiElement element); + protected abstract List getChildren(@NotNull PsiElement element); @Override protected Object @NotNull [] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) { @@ -70,18 +71,21 @@ public static void collectDeclarations(@Nullable final PsiElement element, @NotN return ArrayUtilRt.EMPTY_OBJECT_ARRAY; } - final List children = getChildren(name); + final List children = getChildren(name); final HashMap callerToDescriptorMap = new HashMap<>(); PsiElement baseClass = element instanceof DartMethodDeclaration ? PsiTreeUtil.getParentOfType(name, DartClass.class) : null; - for (PsiElement caller : children) { - if (isInScope(baseClass, caller, myScopeType)) { - DartCallHierarchyNodeDescriptor callerDescriptor = callerToDescriptorMap.get(caller); + for (DartCallChild caller : children) { + if (isInScope(baseClass, caller.getElement(), myScopeType)) { + DartCallHierarchyNodeDescriptor callerDescriptor = callerToDescriptorMap.get(caller.getElement()); if (callerDescriptor == null) { - callerDescriptor = new DartCallHierarchyNodeDescriptor(descriptor, caller, false); - callerToDescriptorMap.put(caller, callerDescriptor); + callerDescriptor = new DartCallHierarchyNodeDescriptor(descriptor, caller.getElement(), false); + callerToDescriptorMap.put(caller.getElement(), callerDescriptor); descriptors.add(callerDescriptor); } + if (caller.getReference() instanceof PsiReference) { + callerDescriptor.addReference((PsiReference)caller.getReference()); + } } } } diff --git a/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCalleeTreeStructure.java b/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCalleeTreeStructure.java index 48bf8094922..0762336498e 100644 --- a/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCalleeTreeStructure.java +++ b/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCalleeTreeStructure.java @@ -25,7 +25,7 @@ public DartCalleeTreeStructure(Project project, PsiElement element, String curre super(project, element, currentScopeType); } - private static void getCallees(@NotNull PsiElement element, @NotNull List results) { + private static void getCallees(@NotNull PsiElement element, @NotNull List results) { DartComponentName name = (DartComponentName)element; DartComponent decl = (DartComponent)name.getParent(); PsiFile file = decl.getContainingFile(); @@ -39,7 +39,7 @@ private static void getCallees(@NotNull PsiElement element, @NotNull List regions, - @NotNull List results) { + @NotNull List results) { component.acceptChildren(new DartRecursiveVisitor() { @Override public void visitReferenceExpression(@NotNull DartReferenceExpression reference) { @@ -49,7 +49,7 @@ public void visitReferenceExpression(@NotNull DartReferenceExpression reference) if (isExecutable(target)) { PsiElement element = getDeclaration(target, reference); if (element != null) { - results.add(element); + results.add(new DartCallChild(element, reference)); } } } @@ -86,8 +86,8 @@ private static List getRegionAt(int offset, @NotNull List< @NotNull @Override - protected List getChildren(@NotNull PsiElement element) { - final List list = new ArrayList<>(); + protected List getChildren(@NotNull PsiElement element) { + final List list = new ArrayList<>(); getCallees(element, list); return list; } diff --git a/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallerTreeStructure.java b/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallerTreeStructure.java index 453fe543aeb..8bc1805356e 100644 --- a/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallerTreeStructure.java +++ b/Dart/src/com/jetbrains/lang/dart/ide/hierarchy/call/DartCallerTreeStructure.java @@ -18,7 +18,7 @@ public DartCallerTreeStructure(Project project, PsiElement element, String curre super(project, element, currentScopeType); } - private static void getCallers(@NotNull PsiElement element, @NotNull List results, @NotNull GlobalSearchScope scope) { + private static void getCallers(@NotNull PsiElement element, @NotNull List results, @NotNull GlobalSearchScope scope) { FindUsagesHandler finder = createFindUsageHandler(element); final CommonProcessors.CollectProcessor processor = new CommonProcessors.CollectProcessor<>(); FindUsagesOptions options = new FindUsagesOptions(scope); @@ -33,8 +33,8 @@ private static void getCallers(@NotNull PsiElement element, @NotNull List getChildren(@NotNull PsiElement element) { - final List list = new ArrayList<>(); + protected List getChildren(@NotNull PsiElement element) { + final List list = new ArrayList<>(); getCallers(element, list, getScope()); return list; } diff --git a/Dart/testSrc/com/jetbrains/dart/analysisServer/DartCallHierarchyTest.java b/Dart/testSrc/com/jetbrains/dart/analysisServer/DartCallHierarchyTest.java index 98e7215b7ef..857c30b66db 100644 --- a/Dart/testSrc/com/jetbrains/dart/analysisServer/DartCallHierarchyTest.java +++ b/Dart/testSrc/com/jetbrains/dart/analysisServer/DartCallHierarchyTest.java @@ -12,6 +12,7 @@ import com.intellij.util.indexing.FindSymbolParameters; import com.jetbrains.lang.dart.ide.DartClassContributor; import com.jetbrains.lang.dart.ide.DartSymbolContributor; +import com.jetbrains.lang.dart.ide.hierarchy.call.DartCallChild; import com.jetbrains.lang.dart.ide.hierarchy.call.DartCallHierarchyTreeStructure; import com.jetbrains.lang.dart.ide.hierarchy.call.DartCalleeTreeStructure; import com.jetbrains.lang.dart.ide.hierarchy.call.DartCallerTreeStructure; @@ -103,10 +104,10 @@ public void visitReferenceExpression(@NotNull DartReferenceExpression reference) if (parent != null) { type = parent.getNode().getElementType(); if (type == CALL_EXPRESSION) { - List results = new ArrayList<>(); + List results = new ArrayList<>(); DartCallHierarchyTreeStructure.collectDeclarations(reference.resolve(), results); if (!results.isEmpty()) { - result[0] = results.get(0); + result[0] = results.get(0).getElement(); throw new ExitVisitor(); } }