Grouping results of CustomUsageSearcher

Answered

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.

6 comments
Comment actions Permalink

Hi Simon,

Please provide your current implementations snippets.

0
Comment actions Permalink

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.

0
Comment actions Permalink

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.

0
Comment actions Permalink

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 ;-)

0
Comment actions Permalink

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.

 

0
Comment actions Permalink

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:

    @Override
    public @Nullable Object getData(@NotNull @NonNls String dataId) {
        if (UsageView.USAGE_INFO_KEY.is(dataId)) {
            final PsiFile fileFromText = project.getService(PsiFileFactory.class).createFileFromText("dummy", PlainTextFileType.INSTANCE, "No preview available", 0, true);

            return new UsageInfo(fileFromText);
        }

        return null;
    }

The important missing part for me was the "true" parameter.

0

Please sign in to leave a comment.