Skip to content

Commit

Permalink
Merge pull request #95 from kdbinsidebrains/backtracing
Browse files Browse the repository at this point in the history
5.5.0: Call Hierarchy added (Slice Provider removed)
  • Loading branch information
smklimenko authored Feb 25, 2024
2 parents 7743604 + bdd7823 commit 216b8a8
Show file tree
Hide file tree
Showing 15 changed files with 538 additions and 183 deletions.
10 changes: 10 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# KdbInsideBrains Changelog

## [5.5.0]

### Added

- _Call Hierarchy_ functionality added: https://www.kdbinsidebrains.dev/features/navigation

### Fixed

- _Structure View_ doesn't display loading file for import system function.

## [5.4.0]

### Added
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package org.kdb.inside.brains.lang.hierarchy;

import com.intellij.ide.hierarchy.CallHierarchyBrowserBase;
import com.intellij.ide.hierarchy.HierarchyBrowserManager;
import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
import com.intellij.ide.hierarchy.HierarchyTreeStructure;
import com.intellij.ide.util.treeView.AlphaComparator;
import com.intellij.ide.util.treeView.NodeDescriptor;
import com.intellij.openapi.actionSystem.ActionGroup;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.IdeActions;
import com.intellij.psi.PsiElement;
import com.intellij.ui.PopupHandler;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kdb.inside.brains.psi.QPsiUtil;
import org.kdb.inside.brains.psi.QVarDeclaration;

import javax.swing.*;
import java.util.Comparator;
import java.util.Map;

// PyCallHierarchyBrowser is used as an example
public class QCallHierarchyBrowser extends CallHierarchyBrowserBase {
private static final Comparator<NodeDescriptor<?>> NODE_DESCRIPTOR_COMPARATOR = Comparator.comparingInt(NodeDescriptor::getIndex);

public QCallHierarchyBrowser(@NotNull PsiElement qVar) {
super(qVar.getProject(), qVar);
}

@Override
protected @Nullable PsiElement getElementFromDescriptor(@NotNull HierarchyNodeDescriptor descriptor) {
if (descriptor instanceof QHierarchyNodeDescriptor qd) {
return qd.getPsiElement();
}
return null;
}

@Override
protected void createTrees(@NotNull Map<? super @Nls String, ? super JTree> trees) {
final ActionGroup group = (ActionGroup) ActionManager.getInstance().getAction(IdeActions.GROUP_CALL_HIERARCHY_POPUP);

trees.put(getCallerType(), createHierarchyTree(group));
trees.put(getCalleeType(), createHierarchyTree(group));
}

private JTree createHierarchyTree(ActionGroup group) {
final JTree tree = createTree(false);
PopupHandler.installPopupMenu(tree, group, ActionPlaces.CALL_HIERARCHY_VIEW_POPUP);
return tree;
}

@Override
protected boolean isApplicableElement(@NotNull PsiElement element) {
return element instanceof QVarDeclaration v && QPsiUtil.isGlobalDeclaration(v);
}

@Override
protected @Nullable HierarchyTreeStructure createHierarchyTreeStructure(@NotNull String type, @NotNull PsiElement psiElement) {
if (getCallerType().equals(type)) {
return new QCallerFunctionTreeStructure(myProject, psiElement, getCurrentScopeType());
} else if (getCalleeType().equals(type)) {
return new QCalleeFunctionTreeStructure(myProject, psiElement, getCurrentScopeType());
} else {
return null;
}
}

@Override
protected @Nullable Comparator<NodeDescriptor<?>> getComparator() {
final HierarchyBrowserManager instance = HierarchyBrowserManager.getInstance(myProject);
if (instance != null && instance.getState() != null && instance.getState().SORT_ALPHABETICALLY) {
return AlphaComparator.INSTANCE;
} else {
return NODE_DESCRIPTOR_COMPARATOR;
}
}

/*
Defined in PyCallHierarchyBrowser but not sure it's correct
@Override
public @NotNull ActionUpdateThread getActionUpdateThread() {
return ActionUpdateThread.EDT;
}
*/
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.kdb.inside.brains.lang.hierarchy;

import com.intellij.ide.hierarchy.CallHierarchyBrowserBase;
import com.intellij.ide.hierarchy.HierarchyBrowser;
import com.intellij.ide.hierarchy.HierarchyProvider;
import com.intellij.openapi.actionSystem.CommonDataKeys;
import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.kdb.inside.brains.psi.QPsiUtil;
import org.kdb.inside.brains.psi.QVarDeclaration;

public class QCallHierarchyProvider implements HierarchyProvider {
@Override
public @Nullable PsiElement getTarget(@NotNull DataContext dataContext) {
final Project project = CommonDataKeys.PROJECT.getData(dataContext);
if (project == null) {
return null;
}
final PsiElement element = CommonDataKeys.PSI_ELEMENT.getData(dataContext);
if (element instanceof QVarDeclaration v && QPsiUtil.isGlobalDeclaration(v)) {
return element;
}
return null;
}

@Override
public @NotNull HierarchyBrowser createHierarchyBrowser(@NotNull PsiElement target) {
return new QCallHierarchyBrowser(target);
}

@Override
public void browserActivated(@NotNull HierarchyBrowser hierarchyBrowser) {
((QCallHierarchyBrowser) hierarchyBrowser).changeView(CallHierarchyBrowserBase.getCallerType());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package org.kdb.inside.brains.lang.hierarchy;

import com.intellij.ide.hierarchy.HierarchyNodeDescriptor;
import com.intellij.ide.hierarchy.HierarchyTreeStructure;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;
import org.kdb.inside.brains.psi.QVarDeclaration;

import java.util.*;

public abstract class QCallHierarchyTreeStructureBase extends HierarchyTreeStructure {
private final String myScopeType;

protected QCallHierarchyTreeStructureBase(@NotNull Project project, PsiElement element, String currentScopeType) {
super(project, new QHierarchyNodeDescriptor(project, null, element, true));
this.myScopeType = currentScopeType;
}

@NotNull
protected abstract Map<PsiElement, Collection<PsiElement>> getChildren(@NotNull QVarDeclaration element);

@Override
protected Object @NotNull [] buildChildren(@NotNull HierarchyNodeDescriptor descriptor) {
if (!(descriptor instanceof QHierarchyNodeDescriptor desc)) {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}

if (desc.isRecursion()) {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}

final PsiElement element = desc.getPsiElement();
if (!(element instanceof QVarDeclaration declaration)) {
return ArrayUtil.EMPTY_OBJECT_ARRAY;
}

final List<QHierarchyNodeDescriptor> descriptors = new ArrayList<>();

final Map<PsiElement, Collection<PsiElement>> children = getChildren(declaration);
final Map<PsiElement, QHierarchyNodeDescriptor> callerToDescriptorMap = new HashMap<>(children.size());
children.forEach((c, u) -> {
if (!isInScope(null, c, myScopeType)) {
return;
}

QHierarchyNodeDescriptor callerDescriptor = callerToDescriptorMap.get(c);
if (callerDescriptor == null) {
callerDescriptor = new QHierarchyNodeDescriptor(descriptor.getProject(), descriptor, c, u, false);
callerToDescriptorMap.put(c, callerDescriptor);
descriptors.add(callerDescriptor);
}
});
return ArrayUtil.toObjectArray(descriptors);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package org.kdb.inside.brains.lang.hierarchy;

import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.kdb.inside.brains.psi.*;

import java.util.Collection;
import java.util.Map;

public class QCalleeFunctionTreeStructure extends QCallHierarchyTreeStructureBase {
protected QCalleeFunctionTreeStructure(@NotNull Project project, PsiElement element, String currentScopeType) {
super(project, element, currentScopeType);
}

@Override
protected @NotNull Map<PsiElement, Collection<PsiElement>> getChildren(@NotNull QVarDeclaration declaration) {
final PsiElement parent = declaration.getParent();
if (!(parent instanceof QAssignmentExpr exp)) {
return Map.of();
}

if (!(exp.getExpression() instanceof QLambdaExpr)) {
return Map.of();
}

final Collection<QPsiElement> children = PsiTreeUtil.findChildrenOfAnyType(exp, QVarReference.class, QSymbol.class);

final MultiMap<PsiElement, PsiElement> res = MultiMap.createOrderedSet();
for (QPsiElement child : children) {
final PsiReference reference = child.getReference();
if (reference == null) {
continue;
}
final PsiElement el = reference.resolve();
if (el instanceof QVarDeclaration d && QPsiUtil.isGlobalDeclaration(d)) {
res.putValue(el, child);
}
}
return res.freezeValues();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package org.kdb.inside.brains.lang.hierarchy;

import com.intellij.find.findUsages.FindUsagesHandlerBase;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiElement;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.usageView.UsageInfo;
import com.intellij.util.CommonProcessors;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
import org.kdb.inside.brains.psi.*;

import java.util.Collection;
import java.util.Map;

public class QCallerFunctionTreeStructure extends QCallHierarchyTreeStructureBase {
protected QCallerFunctionTreeStructure(@NotNull Project project, PsiElement element, String currentScopeType) {
super(project, element, currentScopeType);
}

@Override
protected @NotNull Map<PsiElement, Collection<PsiElement>> getChildren(@NotNull QVarDeclaration declaration) {
final Collection<UsageInfo> usages = findUsages(declaration);

final MultiMap<PsiElement, PsiElement> res = MultiMap.createOrderedSet();
for (UsageInfo usage : usages) {
PsiElement element = usage.getElement();
if (element == declaration || !(element instanceof QPsiElement qEl)) {
continue;
}

if (!(element instanceof QVarReference) && !(element instanceof QSymbol)) {
continue;
}

final QAssignmentExpr assign = getGlobalLambdaAssignment(element);
if (assign != null) {
res.putValue(assign.getVarDeclaration(), element);
} else {
res.putValue(element.getContainingFile(), element);
}
}
return res.freezeValues();
}

private QAssignmentExpr getGlobalLambdaAssignment(PsiElement el) {
PsiElement element = el;
while (element != null) {
final QAssignmentExpr assign = PsiTreeUtil.getParentOfType(element, QAssignmentExpr.class);
if (assign != null && QPsiUtil.isGlobalDeclaration(assign) && assign.getExpression() instanceof QLambdaExpr) {
return assign;
}
element = assign;
}
return null;
}


private @NotNull Collection<UsageInfo> findUsages(@NotNull QVarDeclaration declaration) {
final FindUsagesHandlerBase handler = new FindUsagesHandlerBase(declaration);
final CommonProcessors.CollectProcessor<UsageInfo> processor = new CommonProcessors.CollectProcessor<>();
handler.processElementUsages(declaration, processor, handler.getFindUsagesOptions(null));
return processor.getResults();
}
}
Loading

0 comments on commit 216b8a8

Please sign in to comment.