mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-05 08:06:56 +07:00
AT-339 Add metrics for popup shown and first element found to the performance tests for finding usages
GitOrigin-RevId: 07f7795357bb92b3125dd08739a0cdc7f8a2da6b
This commit is contained in:
committed by
intellij-monorepo-bot
parent
460522beb6
commit
7530cd5f50
@@ -1,116 +0,0 @@
|
||||
package com.intellij.java.performancePlugin
|
||||
|
||||
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction
|
||||
import com.intellij.find.FindManager
|
||||
import com.intellij.find.findUsages.FindUsagesHandlerFactory.OperationMode
|
||||
import com.intellij.find.findUsages.FindUsagesManager
|
||||
import com.intellij.find.findUsages.JavaClassFindUsagesOptions
|
||||
import com.intellij.find.findUsages.JavaMethodFindUsagesOptions
|
||||
import com.intellij.find.findUsages.JavaVariableFindUsagesOptions
|
||||
import com.intellij.find.impl.FindManagerImpl
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.ui.playback.PlaybackContext
|
||||
import com.intellij.openapi.ui.playback.commands.AbstractCommand
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.psi.PsiDocumentManager
|
||||
import com.intellij.psi.PsiNamedElement
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.usages.Usage
|
||||
import com.intellij.util.Processors
|
||||
import com.jetbrains.performancePlugin.PerformanceTestSpan.TRACER
|
||||
import com.jetbrains.performancePlugin.commands.FindUsagesCommand
|
||||
import com.jetbrains.performancePlugin.commands.FindUsagesCommand.storeMetricsDumpFoundUsages
|
||||
import com.jetbrains.performancePlugin.commands.GoToNamedElementCommand
|
||||
import com.jetbrains.performancePlugin.utils.ActionCallbackProfilerStopper
|
||||
import io.opentelemetry.api.trace.Span
|
||||
import io.opentelemetry.context.Context
|
||||
import org.jetbrains.concurrency.Promise
|
||||
import org.jetbrains.concurrency.toPromise
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
|
||||
class FindUsagesJavaCommand(text: String, line: Int) : AbstractCommand(text, line) {
|
||||
|
||||
companion object {
|
||||
const val PREFIX = CMD_PREFIX + "findUsagesJava"
|
||||
private val LOG = Logger.getInstance(FindUsagesJavaCommand::class.java)
|
||||
}
|
||||
|
||||
override fun _execute(context: PlaybackContext): Promise<Any?> {
|
||||
val actionCallback = ActionCallbackProfilerStopper()
|
||||
val arguments = text.split(" ".toRegex()).toTypedArray()
|
||||
val position = if (arguments.size == 3) arguments[1] else null
|
||||
val elementName = if (arguments.size == 3) arguments[2] else arguments[1]
|
||||
if (position != null) {
|
||||
val result = GoToNamedElementCommand(GoToNamedElementCommand.PREFIX + " $position $elementName", -1).execute(context)
|
||||
result.onError {
|
||||
actionCallback.reject("fail to go to element $elementName")
|
||||
}
|
||||
}
|
||||
val findUsagesFinished = CountDownLatch(1)
|
||||
val allUsages: List<Usage> = ArrayList()
|
||||
val project = context.project
|
||||
val span: Ref<Span> = Ref()
|
||||
DumbService.getInstance(project).smartInvokeLater(Context.current().wrap(fun() {
|
||||
val editor = FileEditorManager.getInstance(project).selectedTextEditor
|
||||
if (editor == null) {
|
||||
actionCallback.reject("The action invoked without editor")
|
||||
return
|
||||
}
|
||||
val psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.document)
|
||||
if (psiFile == null) {
|
||||
actionCallback.reject("Psi File is not found")
|
||||
return
|
||||
}
|
||||
val offset = editor.caretModel.offset
|
||||
val element = when (GotoDeclarationAction.findElementToShowUsagesOf(editor, offset)) {
|
||||
null -> GotoDeclarationAction.findTargetElement(project, editor, offset)
|
||||
else -> GotoDeclarationAction.findElementToShowUsagesOf(editor, offset)
|
||||
}
|
||||
if (element == null) {
|
||||
actionCallback.reject("Can't find an element under $offset offset.")
|
||||
return
|
||||
}
|
||||
val foundElementName = (element as PsiNamedElement).name
|
||||
if (elementName.isNotEmpty()) {
|
||||
check(
|
||||
foundElementName != null && foundElementName == elementName) { "Found element name $foundElementName does not correspond to expected $elementName" }
|
||||
}
|
||||
LOG.info("Command find usages is called on element $element")
|
||||
val findUsagesManager = (FindManager.getInstance(project) as FindManagerImpl).findUsagesManager
|
||||
val handler = findUsagesManager.getFindUsagesHandler(element, OperationMode.USAGES_WITH_DEFAULT_OPTIONS)
|
||||
if (handler == null) {
|
||||
actionCallback.reject("No find usage handler found for the element:" + element.text)
|
||||
return
|
||||
}
|
||||
val findUsagesOptions = when {
|
||||
element.toString().contains("PsiClass") -> JavaClassFindUsagesOptions(project).apply {
|
||||
searchScope = GlobalSearchScope.allScope(project)
|
||||
}
|
||||
element.toString().contains("PsiMethod") -> JavaMethodFindUsagesOptions(project).apply {
|
||||
searchScope = GlobalSearchScope.allScope(project)
|
||||
}
|
||||
else -> JavaVariableFindUsagesOptions(project).apply {
|
||||
searchScope = GlobalSearchScope.allScope(project)
|
||||
}
|
||||
}
|
||||
val collectProcessor = Processors.cancelableCollectProcessor(Collections.synchronizedList(allUsages))
|
||||
span.set(TRACER.spanBuilder(FindUsagesCommand.SPAN_NAME).startSpan())
|
||||
FindUsagesManager.startProcessUsages(handler, handler.primaryElements, handler.secondaryElements, collectProcessor,
|
||||
findUsagesOptions) { findUsagesFinished.countDown() }
|
||||
}))
|
||||
try {
|
||||
findUsagesFinished.await()
|
||||
span.get().setAttribute("number", allUsages.size.toLong())
|
||||
span.get().end()
|
||||
}
|
||||
catch (e: InterruptedException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
storeMetricsDumpFoundUsages(allUsages, project)
|
||||
actionCallback.setDone()
|
||||
return actionCallback.toPromise()
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ final class JavaCommandProvider implements CommandProvider {
|
||||
public @NotNull
|
||||
Map<String, CreateCommand> getCommands() {
|
||||
return Map.of(
|
||||
FindUsagesJavaCommand.PREFIX, FindUsagesJavaCommand::new,
|
||||
BuildCommand.PREFIX, BuildCommand::new,
|
||||
SyncJpsLibrariesCommand.PREFIX, SyncJpsLibrariesCommand::new,
|
||||
CreateJavaFileCommand.PREFIX, CreateJavaFileCommand::new
|
||||
|
||||
@@ -56,6 +56,8 @@ import com.intellij.openapi.util.text.HtmlBuilder;
|
||||
import com.intellij.openapi.util.text.HtmlChunk;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.wm.IdeFocusManager;
|
||||
import com.intellij.platform.diagnostic.telemetry.IJTracer;
|
||||
import com.intellij.platform.diagnostic.telemetry.TelemetryTracer;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.search.LocalSearchScope;
|
||||
@@ -77,6 +79,8 @@ import com.intellij.util.concurrency.EdtScheduledExecutorService;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.messages.MessageBusConnection;
|
||||
import com.intellij.util.ui.*;
|
||||
import io.opentelemetry.api.trace.Span;
|
||||
import io.opentelemetry.context.Scope;
|
||||
import org.jetbrains.annotations.*;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -91,6 +95,7 @@ import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
@@ -103,6 +108,7 @@ import java.util.stream.Collectors;
|
||||
import static com.intellij.find.actions.ResolverKt.findShowUsages;
|
||||
import static com.intellij.find.actions.ShowUsagesActionHandler.getSecondInvocationHint;
|
||||
import static com.intellij.find.findUsages.FindUsagesHandlerFactory.OperationMode.USAGES_WITH_DEFAULT_OPTIONS;
|
||||
import static com.intellij.util.FindUsagesScopeKt.FindUsagesScope;
|
||||
import static com.intellij.util.ObjectUtils.doIfNotNull;
|
||||
import static org.jetbrains.annotations.Nls.Capitalization.Sentence;
|
||||
|
||||
@@ -112,6 +118,8 @@ public class ShowUsagesAction extends AnAction implements PopupAction, HintManag
|
||||
private static final String SPLITTER_SERVICE_KEY = "ShowUsagesActions.splitterServiceKey";
|
||||
private static final String PREVIEW_PROPERTY_KEY = "ShowUsagesActions.previewPropertyKey";
|
||||
|
||||
private final static IJTracer myFindUsagesTracer = TelemetryTracer.getInstance().getTracer(FindUsagesScope);
|
||||
|
||||
private static int ourPopupDelayTimeout = 300;
|
||||
|
||||
public ShowUsagesAction() {
|
||||
@@ -241,14 +249,23 @@ public class ShowUsagesAction extends AnAction implements PopupAction, HintManag
|
||||
HintManager.getInstance().hideHints(HintManager.HIDE_BY_ANY_KEY, false, false);
|
||||
}
|
||||
|
||||
public static void startFindUsages(@NotNull PsiElement element, @NotNull RelativePoint popupPosition, @Nullable Editor editor) {
|
||||
@ApiStatus.Internal
|
||||
public static Future<Collection<Usage>> startFindUsagesWithResult(@NotNull PsiElement element,
|
||||
@NotNull RelativePoint popupPosition,
|
||||
@Nullable Editor editor,
|
||||
@Nullable SearchScope scope) {
|
||||
Project project = element.getProject();
|
||||
FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(project)).getFindUsagesManager();
|
||||
FindUsagesHandlerBase handler = findUsagesManager.getFindUsagesHandler(element, USAGES_WITH_DEFAULT_OPTIONS);
|
||||
if (handler == null) return;
|
||||
if (handler == null) return null;
|
||||
//noinspection deprecation
|
||||
FindUsagesOptions options = handler.getFindUsagesOptions(DataManager.getInstance().getDataContext());
|
||||
showElementUsages(ShowUsagesParameters.initial(project, editor, popupPosition), createActionHandler(handler, options));
|
||||
if (scope != null) options.searchScope = scope;
|
||||
return showElementUsagesWithResult(ShowUsagesParameters.initial(project, editor, popupPosition), createActionHandler(handler, options));
|
||||
}
|
||||
|
||||
public static void startFindUsages(@NotNull PsiElement element, @NotNull RelativePoint popupPosition, @Nullable Editor editor) {
|
||||
startFindUsagesWithResult(element, popupPosition, editor, null);
|
||||
}
|
||||
|
||||
private static void rulesChanged(@NotNull UsageViewImpl usageView, @NotNull PingEDT pingEDT, JBPopup popup) {
|
||||
@@ -407,8 +424,18 @@ public class ShowUsagesAction extends AnAction implements PopupAction, HintManag
|
||||
}
|
||||
|
||||
static void showElementUsages(@NotNull ShowUsagesParameters parameters, @NotNull ShowUsagesActionHandler actionHandler) {
|
||||
showElementUsagesWithResult(parameters, actionHandler);
|
||||
}
|
||||
|
||||
static Future<Collection<Usage>> showElementUsagesWithResult(@NotNull ShowUsagesParameters parameters,
|
||||
@NotNull ShowUsagesActionHandler actionHandler) {
|
||||
ApplicationManager.getApplication().assertIsDispatchThread();
|
||||
|
||||
Span findUsageSpan = myFindUsagesTracer.spanBuilder("findUsages").startSpan();
|
||||
Scope opentelemetryScope = findUsageSpan.makeCurrent();
|
||||
Span popupSpan = myFindUsagesTracer.spanBuilder("findUsage_popup").startSpan();
|
||||
Span firstUsageSpan = myFindUsagesTracer.spanBuilder("findUsages_firstUsage").startSpan();
|
||||
|
||||
Project project = parameters.project;
|
||||
UsageViewImpl usageView = createUsageView(project, actionHandler.getTargetLanguage());
|
||||
ReadAction.nonBlocking(() -> actionHandler.getEventData()).submit(AppExecutorUtil.getAppExecutorService()).onSuccess(
|
||||
@@ -478,6 +505,7 @@ public class ShowUsagesAction extends AnAction implements PopupAction, HintManag
|
||||
EdtScheduledExecutorService.getInstance().schedule(() -> {
|
||||
if (!usageView.isDisposed()) {
|
||||
showPopupIfNeedTo(popup, parameters.popupPosition, popupShownTimeRef);
|
||||
popupSpan.end();
|
||||
}
|
||||
}, ourPopupDelayTimeout, TimeUnit.MILLISECONDS);
|
||||
}
|
||||
@@ -555,6 +583,7 @@ public class ShowUsagesAction extends AnAction implements PopupAction, HintManag
|
||||
return true;
|
||||
}
|
||||
synchronized (usages) {
|
||||
firstUsageSpan.end();
|
||||
if (visibleUsages.size() >= parameters.maxUsages) {
|
||||
tooManyResults.set(true);
|
||||
return false;
|
||||
@@ -583,11 +612,13 @@ public class ShowUsagesAction extends AnAction implements PopupAction, HintManag
|
||||
|
||||
UsageSearcher usageSearcher = actionHandler.createUsageSearcher();
|
||||
long searchStarted = System.nanoTime();
|
||||
var result = new CompletableFuture<Collection<Usage>>();
|
||||
FindUsagesManager.startProcessUsages(indicator, project, usageSearcher, collect, () -> ApplicationManager.getApplication().invokeLater(
|
||||
() -> {
|
||||
showUsagesPopupData.header.disposeProcessIcon();
|
||||
pingEDT.ping(); // repaint status
|
||||
synchronized (usages) {
|
||||
findUsageSpan.setAttribute("number", usages.size());
|
||||
if (visibleUsages.isEmpty()) {
|
||||
if (usages.isEmpty()) {
|
||||
String hint = UsageViewBundle.message("no.usages.found.in", searchScope.getDisplayName());
|
||||
@@ -621,8 +652,9 @@ public class ShowUsagesAction extends AnAction implements PopupAction, HintManag
|
||||
}
|
||||
}
|
||||
}
|
||||
result.complete(usages);
|
||||
}
|
||||
|
||||
findUsageSpan.end();
|
||||
long current = System.nanoTime();
|
||||
UsageViewStatisticsCollector.logSearchFinished(project, usageView,
|
||||
actionHandler.getTargetClass(), searchScope, actionHandler.getTargetLanguage(),
|
||||
@@ -635,6 +667,8 @@ public class ShowUsagesAction extends AnAction implements PopupAction, HintManag
|
||||
},
|
||||
project.getDisposed()
|
||||
));
|
||||
opentelemetryScope.close();
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void toggleFilters(@NotNull List<? extends ToggleAction> unselectedActions) {
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.util
|
||||
|
||||
import com.intellij.platform.diagnostic.telemetry.PlatformMetrics
|
||||
import com.intellij.platform.diagnostic.telemetry.Scope
|
||||
|
||||
@JvmField
|
||||
val FindUsagesScope = Scope("findUsages", PlatformMetrics)
|
||||
@@ -4,7 +4,6 @@ package com.intellij.performance.performancePlugin
|
||||
import com.intellij.performance.performancePlugin.commands.AssertKotlinFileInSpecificRootCommand
|
||||
import com.intellij.performance.performancePlugin.commands.ClearLibraryCaches
|
||||
import com.intellij.performance.performancePlugin.commands.ClearSourceCaches
|
||||
import com.intellij.performance.performancePlugin.commands.FindUsagesKotlinCommand
|
||||
import com.intellij.performance.performancePlugin.commands.GCCommand
|
||||
import com.intellij.performance.performancePlugin.commands.KotlinEditorOptionsChangeCommand
|
||||
import com.intellij.performance.performancePlugin.commands.CreateKotlinFileCommand
|
||||
@@ -20,7 +19,6 @@ class KotlinPluginCommandProvider : CommandProvider {
|
||||
GCCommand.PREFIX to CreateCommand(::GCCommand),
|
||||
AssertKotlinFileInSpecificRootCommand.PREFIX to CreateCommand(::AssertKotlinFileInSpecificRootCommand),
|
||||
KotlinEditorOptionsChangeCommand.PREFIX to CreateCommand(::KotlinEditorOptionsChangeCommand),
|
||||
FindUsagesKotlinCommand.PREFIX to CreateCommand(::FindUsagesKotlinCommand),
|
||||
CreateKotlinFileCommand.PREFIX to CreateCommand(::CreateKotlinFileCommand)
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
package com.intellij.performance.performancePlugin.commands
|
||||
|
||||
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction
|
||||
import com.intellij.find.FindManager
|
||||
import com.intellij.find.findUsages.FindUsagesHandlerFactory
|
||||
import com.intellij.find.findUsages.FindUsagesManager
|
||||
import com.intellij.find.impl.FindManagerImpl
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.project.DumbService
|
||||
import com.intellij.openapi.ui.playback.PlaybackContext
|
||||
import com.intellij.openapi.util.Ref
|
||||
import com.intellij.usages.Usage
|
||||
import com.intellij.util.Processors
|
||||
import com.jetbrains.performancePlugin.commands.FindUsagesCommand
|
||||
import com.jetbrains.performancePlugin.commands.GoToNamedElementCommand
|
||||
|
||||
import com.jetbrains.performancePlugin.commands.PerformanceCommand
|
||||
import com.jetbrains.performancePlugin.utils.ActionCallbackProfilerStopper
|
||||
import io.opentelemetry.api.trace.Span
|
||||
import io.opentelemetry.context.Context
|
||||
import org.jetbrains.concurrency.Promise
|
||||
import org.jetbrains.concurrency.toPromise
|
||||
import org.jetbrains.kotlin.idea.base.searching.usages.KotlinClassFindUsagesOptions
|
||||
import org.jetbrains.kotlin.idea.base.searching.usages.KotlinFunctionFindUsagesOptions
|
||||
import org.jetbrains.kotlin.idea.base.searching.usages.KotlinPropertyFindUsagesOptions
|
||||
import java.util.*
|
||||
import java.util.concurrent.CountDownLatch
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class FindUsagesKotlinCommand(text: String, line: Int) : PerformanceCommand(text, line) {
|
||||
companion object {
|
||||
const val NAME = "findUsagesKotlin"
|
||||
const val PREFIX = CMD_PREFIX + NAME
|
||||
private val LOG = Logger.getInstance(FindUsagesKotlinCommand::class.java)
|
||||
}
|
||||
|
||||
override fun getName(): String {
|
||||
return NAME
|
||||
}
|
||||
|
||||
override fun _execute(context: PlaybackContext): Promise<Any?> {
|
||||
val actionCallback = ActionCallbackProfilerStopper()
|
||||
val arguments = text.split(Regex.fromLiteral(" ")).toTypedArray()
|
||||
val position = arguments[1]
|
||||
val elementName = arguments[2]
|
||||
val result = GoToNamedElementCommand(GoToNamedElementCommand.PREFIX + " $position $elementName", -1).execute(context)
|
||||
result.onError {
|
||||
actionCallback.reject("fail to go to element $elementName")
|
||||
}
|
||||
result.blockingGet(30, TimeUnit.SECONDS)
|
||||
val project = context.project
|
||||
val findUsagesFinished = CountDownLatch(1)
|
||||
val allUsages: List<Usage> = ArrayList()
|
||||
val span: Ref<Span> = Ref()
|
||||
DumbService.getInstance(project).smartInvokeLater(Context.current().wrap(fun() {
|
||||
|
||||
val editor = FileEditorManager.getInstance(context.project).selectedTextEditor
|
||||
val offset = editor!!.caretModel.offset
|
||||
val element = when (GotoDeclarationAction.findElementToShowUsagesOf(editor, offset)) {
|
||||
null -> GotoDeclarationAction.findTargetElement(project, editor, offset)
|
||||
else -> GotoDeclarationAction.findElementToShowUsagesOf(editor, offset)
|
||||
}
|
||||
if (element == null) {
|
||||
actionCallback.reject("Can't find an element under current $offset offset.")
|
||||
return
|
||||
}
|
||||
|
||||
val findUsagesManager = (FindManager.getInstance(project) as FindManagerImpl).findUsagesManager
|
||||
val handler = findUsagesManager.getFindUsagesHandler(element, FindUsagesHandlerFactory.OperationMode.USAGES_WITH_DEFAULT_OPTIONS)
|
||||
if (handler == null) {
|
||||
actionCallback.reject("No find usage handler found for the element:" + element.text)
|
||||
return
|
||||
}
|
||||
|
||||
val findUsagesOptions = when (element.toString()) {
|
||||
"FUN" -> KotlinFunctionFindUsagesOptions(project).apply {
|
||||
isOverridingMethods = false
|
||||
isImplementingMethods = false
|
||||
isCheckDeepInheritance = true
|
||||
isIncludeInherited = false
|
||||
isIncludeOverloadUsages = false
|
||||
isImplicitToString = true
|
||||
isSearchForBaseMethod = true
|
||||
isSkipImportStatements = false
|
||||
isSearchForTextOccurrences = false
|
||||
isUsages = true
|
||||
searchExpected = true
|
||||
}
|
||||
"CLASS", "OBJECT_DECLARATION" -> KotlinClassFindUsagesOptions(project).apply {
|
||||
searchExpected = true
|
||||
searchConstructorUsages = true
|
||||
isMethodsUsages = false
|
||||
isFieldsUsages = false
|
||||
isDerivedClasses = false
|
||||
isImplementingClasses = false
|
||||
isDerivedInterfaces = false
|
||||
isCheckDeepInheritance = true
|
||||
isIncludeInherited = false
|
||||
isSkipImportStatements = false
|
||||
isSearchForTextOccurrences = true
|
||||
isUsages = true
|
||||
}
|
||||
else -> KotlinPropertyFindUsagesOptions(project).apply {
|
||||
searchExpected = true
|
||||
isReadWriteAccess = true
|
||||
searchOverrides = false
|
||||
isReadAccess = true
|
||||
isWriteAccess = true
|
||||
isSearchForAccessors = false
|
||||
isSearchInOverridingMethods = false
|
||||
isSearchForBaseAccessors = false
|
||||
isSkipImportStatements = false
|
||||
isSearchForTextOccurrences = false
|
||||
isUsages = true
|
||||
}
|
||||
}
|
||||
val collectProcessor = Processors.cancelableCollectProcessor(Collections.synchronizedList(allUsages))
|
||||
span.set(startSpan(FindUsagesCommand.SPAN_NAME))
|
||||
FindUsagesManager.startProcessUsages(handler, handler.primaryElements, handler.secondaryElements, collectProcessor,
|
||||
findUsagesOptions) { findUsagesFinished.countDown() }
|
||||
}))
|
||||
try {
|
||||
findUsagesFinished.await()
|
||||
span.get().setAttribute("number", allUsages.size.toLong())
|
||||
span.get().end()
|
||||
}
|
||||
catch (e: InterruptedException) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
FindUsagesCommand.storeMetricsDumpFoundUsages(allUsages, project)
|
||||
actionCallback.setDone()
|
||||
return actionCallback.toPromise()
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,7 @@ class AssertFindUsagesCommand(text: String, line: Int) : PlaybackCommandCoroutin
|
||||
}
|
||||
|
||||
override suspend fun doExecute(context: PlaybackContext) {
|
||||
val lastUsageReport = FindUsagesCommand.getFoundUsagesJsonPath()
|
||||
val lastUsageReport = FindUsagesDumper.getFoundUsagesJsonPath()
|
||||
if (lastUsageReport == null) {
|
||||
throw IllegalStateException("Find usages report is null")
|
||||
}
|
||||
@@ -24,7 +24,7 @@ class AssertFindUsagesCommand(text: String, line: Int) : PlaybackCommandCoroutin
|
||||
throw IllegalStateException("Provide expected count of usages")
|
||||
}
|
||||
|
||||
val data: FindUsagesCommand.FoundUsagesReport = DataDumper.read(lastUsageReport, FindUsagesCommand.FoundUsagesReport::class.java)
|
||||
val data: FindUsagesDumper.FoundUsagesReport = DataDumper.read(lastUsageReport, FindUsagesDumper.FoundUsagesReport::class.java)
|
||||
val expected = args[0].toInt()
|
||||
if (data.totalNumberOfUsages != expected) {
|
||||
throw IllegalStateException("Expected ${expected} find usages, but got ${data.totalNumberOfUsages}")
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.jetbrains.performancePlugin.commands;
|
||||
|
||||
import com.sampullara.cli.Argument;
|
||||
|
||||
public class FindUsagesArguments {
|
||||
@Argument
|
||||
String position;
|
||||
|
||||
@Argument
|
||||
String scope = "All Places";
|
||||
|
||||
@Argument
|
||||
String expectedName;
|
||||
}
|
||||
@@ -1,257 +0,0 @@
|
||||
package com.jetbrains.performancePlugin.commands;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction;
|
||||
import com.intellij.platform.diagnostic.telemetry.impl.TraceUtil;
|
||||
import com.intellij.find.FindManager;
|
||||
import com.intellij.find.findUsages.FindUsagesHandler;
|
||||
import com.intellij.find.findUsages.FindUsagesHandlerFactory;
|
||||
import com.intellij.find.findUsages.FindUsagesManager;
|
||||
import com.intellij.find.findUsages.FindUsagesOptions;
|
||||
import com.intellij.find.impl.FindManagerImpl;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.project.DumbService;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.playback.PlaybackContext;
|
||||
import com.intellij.openapi.ui.playback.commands.AbstractCommand;
|
||||
import com.intellij.openapi.util.ActionCallback;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.PsiDocumentManager;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.usages.Usage;
|
||||
import com.intellij.usages.UsageInfo2UsageAdapter;
|
||||
import com.intellij.util.Processor;
|
||||
import com.intellij.util.Processors;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.indexing.diagnostic.dump.paths.PortableFilePath;
|
||||
import com.intellij.util.indexing.diagnostic.dump.paths.PortableFilePaths;
|
||||
import com.jetbrains.performancePlugin.PerformanceTestSpan;
|
||||
import com.jetbrains.performancePlugin.utils.ActionCallbackProfilerStopper;
|
||||
import com.jetbrains.performancePlugin.utils.DataDumper;
|
||||
import io.opentelemetry.api.trace.StatusCode;
|
||||
import io.opentelemetry.context.Context;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.concurrency.Promise;
|
||||
import org.jetbrains.concurrency.Promises;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
/**
|
||||
* Command looks for and count psi usages of element under caret.
|
||||
* Example: %findUsages
|
||||
*/
|
||||
public class FindUsagesCommand extends AbstractCommand {
|
||||
private static final String DUMP_FOUND_USAGES_DESTINATION_FILE = "find.usages.command.found.usages.list.file";
|
||||
|
||||
public static final String PREFIX = CMD_PREFIX + "findUsages";
|
||||
private static final Logger LOG = Logger.getInstance(FindUsagesCommand.class);
|
||||
public static final String SPAN_NAME = "findUsages";
|
||||
|
||||
public FindUsagesCommand(String text, int line) {
|
||||
super(text, line);
|
||||
}
|
||||
|
||||
@SuppressWarnings("TestOnlyProblems")
|
||||
@Override
|
||||
protected @NotNull Promise<Object> _execute(@NotNull PlaybackContext context) {
|
||||
final ActionCallback actionCallback = new ActionCallbackProfilerStopper();
|
||||
CountDownLatch findUsagesFinished = new CountDownLatch(1);
|
||||
List<Usage> allUsages = new ArrayList<>();
|
||||
@NotNull Project project = context.getProject();
|
||||
TraceUtil.runWithSpanThrows(PerformanceTestSpan.TRACER, SPAN_NAME, span -> {
|
||||
DumbService.getInstance(project).smartInvokeLater(Context.current().wrap(() -> {
|
||||
Editor editor = FileEditorManager.getInstance(project).getSelectedTextEditor();
|
||||
if (editor == null) {
|
||||
span.setStatus(StatusCode.ERROR, "The action invoked without editor");
|
||||
actionCallback.reject("The action invoked without editor");
|
||||
return;
|
||||
}
|
||||
PsiFile psiFile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument());
|
||||
if (psiFile == null) {
|
||||
span.setStatus(StatusCode.ERROR, "Psi File is not found");
|
||||
actionCallback.reject("Psi File is not found");
|
||||
return;
|
||||
}
|
||||
int offset = editor.getCaretModel().getOffset();
|
||||
PsiElement element;
|
||||
span.addEvent("Finding declaration");
|
||||
if (GotoDeclarationAction.findElementToShowUsagesOf(editor, offset) == null) {
|
||||
element = GotoDeclarationAction.findTargetElement(project, editor, offset);
|
||||
}
|
||||
else {
|
||||
element = GotoDeclarationAction.findElementToShowUsagesOf(editor, offset);
|
||||
}
|
||||
if (element == null) {
|
||||
span.setStatus(StatusCode.ERROR, "Can't find an element under " + offset + " offset.");
|
||||
actionCallback.reject("Can't find an element under " + offset + " offset.");
|
||||
return;
|
||||
}
|
||||
span.addEvent("Command find usages is called on element " + element);
|
||||
LOG.info("Command find usages is called on element " + element);
|
||||
FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(project)).getFindUsagesManager();
|
||||
FindUsagesHandler handler = findUsagesManager.getFindUsagesHandler(
|
||||
element, FindUsagesHandlerFactory.OperationMode.HIGHLIGHT_USAGES
|
||||
);
|
||||
if (handler == null) {
|
||||
span.setStatus(StatusCode.ERROR, "No find usage handler found for the element:" + element.getText());
|
||||
actionCallback.reject("No find usage handler found for the element:" + element.getText());
|
||||
return;
|
||||
}
|
||||
FindUsagesOptions findUsagesOptions = new FindUsagesOptions(project);
|
||||
findUsagesOptions.isUsages = true;
|
||||
findUsagesOptions.isSearchForTextOccurrences = false;
|
||||
findUsagesOptions.searchScope = GlobalSearchScope.allScope(project);
|
||||
Processor<Usage> collectProcessor = Processors.cancelableCollectProcessor(Collections.synchronizedList(allUsages));
|
||||
FindUsagesManager.startProcessUsages(handler,
|
||||
handler.getPrimaryElements(),
|
||||
handler.getSecondaryElements(),
|
||||
collectProcessor,
|
||||
findUsagesOptions,
|
||||
() -> {
|
||||
findUsagesFinished.countDown();
|
||||
});
|
||||
}));
|
||||
try {
|
||||
findUsagesFinished.await();
|
||||
span.setAttribute("number", allUsages.size());
|
||||
}
|
||||
catch (InterruptedException e) {
|
||||
span.recordException(e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
storeMetricsDumpFoundUsages(allUsages, project);
|
||||
actionCallback.setDone();
|
||||
return Promises.toPromise(actionCallback);
|
||||
}
|
||||
|
||||
public static void storeMetricsDumpFoundUsages(List<Usage> allUsages, @NotNull Project project) {
|
||||
List<FoundUsage> foundUsages = ContainerUtil.map(allUsages, usage -> convertToFoundUsage(project, usage));
|
||||
Path jsonPath = getFoundUsagesJsonPath();
|
||||
if (jsonPath != null) {
|
||||
dumpFoundUsagesToFile(foundUsages, jsonPath);
|
||||
}
|
||||
}
|
||||
|
||||
public static void dumpFoundUsagesToFile(@NotNull List<FoundUsage> foundUsages,
|
||||
@NotNull Path jsonPath) {
|
||||
LOG.info("Found usages will be dumped to " + jsonPath);
|
||||
Collections.sort(foundUsages);
|
||||
|
||||
FoundUsagesReport foundUsagesReport = new FoundUsagesReport(foundUsages.size(), foundUsages);
|
||||
DataDumper.dump(foundUsagesReport, jsonPath);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FoundUsage convertToFoundUsage(@NotNull Project project, @NotNull Usage usage) {
|
||||
PortableFilePath portableFilePath = null;
|
||||
Integer line = null;
|
||||
if (usage instanceof UsageInfo2UsageAdapter adapter) {
|
||||
VirtualFile file = ReadAction.compute(() -> adapter.getFile());
|
||||
if (file != null) {
|
||||
portableFilePath = PortableFilePaths.INSTANCE.getPortableFilePath(file, project);
|
||||
}
|
||||
line = adapter.getLine() + 1;
|
||||
}
|
||||
String text = ReadAction.compute(() -> usage.getPresentation().getPlainText());
|
||||
return new FoundUsage(text, portableFilePath, line);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Path getFoundUsagesJsonPath() {
|
||||
String property = System.getProperty(DUMP_FOUND_USAGES_DESTINATION_FILE);
|
||||
if (property != null) {
|
||||
return Paths.get(property);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FoundUsagesReport parseFoundUsagesReportFromFile(@NotNull Path reportPath) throws IOException {
|
||||
return DataDumper.objectMapper.readValue(reportPath.toFile(), FoundUsagesReport.class);
|
||||
}
|
||||
|
||||
public static final class FoundUsagesReport {
|
||||
public final int totalNumberOfUsages;
|
||||
public final List<FoundUsage> usages;
|
||||
|
||||
@JsonCreator
|
||||
public FoundUsagesReport(
|
||||
@JsonProperty("totalNumberOfUsages") int totalNumberOfUsages,
|
||||
@JsonProperty("usages") @NotNull List<FoundUsage> foundUsages
|
||||
) {
|
||||
this.totalNumberOfUsages = totalNumberOfUsages;
|
||||
this.usages = foundUsages;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static final class FoundUsage implements Comparable<FoundUsage> {
|
||||
public final @NotNull String text;
|
||||
public final @Nullable PortableFilePath portableFilePath;
|
||||
public final @Nullable Integer line;
|
||||
|
||||
@JsonCreator
|
||||
private FoundUsage(
|
||||
@JsonProperty("text") @NotNull String text,
|
||||
@JsonProperty("portableFilePath") @Nullable PortableFilePath portableFilePath,
|
||||
@JsonProperty("line") @Nullable Integer line
|
||||
) {
|
||||
this.portableFilePath = portableFilePath;
|
||||
this.text = text;
|
||||
this.line = line;
|
||||
}
|
||||
|
||||
private static final Comparator<FoundUsage> COMPARATOR =
|
||||
Comparator.<FoundUsage, String>comparing(usage -> usage.portableFilePath != null ? usage.portableFilePath.getPresentablePath() : "")
|
||||
.thenComparingInt(usage -> usage.line != null ? usage.line : -1)
|
||||
.thenComparing(usage -> usage.text);
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull FindUsagesCommand.FoundUsage other) {
|
||||
return COMPARATOR.compare(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof FoundUsage usage)) return false;
|
||||
return text.equals(usage.text) &&
|
||||
Objects.equals(portableFilePath, usage.portableFilePath) &&
|
||||
Objects.equals(line, usage.line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(text, portableFilePath, line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (portableFilePath != null) {
|
||||
builder.append("In file '").append(portableFilePath.getPresentablePath()).append("' ");
|
||||
}
|
||||
if (line != null) {
|
||||
builder.append("(at line #").append(line).append(") ");
|
||||
}
|
||||
if (builder.length() > 0) {
|
||||
builder.append("\n");
|
||||
}
|
||||
builder.append(text);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.jetbrains.performancePlugin.commands
|
||||
|
||||
import com.intellij.codeInsight.navigation.actions.GotoDeclarationAction
|
||||
import com.intellij.find.FindSettings
|
||||
import com.intellij.find.actions.ShowUsagesAction
|
||||
import com.intellij.find.findUsages.FindUsagesOptions
|
||||
import com.intellij.openapi.application.EDT
|
||||
import com.intellij.openapi.diagnostic.Logger
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager
|
||||
import com.intellij.openapi.options.advanced.AdvancedSettings
|
||||
import com.intellij.openapi.ui.playback.PlaybackContext
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||
import com.intellij.psi.PsiNamedElement
|
||||
import com.intellij.usages.Usage
|
||||
import com.sampullara.cli.Args
|
||||
import io.opentelemetry.context.Context
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jetbrains.annotations.NonNls
|
||||
import java.util.concurrent.Future
|
||||
|
||||
/**
|
||||
* Command to execute find usages with popup
|
||||
* Example: %findUsages [-position <elementName>] [-expectedName <elementName>] [-scope <All Places>] <WARMUP>
|
||||
*/
|
||||
class FindUsagesCommand(text: String, line: Int) : PerformanceCommandCoroutineAdapter(text, line) {
|
||||
companion object {
|
||||
const val PREFIX: @NonNls String = CMD_PREFIX + "findUsages"
|
||||
const val SPAN_NAME: @NonNls String = "findUsages"
|
||||
private val LOG = Logger.getInstance(FindUsagesCommand::class.java)
|
||||
}
|
||||
|
||||
@Suppress("TestOnlyProblems")
|
||||
override suspend fun doExecute(context: PlaybackContext) {
|
||||
val options = FindUsagesArguments()
|
||||
Args.parse(options, extractCommandArgument(PREFIX).split(" ").toTypedArray())
|
||||
|
||||
val position = options.position
|
||||
val elementName = options.expectedName
|
||||
if (position != null) {
|
||||
val result = GoToNamedElementCommand(GoToNamedElementCommand.PREFIX + " $position $elementName", -1).execute(context)
|
||||
result.onError {
|
||||
throw Exception("fail to go to element $elementName")
|
||||
}
|
||||
}
|
||||
|
||||
val currentOTContext = Context.current()
|
||||
var findUsagesFuture: Future<Collection<Usage>>
|
||||
withContext(Dispatchers.EDT) {
|
||||
currentOTContext.makeCurrent().use {
|
||||
val editor = FileEditorManager.getInstance(context.project).selectedTextEditor
|
||||
if (editor == null) {
|
||||
throw Exception("No editor is opened")
|
||||
}
|
||||
val offset = editor.caretModel.offset
|
||||
val element = if (GotoDeclarationAction.findElementToShowUsagesOf(editor, offset) == null) {
|
||||
GotoDeclarationAction.findTargetElement(context.project, editor, offset)
|
||||
}
|
||||
else {
|
||||
GotoDeclarationAction.findElementToShowUsagesOf(editor, offset)
|
||||
}
|
||||
if (element == null) {
|
||||
throw Exception("Can't find an element under $offset offset.")
|
||||
}
|
||||
|
||||
val foundElementName = (element as PsiNamedElement).name
|
||||
if (!elementName.isNullOrEmpty()) {
|
||||
check(
|
||||
foundElementName != null && foundElementName == elementName) { "Found element name $foundElementName does not correspond to expected $elementName" }
|
||||
}
|
||||
LOG.info("Command find usages is called on element $element")
|
||||
|
||||
val popupPosition = JBPopupFactory.getInstance().guessBestPopupLocation(editor)
|
||||
|
||||
//configuration for find usages
|
||||
AdvancedSettings.setInt("ide.usages.page.size", Int.MAX_VALUE) //by default, it's 100, we need to find all usages to compare
|
||||
val scope = FindUsagesOptions.findScopeByName(context.project, null, options.scope)
|
||||
findUsagesFuture = ShowUsagesAction.startFindUsagesWithResult(element, popupPosition, editor, scope)
|
||||
}
|
||||
|
||||
}
|
||||
val results = findUsagesFuture.get()
|
||||
FindUsagesDumper.storeMetricsDumpFoundUsages(results.toMutableList(), context.project)
|
||||
}
|
||||
|
||||
override fun getName(): String = PREFIX
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package com.jetbrains.performancePlugin.commands;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.usages.Usage;
|
||||
import com.intellij.usages.UsageInfo2UsageAdapter;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.indexing.diagnostic.dump.paths.PortableFilePath;
|
||||
import com.intellij.util.indexing.diagnostic.dump.paths.PortableFilePaths;
|
||||
import com.jetbrains.performancePlugin.utils.DataDumper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
public class FindUsagesDumper {
|
||||
private static final String DUMP_FOUND_USAGES_DESTINATION_FILE = "find.usages.command.found.usages.list.file";
|
||||
private static final Logger LOG = Logger.getInstance(FindUsagesDumper.class);
|
||||
|
||||
public static void storeMetricsDumpFoundUsages(List<Usage> allUsages, @NotNull Project project) {
|
||||
List<FoundUsage> foundUsages = ContainerUtil.map(allUsages, usage -> convertToFoundUsage(project, usage));
|
||||
Path jsonPath = getFoundUsagesJsonPath();
|
||||
if (jsonPath != null) {
|
||||
dumpFoundUsagesToFile(foundUsages, jsonPath);
|
||||
}
|
||||
}
|
||||
|
||||
public static void dumpFoundUsagesToFile(@NotNull List<FoundUsage> foundUsages,
|
||||
@NotNull Path jsonPath) {
|
||||
LOG.info("Found usages will be dumped to " + jsonPath);
|
||||
Collections.sort(foundUsages);
|
||||
|
||||
FoundUsagesReport foundUsagesReport = new FoundUsagesReport(foundUsages.size(), foundUsages);
|
||||
DataDumper.dump(foundUsagesReport, jsonPath);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FoundUsage convertToFoundUsage(@NotNull Project project, @NotNull Usage usage) {
|
||||
PortableFilePath portableFilePath = null;
|
||||
Integer line = null;
|
||||
if (usage instanceof UsageInfo2UsageAdapter adapter) {
|
||||
VirtualFile file = ReadAction.compute(() -> adapter.getFile());
|
||||
if (file != null) {
|
||||
portableFilePath = PortableFilePaths.INSTANCE.getPortableFilePath(file, project);
|
||||
}
|
||||
line = adapter.getLine() + 1;
|
||||
}
|
||||
String text = ReadAction.compute(() -> usage.getPresentation().getPlainText());
|
||||
return new FoundUsage(text, portableFilePath, line);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static Path getFoundUsagesJsonPath() {
|
||||
String property = System.getProperty(DUMP_FOUND_USAGES_DESTINATION_FILE);
|
||||
if (property != null) {
|
||||
return Paths.get(property);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static FoundUsagesReport parseFoundUsagesReportFromFile(@NotNull Path reportPath) throws IOException {
|
||||
return DataDumper.objectMapper.readValue(reportPath.toFile(), FoundUsagesReport.class);
|
||||
}
|
||||
|
||||
public static final class FoundUsagesReport {
|
||||
public final int totalNumberOfUsages;
|
||||
public final List<FoundUsage> usages;
|
||||
|
||||
@JsonCreator
|
||||
public FoundUsagesReport(
|
||||
@JsonProperty("totalNumberOfUsages") int totalNumberOfUsages,
|
||||
@JsonProperty("usages") @NotNull List<FoundUsage> foundUsages
|
||||
) {
|
||||
this.totalNumberOfUsages = totalNumberOfUsages;
|
||||
this.usages = foundUsages;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonInclude(JsonInclude.Include.NON_NULL)
|
||||
public static final class FoundUsage implements Comparable<FoundUsage> {
|
||||
public final @NotNull String text;
|
||||
public final @Nullable PortableFilePath portableFilePath;
|
||||
public final @Nullable Integer line;
|
||||
|
||||
@JsonCreator
|
||||
private FoundUsage(
|
||||
@JsonProperty("text") @NotNull String text,
|
||||
@JsonProperty("portableFilePath") @Nullable PortableFilePath portableFilePath,
|
||||
@JsonProperty("line") @Nullable Integer line
|
||||
) {
|
||||
this.portableFilePath = portableFilePath;
|
||||
this.text = text;
|
||||
this.line = line;
|
||||
}
|
||||
|
||||
private static final Comparator<FoundUsage> COMPARATOR =
|
||||
Comparator.<FoundUsage, String>comparing(usage -> usage.portableFilePath != null ? usage.portableFilePath.getPresentablePath() : "")
|
||||
.thenComparingInt(usage -> usage.line != null ? usage.line : -1)
|
||||
.thenComparing(usage -> usage.text);
|
||||
|
||||
@Override
|
||||
public int compareTo(@NotNull FindUsagesDumper.FoundUsage other) {
|
||||
return COMPARATOR.compare(this, other);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (!(o instanceof FoundUsage usage)) return false;
|
||||
return text.equals(usage.text) &&
|
||||
Objects.equals(portableFilePath, usage.portableFilePath) &&
|
||||
Objects.equals(line, usage.line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(text, portableFilePath, line);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder builder = new StringBuilder();
|
||||
if (portableFilePath != null) {
|
||||
builder.append("In file '").append(portableFilePath.getPresentablePath()).append("' ");
|
||||
}
|
||||
if (line != null) {
|
||||
builder.append("(at line #").append(line).append(") ");
|
||||
}
|
||||
if (!builder.isEmpty()) {
|
||||
builder.append("\n");
|
||||
}
|
||||
builder.append(text);
|
||||
return builder.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user