diff --git a/platform/lang-impl/src/com/intellij/ide/actions/searcheverywhere/AbstractGotoSEContributor.kt b/platform/lang-impl/src/com/intellij/ide/actions/searcheverywhere/AbstractGotoSEContributor.kt index dbd272e10050..213224860c3f 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/searcheverywhere/AbstractGotoSEContributor.kt +++ b/platform/lang-impl/src/com/intellij/ide/actions/searcheverywhere/AbstractGotoSEContributor.kt @@ -170,6 +170,15 @@ abstract class AbstractGotoSEContributor @ApiStatus.Internal protected construct } return current } + + @ApiStatus.Internal + fun createScopes(project: Project, psiContext: SmartPsiElementPointer?): List { + @Suppress("DEPRECATION") + return project.getService(ScopeService::class.java) + .createModel(EnumSet.of(ScopeOption.LIBRARIES, ScopeOption.EMPTY_SCOPES)) + .getScopesImmediately(createContext(project, psiContext)) + .scopeDescriptors + } } @ApiStatus.Internal @@ -190,10 +199,7 @@ abstract class AbstractGotoSEContributor @ApiStatus.Internal protected construct protected open fun createScopes(): List { @Suppress("DEPRECATION") - return myProject.getService(ScopeService::class.java) - .createModel(EnumSet.of(ScopeOption.LIBRARIES, ScopeOption.EMPTY_SCOPES)) - .getScopesImmediately(createContext(myProject, myPsiContext)) - .scopeDescriptors + return createScopes(myProject, myPsiContext) } override fun getSearchProviderId(): String = javaClass.simpleName diff --git a/platform/lang-impl/src/com/intellij/ide/actions/searcheverywhere/NonIndexableFilesSEContributor.kt b/platform/lang-impl/src/com/intellij/ide/actions/searcheverywhere/NonIndexableFilesSEContributor.kt index 0b3a4c9bc066..dac71fa3f044 100644 --- a/platform/lang-impl/src/com/intellij/ide/actions/searcheverywhere/NonIndexableFilesSEContributor.kt +++ b/platform/lang-impl/src/com/intellij/ide/actions/searcheverywhere/NonIndexableFilesSEContributor.kt @@ -2,6 +2,7 @@ package com.intellij.ide.actions.searcheverywhere import com.intellij.ide.IdeBundle +import com.intellij.ide.actions.GotoActionBase import com.intellij.ide.actions.GotoFileItemProvider import com.intellij.ide.actions.SearchEverywherePsiRenderer import com.intellij.ide.actions.searcheverywhere.footer.createPsiExtendedInfo @@ -10,6 +11,7 @@ import com.intellij.ide.util.scopeChooser.ScopeDescriptor import com.intellij.openapi.actionSystem.AnActionEvent import com.intellij.openapi.application.ReadAction import com.intellij.openapi.application.runReadAction +import com.intellij.openapi.application.runReadActionBlocking import com.intellij.openapi.diagnostic.Logger import com.intellij.openapi.progress.ProgressIndicator import com.intellij.openapi.project.DumbAware @@ -22,6 +24,8 @@ import com.intellij.psi.PsiElement import com.intellij.psi.PsiFile import com.intellij.psi.PsiFileSystemItem import com.intellij.psi.PsiManager +import com.intellij.psi.SmartPointerManager +import com.intellij.psi.SmartPsiElementPointer import com.intellij.psi.codeStyle.NameUtil import com.intellij.util.Processor import com.intellij.util.indexing.FilesDeque @@ -39,6 +43,9 @@ import kotlin.concurrent.atomics.ExperimentalAtomicApi */ @ApiStatus.Internal interface FilesTabSEContributor { + val psiContext: SmartPsiElementPointer? + val project: Project + fun setScope(scope: ScopeDescriptor) fun setHiddenTypes(hiddenTypes: List) @@ -73,11 +80,14 @@ class NonIndexableFilesSEContributor(event: AnActionEvent) : WeightedSearchEvery DumbAware, FilesTabSEContributor, SearchEverywhereExtendedInfoProvider { - private val project: Project = event.project!! + override val project: Project = event.project!! private val navigationHandler: SearchEverywhereNavigationHandler = FileSearchEverywhereNavigationContributionHandler(project) private val scope: AtomicReference = AtomicReference(null) private val hiddenTypes: AtomicReference> = AtomicReference(emptySet()) + override val psiContext: SmartPsiElementPointer? = GotoActionBase.getPsiContext(event)?.let { context -> + SmartPointerManager.getInstance(project).createSmartPsiElementPointer(context) + } override fun getSearchProviderId(): String = ID @@ -137,7 +147,7 @@ class NonIndexableFilesSEContributor(event: AnActionEvent) : WeightedSearchEvery */ val pathMatcher = GotoFileItemProvider.getQualifiedNameMatcher(pathPattern) - val nameMatcher = NameUtil.buildMatcher("*" + namePattern) + val nameMatcher = NameUtil.buildMatcher("*$namePattern") .withMatchingMode(MatchingMode.IGNORE_CASE) .preferringStartMatches() .build() @@ -163,7 +173,7 @@ class NonIndexableFilesSEContributor(event: AnActionEvent) : WeightedSearchEvery if (file == null) break val workspaceFileIndex = WorkspaceFileIndexEx.getInstance(project) - val nonIndexableRoot = runReadAction { workspaceFileIndex.findNonIndexableFileSet(file) }?.root + val nonIndexableRoot = runReadActionBlocking { workspaceFileIndex.findNonIndexableFileSet(file) }?.root // path includes root val pathFromNonIndexableRoot = file.path.removePrefix(nonIndexableRoot?.parent?.path ?: "").removePrefix("/") diff --git a/platform/searchEverywhere/backend/src/providers/target/SeTargetsProviderDelegate.kt b/platform/searchEverywhere/backend/src/providers/target/SeTargetsProviderDelegate.kt index ac6120b035ce..a8f3114e2aa5 100644 --- a/platform/searchEverywhere/backend/src/providers/target/SeTargetsProviderDelegate.kt +++ b/platform/searchEverywhere/backend/src/providers/target/SeTargetsProviderDelegate.kt @@ -74,11 +74,7 @@ class SeTargetsProviderDelegate(private val contributorWrapper: SeAsyncContribut scopeProviderDelegate?.let { scopeProviderDelegate -> SeEverywhereFilter.isEverywhere(params.filter)?.let { isEverywhere -> - val selectedScopeId = scopeProviderDelegate.searchScopesInfo.getValue()?.let { searchScopesInfo -> - if (isEverywhere) searchScopesInfo.everywhereScopeId else searchScopesInfo.projectScopeId - } ?: return@let - - scopeProviderDelegate.applyScope(selectedScopeId, false) + scopeProviderDelegate.applyScope(isEverywhere, false) } ?: run { val targetsFilter = SeTargetsFilter.from(params.filter) SeTypeVisibilityStateProviderDelegate.applyTypeVisibilityStates(contributor, targetsFilter.hiddenTypes) diff --git a/platform/searchEverywhere/shared/src/providers/ScopeChooserActionProviderDelegate.kt b/platform/searchEverywhere/shared/src/providers/ScopeChooserActionProviderDelegate.kt index 6bd81e784914..d251c3e4fb7f 100644 --- a/platform/searchEverywhere/shared/src/providers/ScopeChooserActionProviderDelegate.kt +++ b/platform/searchEverywhere/shared/src/providers/ScopeChooserActionProviderDelegate.kt @@ -1,13 +1,19 @@ // 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.platform.searchEverywhere.providers +import com.intellij.ide.actions.searcheverywhere.AbstractGotoSEContributor +import com.intellij.ide.actions.searcheverywhere.FilesTabSEContributor +import com.intellij.ide.actions.searcheverywhere.FilesTabSEContributor.Companion.unwrapFilesTabContributorIfPossible import com.intellij.ide.actions.searcheverywhere.ScopeChooserAction import com.intellij.ide.util.scopeChooser.ScopeDescriptor +import com.intellij.ide.util.scopeChooser.ScopeIdMapper import com.intellij.openapi.application.readAction +import com.intellij.openapi.application.runReadActionBlocking import com.intellij.platform.scopes.SearchScopeData import com.intellij.platform.scopes.SearchScopesInfo import com.intellij.platform.searchEverywhere.utils.SuspendLazyProperty import com.intellij.platform.searchEverywhere.utils.suspendLazy +import com.intellij.psi.search.GlobalSearchScope import org.jetbrains.annotations.ApiStatus import java.util.UUID import kotlin.concurrent.atomics.AtomicReference @@ -19,11 +25,13 @@ class ScopeChooserActionProviderDelegate private constructor(private val contrib val searchScopesInfo: SuspendLazyProperty = suspendLazy { getSearchScopesInfo() } - @Volatile - private var scopeIdToScope: AtomicReference> = AtomicReference(emptyMap()) + private val availableScopes: AtomicReference = + contributorWrapper.contributor.unwrapFilesTabContributorIfPossible()?.let { filesTabContributor -> + AtomicReference(SeScopeByIdFiles(filesTabContributor)) + } ?: AtomicReference(SeScopeByIdAtomicMap(emptyMap(), null, null)) - suspend fun getSearchScopesInfo(): SearchScopesInfo? { - val scopeChooserAction: ScopeChooserAction = contributorWrapper.contributor.getActions({ }).filterIsInstance().firstOrNull() + private suspend fun getSearchScopesInfo(): SearchScopesInfo? { + val scopeChooserAction: ScopeChooserAction = contributorWrapper.contributor.getActions { }.filterIsInstance().firstOrNull() ?: return null @@ -33,54 +41,103 @@ class ScopeChooserActionProviderDelegate private constructor(private val contrib val scopeDataList = readAction { scopeChooserAction.scopesWithSeparators }.mapNotNull { scope -> - val key = UUID.randomUUID().toString() - val data = SearchScopeData.Companion.from(scope, key) + val key = scope.displayName?.let { + "${UUID.randomUUID()}$SCOPE_ID_SEPARATOR${ScopeIdMapper.instance.getScopeSerializationId(it)}" + } ?: return@mapNotNull null + + val data = SearchScopeData.from(scope, key) if (data != null) all[key] = scope data } - scopeIdToScope.store(all) val selectedScopeId = selectedScope.scope?.displayName.let { name -> scopeDataList.firstOrNull { - @Suppress("HardCodedStringLiteral") it.name == name }?.scopeId } val everywhereScopeId = scopeChooserAction.everywhereScopeName?.let { name -> scopeDataList.firstOrNull { - @Suppress("HardCodedStringLiteral") it.name == name }?.scopeId } val projectScopeId = scopeChooserAction.projectScopeName?.let { name -> scopeDataList.firstOrNull { - @Suppress("HardCodedStringLiteral") it.name == name }?.scopeId } + availableScopes.store(SeScopeByIdAtomicMap(all, everywhereScopeId = everywhereScopeId, projectScopeId = projectScopeId)) + return SearchScopesInfo(scopeDataList, selectedScopeId, projectScopeId, everywhereScopeId) } + fun applyScope(isEverywhere: Boolean, isAutoTogglePossible: Boolean) { + val scope = availableScopes.load()[isEverywhere] ?: return + applyScope(scope, isAutoTogglePossible) + } + fun applyScope(scopeId: String?, isAutoTogglePossible: Boolean) { if (scopeId == null) return - val scope = scopeIdToScope.load()[scopeId] ?: return + val scope = availableScopes.load()[scopeId] ?: return + applyScope(scope, isAutoTogglePossible) + } + fun applyScope(scope: ScopeDescriptor, isAutoTogglePossible: Boolean) { contributorWrapper.contributor.getActions { }.filterIsInstance().firstOrNull()?.let { it.setScopeIsDefaultAndAutoSet(isAutoTogglePossible) it.onScopeSelected(scope) } + ?: contributorWrapper.contributor.unwrapFilesTabContributorIfPossible()?.setScope(scope) } companion object { fun createOrNull(contributorWrapper: SeAsyncContributorWrapper): ScopeChooserActionProviderDelegate? = - if (contributorWrapper.contributor.getActions { }.any { it is ScopeChooserAction }) + if (contributorWrapper.contributor.unwrapFilesTabContributorIfPossible() != null || + contributorWrapper.contributor.getActions { }.any { it is ScopeChooserAction }) ScopeChooserActionProviderDelegate(contributorWrapper) else null } -} \ No newline at end of file +} + +private const val SCOPE_ID_SEPARATOR = '_' + +private interface SeScopeById { + operator fun get(isEverywhere: Boolean): ScopeDescriptor? + operator fun get(scopeId: String): ScopeDescriptor? +} + +private class SeScopeByIdAtomicMap( + private val scopeIdToScope: Map, + private val everywhereScopeId: String?, + private val projectScopeId: String?, +) : SeScopeById { + override fun get(isEverywhere: Boolean): ScopeDescriptor? = + (if (isEverywhere) everywhereScopeId else projectScopeId)?.let { scopeIdToScope[it] } + + override fun get(scopeId: String): ScopeDescriptor? = scopeIdToScope[scopeId] +} + +private class SeScopeByIdFiles(filesContributor: FilesTabSEContributor): SeScopeById { + private val scopes = runReadActionBlocking { + AbstractGotoSEContributor.createScopes(filesContributor.project, filesContributor.psiContext).mapNotNull { + val name = it.displayName ?: return@mapNotNull null + ScopeIdMapper.instance.getScopeSerializationId(name) to it + }.toMap() + } + + private val projectScopeId: String = GlobalSearchScope.projectScope(filesContributor.project).displayName.let { + ScopeIdMapper.instance.getScopeSerializationId(it) + } + + private val everywhereScopeId: String = GlobalSearchScope.everythingScope(filesContributor.project).displayName.let { + ScopeIdMapper.instance.getScopeSerializationId(it) + } + + override fun get(isEverywhere: Boolean): ScopeDescriptor? = (if (isEverywhere) everywhereScopeId else projectScopeId).let { scopes[it] } + override fun get(scopeId: String): ScopeDescriptor? = scopes[scopeId.substringAfter(SCOPE_ID_SEPARATOR)] +}