diff --git a/plugins/performanceTesting/commands-model/src/com/intellij/tools/ide/performanceTesting/commands/generalCommandChain.kt b/plugins/performanceTesting/commands-model/src/com/intellij/tools/ide/performanceTesting/commands/generalCommandChain.kt index ec33ea586949..6196b5322f95 100644 --- a/plugins/performanceTesting/commands-model/src/com/intellij/tools/ide/performanceTesting/commands/generalCommandChain.kt +++ b/plugins/performanceTesting/commands-model/src/com/intellij/tools/ide/performanceTesting/commands/generalCommandChain.kt @@ -173,6 +173,7 @@ fun T.findUsages(expectedElementName: String = "", scope: Str fun T.findUsagesInToolWindow(expectedElementName: String = "", scope: String = "Project Files", warmup: Boolean = false): T = apply { navigateAndFindUsages(expectedElementName, "", scope, warmup = warmup, runInToolWindow = true) + addCommand("${CMD_PREFIX}findUsagesInToolWindowWait") } fun T.navigateAndFindUsages( @@ -785,6 +786,10 @@ fun T.setRegistry(registry: String, value: Boolean): T = appl addCommand("${CMD_PREFIX}set $registry=$value") } +fun T.setRegistry(registry: String, value: Int): T = apply { + addCommand("${CMD_PREFIX}set $registry=$value") +} + fun T.setRegistry(registry: String, value: String): T = apply { addCommand("${CMD_PREFIX}set $registry=$value") } diff --git a/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/BaseCommandProvider.java b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/BaseCommandProvider.java index 21e42ff14699..6addd444a3bf 100644 --- a/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/BaseCommandProvider.java +++ b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/BaseCommandProvider.java @@ -38,6 +38,7 @@ public final class BaseCommandProvider implements CommandProvider { Map.entry(FindUsagesCommand.PREFIX, FindUsagesCommand::new), Map.entry(FindUsagesInBackgroundCommand.PREFIX, FindUsagesInBackgroundCommand::new), Map.entry(FindUsagesInToolWindowCommand.PREFIX, FindUsagesInToolWindowCommand::new), + Map.entry(FindUsagesInToolWindowWaitCommand.PREFIX, FindUsagesInToolWindowWaitCommand::new), Map.entry(IdeEditorKeyCommand.PREFIX, IdeEditorKeyCommand::new), Map.entry(ShowAltEnter.PREFIX, ShowAltEnter::new), Map.entry(SelectCommand.PREFIX, SelectCommand::new), diff --git a/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/commands/FindUsagesInToolWindowCommand.kt b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/commands/FindUsagesInToolWindowCommand.kt index 150003bff840..1623274a9ba7 100644 --- a/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/commands/FindUsagesInToolWindowCommand.kt +++ b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/commands/FindUsagesInToolWindowCommand.kt @@ -1,34 +1,27 @@ // Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.jetbrains.performancePlugin.commands -import com.intellij.find.FindManager -import com.intellij.find.actions.findUsages -import com.intellij.find.findUsages.FindUsagesOptions -import com.intellij.find.usages.impl.searchTargets +import com.intellij.ide.DataManager +import com.intellij.openapi.actionSystem.ActionManager +import com.intellij.openapi.actionSystem.ActionPlaces +import com.intellij.openapi.actionSystem.AnActionEvent +import com.intellij.openapi.actionSystem.IdeActions +import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.application.EDT -import com.intellij.openapi.application.readAction import com.intellij.openapi.components.serviceAsync -import com.intellij.openapi.fileEditor.FileEditorManager import com.intellij.openapi.ui.playback.PlaybackContext -import com.intellij.openapi.util.registry.Registry -import com.intellij.psi.PsiDocumentManager -import com.intellij.psi.PsiNamedElement -import com.intellij.usages.Usage -import com.intellij.usages.UsageView -import com.intellij.usages.UsageViewManager -import com.intellij.usages.impl.UsageViewElementsListener -import com.jetbrains.performancePlugin.PerformanceTestSpan -import com.jetbrains.performancePlugin.commands.FindUsagesCommand.Companion.getElement -import com.jetbrains.performancePlugin.commands.FindUsagesCommand.Companion.goToElement -import com.sampullara.cli.Args -import io.opentelemetry.api.trace.Span +import com.intellij.openapi.wm.IdeFocusManager import io.opentelemetry.context.Context -import kotlinx.coroutines.* -import kotlin.time.Duration.Companion.milliseconds -import kotlin.time.Duration.Companion.seconds +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.withContext /** - * Command to execute finds usages in the tool window (not in the popup), and to wait for the search to complete. + * Command to execute find usages in the tool window (not in the popup). This command does not take any arguments, it is assumed that the + * caret has been moved to the appropriate location in the editor beforehand. Additionally, the command does not wait for find usages to + * complete; that job is left to [FindUsagesInToolWindowWaitCommand]. + * + * N.B., it is required to set `ide.find.result.count.warning.limit` if your test finds more than 1000 usages; the property should be set on + * the test context as it requires a restart, and shouldn't be set dynamically inside this command. */ class FindUsagesInToolWindowCommand(text: String, line: Int) : PerformanceCommandCoroutineAdapter(text, line) { companion object { @@ -41,93 +34,17 @@ class FindUsagesInToolWindowCommand(text: String, line: Int) : PerformanceComman } override suspend fun doExecute(context: PlaybackContext) { - val options = FindUsagesArguments() - Args.parse(options, extractCommandArgument(PREFIX).split("|").flatMap { it.split(" ", limit = 2) }.toTypedArray(), false) - - val project = context.project - val elementName = options.expectedName - goToElement(options.position, elementName, context) - - var span: Span? = null - var firstUsageSpan: Span? = null - var toolWindowSpan: Span? = null - val currentOTContext = Context.current() withContext(Dispatchers.EDT) { currentOTContext.makeCurrent().use { - val editor = project.serviceAsync().selectedTextEditor - if (editor == null) { - throw Exception("No editor is opened") - } + val focusedComponent = IdeFocusManager.findInstance().focusOwner + val dataContext = DataManager.getInstance().getDataContext(focusedComponent) + val findUsagesAction = ApplicationManager.getApplication().serviceAsync().getAction(IdeActions.ACTION_FIND_USAGES) + val findUsagesActionEvent = AnActionEvent.createFromAnAction(findUsagesAction, null, ActionPlaces.KEYBOARD_SHORTCUT, dataContext) - val scope = readAction { - FindUsagesOptions.findScopeByName(project, null, options.scope) - } - - val rangeMarker = readAction { - editor.document.createRangeMarker(editor.caretModel.offset, editor.caretModel.offset) - } - - val searchTargets = readAction { - PsiDocumentManager.getInstance(project).getPsiFile(editor.document)?.let { searchTargets(it, rangeMarker.startOffset) } - } - - val element = getElement(project, editor, rangeMarker) - - if (!elementName.isNullOrEmpty()) { - val foundElementName = readAction { (element as PsiNamedElement).name } - check(foundElementName != null && foundElementName == elementName) { "Found element name $foundElementName does not correspond to expected $elementName" } - } - - Registry.get("ide.find.result.count.warning.limit").setValue(Integer.MAX_VALUE) - - UsageViewElementsListener.EP_NAME.point.registerExtension(object : UsageViewElementsListener { - override fun beforeUsageAdded(view: UsageView, usage: Usage) { - UsageViewElementsListener.EP_NAME.point.unregisterExtension(this) - - firstUsageSpan?.end() - } - }) - - val tracer = PerformanceTestSpan.getTracer(isWarmupMode) - val parent = PerformanceTestSpan.getContext() - - span = tracer.spanBuilder(SPAN_NAME).setParent(parent).startSpan() - firstUsageSpan = tracer.spanBuilder(FIRST_USAGE_SPAN_NAME).setParent(parent).startSpan() - toolWindowSpan = tracer.spanBuilder(TOOL_WINDOW_SPAN_NAME).setParent(parent).startSpan() - - if (!searchTargets.isNullOrEmpty()) { - findUsages(false, project, scope, searchTargets.first()) - } - else { - FindManager.getInstance(project).findUsages(element!!) - } + findUsagesAction.actionPerformed(findUsagesActionEvent) } } - - var usageView: UsageView? = null - - try { - withTimeout(10.seconds) { - usageView = UsageViewManager.getInstance(project).selectedUsageView - while (usageView == null) { - delay(50.milliseconds) - usageView = UsageViewManager.getInstance(project).selectedUsageView - } - } - } - catch (_: TimeoutCancellationException) { - throw Exception("Timeout while waiting for the usage view to open") - } - - toolWindowSpan!!.end() - - while (usageView!!.isSearchInProgress) { - delay(50.milliseconds) - } - - span!!.setAttribute("number", usageView.usages.size.toLong()) - span.end() } override fun getName(): String = PREFIX diff --git a/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/commands/FindUsagesInToolWindowWaitCommand.kt b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/commands/FindUsagesInToolWindowWaitCommand.kt new file mode 100644 index 000000000000..58964793ccde --- /dev/null +++ b/plugins/performanceTesting/core/src/com/jetbrains/performancePlugin/commands/FindUsagesInToolWindowWaitCommand.kt @@ -0,0 +1,68 @@ +// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. +package com.jetbrains.performancePlugin.commands + +import com.intellij.openapi.application.EDT +import com.intellij.openapi.ui.playback.PlaybackContext +import com.intellij.usages.UsageView +import com.intellij.usages.UsageViewManager +import com.jetbrains.performancePlugin.PerformanceTestSpan +import com.jetbrains.performancePlugin.commands.FindUsagesInToolWindowCommand.Companion.FIRST_USAGE_SPAN_NAME +import com.jetbrains.performancePlugin.commands.FindUsagesInToolWindowCommand.Companion.SPAN_NAME +import com.jetbrains.performancePlugin.commands.FindUsagesInToolWindowCommand.Companion.TOOL_WINDOW_SPAN_NAME +import kotlinx.coroutines.* +import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.Duration.Companion.seconds + +/** + * Command to wait for find usages in the tool window (not in the popup) to complete. See [FindUsagesInToolWindowCommand]. + */ +class FindUsagesInToolWindowWaitCommand(text: String, line: Int) : PerformanceCommandCoroutineAdapter(text, line) { + companion object { + const val NAME: String = "findUsagesInToolWindowWait" + const val PREFIX: String = CMD_PREFIX + NAME + } + + override suspend fun doExecute(context: PlaybackContext) { + val project = context.project + + val tracer = PerformanceTestSpan.getTracer(isWarmupMode) + val parent = PerformanceTestSpan.getContext() + + val span = tracer.spanBuilder(SPAN_NAME).setParent(parent).startSpan() + val firstUsageSpan = tracer.spanBuilder(FIRST_USAGE_SPAN_NAME).setParent(parent).startSpan() + val toolWindowSpan = tracer.spanBuilder(TOOL_WINDOW_SPAN_NAME).setParent(parent).startSpan() + + var usageView: UsageView? = null + + try { + withTimeout(10.seconds) { + usageView = withContext(Dispatchers.EDT) { + UsageViewManager.getInstance(project).selectedUsageView + } + while (usageView == null) { + delay(50.milliseconds) + usageView = withContext(Dispatchers.EDT) { + UsageViewManager.getInstance(project).selectedUsageView + } + } + } + } + catch (_: TimeoutCancellationException) { + throw Exception("Timeout while waiting for the usage view to open") + } + + toolWindowSpan!!.end() + firstUsageSpan?.end() + + while (usageView!!.isSearchInProgress) { + delay(50.milliseconds) + } + + span!!.setAttribute("number", usageView.usages.size.toLong()) + span.end() + + FindUsagesDumper.storeMetricsDumpFoundUsages(usageView.usages.toMutableList(), project) + } + + override fun getName(): String = PREFIX +}