From bdd7823e3d8b6fab9cf68be5db3de26cc26a416d Mon Sep 17 00:00:00 2001 From: smklimenko Date: Sun, 25 Feb 2024 13:38:03 +0000 Subject: [PATCH] 5.5.0: Call Hierarchy added (Slice Provider removed) --- CHANGELOG.md | 10 ++ .../lang/hierarchy/QCallHierarchyBrowser.java | 88 +++++++++++ .../hierarchy/QCallHierarchyProvider.java | 38 +++++ .../QCallHierarchyTreeStructureBase.java | 57 +++++++ .../QCalleeFunctionTreeStructure.java | 45 ++++++ .../QCallerFunctionTreeStructure.java | 65 ++++++++ .../hierarchy/QHierarchyNodeDescriptor.java | 142 ++++++++++++++++++ .../slicer/QSliceLanguageSupportProvider.java | 51 ------- .../brains/lang/slicer/QSliceUsage.java | 86 ----------- .../slicer/QSliceUsageCellRendererBase.java | 33 ---- .../org/kdb/inside/brains/psi/QPsiUtil.java | 31 +++- .../view/struct/QStructureViewElement.java | 14 +- src/main/resources/META-INF/plugin.xml | 5 +- .../kdb/inside/brains/psi/QPsiUtilTest.java | 54 +++++++ version.properties | 2 +- 15 files changed, 538 insertions(+), 183 deletions(-) create mode 100644 src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallHierarchyBrowser.java create mode 100644 src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallHierarchyProvider.java create mode 100644 src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallHierarchyTreeStructureBase.java create mode 100644 src/main/java/org/kdb/inside/brains/lang/hierarchy/QCalleeFunctionTreeStructure.java create mode 100644 src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallerFunctionTreeStructure.java create mode 100644 src/main/java/org/kdb/inside/brains/lang/hierarchy/QHierarchyNodeDescriptor.java delete mode 100644 src/main/java/org/kdb/inside/brains/lang/slicer/QSliceLanguageSupportProvider.java delete mode 100644 src/main/java/org/kdb/inside/brains/lang/slicer/QSliceUsage.java delete mode 100644 src/main/java/org/kdb/inside/brains/lang/slicer/QSliceUsageCellRendererBase.java create mode 100644 src/test/java/org/kdb/inside/brains/psi/QPsiUtilTest.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ccfd996..e423dab 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallHierarchyBrowser.java b/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallHierarchyBrowser.java new file mode 100644 index 0000000..216087f --- /dev/null +++ b/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallHierarchyBrowser.java @@ -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> 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 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> 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; + } +*/ +} diff --git a/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallHierarchyProvider.java b/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallHierarchyProvider.java new file mode 100644 index 0000000..3b622b9 --- /dev/null +++ b/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallHierarchyProvider.java @@ -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()); + } +} diff --git a/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallHierarchyTreeStructureBase.java b/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallHierarchyTreeStructureBase.java new file mode 100644 index 0000000..ed6dc6a --- /dev/null +++ b/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallHierarchyTreeStructureBase.java @@ -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> 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 descriptors = new ArrayList<>(); + + final Map> children = getChildren(declaration); + final Map 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); + } +} diff --git a/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCalleeFunctionTreeStructure.java b/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCalleeFunctionTreeStructure.java new file mode 100644 index 0000000..c0763dc --- /dev/null +++ b/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCalleeFunctionTreeStructure.java @@ -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> 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 children = PsiTreeUtil.findChildrenOfAnyType(exp, QVarReference.class, QSymbol.class); + + final MultiMap 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(); + } +} diff --git a/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallerFunctionTreeStructure.java b/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallerFunctionTreeStructure.java new file mode 100644 index 0000000..91e7542 --- /dev/null +++ b/src/main/java/org/kdb/inside/brains/lang/hierarchy/QCallerFunctionTreeStructure.java @@ -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> getChildren(@NotNull QVarDeclaration declaration) { + final Collection usages = findUsages(declaration); + + final MultiMap 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 findUsages(@NotNull QVarDeclaration declaration) { + final FindUsagesHandlerBase handler = new FindUsagesHandlerBase(declaration); + final CommonProcessors.CollectProcessor processor = new CommonProcessors.CollectProcessor<>(); + handler.processElementUsages(declaration, processor, handler.getFindUsagesOptions(null)); + return processor.getResults(); + } +} diff --git a/src/main/java/org/kdb/inside/brains/lang/hierarchy/QHierarchyNodeDescriptor.java b/src/main/java/org/kdb/inside/brains/lang/hierarchy/QHierarchyNodeDescriptor.java new file mode 100644 index 0000000..9b358d5 --- /dev/null +++ b/src/main/java/org/kdb/inside/brains/lang/hierarchy/QHierarchyNodeDescriptor.java @@ -0,0 +1,142 @@ +package org.kdb.inside.brains.lang.hierarchy; + +import com.intellij.ide.IdeBundle; +import com.intellij.ide.hierarchy.HierarchyNodeDescriptor; +import com.intellij.ide.util.treeView.NodeDescriptor; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.roots.ui.util.CompositeAppearance; +import com.intellij.openapi.util.Comparing; +import com.intellij.pom.Navigatable; +import com.intellij.psi.*; +import com.intellij.ui.SimpleTextAttributes; +import com.intellij.util.containers.ContainerUtil; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.kdb.inside.brains.psi.QAssignmentExpr; +import org.kdb.inside.brains.psi.QLambdaExpr; +import org.kdb.inside.brains.psi.QVarDeclaration; +import org.kdb.inside.brains.psi.QVariable; + +import java.util.Collection; +import java.util.List; + +import static org.kdb.inside.brains.psi.QPsiUtil.getLambdaDescriptor; + +public class QHierarchyNodeDescriptor extends HierarchyNodeDescriptor implements Navigatable { + private final boolean recursion; + private final List> myUsages; + + protected QHierarchyNodeDescriptor(@NotNull Project project, @Nullable NodeDescriptor parent, @NotNull PsiElement element, boolean isBase) { + this(project, parent, element, List.of(), isBase); + } + + protected QHierarchyNodeDescriptor(@NotNull Project project, @Nullable NodeDescriptor parent, @NotNull PsiElement element, @NotNull Collection usages, boolean isBase) { + super(project, parent, element, isBase); + recursion = recursion(element); + + final var pointerManager = SmartPointerManager.getInstance(project); + myUsages = ContainerUtil.map(usages, pointerManager::createSmartPsiElementPointer); + } + + public boolean isRecursion() { + return recursion; + } + + @Override + public boolean update() { + boolean changes = super.update(); + final CompositeAppearance oldText = myHighlightedText; + + myHighlightedText = new CompositeAppearance(); + + final NavigatablePsiElement element = (NavigatablePsiElement) getPsiElement(); + if (element == null) { + return invalidElement(); + } + + final CompositeAppearance.DequeEnd ending = myHighlightedText.getEnding(); + + installIcon(element, false); + ending.addText(getNodeName(element)); + ending.addText(" "); + + int count = myUsages.size(); + if (count > 1) { + String text = IdeBundle.message("node.call.hierarchy.N.usages", count); + myHighlightedText.getEnding().addText(text, HierarchyNodeDescriptor.getUsageCountPrefixAttributes()); + ending.addText(" "); + } + + ending.addText("(" + element.getContainingFile().getName() + ")", HierarchyNodeDescriptor.getPackageNameAttributes()); + + if (isRecursion()) { + ending.addText(" "); + ending.addText("(recursion call)", SimpleTextAttributes.SYNTHETIC_ATTRIBUTES); + } + + myName = myHighlightedText.getText(); + + if (!Comparing.equal(myHighlightedText, oldText)) { + changes = true; + } + return changes; + } + + private String getNodeName(PsiElement element) { + if (element instanceof QVarDeclaration dec) { + final String name = dec.getQualifiedName(); + final PsiElement parent = dec.getParent(); + if (parent instanceof QAssignmentExpr a && a.getExpression() instanceof QLambdaExpr l) { + return getLambdaDescriptor(name, l); + } + } + if (element instanceof QVariable var) { + return var.getQualifiedName(); + } + if (element instanceof PsiFile f) { + return f.getName(); + } + return "Undefined Call: " + element.getText(); + } + + @Override + public void navigate(boolean requestFocus) { + Navigatable element = getNavigationTarget(); + if (element != null && element.canNavigate()) { + element.navigate(requestFocus); + } + } + + @Override + public boolean canNavigate() { + Navigatable element = getNavigationTarget(); + return element != null && element.canNavigate(); + } + + @Override + public boolean canNavigateToSource() { + return canNavigate(); + } + + private @Nullable Navigatable getNavigationTarget() { + if (!myUsages.isEmpty() && myUsages.get(0).getElement() instanceof Navigatable nav) { + return nav; + } + if (getPsiElement() instanceof Navigatable nav) { + return nav; + } + return null; + } + + private boolean recursion(PsiElement d) { + NodeDescriptor e = getParentDescriptor(); + while (e != null) { + final PsiElement psiElement = ((HierarchyNodeDescriptor) e).getPsiElement(); + if (d.equals(psiElement)) { + return true; + } + e = e.getParentDescriptor(); + } + return false; + } +} diff --git a/src/main/java/org/kdb/inside/brains/lang/slicer/QSliceLanguageSupportProvider.java b/src/main/java/org/kdb/inside/brains/lang/slicer/QSliceLanguageSupportProvider.java deleted file mode 100644 index bd21796..0000000 --- a/src/main/java/org/kdb/inside/brains/lang/slicer/QSliceLanguageSupportProvider.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.kdb.inside.brains.lang.slicer; - -import com.intellij.ide.util.treeView.AbstractTreeStructure; -import com.intellij.openapi.actionSystem.DefaultActionGroup; -import com.intellij.psi.PsiElement; -import com.intellij.slicer.*; -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 QSliceLanguageSupportProvider implements SliceLanguageSupportProvider { - @Override - public @NotNull SliceUsage createRootUsage(@NotNull PsiElement element, @NotNull SliceAnalysisParams params) { - return new QSliceUsage(element, params); - } - - @Override - public @Nullable PsiElement getExpressionAtCaret(@NotNull PsiElement element, boolean dataFlowToThis) { - if (dataFlowToThis) { - return null; - } - final PsiElement parent = element.getParent(); - if (parent instanceof QVarDeclaration && QPsiUtil.isGlobalDeclaration((QVarDeclaration) parent)) { - return parent; - } - return null; - } - - @Override - public @NotNull PsiElement getElementForDescription(@NotNull PsiElement element) { - return element; - } - - @Override - public @NotNull SliceUsageCellRendererBase getRenderer() { - return new QSliceUsageCellRendererBase(); - } - - @Override - public void startAnalyzeNullness(@NotNull AbstractTreeStructure structure, @NotNull Runnable finalRunnable) { - } - - @Override - public void startAnalyzeLeafValues(@NotNull AbstractTreeStructure structure, @NotNull Runnable finalRunnable) { - } - - @Override - public void registerExtraPanelActions(@NotNull DefaultActionGroup group, @NotNull SliceTreeBuilder builder) { - } -} diff --git a/src/main/java/org/kdb/inside/brains/lang/slicer/QSliceUsage.java b/src/main/java/org/kdb/inside/brains/lang/slicer/QSliceUsage.java deleted file mode 100644 index 02dcd4b..0000000 --- a/src/main/java/org/kdb/inside/brains/lang/slicer/QSliceUsage.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.kdb.inside.brains.lang.slicer; - -import com.intellij.psi.PsiElement; -import com.intellij.psi.PsiReference; -import com.intellij.psi.util.PsiTreeUtil; -import com.intellij.slicer.SliceAnalysisParams; -import com.intellij.slicer.SliceUsage; -import com.intellij.util.Processor; -import org.jetbrains.annotations.NotNull; -import org.kdb.inside.brains.psi.*; - -import java.util.Collection; -import java.util.Objects; - -public class QSliceUsage extends SliceUsage { - private final boolean recursion; - - public QSliceUsage(@NotNull PsiElement element, @NotNull SliceAnalysisParams params) { - super(element, params); - recursion = recursion(element); - } - - public QSliceUsage(@NotNull QVarDeclaration element, @NotNull SliceUsage parent, @NotNull SliceAnalysisParams params) { - super(element, parent, params); - recursion = recursion(element); - } - - public boolean isRecursion() { - return recursion; - } - - @Override - protected @NotNull SliceUsage copy() { - return new QSliceUsage((QVarDeclaration) Objects.requireNonNull(getElement()), getParent(), params); - } - - @Override - protected void processUsagesFlownFromThe(PsiElement element, Processor uniqueProcessor) { - if (recursion) { - return; - } - - if (!(element instanceof QVarDeclaration)) { - return; - } - - final PsiElement parent = element.getParent(); - if (!(parent instanceof QAssignmentExpr exp)) { - return; - } - - if (!(exp.getExpression() instanceof QLambdaExpr)) { - return; - } - - final Collection children = PsiTreeUtil.findChildrenOfAnyType(exp, QVarReference.class, QSymbol.class); - for (QPsiElement child : children) { - final PsiReference reference = child.getReference(); - if (reference == null) { - continue; - } - final PsiElement el = reference.resolve(); - if (!(el instanceof QVarDeclaration d) || (params.valueFilter != null && !params.valueFilter.allowed(el)) || !params.scope.contains(el)) { - continue; - } - if (QPsiUtil.isGlobalDeclaration(d)) { - uniqueProcessor.process(new QSliceUsage(d, QSliceUsage.this, params)); - } - } - } - - private boolean recursion(PsiElement d) { - SliceUsage e = this.getParent(); - while (e != null) { - if (d.equals(e.getElement())) { - return true; - } - e = e.getParent(); - } - return false; - } - - @Override - protected void processUsagesFlownDownTo(PsiElement element, Processor uniqueProcessor) { - } -} diff --git a/src/main/java/org/kdb/inside/brains/lang/slicer/QSliceUsageCellRendererBase.java b/src/main/java/org/kdb/inside/brains/lang/slicer/QSliceUsageCellRendererBase.java deleted file mode 100644 index a7494cd..0000000 --- a/src/main/java/org/kdb/inside/brains/lang/slicer/QSliceUsageCellRendererBase.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.kdb.inside.brains.lang.slicer; - -import com.intellij.psi.PsiElement; -import com.intellij.slicer.SliceUsage; -import com.intellij.slicer.SliceUsageCellRendererBase; -import com.intellij.ui.SimpleTextAttributes; -import com.intellij.util.FontUtil; -import org.jetbrains.annotations.NotNull; -import org.kdb.inside.brains.psi.QVarDeclaration; - -public class QSliceUsageCellRendererBase extends SliceUsageCellRendererBase { - @Override - public void customizeCellRendererFor(@NotNull SliceUsage sliceUsage) { - final QSliceUsage usage = (QSliceUsage) sliceUsage; - - final PsiElement element = sliceUsage.getElement(); - if (!(element instanceof QVarDeclaration declaration)) { - return; - } - - setIcon(declaration.getIcon(0)); - append(String.valueOf(sliceUsage.getLine()), SimpleTextAttributes.GRAY_ATTRIBUTES); - append(FontUtil.spaceAndThinSpace()); - append(declaration.getQualifiedName(), SimpleTextAttributes.REGULAR_BOLD_ATTRIBUTES); - append(FontUtil.spaceAndThinSpace()); - append(usage.getFile().getName(), SimpleTextAttributes.GRAY_ATTRIBUTES); - - if (usage.isRecursion()) { - append(FontUtil.spaceAndThinSpace()); - append("(recursion call)", SimpleTextAttributes.SYNTHETIC_ATTRIBUTES); - } - } -} \ No newline at end of file diff --git a/src/main/java/org/kdb/inside/brains/psi/QPsiUtil.java b/src/main/java/org/kdb/inside/brains/psi/QPsiUtil.java index df19238..4a2a4a1 100644 --- a/src/main/java/org/kdb/inside/brains/psi/QPsiUtil.java +++ b/src/main/java/org/kdb/inside/brains/psi/QPsiUtil.java @@ -9,6 +9,7 @@ import org.kdb.inside.brains.QFileType; import java.util.function.Predicate; +import java.util.stream.Collectors; public final class QPsiUtil { private QPsiUtil() { @@ -41,7 +42,8 @@ public static boolean isImplicitVariable(@NotNull QVariable variable) { } public static boolean isGlobalDeclaration(@NotNull QAssignmentExpr assignment) { - return isGlobalDeclaration(assignment.getVarDeclaration()); + final QVarDeclaration varDeclaration = assignment.getVarDeclaration(); + return varDeclaration != null && isGlobalDeclaration(varDeclaration); } public static boolean isGlobalDeclaration(@NotNull QVarDeclaration declaration) { @@ -63,6 +65,16 @@ public static boolean isGlobalDeclaration(@NotNull QVarDeclaration declaration) return false; } + public static String getLambdaDescriptor(String name, QLambdaExpr lambda) { + final QParameters parameters = lambda.getParameters(); + if (parameters == null) { + return name + "[]"; + } else { + final String collect = parameters.getVariables().stream().map(QVariable::getName).collect(Collectors.joining(";")); + return name + "[" + collect + "]"; + } + } + /** * Checks is the specified element colon or not. * @@ -160,6 +172,23 @@ public static PsiElement createLambdaDeclaration(Project project, boolean global return QFileType.createFactoryFile(project, b.toString()).getFirstChild(); } + public static String getImportContent(QImport qImport) { + if (qImport instanceof QImportFunction f && f.getExpression() != null) { + String text = "\"" + f.getExpression().getText().trim().substring(3); // remove 'l '; + if (text.startsWith("\"\"")) { + text = text.substring(2); + } + if (text.charAt(0) == '"' && text.charAt(text.length() - 1) == '"') { + text = text.substring(1, text.length() - 1); + } + if (text.charAt(0) == ',') { + text = text.substring(1); + } + return text; + } + return qImport.getFilePath(); + } + public static QVarDeclaration createVarDeclaration(Project project, String name) { return PsiTreeUtil.findChildOfType(QFileType.createFactoryFile(project, name + ":`"), QVarDeclaration.class); } diff --git a/src/main/java/org/kdb/inside/brains/view/struct/QStructureViewElement.java b/src/main/java/org/kdb/inside/brains/view/struct/QStructureViewElement.java index 661d354..2f55b98 100644 --- a/src/main/java/org/kdb/inside/brains/view/struct/QStructureViewElement.java +++ b/src/main/java/org/kdb/inside/brains/view/struct/QStructureViewElement.java @@ -14,6 +14,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.kdb.inside.brains.psi.QPsiUtil.getImportContent; +import static org.kdb.inside.brains.psi.QPsiUtil.getLambdaDescriptor; + public class QStructureViewElement extends PsiTreeElementBase { private final String text; private final PsiElement content; @@ -36,7 +39,7 @@ private QStructureViewElement(PsiElement element, StructureElementType type, Str public static @Nullable QStructureViewElement createViewElement(PsiElement child) { if (child instanceof QImport qImport) { - return new QStructureViewElement(child, StructureElementType.IMPORT, qImport.getFilePath()); + return new QStructureViewElement(child, StructureElementType.IMPORT, getImportContent(qImport)); } else if (child instanceof QCommand) { return new QStructureViewElement(child, StructureElementType.COMMAND, child.getText()); } else if (child instanceof QContext context) { @@ -82,14 +85,7 @@ private QStructureViewElement(PsiElement element, StructureElementType type, Str @NotNull private static QStructureViewElement createLambdaElement(PsiElement element, QLambdaExpr lambda, String namePrefix) { - final QParameters parameters = lambda.getParameters(); - if (parameters == null) { - namePrefix += "[]"; - } else { - final String collect = parameters.getVariables().stream().map(QVariable::getName).collect(Collectors.joining(";")); - namePrefix += "[" + collect + "]"; - } - return new QStructureViewElement(element, StructureElementType.LAMBDA, namePrefix, lambda.getExpressions()); + return new QStructureViewElement(element, StructureElementType.LAMBDA, getLambdaDescriptor(namePrefix, lambda), lambda.getExpressions()); } private static String getExpressionType(QExpression expression) { diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index f5a04ae..c41eda5 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -460,8 +460,9 @@ implementationClass="org.kdb.inside.brains.lang.refactoring.QRefactoringSupportProvider"/> - + + diff --git a/src/test/java/org/kdb/inside/brains/psi/QPsiUtilTest.java b/src/test/java/org/kdb/inside/brains/psi/QPsiUtilTest.java new file mode 100644 index 0000000..61f5555 --- /dev/null +++ b/src/test/java/org/kdb/inside/brains/psi/QPsiUtilTest.java @@ -0,0 +1,54 @@ +package org.kdb.inside.brains.psi; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.*; + +class QPsiUtilTest { + public static QVarDeclaration createVarDeclaration(String name) { + final QVarDeclaration v = mock(QVarDeclaration.class); + when(v.getText()).thenReturn(name); + when(v.getName()).thenReturn(name); + when(v.getQualifiedName()).thenReturn(name); + doCallRealMethod().when(v).getSimpleName(); + return v; + } + + @Test + void getLambdaDescriptor() { + final QLambdaExpr lambda = mock(QLambdaExpr.class); + assertEquals("mock[]", QPsiUtil.getLambdaDescriptor("mock", lambda)); + + final QParameters parameters = mock(QParameters.class); + when(lambda.getParameters()).thenReturn(parameters); + assertEquals("mock[]", QPsiUtil.getLambdaDescriptor("mock", lambda)); + + doReturn(List.of(createVarDeclaration("v1"), createVarDeclaration("v2"))).when(parameters).getVariables(); + assertEquals("mock[v1;v2]", QPsiUtil.getLambdaDescriptor("mock", lambda)); + } + + @Test + void getImportContent() { + final QExpression expr = mock(); + final QImportFunction f = mock(QImportFunction.class); + when(f.getExpression()).thenReturn(expr); + + when(expr.getText()).thenReturn("\"l mock.q\""); + assertEquals("mock.q", QPsiUtil.getImportContent(f)); + + when(expr.getText()).thenReturn("\"l \",var"); + assertEquals("var", QPsiUtil.getImportContent(f)); + + when(expr.getText()).thenReturn("\"l asd/qwe/\",var"); + assertEquals("\"asd/qwe/\",var", QPsiUtil.getImportContent(f)); + + final QImportCommand cmd = mock(QImportCommand.class); + when(cmd.getText()).thenReturn("\\l asd/qwe.q"); + when(cmd.getFilePath()).thenCallRealMethod(); + when(cmd.getFilePathRange()).thenCallRealMethod(); + assertEquals("asd/qwe.q", QPsiUtil.getImportContent(cmd)); + } +} \ No newline at end of file diff --git a/version.properties b/version.properties index f35aee1..9967e76 100644 --- a/version.properties +++ b/version.properties @@ -1 +1 @@ -pluginVersion=5.4.0 \ No newline at end of file +pluginVersion=5.5.0 \ No newline at end of file