From a19bab94147009fa57d4c83d3dfffa0b8f393ce6 Mon Sep 17 00:00:00 2001 From: Alexey Kudravtsev Date: Fri, 1 Jul 2022 14:36:16 +0200 Subject: [PATCH] re-fix "comparator breaks contract" error (e.g. in ea.jetbrains.com/browser/ea_reports/9957034): introduce Usage.getNavigationOffset() and use it to sort children nodes GitOrigin-RevId: 830f5693452c6a2b4ac5c33bd94d06280fdce1a3 --- .../intellij/usages/impl/UsageViewImpl.java | 37 ++++++++----------- .../src/com/intellij/usages/Usage.java | 20 ++++++++++ .../usages/UsageInfo2UsageAdapter.java | 2 +- 3 files changed, 37 insertions(+), 22 deletions(-) diff --git a/platform/usageView-impl/src/com/intellij/usages/impl/UsageViewImpl.java b/platform/usageView-impl/src/com/intellij/usages/impl/UsageViewImpl.java index 3927d1bac83e..2306a3ffb60e 100644 --- a/platform/usageView-impl/src/com/intellij/usages/impl/UsageViewImpl.java +++ b/platform/usageView-impl/src/com/intellij/usages/impl/UsageViewImpl.java @@ -28,6 +28,7 @@ import com.intellij.openapi.util.*; import com.intellij.openapi.vcs.FileStatusListener; import com.intellij.openapi.vcs.FileStatusManager; import com.intellij.openapi.vfs.ReadonlyStatusHandler; +import com.intellij.openapi.vfs.VfsUtilCore; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.pom.Navigatable; import com.intellij.psi.*; @@ -125,29 +126,23 @@ public class UsageViewImpl implements UsageViewEx { public static final Comparator USAGE_COMPARATOR_BY_FILE_AND_OFFSET = (o1, o2) -> { if (o1 == o2) return 0; - if (o1 == NullUsage.INSTANCE) return -1; - if (o2 == NullUsage.INSTANCE) return 1; - if (o1 instanceof Comparable && o2 instanceof Comparable && o1.getClass() == o2.getClass()) { - //noinspection unchecked - int selfcompared = ((Comparable)o1).compareTo(o2); - if (selfcompared != 0) return selfcompared; - - if (o1 instanceof UsageInFile && o2 instanceof UsageInFile) { - UsageInFile u1 = (UsageInFile)o1; - UsageInFile u2 = (UsageInFile)o2; - - VirtualFile f1 = u1.getFile(); - VirtualFile f2 = u2.getFile(); - - if (f1 != null && f1.isValid() && f2 != null && f2.isValid()) { - return f1.getPresentableUrl().compareTo(f2.getPresentableUrl()); - } - } - - return 0; - } + if (o1 == NullUsage.INSTANCE || o1 == null) return -1; + if (o2 == NullUsage.INSTANCE || o2 == null) return 1; + int c = compareByFileAndOffset(o1, o2); + if (c != 0) return c; return o1.toString().compareTo(o2.toString()); }; + + private static int compareByFileAndOffset(@NotNull Usage o1, @NotNull Usage o2) { + VirtualFile file1 = o1 instanceof UsageInFile ? ((UsageInFile)o1).getFile() : null; + VirtualFile file2 = o2 instanceof UsageInFile ? ((UsageInFile)o2).getFile() : null; + if (file1 == null || file2 == null) return 0; + if (file1.equals(file2)) { + return Integer.compare(o1.getNavigationOffset(), o2.getNavigationOffset()); + } + return VfsUtilCore.compareByPath(file1, file2); + } + @NonNls public static final String HELP_ID = "ideaInterface.find"; private UsageContextPanel myCurrentUsageContextPanel; // accessed in EDT only private final List myAllUsageContextPanels = new ArrayList<>(); // accessed in EDT only diff --git a/platform/usageView/src/com/intellij/usages/Usage.java b/platform/usageView/src/com/intellij/usages/Usage.java index a0cb2eba952c..d05e0b15c97d 100644 --- a/platform/usageView/src/com/intellij/usages/Usage.java +++ b/platform/usageView/src/com/intellij/usages/Usage.java @@ -1,7 +1,10 @@ // Copyright 2000-2021 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file. package com.intellij.usages; +import com.intellij.openapi.editor.LogicalPosition; import com.intellij.openapi.fileEditor.FileEditorLocation; +import com.intellij.openapi.fileEditor.TextEditor; +import com.intellij.openapi.fileEditor.TextEditorLocation; import com.intellij.pom.Navigatable; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -23,4 +26,21 @@ public interface Usage extends Navigatable { void selectInEditor(); void highlightInEditor(); + + /** + * @return offset of this usage in its containing file to which the corresponding "Go to Source" action should navigate, + * or {@code -1} if the offset can't be computed for some reason. + * This offset is used in "Find Usages" tool window tree to group usages by containing file and then by their offsets. + * Please consider overriding this method if you implement {@link Usage} from scratch and can compute its offset efficiently. + * The already existing implementations in the core, like {@link UsageInfo2UsageAdapter} implement this method efficiently enough, so there's no need to override them. + * Also, please make your {@link Usage} implementation extend {@link com.intellij.usages.rules.UsageInFile} to be able to group usages in "Find Usages" tool window. + */ + default int getNavigationOffset() { + FileEditorLocation location = getLocation(); + if (location instanceof TextEditorLocation) { + LogicalPosition position = ((TextEditorLocation)location).getPosition(); + return ((TextEditor)location.getEditor()).getEditor().logicalPositionToOffset(position); + } + return -1; + } } diff --git a/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java b/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java index 3681e3d5a984..aa4bfa16b4d2 100644 --- a/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java +++ b/platform/usageView/src/com/intellij/usages/UsageInfo2UsageAdapter.java @@ -41,7 +41,7 @@ import java.util.concurrent.CompletableFuture; public class UsageInfo2UsageAdapter implements UsageInModule, UsageInfoAdapter, UsageInLibrary, UsageInFile, PsiElementUsage, - MergeableUsage, Comparable, + MergeableUsage, RenameableUsage, DataProvider, UsagePresentation { public static final NotNullFunction CONVERTER = UsageInfo2UsageAdapter::new; private static final Comparator BY_NAVIGATION_OFFSET = Comparator.comparingInt(UsageInfo::getNavigationOffset);