mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-03-22 15:10:43 +07:00
[Rider] Composable SE contributor extensions, part 1
Now AbstractSEContributor is extensible by a list of modules, which are essentially "sub-contributors". (cherry picked from commit c0ac76f0740863689e4cb70e64b9efbc25a0b6b6) GitOrigin-RevId: 675858bd0d19a6895a71146ea3e71b208a808ba1
This commit is contained in:
committed by
intellij-monorepo-bot
parent
0ede7170c0
commit
78918f00c0
@@ -51,6 +51,8 @@ import com.intellij.psi.search.SearchScope
|
||||
import com.intellij.psi.util.PsiUtilCore
|
||||
import com.intellij.util.IntPair
|
||||
import com.intellij.util.Processor
|
||||
import com.intellij.util.containers.map2Array
|
||||
import com.intellij.util.containers.toArray
|
||||
import com.intellij.util.indexing.FindSymbolParameters
|
||||
import it.unimi.dsi.fastutil.ints.IntArrayList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
@@ -89,6 +91,13 @@ abstract class AbstractGotoSEContributor protected constructor(event: AnActionEv
|
||||
@JvmField
|
||||
protected val myPsiContext: SmartPsiElementPointer<PsiElement?>?
|
||||
|
||||
@ApiStatus.Internal var contributorModules: List<SearchEverywhereContributorModule>? = null
|
||||
|
||||
@ApiStatus.Internal
|
||||
protected constructor(event: AnActionEvent, contributorModules: List<SearchEverywhereContributorModule>?) : this(event) {
|
||||
this.contributorModules = contributorModules
|
||||
}
|
||||
|
||||
init {
|
||||
val context = GotoActionBase.getPsiContext(event)
|
||||
myPsiContext = if (context == null) null else SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(context)
|
||||
@@ -205,6 +214,10 @@ abstract class AbstractGotoSEContributor protected constructor(event: AnActionEv
|
||||
|
||||
override fun isShownInSeparateTab(): Boolean = true
|
||||
|
||||
@ApiStatus.Internal
|
||||
protected var currentSearchEverywhereAction: SearchEverywhereToggleAction? = null
|
||||
private set
|
||||
|
||||
protected fun <T> doGetActions(
|
||||
filter: PersistentSearchEverywhereContributorFilter<T>?,
|
||||
statisticsCollector: ElementsChooser.StatisticsCollector<T>?,
|
||||
@@ -214,8 +227,7 @@ abstract class AbstractGotoSEContributor protected constructor(event: AnActionEv
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
val result = ArrayList<AnAction>()
|
||||
result.add(object : ScopeChooserAction() {
|
||||
val toggleAction = object : ScopeChooserAction() {
|
||||
val canToggleEverywhere = everywhereScope != projectScope
|
||||
|
||||
override fun onScopeSelected(o: ScopeDescriptor) {
|
||||
@@ -249,7 +261,12 @@ abstract class AbstractGotoSEContributor protected constructor(event: AnActionEv
|
||||
override fun setScopeIsDefaultAndAutoSet(scopeDefaultAndAutoSet: Boolean) {
|
||||
isScopeDefaultAndAutoSet = scopeDefaultAndAutoSet
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
currentSearchEverywhereAction = toggleAction
|
||||
|
||||
val result = ArrayList<AnAction>()
|
||||
result.add(toggleAction)
|
||||
result.add(PreviewAction())
|
||||
result.add(SearchEverywhereFiltersAction(filter, onChanged, statisticsCollector))
|
||||
return result
|
||||
@@ -277,6 +294,22 @@ abstract class AbstractGotoSEContributor protected constructor(event: AnActionEv
|
||||
pattern: String,
|
||||
progressIndicator: ProgressIndicator,
|
||||
consumer: Processor<in FoundItemDescriptor<Any>>,
|
||||
) {
|
||||
fetchWeightedElementsMixing(
|
||||
pattern, progressIndicator, consumer,
|
||||
{ localPattern, localProgressIndicator, localConsumer -> performByGotoContributorSearch(localPattern, localProgressIndicator, localConsumer) },
|
||||
*(
|
||||
contributorModules?.map2Array<SearchEverywhereContributorModule, (String, ProgressIndicator, Processor<in FoundItemDescriptor<Any>>) -> Unit> {
|
||||
{ localPattern, localProgressIndicator, localConsumer -> it.perProductFetchWeightedElements(localPattern, localProgressIndicator, localConsumer) }
|
||||
} ?: emptyArray()
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun performByGotoContributorSearch(
|
||||
pattern: String,
|
||||
progressIndicator: ProgressIndicator,
|
||||
consumer: Processor<in FoundItemDescriptor<Any>>
|
||||
) {
|
||||
if (!isEmptyPatternSupported && pattern.isEmpty()) {
|
||||
return
|
||||
@@ -298,6 +331,17 @@ abstract class AbstractGotoSEContributor protected constructor(event: AnActionEv
|
||||
|
||||
val everywhere = scope.isSearchInLibraries
|
||||
val viewModel = MyViewModel(myProject, model)
|
||||
|
||||
if (LOG.isTraceEnabled) {
|
||||
LOG.trace(buildString {
|
||||
append("!! Collecting Goto SE items for ").append(this@AbstractGotoSEContributor::class.simpleName).append(" !!\n")
|
||||
append("PSI context is: ").append(context).append("\n")
|
||||
append("Provider is: ").append(provider::class.simpleName).append("\n")
|
||||
append("Search scope is: ").append(scope.displayName).append("\n")
|
||||
append("Is libraries search? ").append(if (everywhere) "YES" else "NO").append("\n")
|
||||
})
|
||||
}
|
||||
|
||||
when (provider) {
|
||||
is ChooseByNameInScopeItemProvider -> {
|
||||
val parameters = FindSymbolParameters.wrap(pattern, scope)
|
||||
@@ -378,75 +422,98 @@ abstract class AbstractGotoSEContributor protected constructor(event: AnActionEv
|
||||
|
||||
override fun showInFindResults(): Boolean = true
|
||||
|
||||
// This sets off-the-stack coroutining for some (most) inputs. Take care.
|
||||
// a "true" return value does NOT mean that the navigation was successful.
|
||||
override fun processSelectedItem(selected: Any, modifiers: Int, searchText: String): Boolean {
|
||||
contributorModules?.forEach {
|
||||
val processedFlag = it.processSelectedItem(selected, modifiers, searchText)
|
||||
if (processedFlag != null) {
|
||||
return processedFlag
|
||||
}
|
||||
}
|
||||
|
||||
return processByGotoSelectedItem(selected, modifiers, searchText)
|
||||
}
|
||||
|
||||
@Suppress("SameReturnValue")
|
||||
private fun processByGotoSelectedItem(selected: Any, modifiers: Int, searchText: String): Boolean {
|
||||
if (selected !is PsiElement) {
|
||||
if (LOG.isTraceEnabled) {
|
||||
LOG.trace("Selected item for $searchText is not PsiElement, it is: ${selected}; performing non-suspending navigation")
|
||||
}
|
||||
EditSourceUtil.navigate((selected as NavigationItem), true, false)
|
||||
return true
|
||||
}
|
||||
|
||||
project.service<SearchEverywhereContributorCoroutineScopeHolder>().coroutineScope.launch(ClientId.coroutineContext()) {
|
||||
val command = readAction {
|
||||
if (!selected.isValid) {
|
||||
LOG.warn("Cannot navigate to invalid PsiElement")
|
||||
return@readAction null
|
||||
}
|
||||
|
||||
val psiElement = preparePsi(selected, searchText)
|
||||
val file =
|
||||
if (selected is PsiFile) selected.virtualFile
|
||||
else PsiUtilCore.getVirtualFile(psiElement)
|
||||
|
||||
val extendedNavigatable = if (file == null) {
|
||||
null
|
||||
}
|
||||
else {
|
||||
val position = getLineAndColumn(searchText)
|
||||
if (position.first >= 0 || position.second >= 0) {
|
||||
//todo create navigation request by line&column, not by offset only
|
||||
OpenFileDescriptor(project, file, position.first, position.second)
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
suspend {
|
||||
@Suppress("DEPRECATION")
|
||||
val navigationOptions = NavigationOptions.defaultOptions()
|
||||
.openInRightSplit((modifiers and InputEvent.SHIFT_MASK) != 0)
|
||||
.preserveCaret(true)
|
||||
if (extendedNavigatable == null) {
|
||||
if (file == null) {
|
||||
val navigatable = psiElement as? Navigatable
|
||||
if (navigatable != null) {
|
||||
// Navigation items from rd protocol often lack .containingFile or other PSI extensions, and are only expected to be
|
||||
// navigated through the Navigatable API.
|
||||
// This fallback is for items like that.
|
||||
val navRequest = RawNavigationRequest(navigatable, true)
|
||||
project.serviceAsync<NavigationService>().navigate(navRequest, navigationOptions)
|
||||
} else {
|
||||
LOG.warn("Cannot navigate to invalid PsiElement (psiElement=$psiElement, selected=$selected)")
|
||||
}
|
||||
}
|
||||
else {
|
||||
createSourceNavigationRequest(element = psiElement, file = file, searchText = searchText)?.let {
|
||||
project.serviceAsync<NavigationService>().navigate(it, navigationOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
project.serviceAsync<NavigationService>().navigate(extendedNavigatable, navigationOptions)
|
||||
triggerLineOrColumnFeatureUsed(extendedNavigatable)
|
||||
}
|
||||
}
|
||||
val navigatingAction = readAction { tryMakeNavigatingFunction(selected, modifiers, searchText) }
|
||||
if (navigatingAction != null) {
|
||||
navigatingAction()
|
||||
}
|
||||
else {
|
||||
LOG.warn("Selected $selected produced an invalid navigation action! Doing nothing!")
|
||||
}
|
||||
|
||||
command?.invoke()
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun tryMakeNavigatingFunction(selected: PsiElement, modifiers: Int, searchText: String): (suspend () -> Unit)? {
|
||||
if (!selected.isValid) {
|
||||
LOG.warn("Cannot navigate to invalid PsiElement")
|
||||
return null
|
||||
}
|
||||
|
||||
val psiElement = preparePsi(selected, searchText)
|
||||
val file =
|
||||
if (selected is PsiFile) selected.virtualFile
|
||||
else PsiUtilCore.getVirtualFile(psiElement)
|
||||
|
||||
val extendedNavigatable = if (file == null) {
|
||||
null
|
||||
}
|
||||
else {
|
||||
val position = getLineAndColumn(searchText)
|
||||
if (position.first >= 0 || position.second >= 0) {
|
||||
//todo create navigation request by line&column, not by offset only
|
||||
OpenFileDescriptor(project, file, position.first, position.second)
|
||||
}
|
||||
else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
return suspend {
|
||||
@Suppress("DEPRECATION")
|
||||
val navigationOptions = NavigationOptions.defaultOptions()
|
||||
.openInRightSplit((modifiers and InputEvent.SHIFT_MASK) != 0)
|
||||
.preserveCaret(true)
|
||||
if (extendedNavigatable == null) {
|
||||
if (file == null) {
|
||||
val navigatable = psiElement as? Navigatable
|
||||
if (navigatable != null) {
|
||||
// Navigation items from rd protocol often lack .containingFile or other PSI extensions, and are only expected to be
|
||||
// navigated through the Navigatable API.
|
||||
// This fallback is for items like that.
|
||||
val navRequest = RawNavigationRequest(navigatable, true)
|
||||
project.serviceAsync<NavigationService>().navigate(navRequest, navigationOptions)
|
||||
} else {
|
||||
LOG.warn("Cannot navigate to invalid PsiElement (psiElement=$psiElement, selected=$selected)")
|
||||
}
|
||||
}
|
||||
else {
|
||||
createSourceNavigationRequest(element = psiElement, file = file, searchText = searchText)?.let {
|
||||
project.serviceAsync<NavigationService>().navigate(it, navigationOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
project.serviceAsync<NavigationService>().navigate(extendedNavigatable, navigationOptions)
|
||||
triggerLineOrColumnFeatureUsed(extendedNavigatable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
protected open suspend fun createSourceNavigationRequest(
|
||||
element: PsiElement,
|
||||
@@ -488,7 +555,9 @@ abstract class AbstractGotoSEContributor protected constructor(event: AnActionEv
|
||||
|
||||
override fun isDumbAware(): Boolean = isDumbAware(createModel(myProject))
|
||||
|
||||
override fun getElementsRenderer(): ListCellRenderer<in Any?> = SearchEverywherePsiRenderer(this)
|
||||
override fun getElementsRenderer(): ListCellRenderer<in Any?> =
|
||||
contributorModules?.firstNotNullOfOrNull { it.getOverridingElementRenderer(this) }
|
||||
?: SearchEverywherePsiRenderer(this)
|
||||
|
||||
@Suppress("OVERRIDE_DEPRECATION")
|
||||
override fun getElementPriority(element: Any, searchPattern: String): Int = 50
|
||||
|
||||
@@ -32,6 +32,11 @@ open class ClassSearchEverywhereContributor(event: AnActionEvent)
|
||||
: AbstractGotoSEContributor(event), EssentialContributor, SearchEverywherePreviewProvider {
|
||||
private val filter = createLanguageFilter(event.getRequiredData(CommonDataKeys.PROJECT))
|
||||
|
||||
@Internal
|
||||
constructor(event: AnActionEvent, contributorModules: List<SearchEverywhereContributorModule>?) : this(event) {
|
||||
this.contributorModules = contributorModules
|
||||
}
|
||||
|
||||
companion object {
|
||||
@JvmStatic
|
||||
fun createLanguageFilter(project: Project): PersistentSearchEverywhereContributorFilter<LanguageRef> {
|
||||
@@ -76,7 +81,9 @@ open class ClassSearchEverywhereContributor(event: AnActionEvent)
|
||||
return super.getElementPriority(element, searchPattern) + 5
|
||||
}
|
||||
|
||||
override fun createExtendedInfo(): ExtendedInfo? = createPsiExtendedInfo()
|
||||
override fun createExtendedInfo(): ExtendedInfo? = createPsiExtendedInfo().let {
|
||||
contributorModules?.firstNotNullOfOrNull { mod -> mod.mixinExtendedInfo(it) } ?: it
|
||||
}
|
||||
|
||||
override suspend fun createSourceNavigationRequest(element: PsiElement, file: VirtualFile, searchText: String): NavigationRequest? {
|
||||
val memberName = getMemberName(searchText)
|
||||
|
||||
@@ -0,0 +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.intellij.ide.actions.searcheverywhere
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.util.Processor
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
import javax.swing.ListCellRenderer
|
||||
|
||||
// Rider SE needs (a) deeper (b) more dynamic integrations with the search everywhere process
|
||||
// due to
|
||||
// (a) Rider essentially having its own Global Navigation implementation on its backend with its own rules.
|
||||
// (b) Rider needing to -compositionally- integrate both with the "vanilla" and the "semantic" search.
|
||||
@ApiStatus.Internal
|
||||
interface SearchEverywhereContributorModule {
|
||||
|
||||
// Extended info should be handled differently. Prefer composition over tag interfaces and inheritance of ExtendedInfoProvider
|
||||
fun mixinExtendedInfo(baseExtendedInfo: ExtendedInfo): ExtendedInfo
|
||||
|
||||
fun processSelectedItem(item: Any, modifiers: Int, searchTest: String): Boolean?
|
||||
|
||||
fun getOverridingElementRenderer(parent: Disposable): ListCellRenderer<in Any?>
|
||||
|
||||
fun perProductFetchWeightedElements(pattern: String, progressIndicator: ProgressIndicator, consumer: Processor<in FoundItemDescriptor<Any>>)
|
||||
|
||||
fun currentSearchEverywhereToggledActionChanged(newAction: SearchEverywhereToggleAction)
|
||||
}
|
||||
@@ -9,6 +9,7 @@ import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import org.jetbrains.annotations.ApiStatus;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -25,6 +26,12 @@ public class SymbolSearchEverywhereContributor extends AbstractGotoSEContributor
|
||||
|
||||
private final PersistentSearchEverywhereContributorFilter<LanguageRef> myFilter;
|
||||
|
||||
@ApiStatus.Internal
|
||||
public SymbolSearchEverywhereContributor(@NotNull AnActionEvent event, @Nullable List<SearchEverywhereContributorModule> contributorModules) {
|
||||
super(event, contributorModules);
|
||||
myFilter = ClassSearchEverywhereContributor.createLanguageFilter(event.getRequiredData(CommonDataKeys.PROJECT));
|
||||
}
|
||||
|
||||
public SymbolSearchEverywhereContributor(@NotNull AnActionEvent event) {
|
||||
super(event);
|
||||
myFilter = ClassSearchEverywhereContributor.createLanguageFilter(event.getRequiredData(CommonDataKeys.PROJECT));
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.ide.actions.searcheverywhere
|
||||
|
||||
import com.intellij.openapi.application.readAction
|
||||
import com.intellij.openapi.progress.ProgressIndicator
|
||||
import com.intellij.openapi.progress.indicatorRunBlockingCancellable
|
||||
import com.intellij.openapi.progress.runBlockingCancellable
|
||||
import com.intellij.util.Processor
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread
|
||||
import org.jetbrains.annotations.ApiStatus
|
||||
|
||||
/**
|
||||
* A function that sequentially flat maps several Search Everywhere item fetch processes.
|
||||
*
|
||||
* A typical usage can be seen in [RiderGotoClassSearchEverywhereContributor] and [RiderGotoSymbolSearchEverywhereContributor].
|
||||
* There it is used to fetch items both from the IDEA contributor and the Rider backend.
|
||||
*
|
||||
* @param consumeInCancellableRead switches whether the [finalConsumer] is applied under a [runBlockingCancellable] + [readAction] or not.
|
||||
* Important for [PSIPresentationBgRendererWrapper], which implicitly requires [finalConsumer] to
|
||||
* be run in such a context.
|
||||
*/
|
||||
@ApiStatus.Internal
|
||||
@RequiresBackgroundThread
|
||||
fun fetchWeightedElementsMixing(
|
||||
pattern: String,
|
||||
progressIndicator: ProgressIndicator,
|
||||
finalConsumer: Processor<in FoundItemDescriptor<Any>>,
|
||||
vararg fetchers: (String, ProgressIndicator, Processor<in FoundItemDescriptor<Any>>) -> Unit,
|
||||
) {
|
||||
val itemPool = mutableListOf<FoundItemDescriptor<Any>>()
|
||||
val adderClosure = { it: FoundItemDescriptor<Any> -> itemPool.add(it) }
|
||||
|
||||
fetchers.forEach { fetcher -> fetcher(pattern, progressIndicator, adderClosure) }
|
||||
|
||||
itemPool.sortBy { -it.weight }
|
||||
|
||||
@Suppress("DEPRECATION")
|
||||
indicatorRunBlockingCancellable(progressIndicator) {
|
||||
readAction { itemPool.forEach { if (!finalConsumer.process(it)) return@readAction } }
|
||||
}
|
||||
}
|
||||
|
||||
@ApiStatus.Internal
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
fun <T> makeTypeErasingConsumer(processor: Processor<in FoundItemDescriptor<Any>>) =
|
||||
{ genericDescriptor: FoundItemDescriptor<T> -> (genericDescriptor as? FoundItemDescriptor<Any>)?.let { processor.process(it) } == true }
|
||||
Reference in New Issue
Block a user