AT-3543 split FindUsagesInToolWindowCommand into two commands, one that can be run in split mode, and another that waits on backend

(cherry picked from commit e8bd6241bb7216d2631ee6e2c0c73392bea013a9)

IJ-CR-182408

GitOrigin-RevId: 2475891b4e21b3efde2c3d71b95d6f4201b53762
This commit is contained in:
Henry Wylde
2025-11-11 10:45:46 +13:00
committed by intellij-monorepo-bot
parent a28190efe3
commit fbb509b0ac
4 changed files with 94 additions and 103 deletions

View File

@@ -173,6 +173,7 @@ fun <T : CommandChain> T.findUsages(expectedElementName: String = "", scope: Str
fun <T : CommandChain> 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 : CommandChain> T.navigateAndFindUsages(
@@ -785,6 +786,10 @@ fun <T : CommandChain> T.setRegistry(registry: String, value: Boolean): T = appl
addCommand("${CMD_PREFIX}set $registry=$value")
}
fun <T : CommandChain> T.setRegistry(registry: String, value: Int): T = apply {
addCommand("${CMD_PREFIX}set $registry=$value")
}
fun <T : CommandChain> T.setRegistry(registry: String, value: String): T = apply {
addCommand("${CMD_PREFIX}set $registry=$value")
}

View File

@@ -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),

View File

@@ -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<FileEditorManager>().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<ActionManager>().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

View File

@@ -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
}