Grouping results of CustomUsageSearcher
I have added a CustomUsageSearcher which returns non-navigatable results. But every result is shown in the usage view tree as a leaf below a "1 results" node:

I used EP com.intellij.fileStructureGroupRuleProvider which allows me to group the results under each node but I still have a first-level node for every usage:

I use two grouping rules with orders 0 and 1. How can I group results so that all are below the first level group, some in a couple of second level groups and of those some in multiple third level groups. So I want e.g. "13 results", two "A project" nodes and in those file nodes which contain leaves.
I assume I have to create one ProjectUsageGroup for each project node and ClassUsageGroup for each class node in that but I'm still unclear on the top-level grouping.
My usages implement UsageInFile and return a dummy file.
Thanks.
请先登录再写评论。
Hi Simon,
Please provide your current implementations snippets.
Hi Karol,
sure, thank you.
My CustomUsageSearcher:
public void processElementUsages(@NotNull PsiElement element, @NotNull Processor<? super Usage> processor, @NotNull FindUsagesOptions options) {if (element.getLanguage() != JavaLanguage.INSTANCE) {
return;
}
final ReferencesDataProvider dataProvider = ApplicationManager.getApplication().getService(ReferencesDataProvider.class);
final Map<CustomerProject, Collection<String>> referencesForPsiElement = dataProvider.getReferencesForPsiElement(element);
// This returns a Map of projects using the element, with a collection of files per project.
// I would like to have them all shown under one root node, then one node per project, under that a leaf for every file
for (Map.Entry<CustomerProject, Collection<String>> entry : referencesForPsiElement.entrySet()) {
for (String fileName : entry.getValue()) {
processor.process(new ReferenceUsage(element, entry.getKey().getName() + " - " + fileName));
}
}
}
My Usage:
class ReferenceUsage implements UsageInFile{
private final Project project;
private final String text;
public ReferenceUsage(@NotNull PsiElement psiElement, String text) {
this.project = psiElement.getProject();
this.text = text;
}
@Override
public VirtualFile getFile() {
return new LightVirtualFile();
}
@Override
public @NotNull UsagePresentation getPresentation() {
return new UsagePresentation() {
@Override
public @Nullable Icon getIcon() {
return null;
}
@Override
public @NotNull TextChunk @NotNull [] getText() {
return new TextChunk[]{new TextChunk(SimpleTextAttributes.REGULAR_ATTRIBUTES.toTextAttributes(), text)};
}
@Override
public @NotNull String getPlainText() {
return text;
}
@Override
public @NlsContexts.Tooltip @Nullable String getTooltipText() {
return null;
}
};
}
@Override
public boolean isValid() {
return true;
}
@Override
public boolean isReadOnly() {
return true;
}
@Override
public @Nullable FileEditorLocation getLocation() {
return null;
}
@Override
public void selectInEditor() {
}
@Override
public void highlightInEditor() {
}
@Override
public void navigate(boolean requestFocus) {
// TODO sist 04.04.2022: Download file and show
final VirtualFile virtualFile = new LightVirtualFile(text, JavaFileType.INSTANCE, "Helloooooo");
FileEditorManager.getInstance(project).openTextEditor(new OpenFileDescriptor(project, virtualFile), true);
}
@Override
public boolean canNavigate() {
return false;
}
@Override
public boolean canNavigateToSource() {
return true;
}
}
Andy my GroupRulePRovider
public class UsageGroupRuleProvider implements FileStructureGroupRuleProvider {
public static final ReferenceUsageGroupingRule REFERENCE_USAGE_GROUPING_RULE = new ReferenceUsageGroupingRule();
@Override
public @Nullable UsageGroupingRule getUsageGroupingRule(@NotNull Project project) {
return REFERENCE_USAGE_GROUPING_RULE;
}
private static class ReferenceUsageGroupingRule implements UsageGroupingRuleEx {
@Override
public @NotNull List<UsageGroup> getParentGroupsFor(@NotNull Usage usage, UsageTarget @NotNull [] targets) {
if (usage instanceof ReferenceUsage) {
return Arrays.asList(new ProjectUsageGroup(0), new ClassUsageGroup(1));
}
return Collections.emptyList();
}
}
private static class ProjectUsageGroup extends UsageGroupBase {
protected ProjectUsageGroup(int order) {
super(order);
}
@Override
public @NlsContexts.ListItem @NotNull String getPresentableGroupText() {
return "A project";
}
}
private static class ClassUsageGroup extends UsageGroupBase {
protected ClassUsageGroup(int order) {
super(order);
}
@Override
public @NlsContexts.ListItem @NotNull String getPresentableGroupText() {
return "A file";
}
}
}
While we're at it, I would also like to show a preview for the results but got stuck. It looks like this only works with PsiElements with an actual file behind it. I debugged into UsagePreviewPanel but `com.intellij.usages.impl.UsagePreviewPanel#resetEditor` returned because the it is unable to find the document behind my `LightVirtualFile` (even when my usage returned one with content). Should I open a new thread for that?
Thank you, it's appreciated.
Hi Simon,
Please start with implementing equals methods for your usage groups so that they are equal for different usages, and check if it solves the issue.
That didn't help. Neither method is actually called. I also already tried using constants for the groups which didn't help either.
I think the problem is related to `com.intellij.usages.impl.GroupNode#insertGroupNode` and `com.intellij.usages.impl.GroupNode#getNodeIndex`. `com.intellij.usages.impl.GroupNode.NodeComparator#compare` returns a negative value when comparing to FileGroupingRules:
That way a new node is created for each of the usages. I might be completely wrong though ;-)
Yaaaay I got it working! The problem was that `de.sist.tph.intellij.breakingapi.nativeusage.ReferenceUsage#getFile` always returned a new instance of VirtualFile. I replaced that with a constant and now it's working.
If anybody else by any chance finds this threads and wonders (like me) how to show a preview:
I let my Usage implement UsageProvider and return this:
The important missing part for me was the "true" parameter.