mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-15 02:59:33 +07:00
[platform] support custom content entries in Module::getModuleContent*Scope methods (IDEA-307392)
If some IDE (e.g. Rider) contributes custom files to the module content which aren't located under the module content roots, they still need to be included in Module::getModuleContentScope. In order to support this, separate classes for content of a module and for content of a module and its dependencies were extracted from ModuleWithDependenciesScope. These new classes use ProjectFileIndex::getModuleForFile instead of getContentRootForFile, so they work for custom content files, and this also works faster than the generic implementation from ModuleWithDependenciesScope. VirtualFileEnumeration isn't supported for ModuleContentScope because it seems that this optimization is relevant for ModuleWithDependenciesScope only, and currently there is no efficient way to compute all content roots of a module including custom ones. GitOrigin-RevId: df8ec70a81d1a2954f64abc13a25fafd97a31eda
This commit is contained in:
committed by
intellij-monorepo-bot
parent
c7ff71c8bf
commit
6146dfc7c3
@@ -78,7 +78,9 @@ public class ModuleScopesTest extends JavaModuleTestCase {
|
||||
VirtualFile file = myFixture.createFile("a/data/A.java", "class A {}");
|
||||
PsiTestUtil.addContentRoot(module, file.getParent());
|
||||
assertFalse(module.getModuleScope().contains(file));
|
||||
assertTrue(module.getModuleContentScope().contains(file));
|
||||
assertFalse(module.getModuleWithDependenciesScope().contains(file));
|
||||
assertTrue(module.getModuleContentWithDependenciesScope().contains(file));
|
||||
assertFalse(module.getModuleWithDependenciesAndLibrariesScope(true).contains(file));
|
||||
}
|
||||
|
||||
@@ -127,6 +129,7 @@ public class ModuleScopesTest extends JavaModuleTestCase {
|
||||
VirtualFile classB = myFixture.createFile("b/Test.java", "public class Test { }");
|
||||
assertTrue(moduleA.getModuleWithDependenciesAndLibrariesScope(true).contains(classB));
|
||||
assertFalse(moduleA.getModuleWithDependenciesAndLibrariesScope(false).contains(classB));
|
||||
assertFalse(moduleA.getModuleContentWithDependenciesScope().contains(classB));
|
||||
assertFalse(moduleA.getModuleWithDependenciesAndLibrariesScope(false).isSearchInModuleContent(moduleB));
|
||||
|
||||
VirtualFile[] compilationClasspath = getCompilationClasspath(moduleA);
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
// Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
@file:Suppress("EqualsOrHashCode")
|
||||
package com.intellij.openapi.module.impl.scopes
|
||||
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.roots.ModuleRootManager
|
||||
import com.intellij.openapi.roots.ProjectFileIndex
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.psi.search.GlobalSearchScope
|
||||
import com.intellij.util.indexing.IndexingBundle
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
|
||||
|
||||
internal class ModuleContentScope(private val module: Module) : GlobalSearchScope(module.project) {
|
||||
private val projectFileIndex = ProjectFileIndex.getInstance(module.project)
|
||||
|
||||
override fun contains(file: VirtualFile): Boolean = projectFileIndex.getModuleForFile(file) == module
|
||||
override fun isSearchInModuleContent(aModule: Module): Boolean = aModule == module
|
||||
override fun isSearchInLibraries(): Boolean = false
|
||||
override fun equals(other: Any?): Boolean = (other as? ModuleContentScope)?.module == module
|
||||
override fun calcHashCode(): Int = module.hashCode()
|
||||
override fun toString(): String = "ModuleContentScope{module=${module.name}}"
|
||||
override fun getDisplayName(): String = IndexingBundle.message("search.scope.module", module.name)
|
||||
}
|
||||
|
||||
internal class ModuleWithDependenciesContentScope(private val rootModule: Module) : GlobalSearchScope(rootModule.project) {
|
||||
private val moduleToOrder : Object2IntMap<Module>
|
||||
private val projectFileIndex = ProjectFileIndex.getInstance(rootModule.project)
|
||||
|
||||
init {
|
||||
moduleToOrder = Object2IntOpenHashMap()
|
||||
var i = 1
|
||||
ModuleRootManager.getInstance(rootModule).orderEntries().recursively().withoutLibraries().withoutSdk().productionOnly().forEachModule {
|
||||
moduleToOrder.put(it, i++)
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
override fun contains(file: VirtualFile): Boolean {
|
||||
val module = projectFileIndex.getModuleForFile(file)
|
||||
return module != null && module in moduleToOrder
|
||||
}
|
||||
|
||||
override fun isSearchInModuleContent(aModule: Module): Boolean = aModule in moduleToOrder
|
||||
override fun isSearchInLibraries(): Boolean = false
|
||||
|
||||
override fun compare(file1: VirtualFile, file2: VirtualFile): Int {
|
||||
val order1 = projectFileIndex.getModuleForFile(file1)?.let { moduleToOrder.getInt(it) } ?: 0
|
||||
val order2 = projectFileIndex.getModuleForFile(file2)?.let { moduleToOrder.getInt(it) } ?: 0
|
||||
//see javadoc of GlobalSearchScope::compareTo: a positive result indicates that file1 is located in the classpath before file2
|
||||
return order2.compareTo(order1)
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean = (other as? ModuleWithDependenciesContentScope)?.rootModule == rootModule
|
||||
override fun calcHashCode(): Int = rootModule.hashCode()
|
||||
override fun toString(): String = "ModuleWithDependenciesContentScope{rootModule=${rootModule.name}}"
|
||||
override fun getDisplayName(): String = IndexingBundle.message("search.scope.module", rootModule.name)
|
||||
}
|
||||
@@ -16,6 +16,7 @@ public class ModuleScopeProviderImpl implements ModuleScopeProvider {
|
||||
private final IntObjectMap<GlobalSearchScope> myScopeCache =
|
||||
ConcurrentCollectionFactory.createConcurrentIntObjectMap();
|
||||
private ModuleWithDependentsTestScope myModuleTestsWithDependentsScope;
|
||||
private volatile ModuleWithDependenciesContentScope myModuleWithDependenciesContentScope;
|
||||
|
||||
public ModuleScopeProviderImpl(@NotNull Module module) {
|
||||
myModule = module;
|
||||
@@ -58,13 +59,17 @@ public class ModuleScopeProviderImpl implements ModuleScopeProvider {
|
||||
@NotNull
|
||||
@Override
|
||||
public GlobalSearchScope getModuleContentScope() {
|
||||
return getCachedScope(ModuleWithDependenciesScope.CONTENT);
|
||||
return new ModuleContentScope(myModule);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public GlobalSearchScope getModuleContentWithDependenciesScope() {
|
||||
return getCachedScope(ModuleWithDependenciesScope.CONTENT | ModuleWithDependenciesScope.MODULES);
|
||||
ModuleWithDependenciesContentScope scope = myModuleWithDependenciesContentScope;
|
||||
if (scope == null) {
|
||||
myModuleWithDependenciesContentScope = scope = new ModuleWithDependenciesContentScope(myModule);
|
||||
}
|
||||
return scope;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -112,5 +117,6 @@ public class ModuleScopeProviderImpl implements ModuleScopeProvider {
|
||||
public void clearCache() {
|
||||
myScopeCache.clear();
|
||||
myModuleTestsWithDependentsScope = null;
|
||||
myModuleWithDependenciesContentScope = null;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +41,8 @@ public final class ModuleWithDependenciesScope extends GlobalSearchScope impleme
|
||||
public static final int LIBRARIES = 0x02;
|
||||
public static final int MODULES = 0x04;
|
||||
public static final int TESTS = 0x08;
|
||||
public static final int CONTENT = 0x20;
|
||||
|
||||
@MagicConstant(flags = {COMPILE_ONLY, LIBRARIES, MODULES, TESTS, CONTENT})
|
||||
@MagicConstant(flags = {COMPILE_ONLY, LIBRARIES, MODULES, TESTS})
|
||||
@interface ScopeConstant {}
|
||||
|
||||
private static final Key<CachedValue<ConcurrentMap<Integer, VirtualFileEnumeration>>> CACHED_FILE_ID_ENUMERATIONS_KEY =
|
||||
@@ -68,25 +67,14 @@ public final class ModuleWithDependenciesScope extends GlobalSearchScope impleme
|
||||
|
||||
private Object2IntMap<VirtualFile> calcRoots(@Nullable ModelBranch branch) {
|
||||
Set<VirtualFile> roots = new LinkedHashSet<>();
|
||||
if (hasOption(CONTENT)) {
|
||||
Set<Module> modules = calcModules();
|
||||
myModules = new HashSet<>(modules);
|
||||
for (Module m : modules) {
|
||||
for (ContentEntry entry : ModuleRootManager.getInstance(m).getContentEntries()) {
|
||||
ContainerUtil.addIfNotNull(roots, branch == null ? entry.getFile() : branch.findFileByUrl(entry.getUrl()));
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
OrderRootsEnumerator en = getOrderEnumeratorForOptions().roots(entry -> {
|
||||
if (entry instanceof ModuleOrderEntry || entry instanceof ModuleSourceOrderEntry) return OrderRootType.SOURCES;
|
||||
return OrderRootType.CLASSES;
|
||||
});
|
||||
if (branch == null) {
|
||||
Collections.addAll(roots, en.getRoots());
|
||||
} else {
|
||||
roots.addAll(ContainerUtil.mapNotNull(en.getUrls(), branch::findFileByUrl));
|
||||
}
|
||||
OrderRootsEnumerator en = getOrderEnumeratorForOptions().roots(entry -> {
|
||||
if (entry instanceof ModuleOrderEntry || entry instanceof ModuleSourceOrderEntry) return OrderRootType.SOURCES;
|
||||
return OrderRootType.CLASSES;
|
||||
});
|
||||
if (branch == null) {
|
||||
Collections.addAll(roots, en.getRoots());
|
||||
} else {
|
||||
roots.addAll(ContainerUtil.mapNotNull(en.getUrls(), branch::findFileByUrl));
|
||||
}
|
||||
|
||||
int i = 1;
|
||||
@@ -109,9 +97,7 @@ public final class ModuleWithDependenciesScope extends GlobalSearchScope impleme
|
||||
|
||||
@NotNull
|
||||
private Set<Module> calcModules() {
|
||||
// In the case that hasOption(CONTENT), the order of the modules set matters for
|
||||
// ordering the content roots, so use a LinkedHashSet
|
||||
Set<Module> modules = new LinkedHashSet<>();
|
||||
Set<Module> modules = new HashSet<>();
|
||||
OrderEnumerator en = getOrderEnumeratorForOptions();
|
||||
en.forEach(each -> {
|
||||
if (each instanceof ModuleOrderEntry) {
|
||||
@@ -162,12 +148,8 @@ public final class ModuleWithDependenciesScope extends GlobalSearchScope impleme
|
||||
|
||||
@Override
|
||||
public boolean contains(@NotNull VirtualFile file) {
|
||||
Object2IntMap<VirtualFile> roots = getRoots(file);
|
||||
if (hasOption(CONTENT)) {
|
||||
return roots.containsKey(myProjectFileIndex.getContentRootForFile(file));
|
||||
}
|
||||
VirtualFile root = myProjectFileIndex.getModuleSourceOrLibraryClassesRoot(file);
|
||||
return root != null && roots.containsKey(root);
|
||||
return root != null && getRoots(file).containsKey(root);
|
||||
}
|
||||
|
||||
private Object2IntMap<VirtualFile> getRoots(@NotNull VirtualFile file) {
|
||||
@@ -188,8 +170,8 @@ public final class ModuleWithDependenciesScope extends GlobalSearchScope impleme
|
||||
|
||||
@Override
|
||||
public int compare(@NotNull VirtualFile file1, @NotNull VirtualFile file2) {
|
||||
VirtualFile r1 = getFileRoot(file1);
|
||||
VirtualFile r2 = getFileRoot(file2);
|
||||
VirtualFile r1 = myProjectFileIndex.getModuleSourceOrLibraryClassesRoot(file1);
|
||||
VirtualFile r2 = myProjectFileIndex.getModuleSourceOrLibraryClassesRoot(file2);
|
||||
if (Comparing.equal(r1, r2)) return 0;
|
||||
|
||||
if (r1 == null) return -1;
|
||||
@@ -203,14 +185,6 @@ public final class ModuleWithDependenciesScope extends GlobalSearchScope impleme
|
||||
return i1 > 0 ? 1 : -1;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private VirtualFile getFileRoot(@NotNull VirtualFile file) {
|
||||
if (hasOption(CONTENT)) {
|
||||
return myProjectFileIndex.getContentRootForFile(file);
|
||||
}
|
||||
return myProjectFileIndex.getModuleSourceOrLibraryClassesRoot(file);
|
||||
}
|
||||
|
||||
@TestOnly
|
||||
public Collection<VirtualFile> getRoots() {
|
||||
List<VirtualFile> result = new ArrayList<>(myRoots.keySet());
|
||||
|
||||
@@ -137,7 +137,7 @@ public class GlobalSearchScopeTest extends HeavyPlatformTestCase {
|
||||
assertNotNull(moduleRoot2);
|
||||
PsiTestUtil.addSourceRoot(getModule(), moduleRoot2);
|
||||
|
||||
GlobalSearchScope modScope = getModule().getModuleContentScope();
|
||||
GlobalSearchScope modScope = getModule().getModuleScope();
|
||||
int compare = modScope.compare(moduleRoot, moduleRoot2);
|
||||
assertTrue(compare != 0);
|
||||
GlobalSearchScope union = modScope.uniteWith(GlobalSearchScope.EMPTY_SCOPE);
|
||||
|
||||
@@ -3,6 +3,8 @@ package com.intellij.workspaceModel.core.fileIndex
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.readAction
|
||||
import com.intellij.openapi.module.Module
|
||||
import com.intellij.openapi.roots.ProjectFileIndex
|
||||
import com.intellij.openapi.roots.impl.assertIteratedContent
|
||||
import com.intellij.openapi.vfs.VirtualFile
|
||||
import com.intellij.testFramework.junit5.TestApplication
|
||||
@@ -10,6 +12,7 @@ import com.intellij.testFramework.junit5.TestDisposable
|
||||
import com.intellij.testFramework.rules.ProjectModelExtension
|
||||
import com.intellij.testFramework.workspaceModel.updateProjectModelAsync
|
||||
import com.intellij.util.indexing.testEntities.IndexingTestEntity
|
||||
import com.intellij.workspaceModel.core.fileIndex.impl.ModuleRelatedRootData
|
||||
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexImpl
|
||||
import com.intellij.workspaceModel.ide.NonPersistentEntitySource
|
||||
import com.intellij.workspaceModel.ide.WorkspaceModel
|
||||
@@ -18,11 +21,10 @@ import com.intellij.workspaceModel.ide.toVirtualFileUrl
|
||||
import com.intellij.workspaceModel.storage.EntityStorage
|
||||
import com.intellij.workspaceModel.storage.url.VirtualFileUrlManager
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import org.junit.jupiter.api.Assertions.*
|
||||
import org.junit.jupiter.api.BeforeEach
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.extension.RegisterExtension
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
@TestApplication
|
||||
class CustomContentFileSetTest {
|
||||
@@ -33,14 +35,19 @@ class CustomContentFileSetTest {
|
||||
private val fileIndex
|
||||
get() = WorkspaceFileIndex.getInstance(projectModel.project)
|
||||
|
||||
private val projectFileIndex
|
||||
get() = ProjectFileIndex.getInstance(projectModel.project)
|
||||
|
||||
@TestDisposable
|
||||
private lateinit var disposable: Disposable
|
||||
private lateinit var customContentFileSetRoot: VirtualFile
|
||||
private lateinit var module: Module
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
customContentFileSetRoot = projectModel.baseProjectDir.newVirtualDirectory("root")
|
||||
WorkspaceFileIndexImpl.EP_NAME.point.registerExtension(CustomContentFileSetContributor(), disposable)
|
||||
module = projectModel.createModule()
|
||||
WorkspaceFileIndexImpl.EP_NAME.point.registerExtension(CustomContentFileSetContributor(module), disposable)
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -59,6 +66,9 @@ class CustomContentFileSetTest {
|
||||
|
||||
readAction {
|
||||
assertTrue(fileIndex.isInContent(file))
|
||||
assertEquals(module, projectFileIndex.getModuleForFile(file))
|
||||
assertTrue(module.moduleContentScope.contains(file))
|
||||
assertFalse(module.moduleScope.contains(file))
|
||||
assertIteratedContent(projectModel.project, mustContain = listOf(root, file))
|
||||
}
|
||||
|
||||
@@ -68,18 +78,22 @@ class CustomContentFileSetTest {
|
||||
|
||||
readAction {
|
||||
assertFalse(fileIndex.isInContent(root))
|
||||
assertNull(projectFileIndex.getModuleForFile(file))
|
||||
assertFalse(module.moduleContentScope.contains(file))
|
||||
assertIteratedContent(projectModel.project, mustNotContain = listOf(root, file))
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomContentFileSetContributor : WorkspaceFileIndexContributor<IndexingTestEntity> {
|
||||
private class CustomContentFileSetContributor(private val module: Module) : WorkspaceFileIndexContributor<IndexingTestEntity> {
|
||||
override val entityClass: Class<IndexingTestEntity>
|
||||
get() = IndexingTestEntity::class.java
|
||||
|
||||
override fun registerFileSets(entity: IndexingTestEntity, registrar: WorkspaceFileSetRegistrar, storage: EntityStorage) {
|
||||
for (root in entity.roots) {
|
||||
registrar.registerFileSet(root, WorkspaceFileKind.CONTENT, entity, null)
|
||||
registrar.registerFileSet(root, WorkspaceFileKind.CONTENT, entity, CustomModuleRelatedRootData(module))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class CustomModuleRelatedRootData(override val module: Module): ModuleRelatedRootData
|
||||
}
|
||||
Reference in New Issue
Block a user