[platform] initial version of WorkspaceFileIndex (IDEA-276394, IDEA-298358)

This introduces WorkspaceFileIndex, which uses information provided by WorkspaceFileIndexContributor extensions to associate files with entities from Workspace Model. The index is updated incrementally when entities from the workspace model are changed, the parts of it taken from AdditionalLibraryRootsProvider and DirectoryIndexExcludePolicy are updated non-incrementally on rootsChanged as before.

Implementations of the existing ProjectFileIndex and ModuleFileIndex are changed to delegate to WorkspaceFileIndex if 'platform.projectModel.workspace.model.file.index' registry option is enabled. Some methods of ProjectFileIndex and ModuleFileIndex still use the old DirectoryIndex, also there is some code which use it directly.

GitOrigin-RevId: 6acb68dd461b372a387354c5234004efca3f2931
This commit is contained in:
Nikolay Chashnikov
2022-10-12 14:27:13 +02:00
committed by intellij-monorepo-bot
parent b4a45be687
commit 056aca7669
33 changed files with 1655 additions and 63 deletions

View File

@@ -11,6 +11,7 @@ import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.testFramework.JavaPsiTestCase;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexEx;
import java.io.File;
@@ -57,7 +58,10 @@ public class SameSourceRootInTwoModulesTest extends JavaPsiTestCase {
PsiTestUtil.addSourceRoot(anotherModule, mySrcDir1);
assertEquals(anotherModule, ModuleUtilCore.findModuleForFile(file, myProject));
assertNotNull(ModuleUtilCore.findModuleForFile(file, myProject));
if (!WorkspaceFileIndexEx.IS_ENABLED) {
assertEquals(anotherModule, ModuleUtilCore.findModuleForFile(file, myProject));
}
});
}
}

View File

@@ -13,6 +13,7 @@ import com.intellij.openapi.util.Condition;
import com.intellij.openapi.vfs.LocalFileSystem;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaSourceRootType;
@@ -189,8 +190,14 @@ public class DirectoryIndexForExcludePatternsTest extends DirectoryIndexTestCase
VirtualFile java = createChildData(myLibraryRoot, "A.java");
registerLibrary(myLibraryRoot, file -> "dir".contentEquals(file.getNameSequence()));
assertNotExcluded(txt2);
assertNotInLibrarySources(txt2, myModule);
if (WorkspaceFileIndexEx.IS_ENABLED) {
assertExcluded(txt2, myModule);
assertNotInLibrarySources(txt2, null);
}
else {
assertNotExcluded(txt2);
assertNotInLibrarySources(txt2, myModule);
}
assertNotExcluded(txt1);
assertInLibrarySources(txt1, myModule);
@@ -227,10 +234,19 @@ public class DirectoryIndexForExcludePatternsTest extends DirectoryIndexTestCase
VirtualFile java = createChildData(myLibraryRoot, "A.java");
registerLibrary(myLibraryRoot, file -> file.equals(myLibraryRoot));
assertInProject(txt);
assertNotInLibrarySources(txt, myModule);
assertInProject(java);
assertNotInLibrarySources(java, myModule);
if (WorkspaceFileIndexEx.IS_ENABLED) {
assertExcluded(txt, myModule);
assertNotInLibrarySources(txt, null);
assertExcluded(java, myModule);
assertNotInLibrarySources(java, null);
}
else {
assertInProject(txt);
assertNotInLibrarySources(txt, myModule);
assertInProject(java);
assertNotInLibrarySources(java, myModule);
}
assertIndexableContent(Arrays.asList(txt, java), null);
}

View File

@@ -11,6 +11,7 @@ import com.intellij.project.stateStore
import com.intellij.testFramework.PsiTestUtil
import com.intellij.util.io.directoryContent
import com.intellij.util.io.generateInVirtualTempDir
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexEx
// directory-based project must be used to ensure that .iws/.ipr file won't break the test (they may be created if workspace model is used)
class ModulesInProjectViewTest : BaseProjectViewTestCase() {
@@ -59,17 +60,20 @@ class ModulesInProjectViewTest : BaseProjectViewTestCase() {
runUnderModalProgressIfIsEdt {
ModuleManager.getInstance(myProject).setUnloadedModules(listOf("unloaded", "unloaded-inner"))
}
assertStructureEqual("""
Project
loaded
unloaded-inner
subdir
y.txt
unloaded
loaded-inner
subdir
z.txt
""".trimIndent())
if (!WorkspaceFileIndexEx.IS_ENABLED) {
//todo temporarily dispose until events are fired after unloading
assertStructureEqual("""
Project
loaded
unloaded-inner
subdir
y.txt
unloaded
loaded-inner
subdir
z.txt
""".trimIndent())
}
}
fun `test unloaded module with qualified name`() {

View File

@@ -10,6 +10,8 @@ import com.intellij.util.indexing.FileBasedIndexImpl;
import com.intellij.util.indexing.UnindexedFilesUpdater;
import com.intellij.util.indexing.roots.AdditionalLibraryRootsContributor;
import com.intellij.util.indexing.roots.IndexableFilesIterator;
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndex;
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexEx;
import org.jetbrains.annotations.Nls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -30,6 +32,7 @@ public class AdditionalLibraryRootsListenerHelperImpl implements AdditionalLibra
if (directoryIndex instanceof DirectoryIndexImpl) {
((DirectoryIndexImpl)directoryIndex).reset();
}
((WorkspaceFileIndexEx)WorkspaceFileIndex.getInstance(project)).resetCustomContributors();
additionalLibraryRootsChanged(project, presentableLibraryName, oldRoots, newRoots, libraryNameForDebug);
}

View File

@@ -39,6 +39,8 @@ import com.intellij.util.indexing.EntityIndexingService
import com.intellij.util.indexing.IndexableFilesIndex
import com.intellij.util.indexing.roots.IndexableFilesIndexImpl
import com.intellij.util.io.systemIndependentPath
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndex
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexEx
import kotlinx.coroutines.*
import org.jetbrains.annotations.TestOnly
import java.lang.Runnable
@@ -220,6 +222,7 @@ open class ProjectRootManagerComponent(project: Project) : ProjectRootManagerImp
if (IndexableFilesIndex.shouldBeUsed()) {
IndexableFilesIndexImpl.getInstanceImpl(myProject).beforeRootsChanged()
}
(WorkspaceFileIndex.getInstance(myProject) as WorkspaceFileIndexEx).resetCustomContributors()
myProject.messageBus.syncPublisher(ProjectTopics.PROJECT_ROOTS).beforeRootsChange(ModuleRootEventImpl(myProject, fileTypes))
}
finally {
@@ -231,6 +234,7 @@ open class ProjectRootManagerComponent(project: Project) : ProjectRootManagerImp
isFiringEvent = true
try {
(DirectoryIndex.getInstance(myProject) as? DirectoryIndexImpl)?.reset()
(WorkspaceFileIndex.getInstance(myProject) as WorkspaceFileIndexEx).resetCustomContributors()
val isFromWorkspaceOnly = EntityIndexingService.getInstance().isFromWorkspaceOnly(indexingInfos)
if (IndexableFilesIndex.shouldBeUsed()) {

View File

@@ -25,11 +25,14 @@ import com.intellij.util.containers.CollectionFactory
import com.intellij.util.containers.ContainerUtil
import com.intellij.util.indexing.EntityIndexingServiceEx
import com.intellij.util.io.URLUtil
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndex
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexEx
import com.intellij.workspaceModel.ide.WorkspaceModel
import com.intellij.workspaceModel.ide.getInstance
import com.intellij.workspaceModel.ide.impl.legacyBridge.project.ProjectRootManagerBridge
import com.intellij.workspaceModel.ide.impl.legacyBridge.project.ProjectRootsChangeListener.Companion.shouldFireRootsChanged
import com.intellij.workspaceModel.ide.impl.legacyBridge.watcher.VirtualFileUrlWatcher.Companion.calculateAffectedEntities
import com.intellij.workspaceModel.ide.impl.virtualFile
import com.intellij.workspaceModel.storage.EntityStorage
import com.intellij.workspaceModel.storage.WorkspaceEntity
import com.intellij.workspaceModel.storage.bridgeEntities.api.ModuleId
@@ -175,21 +178,25 @@ private class RootsChangeWatcher(private val project: Project) {
allRootsWereRemoved: Boolean) {
val includingJarDirectory = getIncludingJarDirectory(storage, virtualFileUrl)
if (includingJarDirectory != null) {
val entities = if (allRootsWereRemoved) emptyList()
else
storage.getVirtualFileUrlIndex().findEntitiesByUrl(includingJarDirectory).toList()
entityChanges.addAll(entities.map { pair -> pair.first })
val entities = storage.getVirtualFileUrlIndex().findEntitiesByUrl(includingJarDirectory).map { it.first }.toList()
entityChanges.addAffectedEntities(entities, allRootsWereRemoved)
return
}
val affectedEntities = mutableListOf<EntityWithVirtualFileUrl>()
calculateAffectedEntities(storage, virtualFileUrl, affectedEntities)
virtualFileUrl.subTreeFileUrls.forEach { fileUrl -> calculateAffectedEntities(storage, fileUrl, affectedEntities) }
if (allRootsWereRemoved) {
entityChanges.addFileToInvalidate(virtualFileUrl.virtualFile)
}
virtualFileUrl.subTreeFileUrls.forEach { fileUrl ->
if (calculateAffectedEntities(storage, fileUrl, affectedEntities) && allRootsWereRemoved) {
entityChanges.addFileToInvalidate(fileUrl.virtualFile)
}
}
if (affectedEntities.any { it.propertyName != "entitySource" && shouldFireRootsChanged(it.entity, project) }
|| virtualFileUrl.url in projectFilePaths) {
val changes = if (allRootsWereRemoved) emptyList() else affectedEntities.map { it.entity }
entityChanges.addAll(changes)
entityChanges.addAffectedEntities(affectedEntities.map { it.entity }, allRootsWereRemoved)
}
}
@@ -202,6 +209,17 @@ private class RootsChangeWatcher(private val project: Project) {
private fun fireRootsChangeEvent(beforeRootsChanged: Boolean = false, entityChangesStorage: EntityChangeStorage) {
ApplicationManager.getApplication().assertWriteAccessAllowed()
val affectedEntities = entityChangesStorage.affectedEntities
if (WorkspaceFileIndexEx.IS_ENABLED && affectedEntities.isNotEmpty()) {
val workspaceFileIndex = WorkspaceFileIndex.getInstance(project) as WorkspaceFileIndexEx
if (beforeRootsChanged) {
workspaceFileIndex.markDirty(affectedEntities, entityChangesStorage.filesToInvalidate)
}
else {
workspaceFileIndex.markDirty(affectedEntities, entityChangesStorage.filesToInvalidate)
workspaceFileIndex.updateDirtyEntities()
}
}
val indexingInfo = entityChangesStorage.createIndexingInfo()
if (indexingInfo != null && !isRootChangeForbidden()) {
val projectRootManager = ProjectRootManager.getInstance(project) as ProjectRootManagerBridge
@@ -268,15 +286,25 @@ private class RootsChangeWatcher(private val project: Project) {
}
private class EntityChangeStorage {
private var entities: MutableList<WorkspaceEntity>? = null
private var entitiesToReindex: MutableList<WorkspaceEntity>? = null
val affectedEntities = HashSet<WorkspaceEntity>()
val filesToInvalidate = HashSet<VirtualFile>()
private fun initChanges(): MutableList<WorkspaceEntity> = entities ?: (mutableListOf<WorkspaceEntity>().also { entities = it })
private fun initChanges(): MutableList<WorkspaceEntity> = entitiesToReindex ?: (mutableListOf<WorkspaceEntity>().also { entitiesToReindex = it })
fun addAll(addedEntities: Collection<WorkspaceEntity>) {
initChanges().addAll(addedEntities)
}
fun createIndexingInfo() = entities?.let {
fun addAffectedEntities(entities: Collection<WorkspaceEntity>, allRootsWereRemoved: Boolean) {
affectedEntities.addAll(entities)
val toReindex = initChanges()
if (!allRootsWereRemoved) {
toReindex.addAll(entities)
}
}
fun createIndexingInfo() = entitiesToReindex?.let {
EntityIndexingServiceEx.getInstanceEx().createWorkspaceEntitiesRootsChangedInfo(it)
}
fun addFileToInvalidate(file: VirtualFile?) {
file?.let { filesToInvalidate.add(it) }
}
}

View File

@@ -107,10 +107,13 @@ open class VirtualFileUrlWatcher(val project: Project) {
fun getInstance(project: Project): VirtualFileUrlWatcher = project.service()
internal fun calculateAffectedEntities(storage: EntityStorage, virtualFileUrl: VirtualFileUrl,
aggregator: MutableList<EntityWithVirtualFileUrl>) {
aggregator: MutableList<EntityWithVirtualFileUrl>): Boolean {
var hasEntities = false
storage.getVirtualFileUrlIndex().findEntitiesByUrl(virtualFileUrl).forEach {
aggregator.add(EntityWithVirtualFileUrl(it.first, virtualFileUrl, it.second))
hasEntities = true
}
return hasEntities
}
}

View File

@@ -17,6 +17,7 @@ import com.intellij.testFramework.HeavyPlatformTestCase;
import com.intellij.testFramework.PsiTestUtil;
import com.intellij.testFramework.VfsTestUtil;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.java.JavaResourceRootType;
@@ -375,8 +376,14 @@ public class DirectoryIndexTest extends DirectoryIndexTestCase {
checkInfo(myResDir, myModule, true, false, "", myResDirFolder, JavaResourceRootType.RESOURCE, myModule);
assertInstanceOf(assertOneElement(toArray(myFileIndex.getOrderEntriesForFile(myResDir))), ModuleSourceOrderEntry.class);
checkInfo(myExcludedLibSrcDir, null, true, false, "lib.src.exc", null, null, myModule3, myModule);
checkInfo(myExcludedLibClsDir, null, true, false, "lib.cls.exc", null, null, myModule3);
if (WorkspaceFileIndexEx.IS_ENABLED) {
assertExcluded(myExcludedLibSrcDir, myModule);
assertExcluded(myExcludedLibClsDir, myModule);
}
else {
checkInfo(myExcludedLibSrcDir, null, true, false, "lib.src.exc", null, null, myModule3, myModule);
checkInfo(myExcludedLibClsDir, null, true, false, "lib.cls.exc", null, null, myModule3);
}
checkPackage("lib.src.exc", true, myExcludedLibSrcDir);
checkPackage("lib.cls.exc", true, myExcludedLibClsDir);

View File

@@ -5,6 +5,7 @@ import com.intellij.openapi.module.Module
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.openapi.roots.impl.ProjectFileIndexScopes.EXCLUDED
import com.intellij.openapi.roots.impl.ProjectFileIndexScopes.EXCLUDED_FROM_MODULE_ONLY
import com.intellij.openapi.roots.impl.ProjectFileIndexScopes.IN_CONTENT
import com.intellij.openapi.roots.impl.ProjectFileIndexScopes.IN_LIBRARY
import com.intellij.openapi.roots.impl.ProjectFileIndexScopes.IN_MODULE_SOURCE_BUT_NOT_IN_LIBRARY_SOURCE
@@ -17,6 +18,7 @@ import com.intellij.testFramework.PsiTestUtil
import com.intellij.testFramework.junit5.RunInEdt
import com.intellij.testFramework.junit5.TestApplication
import com.intellij.testFramework.rules.ProjectModelExtension
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexEx
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
@@ -89,7 +91,12 @@ class NestedModuleAndLibraryRootsInProjectFileIndex {
fileIndex.assertInModule(file, module, moduleDir, IN_CONTENT or IN_LIBRARY)
PsiTestUtil.addExcludedRoot(module, excludedDir)
fileIndex.assertScope(file, IN_LIBRARY)
if (WorkspaceFileIndexEx.IS_ENABLED) {
fileIndex.assertInModule(file, module, moduleDir, IN_LIBRARY or EXCLUDED_FROM_MODULE_ONLY)
}
else {
fileIndex.assertScope(file, IN_LIBRARY)
}
projectModel.modifyLibrary(library) {
it.addExcludedRoot(excludedDir.url)
@@ -151,7 +158,13 @@ class NestedModuleAndLibraryRootsInProjectFileIndex {
projectModel.addModuleLevelLibrary(module, "lib") {
it.addRoot(root, OrderRootType.CLASSES)
}
fileIndex.assertScope(file, IN_LIBRARY)
fileIndex.assertScope(root, IN_LIBRARY)
if (WorkspaceFileIndexEx.IS_ENABLED) {
fileIndex.assertInModule(file, module, root, IN_LIBRARY or EXCLUDED_FROM_MODULE_ONLY)
fileIndex.assertInModule(root, module, root, IN_LIBRARY or EXCLUDED_FROM_MODULE_ONLY)
}
else {
fileIndex.assertScope(file, IN_LIBRARY)
fileIndex.assertScope(root, IN_LIBRARY)
}
}
}

View File

@@ -4,11 +4,13 @@ package com.intellij.openapi.roots.impl
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.ProjectFileIndex
import com.intellij.openapi.roots.impl.ProjectFileIndexScopes.EXCLUDED
import com.intellij.openapi.roots.impl.ProjectFileIndexScopes.IN_CONTENT
import com.intellij.openapi.roots.impl.ProjectFileIndexScopes.assertInModule
import com.intellij.testFramework.PsiTestUtil
import com.intellij.testFramework.junit5.RunInEdt
import com.intellij.testFramework.junit5.TestApplication
import com.intellij.testFramework.rules.ProjectModelExtension
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexEx
import org.junit.jupiter.api.Assertions.*
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.RegisterExtension
@@ -68,7 +70,8 @@ class NestedModuleRootsInProjectFileIndex {
fileIndex.assertInModule(innerFile, outerModule, outerDir, EXCLUDED)
PsiTestUtil.addContentRoot(innerModule, innerFile.parent)
fileIndex.assertInModule(innerFile, innerModule, innerFile.parent)
val scope = if (WorkspaceFileIndexEx.IS_ENABLED) EXCLUDED else IN_CONTENT
fileIndex.assertInModule(innerFile, innerModule, innerFile.parent, scope)
PsiTestUtil.removeContentEntry(innerModule, innerFile.parent)
fileIndex.assertInModule(innerFile, outerModule, outerDir, EXCLUDED)

View File

@@ -15,6 +15,9 @@
dynamic="true"/>
<extensionPoint name="workspaceModel.facetContributor" interface="com.intellij.workspaceModel.ide.legacyBridge.WorkspaceFacetContributor"
dynamic="true"/>
<extensionPoint name="workspaceModel.fileIndexContributor"
interface="com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndexContributor"
dynamic="true"/>
</extensionPoints>
<extensions defaultExtensionNs="com.intellij">
<projectService serviceInterface="com.intellij.openapi.components.PathMacroManager"
@@ -32,6 +35,8 @@
serviceImplementation="com.intellij.openapi.roots.impl.ProjectFileIndexImpl"/>
<projectService serviceInterface="com.intellij.workspaceModel.ide.WorkspaceModel"
serviceImplementation="com.intellij.workspaceModel.ide.impl.WorkspaceModelImpl"/>
<projectService serviceInterface="com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndex"
serviceImplementation="com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexImpl"/>
<projectService serviceInterface="com.intellij.workspaceModel.storage.url.VirtualFileUrlManager"
serviceImplementation="com.intellij.workspaceModel.ide.impl.IdeVirtualFileUrlManagerImpl"/>
<backgroundPostStartupActivity implementation="com.intellij.workspaceModel.ide.impl.VirtualFileUrlsLazyInitializer"/>
@@ -53,5 +58,14 @@
serviceImplementation="com.intellij.openapi.roots.impl.DirectoryIndexImpl" preload="true"/>
<moduleService serviceInterface="com.intellij.openapi.roots.ModuleFileIndex"
serviceImplementation="com.intellij.openapi.roots.impl.ModuleFileIndexImpl"/>
<workspaceModel.fileIndexContributor implementation="com.intellij.workspaceModel.core.fileIndex.impl.ContentRootFileIndexContributor"/>
<workspaceModel.fileIndexContributor implementation="com.intellij.workspaceModel.core.fileIndex.impl.SourceRootFileIndexContributor"/>
<workspaceModel.fileIndexContributor implementation="com.intellij.workspaceModel.core.fileIndex.impl.LibraryRootFileIndexContributor"/>
<workspaceModel.fileIndexContributor implementation="com.intellij.workspaceModel.core.fileIndex.impl.ExcludedRootFileIndexContributor"/>
<registryKey key="platform.projectModel.workspace.model.file.index" defaultValue="false"
description="Use new implementation of ProjectFileIndex based of WorkspaceModel with incremental updates"
restartRequired="true"/>
</extensions>
</idea-plugin>

View File

@@ -5,6 +5,7 @@ import com.intellij.injected.editor.VirtualFileWindow;
import com.intellij.notebook.editor.BackedVirtualFile;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.fileTypes.FileTypeRegistry;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.ContentIterator;
import com.intellij.openapi.roots.ContentIteratorEx;
import com.intellij.openapi.roots.FileIndex;
@@ -13,6 +14,8 @@ import com.intellij.openapi.vfs.VfsUtilCore;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileFilter;
import com.intellij.openapi.vfs.VirtualFileVisitor;
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndex;
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexEx;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
@@ -20,10 +23,12 @@ import org.jetbrains.jps.model.module.JpsModuleSourceRootType;
abstract class FileIndexBase implements FileIndex {
private final FileTypeRegistry myFileTypeRegistry;
final DirectoryIndex myDirectoryIndex;
final WorkspaceFileIndexEx myWorkspaceFileIndex;
FileIndexBase(@NotNull DirectoryIndex directoryIndex) {
myDirectoryIndex = directoryIndex;
FileIndexBase(@NotNull Project project) {
myDirectoryIndex = DirectoryIndex.getInstance(project);
myFileTypeRegistry = FileTypeRegistry.getInstance();
myWorkspaceFileIndex = WorkspaceFileIndexEx.IS_ENABLED ? (WorkspaceFileIndexEx)WorkspaceFileIndex.getInstance(project) : null;
}
protected abstract boolean isScopeDisposed();

View File

@@ -7,6 +7,10 @@ import com.intellij.openapi.roots.*;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileFilter;
import com.intellij.util.IncorrectOperationException;
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileKind;
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSetWithCustomData;
import com.intellij.workspaceModel.core.fileIndex.impl.ModuleContentOrSourceRootData;
import com.intellij.workspaceModel.core.fileIndex.impl.ModuleSourceRootData;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -23,7 +27,7 @@ public class ModuleFileIndexImpl extends FileIndexBase implements ModuleFileInde
private final Module myModule;
public ModuleFileIndexImpl(@NotNull Module module) {
super(DirectoryIndex.getInstance(module.getProject()));
super(module.getProject());
myModule = module;
}
@@ -69,11 +73,25 @@ public class ModuleFileIndexImpl extends FileIndexBase implements ModuleFileInde
@Override
public boolean isInContent(@NotNull VirtualFile fileOrDir) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSetWithCustomData<ModuleContentOrSourceRootData> fileSet =
myWorkspaceFileIndex.findFileSetWithCustomData(fileOrDir, true, true, false, false, ModuleContentOrSourceRootData.class);
return isFromThisModule(fileSet);
}
return isInContent(fileOrDir, getInfoForFileOrDirectory(fileOrDir));
}
private boolean isFromThisModule(@Nullable WorkspaceFileSetWithCustomData<? extends ModuleContentOrSourceRootData> fileSet) {
return fileSet != null && fileSet.getData().getModule().equals(myModule);
}
@Override
public boolean isInSourceContent(@NotNull VirtualFile fileOrDir) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSetWithCustomData<ModuleSourceRootData> fileSet = myWorkspaceFileIndex.findFileSetWithCustomData(fileOrDir, true, true, false, false, ModuleSourceRootData.class);
return fileSet != null && isFromThisModule(fileSet);
}
DirectoryInfo info = getInfoForFileOrDirectory(fileOrDir);
return info.isInModuleSource(fileOrDir) && myModule.equals(info.getModule());
}
@@ -91,12 +109,20 @@ public class ModuleFileIndexImpl extends FileIndexBase implements ModuleFileInde
@Override
public boolean isInTestSourceContent(@NotNull VirtualFile fileOrDir) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSetWithCustomData<ModuleSourceRootData> fileSet = myWorkspaceFileIndex.findFileSetWithCustomData(fileOrDir, true, true, false, false, ModuleSourceRootData.class);
return fileSet != null && isFromThisModule(fileSet) && fileSet.getKind() == WorkspaceFileKind.TEST_CONTENT;
}
DirectoryInfo info = getInfoForFileOrDirectory(fileOrDir);
return info.isInModuleSource(fileOrDir) && myModule.equals(info.getModule()) && isTestSourcesRoot(info);
}
@Override
public boolean isUnderSourceRootOfType(@NotNull VirtualFile fileOrDir, @NotNull Set<? extends JpsModuleSourceRootType<?>> rootTypes) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSetWithCustomData<ModuleSourceRootData> fileSet = myWorkspaceFileIndex.findFileSetWithCustomData(fileOrDir, true, true, false, false, ModuleSourceRootData.class);
return isFromThisModule(fileSet) && ProjectFileIndexImpl.isSourceRootOfType(fileSet, rootTypes);
}
DirectoryInfo info = getInfoForFileOrDirectory(fileOrDir);
return info.isInModuleSource(fileOrDir) && myModule.equals(info.getModule()) && rootTypes.contains(myDirectoryIndex.getSourceRootType(info));
}

View File

@@ -11,6 +11,11 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileFilter;
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileKind;
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSet;
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSetWithCustomData;
import com.intellij.workspaceModel.core.fileIndex.impl.*;
import com.intellij.workspaceModel.ide.impl.legacyBridge.module.roots.SourceRootTypeRegistry;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -30,8 +35,7 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
private final Project myProject;
public ProjectFileIndexImpl(@NotNull Project project) {
super(DirectoryIndex.getInstance(project));
super(project);
myProject = project;
}
@@ -78,22 +82,38 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
@Override
public boolean isExcluded(@NotNull VirtualFile file) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileInternalInfo info = myWorkspaceFileIndex.getFileInfo(file, true, true, true, true, null);
return info == WorkspaceFileInternalInfo.NonWorkspace.IGNORED || info == WorkspaceFileInternalInfo.NonWorkspace.EXCLUDED;
}
DirectoryInfo info = getInfoForFileOrDirectory(file);
return info.isIgnored() || info.isExcluded(file);
}
@Override
public boolean isUnderIgnored(@NotNull VirtualFile file) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileInternalInfo info = myWorkspaceFileIndex.getFileInfo(file, true, true, true, true, null);
return info == WorkspaceFileInternalInfo.NonWorkspace.IGNORED;
}
return getInfoForFileOrDirectory(file).isIgnored();
}
@Override
public boolean isInProject(@NotNull VirtualFile file) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSet fileSet = myWorkspaceFileIndex.findFileSet(file, true, true, true, true);
return fileSet != null;
}
return getInfoForFileOrDirectory(file).isInProject(file);
}
@Override
public boolean isInProjectOrExcluded(@NotNull VirtualFile file) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileInternalInfo info = myWorkspaceFileIndex.getFileInfo(file, true, true, true, true, null);
return info == WorkspaceFileInternalInfo.NonWorkspace.EXCLUDED || !(info instanceof WorkspaceFileInternalInfo.NonWorkspace);
}
DirectoryInfo directoryInfo = getInfoForFileOrDirectory(file);
return directoryInfo.isInProject(file) || directoryInfo.isExcluded(file);
}
@@ -106,6 +126,13 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
@Nullable
@Override
public Module getModuleForFile(@NotNull VirtualFile file, boolean honorExclusion) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSetWithCustomData<ModuleContentOrSourceRootData> fileSet =
myWorkspaceFileIndex.findFileSetWithCustomData(file, honorExclusion, true, false, false, ModuleContentOrSourceRootData.class);
if (fileSet == null) return null;
return fileSet.getData().getModule();
}
if (file instanceof VirtualFileWindow) file = ((VirtualFileWindow)file).getDelegate();
file = BackedVirtualFile.getOriginFileIfBacked(file);
DirectoryInfo info = getInfoForFileOrDirectory(file);
@@ -123,6 +150,11 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
@Override
public VirtualFile getClassRootForFile(@NotNull VirtualFile file) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSet fileSet = myWorkspaceFileIndex.findFileSet(file, true, false, true, false);
if (fileSet == null) return null;
return fileSet.getRoot();
}
return getClassRootForFile(file, getInfoForFileOrDirectory(file));
}
@@ -133,6 +165,11 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
@Override
public VirtualFile getSourceRootForFile(@NotNull VirtualFile file) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSet fileSet = myWorkspaceFileIndex.findFileSetWithCustomData(file, true, true, false, true, ModuleOrLibrarySourceRootData.class);
if (fileSet == null) return null;
return fileSet.getRoot();
}
return getSourceRootForFile(file, getInfoForFileOrDirectory(file));
}
@@ -148,6 +185,16 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
@Override
public VirtualFile getContentRootForFile(@NotNull VirtualFile file, final boolean honorExclusion) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSetWithCustomData<ModuleContentOrSourceRootData> fileSet = myWorkspaceFileIndex.findFileSetWithCustomData(file, honorExclusion, true, false, false,
ModuleContentOrSourceRootData.class);
if (fileSet == null) return null;
VirtualFile contentRoot = fileSet.getData().getCustomContentRoot();
if (contentRoot != null) {
return contentRoot;
}
return fileSet.getRoot();
}
return getContentRootForFile(getInfoForFileOrDirectory(file), file, honorExclusion);
}
@@ -168,24 +215,41 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
@Override
public boolean isLibraryClassFile(@NotNull VirtualFile file) {
if (file.isDirectory()) return false;
if (myWorkspaceFileIndex != null) {
WorkspaceFileSet fileSet = myWorkspaceFileIndex.findFileSet(file, true, false, true, false);
return fileSet != null;
}
DirectoryInfo parentInfo = getInfoForFileOrDirectory(file);
return parentInfo.isInProject(file) && parentInfo.hasLibraryClassRoot();
}
@Override
public boolean isInSource(@NotNull VirtualFile fileOrDir) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSet fileSet = myWorkspaceFileIndex.findFileSetWithCustomData(fileOrDir, true, true, false, true, ModuleOrLibrarySourceRootData.class);
return fileSet != null;
}
DirectoryInfo info = getInfoForFileOrDirectory(fileOrDir);
return info.isInModuleSource(fileOrDir) || info.isInLibrarySource(fileOrDir);
}
@Override
public boolean isInLibraryClasses(@NotNull VirtualFile fileOrDir) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSet fileSet = myWorkspaceFileIndex.findFileSet(fileOrDir, true, false, true, false);
return fileSet != null;
}
DirectoryInfo info = getInfoForFileOrDirectory(fileOrDir);
return info.isInProject(fileOrDir) && info.hasLibraryClassRoot();
}
@Override
public boolean isInLibrarySource(@NotNull VirtualFile fileOrDir) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSet fileSet = myWorkspaceFileIndex.findFileSet(fileOrDir, true, false, false, true);
return fileSet != null;
}
DirectoryInfo info = getInfoForFileOrDirectory(fileOrDir);
return info.isInProject(fileOrDir) && info.isInLibrarySource(fileOrDir);
}
@@ -193,6 +257,10 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
// a slightly faster implementation then the default one
@Override
public boolean isInLibrary(@NotNull VirtualFile fileOrDir) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSet fileSet = myWorkspaceFileIndex.findFileSet(fileOrDir, true, false, true, true);
return fileSet != null;
}
DirectoryInfo info = getInfoForFileOrDirectory(fileOrDir);
return info.isInProject(fileOrDir) && (info.hasLibraryClassRoot() || info.isInLibrarySource(fileOrDir));
}
@@ -204,6 +272,10 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
@Override
public boolean isInContent(@NotNull VirtualFile fileOrDir) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSet fileSet = myWorkspaceFileIndex.findFileSet(fileOrDir, true, true, false, false);
return fileSet != null;
}
return isFileInContent(fileOrDir, getInfoForFileOrDirectory(fileOrDir));
}
@@ -213,21 +285,39 @@ public class ProjectFileIndexImpl extends FileIndexBase implements ProjectFileIn
@Override
public boolean isInSourceContent(@NotNull VirtualFile fileOrDir) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSet fileSet = myWorkspaceFileIndex.findFileSetWithCustomData(fileOrDir, true, true, false, false, ModuleSourceRootData.class);
return fileSet != null;
}
return getInfoForFileOrDirectory(fileOrDir).isInModuleSource(fileOrDir);
}
@Override
public boolean isInTestSourceContent(@NotNull VirtualFile fileOrDir) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSet fileSet = myWorkspaceFileIndex.findFileSetWithCustomData(fileOrDir, true, true, false, false, ModuleSourceRootData.class);
return fileSet != null && fileSet.getKind() == WorkspaceFileKind.TEST_CONTENT;
}
DirectoryInfo info = getInfoForFileOrDirectory(fileOrDir);
return info.isInModuleSource(fileOrDir) && isTestSourcesRoot(info);
}
@Override
public boolean isUnderSourceRootOfType(@NotNull VirtualFile fileOrDir, @NotNull Set<? extends JpsModuleSourceRootType<?>> rootTypes) {
if (myWorkspaceFileIndex != null) {
WorkspaceFileSetWithCustomData<ModuleSourceRootData> fileSet = myWorkspaceFileIndex.findFileSetWithCustomData(fileOrDir, true, true, false, false, ModuleSourceRootData.class);
return isSourceRootOfType(fileSet, rootTypes);
}
DirectoryInfo info = getInfoForFileOrDirectory(fileOrDir);
return info.isInModuleSource(fileOrDir) && rootTypes.contains(myDirectoryIndex.getSourceRootType(info));
}
static boolean isSourceRootOfType(@Nullable WorkspaceFileSetWithCustomData<ModuleSourceRootData> fileSet, @NotNull Set<? extends JpsModuleSourceRootType<?>> rootTypes) {
if (fileSet == null) return false;
JpsModuleSourceRootType<?> type = SourceRootTypeRegistry.getInstance().findTypeById(fileSet.getData().getRootType());
return type != null && rootTypes.contains(type);
}
@Nullable
@Override
public SourceFolder getSourceFolder(@NotNull VirtualFile fileOrDir) {

View File

@@ -10,18 +10,23 @@ import com.intellij.openapi.roots.LibraryOrSdkOrderEntry;
import com.intellij.openapi.roots.OrderRootType;
import com.intellij.openapi.roots.SourceFolder;
import com.intellij.openapi.roots.impl.libraries.LibraryEx;
import com.intellij.openapi.roots.libraries.Library;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.pointers.VirtualFilePointer;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.workspaceModel.ide.impl.UtilsKt;
import com.intellij.workspaceModel.storage.url.VirtualFileUrl;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
class RootFileSupplier {
@ApiStatus.Internal
public class RootFileSupplier {
private static final Logger LOG = Logger.getInstance(RootFileSupplier.class);
static final RootFileSupplier INSTANCE = new RootFileSupplier();
public static final RootFileSupplier INSTANCE = new RootFileSupplier();
@NotNull
List<@NotNull VirtualFile> getUnloadedContentRoots(UnloadedModuleDescription description) {
@@ -29,7 +34,7 @@ class RootFileSupplier {
}
@Nullable
VirtualFile correctRoot(@NotNull VirtualFile file, @NotNull Object container, @Nullable Object containerProvider) {
public VirtualFile correctRoot(@NotNull VirtualFile file, @NotNull Object container, @Nullable Object containerProvider) {
if (!ensureValid(file, container, containerProvider)) {
return null;
}
@@ -37,11 +42,11 @@ class RootFileSupplier {
}
@Nullable
VirtualFile findFileByUrl(String url) {
public VirtualFile findFileByUrl(String url) {
return VirtualFileManager.getInstance().findFileByUrl(url);
}
VirtualFile @NotNull [] getExcludedRoots(LibraryEx library) {
public VirtualFile @NotNull [] getExcludedRoots(LibraryEx library) {
return library.getExcludedRoots();
}
@@ -49,6 +54,10 @@ class RootFileSupplier {
return entry.getRootFiles(type);
}
public VirtualFile @NotNull [] getLibraryRoots(Library library, OrderRootType type) {
return library.getFiles(type);
}
VirtualFile @NotNull [] getSdkRoots(@NotNull Sdk entry, OrderRootType type) {
return entry.getRootProvider().getFiles(type);
}
@@ -63,10 +72,15 @@ class RootFileSupplier {
return sourceFolder.getFile();
}
static RootFileSupplier forBranch(ModelBranch branch) {
@Nullable
public VirtualFile findFile(@NotNull VirtualFileUrl virtualFileUrl) {
return UtilsKt.getVirtualFile(virtualFileUrl);
}
public static RootFileSupplier forBranch(ModelBranch branch) {
return new RootFileSupplier() {
@Override
protected VirtualFile @NotNull [] getExcludedRoots(LibraryEx library) {
public VirtualFile @NotNull [] getExcludedRoots(LibraryEx library) {
return ContainerUtil.mapNotNull(library.getExcludedRootUrls(), this::findFileByUrl).toArray(VirtualFile.EMPTY_ARRAY);
}
@@ -75,6 +89,11 @@ class RootFileSupplier {
return ContainerUtil.mapNotNull(entry.getRootUrls(type), this::findFileByUrl).toArray(VirtualFile.EMPTY_ARRAY);
}
@Override
public VirtualFile @NotNull [] getLibraryRoots(Library library, OrderRootType type) {
return ContainerUtil.mapNotNull(library.getUrls(type), this::findFileByUrl).toArray(VirtualFile.EMPTY_ARRAY);
}
@Override
VirtualFile @NotNull [] getSdkRoots(@NotNull Sdk sdk, OrderRootType type) {
return ContainerUtil.mapNotNull(sdk.getRootProvider().getUrls(type), this::findFileByUrl).toArray(VirtualFile.EMPTY_ARRAY);
@@ -90,13 +109,19 @@ class RootFileSupplier {
return findFileByUrl(sourceFolder.getUrl());
}
@Override
public @Nullable VirtualFile findFile(@NotNull VirtualFileUrl virtualFileUrl) {
return findFileByUrl(virtualFileUrl.getUrl());
}
@Override
protected @NotNull List<@NotNull VirtualFile> getUnloadedContentRoots(UnloadedModuleDescription description) {
return ContainerUtil.mapNotNull(description.getContentRoots(), p -> findFileByUrl(p.getUrl()));
}
@Override
protected @Nullable VirtualFile correctRoot(@NotNull VirtualFile file, @NotNull Object container, @Nullable Object containerProvider) {
@Nullable
public VirtualFile correctRoot(@NotNull VirtualFile file, @NotNull Object container, @Nullable Object containerProvider) {
file = super.correctRoot(file, container, containerProvider);
if (file != null) {
file = branch.findFileCopy(file);
@@ -108,14 +133,14 @@ class RootFileSupplier {
}
@Override
protected @Nullable VirtualFile findFileByUrl(String url) {
public @Nullable VirtualFile findFileByUrl(String url) {
return branch.findFileByUrl(url);
}
};
}
static boolean ensureValid(@NotNull VirtualFile file, @NotNull Object container, @Nullable Object containerProvider) {
public static boolean ensureValid(@NotNull VirtualFile file, @NotNull Object container, @Nullable Object containerProvider) {
if (!file.isValid()) {
if (containerProvider != null) {
LOG.error("Invalid root " + file + " in " + container + " provided by " + containerProvider.getClass());

View File

@@ -11,7 +11,6 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectUtilCore;
import com.intellij.openapi.projectRoots.impl.ProjectJdkImpl;
import com.intellij.openapi.roots.*;
import com.intellij.openapi.roots.impl.ProjectRootManagerImpl;
import com.intellij.openapi.roots.libraries.*;
import com.intellij.openapi.util.*;
import com.intellij.openapi.vfs.VfsUtilCore;
@@ -56,6 +55,7 @@ public class LibraryImpl extends TraceableDisposable implements LibraryEx.Modifi
private final Disposable myPointersDisposable = Disposer.newDisposable();
private final ProjectModelExternalSource myExternalSource;
private final EventDispatcher<RootSetChangedListener> myDispatcher = EventDispatcher.create(RootSetChangedListener.class);
private LibraryRootPointerListener myRootPointerListener;
LibraryImpl(LibraryTable table, @NotNull Element element, ModifiableRootModel rootModel) throws InvalidDataException {
this(table, rootModel, null, null, findPersistentLibraryKind(element), findExternalSource(element));
@@ -234,10 +234,10 @@ public class LibraryImpl extends TraceableDisposable implements LibraryEx.Modifi
@NotNull
private VirtualFilePointerListener getListener() {
Project project = myLibraryTable instanceof ProjectLibraryTable ? ((ProjectLibraryTable)myLibraryTable).getProject() : null;
return project != null
? ProjectRootManagerImpl.getInstanceImpl(project).getRootsValidityChangedListener()
: ProjectJdkImpl.getGlobalVirtualFilePointerListener();
if (myRootPointerListener == null) {
myRootPointerListener = new LibraryRootPointerListener();
}
return myRootPointerListener;
}
@Nullable
@@ -789,4 +789,17 @@ public class LibraryImpl extends TraceableDisposable implements LibraryEx.Modifi
}
return ProjectModelBundle.message("empty.library.title");
}
private class LibraryRootPointerListener implements VirtualFilePointerListener {
@Override
public void beforeValidityChanged(@NotNull VirtualFilePointer @NotNull [] pointers) {
ProjectJdkImpl.getGlobalVirtualFilePointerListener().beforeValidityChanged(pointers);
}
@Override
public void validityChanged(@NotNull VirtualFilePointer @NotNull [] pointers) {
ProjectJdkImpl.getGlobalVirtualFilePointerListener().validityChanged(pointers);
fireRootSetChanged();
}
}
}

View File

@@ -0,0 +1,70 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.core.fileIndex
import com.intellij.openapi.components.service
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.VirtualFile
/**
* Provides access to the information collected from [WorkspaceFileIndexContributor]s.
* If `platform.projectModel.workspace.model.file.index` registry option is enabled, this index is used instead of [com.intellij.openapi.roots.impl.DirectoryIndex]
* in [com.intellij.openapi.roots.ProjectFileIndex] and [com.intellij.openapi.roots.ModuleFileIndex].
*/
interface WorkspaceFileIndex {
companion object {
@JvmStatic
fun getInstance(project: Project): WorkspaceFileIndex = project.service()
}
/**
* Searches for the first parent of [file] (or [file] itself) which has an associated [WorkspaceFileSet] taking into account the passed
* flags.
* If there are several instances of [WorkspaceFileSet] associated with the found file, any of them is returned. If you need to get a
* specific file set in such case, use [findFileSetWithCustomData] instead.
*
* @param honorExclusion if `true` the function will return `null` if [file] is excluded from the found file set
* @param includeContentSets if `true` file sets of [WorkspaceFileKind.CONTENT] kind will be processed
* @param includeExternalSets if `true` file sets of [WorkspaceFileKind.EXTERNAL] kind will be processed
* @param includeExternalSourceSets if `true` file sets of [WorkspaceFileKind.EXTERNAL_SOURCE] kind will be processed
*/
fun findFileSet(file: VirtualFile,
honorExclusion: Boolean,
includeContentSets: Boolean,
includeExternalSets: Boolean,
includeExternalSourceSets: Boolean
): WorkspaceFileSet?
/**
* The same as [findFileSet], but returns a file set which has custom data of type [customDataClass] associated with the found file or
* `null` if no such file set is found.
*/
fun <D: WorkspaceFileSetData> findFileSetWithCustomData(
file: VirtualFile,
honorExclusion: Boolean,
includeContentSets: Boolean,
includeExternalSets: Boolean,
includeExternalSourceSets: Boolean,
customDataClass: Class<out D>?
): WorkspaceFileSetWithCustomData<D>?
}
/**
* Describes a set of files registered by [WorkspaceFileSetRegistrar.registerFileSet] function.
*/
interface WorkspaceFileSet {
val root: VirtualFile
val kind: WorkspaceFileKind
}
/**
* Base interface for custom data which may be associated with [WorkspaceFileSet].
*/
interface WorkspaceFileSetData
/**
* Describes a set of files registered by [WorkspaceFileSetRegistrar.registerFileSet] function with provided custom data.
* Use [WorkspaceFileIndex.findFileSetWithCustomData] to retrieve the data from the index.
*/
interface WorkspaceFileSetWithCustomData<D : WorkspaceFileSetData> : WorkspaceFileSet {
val data: D
}

View File

@@ -0,0 +1,122 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.core.fileIndex
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.workspaceModel.storage.EntityStorage
import com.intellij.workspaceModel.storage.WorkspaceEntity
import com.intellij.workspaceModel.storage.url.VirtualFileUrl
import org.jetbrains.annotations.ApiStatus
/**
* Implement this interface and register the implementation as `com.intellij.workspaceModel.fileIndexContributor` extension in plugin.xml
* file to specify which files mentioned in WorkSpace Model entities should be considered as part of the workspace.
*
* [WorkspaceFileIndex] can be used to access data collected from the contributors.
*/
@ApiStatus.OverrideOnly
interface WorkspaceFileIndexContributor<E : WorkspaceEntity> {
/**
* Specifies interface of entities processed by this contributor.
*/
val entityClass: Class<E>
/**
* Implement this function and call functions from [registrar] to specify files and directories which should be included or excluded from
* the workspace.
*
* The implementation may use properties from [entity] only and don't access other entities and don't use data which may
* change. This is necessary to ensure that [WorkspaceFileIndex] is properly updated when entities change.
*
* This function is currently called synchronously under Write Action, so its implementation should run very fast.
*/
fun registerFileSets(entity: E, registrar: WorkspaceFileSetRegistrar, storage: EntityStorage)
}
/**
* Describes possible kinds of files and directories in the workspace.
*/
enum class WorkspaceFileKind {
/**
* Describes files which are supposed to be edited in the IDE as part of the workspace.
* Files of this kind constitute 'Project Files' scope in UI.
* This kind corresponds to [com.intellij.openapi.roots.FileIndex.isInContent] method in the old API.
*/
CONTENT,
/**
* Subset of [CONTENT] which is used to identify test files.
* Files of this kind constitute 'Project Test Files' scope in UI.
* This kind corresponds to [com.intellij.openapi.roots.FileIndex.isInTestSourceContent] method in the old API.
*/
TEST_CONTENT,
/**
* Describes files which may be referenced by [CONTENT] files, but aren't supposed to be edited in the IDE.
* Often they are in some binary format, though it is not necessary.
* Files of this kind together with [CONTENT] files constitute 'Project and Libraries' scope in UI.
* This kind corresponds to [com.intellij.openapi.roots.ProjectFileIndex.isInLibrary] method in the old API.
*/
EXTERNAL,
/**
* Describes files containing source code of [EXTERNAL] files. They aren't supposed to be edited in the IDE and aren't directly
* referenced from [CONTENT] files. This kind was introduced mainly for compatibility with the old code, it corresponds to
* [com.intellij.openapi.roots.ProjectFileIndex.isInLibrarySource] method.
*/
EXTERNAL_SOURCE
}
/**
* Provides functions which can be used to specify which files should be included or excluded from the workspace.
* This interface may be used only inside implementation of [WorkspaceFileIndexContributor.registerFileSets] function.
*/
interface WorkspaceFileSetRegistrar {
/**
* Includes [root] and all files under it to the workspace.
* Specific files or directories under [root] (or even [root] itself) may be excluded from the workspace by [registerExcludedRoot],
* [registerExclusionPatterns] and [registerExclusionCondition] functions.
* @param kind specify kind which will be assigned to the files
* @param entity first parameter of [WorkspaceFileIndexContributor.registerFileSets] must be passed here
* @param customData optional custom data which will be associated with the root and can be accessed via [WorkspaceFileSetWithCustomData].
*/
fun registerFileSet(root: VirtualFileUrl,
kind: WorkspaceFileKind,
entity: WorkspaceEntity,
customData: WorkspaceFileSetData?)
/**
* A variant of [registerFileSet] function which takes [VirtualFile] instead of [VirtualFileUrl].
* This function is considered as a temporary solution until all contributors to [WorkspaceFileIndex] are migrated to Workspace Model.
*/
fun registerFileSet(root: VirtualFile,
kind: WorkspaceFileKind,
entity: WorkspaceEntity,
customData: WorkspaceFileSetData?)
/**
* Excludes [excludedRoot] and all files under it from the workspace.
* Specific files or directories under [excludedRoot] may be included back by [registerFileSet].
* @param entity first parameter of [WorkspaceFileIndexContributor.registerFileSets] must be passed here
*/
fun registerExcludedRoot(excludedRoot: VirtualFileUrl, entity: WorkspaceEntity)
/**
* Excludes [excludedRoot] and all files under it from [excludedFrom] kind of files.
* This is a temporary solution to keep behavior of old code.
*/
fun registerExcludedRoot(excludedRoot: VirtualFile, excludedFrom: WorkspaceFileKind, entity: WorkspaceEntity)
/**
* Excludes all files and directories under [root] which names match to one of [patterns] (`*` and `?` wildcards are supported) from the
* workspace.
* @param entity first parameter of [WorkspaceFileIndexContributor.registerFileSets] must be passed here
*/
fun registerExclusionPatterns(root: VirtualFileUrl, patterns: List<String>, entity: WorkspaceEntity)
/**
* Excludes all files and directories under [root] which satisfy [condition] from the workspace.
* @param condition may access the passed file and its parents and children only
* @param entity first parameter of [WorkspaceFileIndexContributor.registerFileSets] must be passed here
*/
fun registerExclusionCondition(root: VirtualFile, condition: (VirtualFile) -> Boolean, entity: WorkspaceEntity)
}

View File

@@ -0,0 +1,50 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.core.fileIndex.impl
import com.intellij.openapi.roots.impl.CustomEntityProjectModelInfoProvider
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndexContributor
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileKind
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSetRegistrar
import com.intellij.workspaceModel.ide.impl.legacyBridge.module.findModule
import com.intellij.workspaceModel.storage.EntityStorage
import com.intellij.workspaceModel.storage.WorkspaceEntity
internal class CustomEntityProjectModelInfoProviderBridge<E : WorkspaceEntity>(private val provider: CustomEntityProjectModelInfoProvider<E>)
: WorkspaceFileIndexContributor<E> {
override val entityClass: Class<E>
get() = provider.entityClass
override fun registerFileSets(entity: E, registrar: WorkspaceFileSetRegistrar, storage: EntityStorage) {
provider.getContentRoots(sequenceOf(entity)).forEach {
val module = it.parentModule.findModule(storage)
if (module != null) {
registrar.registerFileSet(it.root, WorkspaceFileKind.CONTENT, entity, ModuleContentRootData(module, it.root))
}
}
provider.getLibraryRoots(sequenceOf(entity)).forEach { libraryRoots ->
libraryRoots.classes.forEach {
registrar.registerFileSet(it, WorkspaceFileKind.EXTERNAL, entity, null)
}
libraryRoots.sources.forEach {
registrar.registerFileSet(it, WorkspaceFileKind.EXTERNAL_SOURCE, entity, null)
}
libraryRoots.excluded.forEach {
registrar.registerExcludedRoot(it, WorkspaceFileKind.EXTERNAL, entity)
}
libraryRoots.excludeFileCondition?.let { condition ->
val allRoots = libraryRoots.classes + libraryRoots.sources
val predicate = condition.transformToCondition(allRoots)
for (root in allRoots) {
registrar.registerExclusionCondition(root, { predicate.value(it) }, entity)
}
}
}
provider.getExcludeSdkRootStrategies(sequenceOf(entity)).forEach { excludeStrategy ->
excludeStrategy.excludeUrls.forEach {
registrar.registerExcludedRoot(it, entity)
}
//todo excludeStrategy.excludeSdkRootsStrategy? currently it's always null
}
}
}

View File

@@ -0,0 +1,85 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.core.fileIndex.impl
import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.AdditionalLibraryRootsProvider
import com.intellij.openapi.roots.ModuleRootManager
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.impl.DirectoryIndexExcludePolicy
import com.intellij.openapi.roots.impl.RootFileSupplier
import com.intellij.openapi.vfs.VirtualFile
import it.unimi.dsi.fastutil.objects.Object2IntMap
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap
class CustomExcludedRootContributors(private val project: Project, private val rootFileSupplier: RootFileSupplier) {
@Volatile
private var customExcludedRoots: Object2IntMap<VirtualFile>? = null
fun getCustomExcludedRootMask(file: VirtualFile): Int {
val roots = getCustomExcludedRoots()
return roots.getInt(file)
}
private fun getCustomExcludedRoots(): Object2IntMap<VirtualFile> {
val roots = customExcludedRoots
if (roots != null) return roots
val newRoots = computeCustomExcludedRoots()
customExcludedRoots = newRoots
return newRoots
}
private fun computeCustomExcludedRoots(): Object2IntMap<VirtualFile> {
val roots = Object2IntOpenHashMap<VirtualFile>()
DirectoryIndexExcludePolicy.EP_NAME.getExtensions(project).forEach { policy ->
policy.excludeUrlsForProject.forEach { url ->
val file = rootFileSupplier.findFileByUrl(url)
if (file != null && RootFileSupplier.ensureValid(file, project, policy)) {
roots.put(file, WorkspaceFileKindMask.ALL)
}
}
policy.excludeSdkRootsStrategy?.let { strategy ->
val sdks = ModuleManager.getInstance(project).modules.mapNotNullTo(HashSet()) { ModuleRootManager.getInstance(it).sdk }
val sdkClasses = sdks.flatMapTo(HashSet()) { it.rootProvider.getFiles(OrderRootType.CLASSES).asList() }
sdks.forEach { sdk ->
strategy.`fun`(sdk).forEach { root ->
if (root !in sdkClasses) {
val correctedRoot = rootFileSupplier.correctRoot(root, sdk, policy)
if (correctedRoot != null) {
roots.put(correctedRoot, WorkspaceFileKindMask.EXTERNAL or roots.getInt(correctedRoot))
}
}
}
}
}
ModuleManager.getInstance(project).modules.forEach { module ->
policy.getExcludeRootsForModule(ModuleRootManager.getInstance(module)).forEach { pointer ->
val file = pointer.file
if (file != null) {
val correctedRoot = rootFileSupplier.correctRoot(file, module, policy)
if (correctedRoot != null) {
roots.put(correctedRoot, WorkspaceFileKindMask.CONTENT or roots.getInt(correctedRoot))
}
}
}
}
}
AdditionalLibraryRootsProvider.EP_NAME.extensionList.forEach { provider ->
provider.getAdditionalProjectLibraries(project).forEach { library ->
library.excludedRoots.forEach { root ->
val correctedRoot = rootFileSupplier.correctRoot(root, library, provider)
if (correctedRoot != null) {
roots.put(correctedRoot, WorkspaceFileKindMask.EXTERNAL or roots.getInt(correctedRoot))
}
}
}
}
return roots
}
fun resetCache() {
customExcludedRoots = null
}
}

View File

@@ -0,0 +1,23 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.core.fileIndex.impl
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndexContributor
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileKind
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSetRegistrar
import com.intellij.workspaceModel.ide.impl.virtualFile
import com.intellij.workspaceModel.storage.EntityStorage
import com.intellij.workspaceModel.storage.bridgeEntities.api.ExcludeUrlEntity
import com.intellij.workspaceModel.storage.bridgeEntities.api.library
class ExcludedRootFileIndexContributor : WorkspaceFileIndexContributor<ExcludeUrlEntity> {
override val entityClass: Class<ExcludeUrlEntity>
get() = ExcludeUrlEntity::class.java
override fun registerFileSets(entity: ExcludeUrlEntity, registrar: WorkspaceFileSetRegistrar, storage: EntityStorage) {
val file = entity.url.virtualFile
if (file != null) {
val kind = if (entity.library != null) WorkspaceFileKind.EXTERNAL else WorkspaceFileKind.CONTENT
registrar.registerExcludedRoot(file, kind, entity)
}
}
}

View File

@@ -0,0 +1,188 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.core.fileIndex.impl
import com.intellij.openapi.project.Project
import com.intellij.openapi.projectRoots.ProjectJdkTable
import com.intellij.openapi.projectRoots.Sdk
import com.intellij.openapi.roots.OrderRootType
import com.intellij.openapi.roots.ProjectRootManager
import com.intellij.openapi.roots.ex.ProjectRootManagerEx
import com.intellij.openapi.roots.impl.RootFileSupplier
import com.intellij.openapi.roots.impl.libraries.LibraryEx
import com.intellij.openapi.roots.libraries.Library
import com.intellij.openapi.roots.libraries.LibraryTablesRegistrar
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.MultiMap
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileKind
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSetData
import com.intellij.workspaceModel.ide.legacyBridge.ModuleDependencyIndex
import com.intellij.workspaceModel.ide.legacyBridge.ModuleDependencyListener
import com.intellij.workspaceModel.storage.EntityReference
import com.intellij.workspaceModel.storage.EntityStorage
import com.intellij.workspaceModel.storage.WorkspaceEntity
import java.util.IdentityHashMap
internal class LibrariesAndSdkContributors(private val project: Project,
private val rootFileSupplier: RootFileSupplier,
private val fileSets: MultiMap<VirtualFile, WorkspaceFileSetImpl>,
private val excludedRoots: MultiMap<VirtualFile, ExcludedRootData>) : ModuleDependencyListener, ProjectRootManagerEx.ProjectJdkListener {
private val sdkRoots = MultiMap.create<Sdk, VirtualFile>()
private val libraryRoots = MultiMap<Library, VirtualFile>(IdentityHashMap())
private val moduleDependencyIndex = ModuleDependencyIndex.getInstance(project)
private var registeredProjectSdk: Sdk? = null
private var includeProjectSdk = false
init {
if (rootFileSupplier == RootFileSupplier.INSTANCE) {
moduleDependencyIndex.addListener(this)
ProjectRootManagerEx.getInstanceEx(project).addProjectJdkListener(this)
}
}
fun registerFileSets() {
var noSdkIsUsed = true
ProjectJdkTable.getInstance().allJdks.forEach { sdk ->
if (moduleDependencyIndex.hasDependencyOn(sdk)) {
registerSdkRoots(sdk)
noSdkIsUsed = false
}
}
if (noSdkIsUsed) {
registerProjectSdkRoots()
}
(LibraryTablesRegistrar.getInstance().customLibraryTables.asSequence() + LibraryTablesRegistrar.getInstance().libraryTable).forEach {
it.libraries.forEach { library ->
if (moduleDependencyIndex.hasDependencyOn(library)) {
registerLibraryRoots(library)
}
}
}
}
private fun registerProjectSdkRoots() {
unregisterProjectSdkRoots()
includeProjectSdk = true
val projectSdk = ProjectRootManager.getInstance(project).projectSdk
if (projectSdk != null) {
registeredProjectSdk = projectSdk
registerSdkRoots(projectSdk)
}
}
private fun registerLibraryRoots(library: Library) {
fun registerLibraryRoots(rootType: OrderRootType,
kind: WorkspaceFileKind,
reference: GlobalLibraryReference,
data: WorkspaceFileSetData) {
rootFileSupplier.getLibraryRoots(library, rootType).forEach { root ->
if (RootFileSupplier.ensureValid(root, library, null)) {
fileSets.putValue(root, WorkspaceFileSetImpl(root, kind, reference, data))
libraryRoots.putValue(library, root)
}
}
}
val reference = GlobalLibraryReference(library)
registerLibraryRoots(OrderRootType.CLASSES, WorkspaceFileKind.EXTERNAL, reference, DummyWorkspaceFileSetData)
registerLibraryRoots(OrderRootType.SOURCES, WorkspaceFileKind.EXTERNAL_SOURCE, reference, LibrarySourceRootFileSetData(null))
(library as? LibraryEx)?.let { rootFileSupplier.getExcludedRoots(it) }?.forEach {
if (RootFileSupplier.ensureValid(it, library, null)) {
excludedRoots.putValue(it, ExcludedRootData(WorkspaceFileKindMask.EXTERNAL, reference))
libraryRoots.putValue(library, it)
}
}
}
private fun registerSdkRoots(sdk: Sdk) {
fun registerSdkRoots(rootType: OrderRootType, kind: WorkspaceFileKind, reference: SdkReference, data: WorkspaceFileSetData) {
sdk.rootProvider.getUrls(rootType).forEach { url ->
val root = rootFileSupplier.findFileByUrl(url)
if (root != null && RootFileSupplier.ensureValid(root, sdk, null)) {
fileSets.putValue(root, WorkspaceFileSetImpl(root, kind, reference, data))
sdkRoots.putValue(sdk, root)
}
}
}
val reference = SdkReference(sdk)
registerSdkRoots(OrderRootType.CLASSES, WorkspaceFileKind.EXTERNAL, reference, DummyWorkspaceFileSetData)
registerSdkRoots(OrderRootType.SOURCES, WorkspaceFileKind.EXTERNAL_SOURCE, reference, LibrarySourceRootFileSetData(null))
}
private fun unregisterSdkRoots(sdk: Sdk) {
val roots = sdkRoots.remove(sdk)
roots?.forEach { root ->
fileSets.removeValueIf(root) { (it.entityReference as? SdkReference)?.sdk == sdk }
}
}
private fun unregisterLibraryRoots(library: Library) {
val roots = libraryRoots.remove(library)
roots?.forEach { root ->
fileSets.removeValueIf(root) { (it.entityReference as? GlobalLibraryReference)?.library === library }
excludedRoots.removeValueIf(root) { (it.entityReference as? GlobalLibraryReference)?.library === library }
}
}
override fun firstDependencyOnSdkAdded() {
unregisterProjectSdkRoots()
}
private fun unregisterProjectSdkRoots() {
registeredProjectSdk?.let { unregisterSdkRoots(it) }
registeredProjectSdk = null
includeProjectSdk = false
}
override fun lastDependencyOnSdkRemoved() {
registerProjectSdkRoots()
}
override fun addedDependencyOn(library: Library) {
if (shouldListen(library)) {
registerLibraryRoots(library)
}
}
private fun shouldListen(library: Library) = library.table?.tableLevel != LibraryTablesRegistrar.PROJECT_LEVEL
override fun referencedLibraryChanged(library: Library) {
if (shouldListen(library)) {
unregisterLibraryRoots(library)
registerLibraryRoots(library)
}
}
override fun removedDependencyOn(library: Library) {
if (shouldListen(library)) {
unregisterLibraryRoots(library)
}
}
override fun addedDependencyOn(sdk: Sdk) {
registerSdkRoots(sdk)
}
override fun referencedSdkChanged(sdk: Sdk) {
unregisterSdkRoots(sdk)
registerSdkRoots(sdk)
}
override fun removedDependencyOn(sdk: Sdk) {
unregisterSdkRoots(sdk)
}
override fun projectJdkChanged() {
if (includeProjectSdk) {
registerProjectSdkRoots()
}
}
}
private class GlobalLibraryReference(val library: Library) : EntityReference<WorkspaceEntity>() {
override fun resolve(storage: EntityStorage): WorkspaceEntity? = null
}
private class SdkReference(val sdk: Sdk) : EntityReference<WorkspaceEntity>() {
override fun resolve(storage: EntityStorage): WorkspaceEntity? = null
}

View File

@@ -0,0 +1,62 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.core.fileIndex.impl
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.AdditionalLibraryRootsProvider
import com.intellij.openapi.roots.impl.RootFileSupplier
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.MultiMap
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileKind
import com.intellij.workspaceModel.storage.EntityReference
import com.intellij.workspaceModel.storage.EntityStorage
import com.intellij.workspaceModel.storage.WorkspaceEntity
internal class SyntheticLibraryContributors(private val project: Project, private val rootFileSupplier: RootFileSupplier) {
private val allRoots = HashSet<VirtualFile>()
internal fun registerFileSets(fileSets: MultiMap<VirtualFile, WorkspaceFileSetImpl>,
excludedRoots: MultiMap<VirtualFile, ExcludedRootData>,
exclusionConditionsByRoot: MultiMap<VirtualFile, ExclusionCondition>) {
AdditionalLibraryRootsProvider.EP_NAME.extensionList.forEach { provider ->
provider.getAdditionalProjectLibraries(project).forEach { library ->
fun registerRoots(files: MutableCollection<VirtualFile>, kind: WorkspaceFileKind) {
files.forEach { root ->
rootFileSupplier.correctRoot(root, library, provider)?.let {
fileSets.putValue(it, WorkspaceFileSetImpl(it, kind, SyntheticLibraryReference, DummyWorkspaceFileSetData))
allRoots.add(it)
}
}
}
//todo use comparisonId for incremental updates?
registerRoots(library.sourceRoots, WorkspaceFileKind.EXTERNAL_SOURCE)
registerRoots(library.binaryRoots, WorkspaceFileKind.EXTERNAL)
library.excludedRoots.forEach {
excludedRoots.putValue(it, ExcludedRootData(WorkspaceFileKindMask.EXTERNAL, SyntheticLibraryReference))
allRoots.add(it)
}
library.unitedExcludeCondition?.let { condition ->
val predicate = { file: VirtualFile -> condition.value(file) }
(library.sourceRoots + library.binaryRoots).forEach { root ->
exclusionConditionsByRoot.putValue(root, ExclusionCondition(root, predicate, SyntheticLibraryReference))
}
}
}
}
}
internal fun unregisterFileSets(fileSets: MultiMap<VirtualFile, WorkspaceFileSetImpl>,
excludedRoots: MultiMap<VirtualFile, ExcludedRootData>,
exclusionConditionsByRoot: MultiMap<VirtualFile, ExclusionCondition>) {
for (root in allRoots) {
fileSets.removeValueIf(root) { it.entityReference is SyntheticLibraryReference }
excludedRoots.removeValueIf(root) { it.entityReference is SyntheticLibraryReference }
exclusionConditionsByRoot.removeValueIf(root) { it.entityReference is SyntheticLibraryReference }
}
allRoots.clear()
}
}
private object SyntheticLibraryReference : EntityReference<WorkspaceEntity>() {
override fun resolve(storage: EntityStorage): WorkspaceEntity? = null
}

View File

@@ -0,0 +1,362 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.core.fileIndex.impl
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.fileTypes.FileTypeRegistry
import com.intellij.openapi.fileTypes.impl.FileTypeAssocTable
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.impl.RootFileSupplier
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.containers.MultiMap
import com.intellij.workspaceModel.core.fileIndex.*
import com.intellij.workspaceModel.ide.WorkspaceModel
import com.intellij.workspaceModel.storage.EntityReference
import com.intellij.workspaceModel.storage.EntityStorage
import com.intellij.workspaceModel.storage.VersionedStorageChange
import com.intellij.workspaceModel.storage.WorkspaceEntity
import com.intellij.workspaceModel.storage.url.VirtualFileUrl
import org.intellij.lang.annotations.MagicConstant
import org.jetbrains.jps.model.fileTypes.FileNameMatcherFactory
internal class WorkspaceFileIndexData(contributorList: List<WorkspaceFileIndexContributor<*>>,
private val project: Project,
private val rootFileSupplier: RootFileSupplier) {
private val contributors = contributorList.groupBy { it.entityClass }
private val fileSets = MultiMap.create<VirtualFile, WorkspaceFileSetImpl>()
private val excludedRoots = MultiMap.create<VirtualFile, ExcludedRootData>()
private val exclusionPatternsByRoot = MultiMap.create<VirtualFile, ExclusionPatterns>()
private val exclusionConditionsByRoot = MultiMap.create<VirtualFile, ExclusionCondition>()
private val customExcludedRootContributors = CustomExcludedRootContributors(project, rootFileSupplier)
private val syntheticLibraryContributors = SyntheticLibraryContributors(project, rootFileSupplier)
private val librariesAndSdkContributors = LibrariesAndSdkContributors(project, rootFileSupplier, fileSets, excludedRoots)
private val storeFileSetRegistrar = StoreFileSetsRegistrarImpl()
private val removeFileSetRegistrar = RemoveFileSetsRegistrarImpl()
private val fileTypeRegistry = FileTypeRegistry.getInstance()
private val dirtyEntities = HashSet<WorkspaceEntity>()
private val dirtyFiles = HashSet<VirtualFile>()
@Volatile
private var hasDirtyEntities = false
init {
val storage = WorkspaceModel.getInstance(project).entityStorage.current
contributors.keys.forEach { entityClass ->
storage.entities(entityClass).forEach {
registerFileSets(it, entityClass, storage)
}
}
syntheticLibraryContributors.registerFileSets(fileSets, excludedRoots, exclusionConditionsByRoot)
librariesAndSdkContributors.registerFileSets()
}
fun getFileInfo(file: VirtualFile,
honorExclusion: Boolean,
includeContentSets: Boolean,
includeExternalSets: Boolean,
includeExternalSourceSets: Boolean,
customDataClass: Class<out WorkspaceFileSetData>?): WorkspaceFileInternalInfo {
if (!file.isValid) return WorkspaceFileInternalInfo.NonWorkspace.INVALID
if (hasDirtyEntities && ApplicationManager.getApplication().isWriteAccessAllowed) {
updateDirtyEntities()
}
val originalKindMask =
(if (includeContentSets) WorkspaceFileKindMask.CONTENT else 0) or
(if (includeExternalSets) WorkspaceFileKindMask.EXTERNAL_BINARY else 0) or
(if (includeExternalSourceSets) WorkspaceFileKindMask.EXTERNAL_SOURCE else 0)
var kindMask = originalKindMask
var current: VirtualFile? = file
var currentExclusionMask = 0
while (current != null) {
if (honorExclusion) {
if (excludedRoots.containsKey(current)) {
val excludedRootData = excludedRoots[current]
excludedRootData.forEach {
currentExclusionMask = currentExclusionMask or it.mask
}
if (currentExclusionMask.inv() and kindMask == 0) {
return WorkspaceFileInternalInfo.NonWorkspace.EXCLUDED
}
}
val exclusionPatterns = exclusionPatternsByRoot[current]
if (exclusionPatterns.isNotEmpty()) {
if (exclusionPatterns.any { it.isExcluded(file) }) {
return WorkspaceFileInternalInfo.NonWorkspace.EXCLUDED
}
}
if (exclusionConditionsByRoot[current].any { it.isExcluded(file) }) {
return WorkspaceFileInternalInfo.NonWorkspace.EXCLUDED
}
val mask = customExcludedRootContributors.getCustomExcludedRootMask(current)
if (mask != 0) {
currentExclusionMask = currentExclusionMask or mask
if (currentExclusionMask.inv() and kindMask == 0) {
return WorkspaceFileInternalInfo.NonWorkspace.EXCLUDED
}
}
}
val fileSets = fileSets[current]
if (fileSets.isNotEmpty()) {
val relevant = fileSets.filter { it.kind.toMask() and kindMask != 0 && !it.isUnloaded(project) }
if (relevant.isNotEmpty()) {
val notExcluded = relevant.filter { it.kind.toMask() and kindMask and currentExclusionMask.inv() != 0 }
if (notExcluded.isNotEmpty()) {
val accepted = notExcluded.find { customDataClass == null || customDataClass.isInstance(it.data) }
return accepted ?: WorkspaceFileInternalInfo.NonWorkspace.NO_SUITABLE_CUSTOM_DATA
}
relevant.forEach {
kindMask = kindMask and it.kind.toMask().inv()
}
if (currentExclusionMask.inv() and kindMask == 0) {
return WorkspaceFileInternalInfo.NonWorkspace.EXCLUDED
}
}
}
if (fileTypeRegistry.isFileIgnored(current)) {
return WorkspaceFileInternalInfo.NonWorkspace.IGNORED
}
current = current.parent
}
if (originalKindMask != kindMask || currentExclusionMask != 0) {
return WorkspaceFileInternalInfo.NonWorkspace.EXCLUDED
}
return WorkspaceFileInternalInfo.NonWorkspace.NOT_UNDER_ROOTS
}
private fun <E : WorkspaceEntity> getContributors(entityClass: Class<out E>): List<WorkspaceFileIndexContributor<E>> {
val value = contributors[entityClass] ?: emptyList()
@Suppress("UNCHECKED_CAST")
return value as List<WorkspaceFileIndexContributor<E>>
}
private fun <E : WorkspaceEntity> registerFileSets(entity: E, entityClass: Class<out E>, storage: EntityStorage) {
getContributors(entityClass).forEach { contributor ->
contributor.registerFileSets(entity, storeFileSetRegistrar, storage)
}
}
private fun <E : WorkspaceEntity> unregisterFileSets(entity: E, entityClass: Class<out E>, storage: EntityStorage) {
getContributors(entityClass).forEach { contributor ->
contributor.registerFileSets(entity, removeFileSetRegistrar, storage)
}
}
private fun <E : WorkspaceEntity> processChanges(event: VersionedStorageChange, entityClass: Class<out E>) {
event.getChanges(entityClass).forEach { change ->
change.oldEntity?.let { unregisterFileSets(it, entityClass, event.storageBefore) }
change.newEntity?.let { registerFileSets(it, entityClass, event.storageAfter) }
}
}
fun resetCustomContributors() {
ApplicationManager.getApplication().assertWriteAccessAllowed()
customExcludedRootContributors.resetCache()
syntheticLibraryContributors.unregisterFileSets(fileSets, excludedRoots, exclusionConditionsByRoot)
syntheticLibraryContributors.registerFileSets(fileSets, excludedRoots, exclusionConditionsByRoot)
}
fun markDirty(entities: Collection<WorkspaceEntity>, files: Collection<VirtualFile>) {
ApplicationManager.getApplication().assertWriteAccessAllowed()
dirtyEntities.addAll(entities)
dirtyFiles.addAll(files)
hasDirtyEntities = dirtyEntities.isNotEmpty()
}
fun onEntitiesChanged(event: VersionedStorageChange) {
ApplicationManager.getApplication().assertWriteAccessAllowed()
contributors.keys.forEach { entityClass ->
processChanges(event, entityClass)
}
}
fun updateDirtyEntities() {
ApplicationManager.getApplication().assertWriteAccessAllowed()
for (file in dirtyFiles) {
fileSets.remove(file)
excludedRoots.remove(file)
exclusionPatternsByRoot.remove(file)
exclusionPatternsByRoot.remove(file)
}
val storage = WorkspaceModel.getInstance(project).entityStorage.current
for (entity in dirtyEntities) {
unregisterFileSets(entity, entity.getEntityInterface(), storage)
registerFileSets(entity, entity.getEntityInterface(), storage)
}
dirtyFiles.clear()
dirtyEntities.clear()
hasDirtyEntities = false
}
private inner class StoreFileSetsRegistrarImpl : WorkspaceFileSetRegistrar {
override fun registerFileSet(root: VirtualFileUrl,
kind: WorkspaceFileKind,
entity: WorkspaceEntity,
customData: WorkspaceFileSetData?) {
val rootFile = rootFileSupplier.findFile(root)
if (rootFile != null) {
registerFileSet(rootFile, kind, entity, customData)
}
}
override fun registerFileSet(root: VirtualFile,
kind: WorkspaceFileKind,
entity: WorkspaceEntity,
customData: WorkspaceFileSetData?) {
fileSets.putValue(root, WorkspaceFileSetImpl(root, kind, entity.createReference(), customData ?: DummyWorkspaceFileSetData))
}
override fun registerExcludedRoot(excludedRoot: VirtualFileUrl, entity: WorkspaceEntity) {
val excludedRootFile = rootFileSupplier.findFile(excludedRoot)
if (excludedRootFile != null) {
excludedRoots.putValue(excludedRootFile, ExcludedRootData(WorkspaceFileKindMask.ALL, entity.createReference()))
}
}
override fun registerExcludedRoot(excludedRoot: VirtualFile,
excludedFrom: WorkspaceFileKind,
entity: WorkspaceEntity) {
val mask = if (excludedFrom == WorkspaceFileKind.EXTERNAL) WorkspaceFileKindMask.EXTERNAL else excludedFrom.toMask()
excludedRoots.putValue(excludedRoot, ExcludedRootData(mask, entity.createReference()))
}
override fun registerExclusionPatterns(root: VirtualFileUrl,
patterns: List<String>,
entity: WorkspaceEntity) {
val rootFile = rootFileSupplier.findFile(root)
if (rootFile != null && !patterns.isEmpty()) {
exclusionPatternsByRoot.putValue(rootFile, ExclusionPatterns(rootFile, patterns, entity.createReference()))
}
}
override fun registerExclusionCondition(root: VirtualFile,
condition: (VirtualFile) -> Boolean,
entity: WorkspaceEntity) {
exclusionConditionsByRoot.putValue(root, ExclusionCondition(root, condition, entity.createReference()))
}
}
private inner class RemoveFileSetsRegistrarImpl : WorkspaceFileSetRegistrar {
override fun registerFileSet(root: VirtualFileUrl,
kind: WorkspaceFileKind,
entity: WorkspaceEntity,
customData: WorkspaceFileSetData?) {
val rootFile = rootFileSupplier.findFile(root)
if (rootFile != null) {
registerFileSet(rootFile, kind, entity, customData)
}
}
override fun registerFileSet(root: VirtualFile,
kind: WorkspaceFileKind,
entity: WorkspaceEntity,
customData: WorkspaceFileSetData?) {
fileSets.removeValueIf(root) { isResolvesTo(it.entityReference, entity) }
}
private fun isResolvesTo(reference: EntityReference<*>, entity: WorkspaceEntity) = reference == entity.createReference<WorkspaceEntity>()
override fun registerExcludedRoot(excludedRoot: VirtualFileUrl, entity: WorkspaceEntity) {
val excludedRootFile = rootFileSupplier.findFile(excludedRoot)
if (excludedRootFile != null) {
//todo compare origins, not just their entities?
excludedRoots.removeValueIf(excludedRootFile) { isResolvesTo(it.entityReference, entity) }
}
}
override fun registerExcludedRoot(excludedRoot: VirtualFile,
excludedFrom: WorkspaceFileKind,
entity: WorkspaceEntity) {
excludedRoots.removeValueIf(excludedRoot) { isResolvesTo(it.entityReference, entity) }
}
override fun registerExclusionPatterns(root: VirtualFileUrl,
patterns: List<String>,
entity: WorkspaceEntity) {
val rootFile = rootFileSupplier.findFile(root)
if (rootFile != null) {
exclusionPatternsByRoot.removeValueIf(rootFile) { isResolvesTo(it.entityReference, entity) }
}
}
override fun registerExclusionCondition(root: VirtualFile,
condition: (VirtualFile) -> Boolean,
entity: WorkspaceEntity) {
exclusionConditionsByRoot.removeValueIf(root) { isResolvesTo(it.entityReference, entity) }
}
}
}
internal class WorkspaceFileSetImpl(override val root: VirtualFile,
override val kind: WorkspaceFileKind,
val entityReference: EntityReference<WorkspaceEntity>,
override val data: WorkspaceFileSetData) : WorkspaceFileSetWithCustomData<WorkspaceFileSetData>, WorkspaceFileInternalInfo {
fun isUnloaded(project: Project): Boolean {
return (data as? UnloadableFileSetData)?.isUnloaded(project) == true
}
}
internal object DummyWorkspaceFileSetData : WorkspaceFileSetData
internal object WorkspaceFileKindMask {
const val CONTENT = 1
const val EXTERNAL_BINARY = 2
const val EXTERNAL_SOURCE = 4
const val EXTERNAL = EXTERNAL_SOURCE or EXTERNAL_BINARY
const val ALL = CONTENT or EXTERNAL
}
internal class ExcludedRootData(@MagicConstant(flagsFromClass = WorkspaceFileKindMask::class) val mask: Int,
val entityReference: EntityReference<WorkspaceEntity>)
private class ExclusionPatterns(val root: VirtualFile, patterns: List<String>,
val entityReference: EntityReference<WorkspaceEntity>) {
val table = FileTypeAssocTable<Boolean>()
init {
for (pattern in patterns) {
table.addAssociation(FileNameMatcherFactory.getInstance().createMatcher(pattern), true)
}
}
fun isExcluded(file: VirtualFile): Boolean {
var current = file
while (current != root) {
if (table.findAssociatedFileType(current.nameSequence) != null) {
return true
}
current = current.parent
}
return false
}
}
internal class ExclusionCondition(val root: VirtualFile, val condition: (VirtualFile) -> Boolean,
val entityReference: EntityReference<WorkspaceEntity>) {
fun isExcluded(file: VirtualFile): Boolean {
var current = file
while (current != root) {
if (condition(current)) {
return true
}
current = current.parent
}
return condition(root)
}
}
internal inline fun <K, V> MultiMap<K, V>.removeValueIf(key: K, crossinline valuePredicate: (V) -> Boolean) {
val collection = get(key)
collection.removeIf { valuePredicate(it) }
if (collection.isEmpty()) {
remove(key)
}
}
private fun WorkspaceFileKind.toMask(): Int {
val mask = when (this) {
WorkspaceFileKind.CONTENT, WorkspaceFileKind.TEST_CONTENT -> WorkspaceFileKindMask.CONTENT
WorkspaceFileKind.EXTERNAL -> WorkspaceFileKindMask.EXTERNAL_BINARY
WorkspaceFileKind.EXTERNAL_SOURCE -> WorkspaceFileKindMask.EXTERNAL_SOURCE
}
return mask
}

View File

@@ -0,0 +1,60 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.core.fileIndex.impl
import com.intellij.openapi.util.registry.Registry
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndex
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileSetData
import com.intellij.workspaceModel.storage.WorkspaceEntity
interface WorkspaceFileIndexEx : WorkspaceFileIndex {
/**
* An internal variant of [findFileSetWithCustomData] method which provides more information if [file] isn't included in the workspace.
*/
fun getFileInfo(file: VirtualFile,
honorExclusion: Boolean,
includeContentSets: Boolean,
includeExternalSets: Boolean,
includeExternalSourceSets: Boolean,
customDataClass: Class<out WorkspaceFileSetData>?): WorkspaceFileInternalInfo
/**
* Reset caches which cannot be updated incrementally.
*/
fun resetCustomContributors()
/**
* Notifies the index about changes in files associated with the entities.
* Must be called inside Write Action, and [updateDirtyEntities] must be called before that Write Action finishes.
* It may happen that an implementation of [com.intellij.openapi.vfs.newvfs.BulkFileListener] will try to get information about changed
* files synchronously during the same Write Action, in that case the index should recalculate the data to provide correct results.
* @param entities entities which refer to files which were created, deleted, moved or renamed
* @param filesToInvalidate files which were deleted or moved to other directories and was referenced from some entities
*/
fun markDirty(entities: Collection<WorkspaceEntity>, filesToInvalidate: Collection<VirtualFile>)
/**
* Forces the index to update entities marked by [markDirty]. Must be called during execution of the same Write Action as [markDirty].
*/
fun updateDirtyEntities()
companion object {
@JvmField
val IS_ENABLED: Boolean = Registry.`is`("platform.projectModel.workspace.model.file.index")
}
}
sealed interface WorkspaceFileInternalInfo {
enum class NonWorkspace : WorkspaceFileInternalInfo {
/** File or one of its parents is marked as 'ignored' */
IGNORED,
/** File or one of its parents is excluded */
EXCLUDED,
/** File is not located under any registered workspace root */
NOT_UNDER_ROOTS,
/** No file set associated with the found file has custom data of specified type */
NO_SUITABLE_CUSTOM_DATA,
/** File is invalid */
INVALID
}
}

View File

@@ -0,0 +1,99 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.core.fileIndex.impl
import com.intellij.injected.editor.VirtualFileWindow
import com.intellij.model.ModelBranch
import com.intellij.notebook.editor.BackedVirtualFile
import com.intellij.openapi.extensions.ExtensionPointName
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.impl.CustomEntityProjectModelInfoProvider
import com.intellij.openapi.roots.impl.RootFileSupplier
import com.intellij.openapi.util.Key
import com.intellij.openapi.util.Pair
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.workspaceModel.core.fileIndex.*
import com.intellij.workspaceModel.storage.VersionedStorageChange
import com.intellij.workspaceModel.storage.WorkspaceEntity
class WorkspaceFileIndexImpl(private val project: Project) : WorkspaceFileIndexEx {
companion object {
private val EP_NAME = ExtensionPointName<WorkspaceFileIndexContributor<*>>("com.intellij.workspaceModel.fileIndexContributor")
private val BRANCH_INDEX_DATA_KEY = Key.create<Pair<Long, WorkspaceFileIndexData>>("BRANCH_WORKSPACE_FILE_INDEX")
}
@Volatile
private var indexData: WorkspaceFileIndexData? = null
override fun findFileSet(file: VirtualFile,
honorExclusion: Boolean,
includeContentSets: Boolean,
includeExternalSets: Boolean,
includeExternalSourceSets: Boolean): WorkspaceFileSet? {
val info = getFileInfo(file, honorExclusion, includeContentSets, includeExternalSets, includeExternalSourceSets, null)
return info as? WorkspaceFileSetImpl
}
override fun <D : WorkspaceFileSetData> findFileSetWithCustomData(file: VirtualFile,
honorExclusion: Boolean,
includeContentSets: Boolean,
includeExternalSets: Boolean,
includeExternalSourceSets: Boolean,
customDataClass: Class<out D>?): WorkspaceFileSetWithCustomData<D>? {
val info = getFileInfo(file, honorExclusion, includeContentSets, includeExternalSets, includeExternalSourceSets, customDataClass)
@Suppress("UNCHECKED_CAST")
return info as? WorkspaceFileSetWithCustomData<D>
}
override fun getFileInfo(file: VirtualFile,
honorExclusion: Boolean,
includeContentSets: Boolean,
includeExternalSets: Boolean,
includeExternalSourceSets: Boolean,
customDataClass: Class<out WorkspaceFileSetData>?): WorkspaceFileInternalInfo {
val unwrappedFile = BackedVirtualFile.getOriginFileIfBacked((file as? VirtualFileWindow)?.delegate ?: file)
return getOrCreateIndexData(unwrappedFile).getFileInfo(unwrappedFile, honorExclusion, includeContentSets, includeExternalSets, includeExternalSourceSets, customDataClass)
}
private fun getOrCreateIndexData(file: VirtualFile): WorkspaceFileIndexData {
val branch = ModelBranch.getFileBranch(file)
if (branch != null) {
return obtainBranchIndexData(branch)
}
var data = indexData
if (data == null) {
data = WorkspaceFileIndexData(contributors, project, RootFileSupplier.INSTANCE)
indexData = data
}
return data
}
private val contributors: List<WorkspaceFileIndexContributor<*>>
get() = EP_NAME.extensionList + CustomEntityProjectModelInfoProvider.EP.extensionList.map { CustomEntityProjectModelInfoProviderBridge(it) }
private fun obtainBranchIndexData(branch: ModelBranch): WorkspaceFileIndexData {
var pair = branch.getUserData(BRANCH_INDEX_DATA_KEY)
val modCount = branch.branchedVfsStructureModificationCount
if (pair == null || pair.first != modCount) {
pair = Pair.create(modCount, WorkspaceFileIndexData(contributors, branch.project, RootFileSupplier.forBranch(branch)))
branch.putUserData(BRANCH_INDEX_DATA_KEY, pair)
}
return pair.second
}
override fun resetCustomContributors() {
indexData?.resetCustomContributors()
}
override fun markDirty(entities: Collection<WorkspaceEntity>, filesToInvalidate: Collection<VirtualFile>) {
indexData?.markDirty(entities, filesToInvalidate)
}
override fun updateDirtyEntities() {
indexData?.updateDirtyEntities()
}
fun onEntitiesChanged(event: VersionedStorageChange) {
indexData?.onEntitiesChanged(event)
}
}

View File

@@ -0,0 +1,100 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.core.fileIndex.impl
import com.intellij.ide.highlighter.ArchiveFileType
import com.intellij.openapi.fileTypes.FileTypeRegistry
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.StandardFileSystems
import com.intellij.openapi.vfs.VfsUtilCore
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.openapi.vfs.VirtualFileVisitor
import com.intellij.util.io.URLUtil
import com.intellij.workspaceModel.core.fileIndex.*
import com.intellij.workspaceModel.ide.impl.virtualFile
import com.intellij.workspaceModel.ide.legacyBridge.ModuleDependencyIndex
import com.intellij.workspaceModel.storage.EntityStorage
import com.intellij.workspaceModel.storage.bridgeEntities.api.*
import com.intellij.workspaceModel.storage.bridgeEntities.api.LibraryRoot.InclusionOptions.*
import com.intellij.workspaceModel.storage.url.VirtualFileUrl
class LibraryRootFileIndexContributor : WorkspaceFileIndexContributor<LibraryEntity> {
override val entityClass: Class<LibraryEntity> get() = LibraryEntity::class.java
override fun registerFileSets(entity: LibraryEntity, registrar: WorkspaceFileSetRegistrar, storage: EntityStorage) {
val projectLibraryId = entity.persistentId.takeIf { it.tableId == LibraryTableId.ProjectLibraryTableId }
val compiledRootsData = LibraryRootFileSetData(projectLibraryId)
val sourceRootFileSetData = LibrarySourceRootFileSetData(projectLibraryId)
for (root in entity.roots) {
val data: LibraryRootFileSetData
val kind: WorkspaceFileKind
when (root.type) {
LibraryRootTypeId.COMPILED -> {
data = compiledRootsData
kind = WorkspaceFileKind.EXTERNAL
}
LibraryRootTypeId.SOURCES -> {
data = sourceRootFileSetData
kind = WorkspaceFileKind.EXTERNAL_SOURCE
}
else -> continue
}
when (root.inclusionOptions) {
ROOT_ITSELF -> registrar.registerFileSet(root.url, kind, entity, data)
ARCHIVES_UNDER_ROOT -> registerArchivesUnderRoot(root.url, registrar, data, kind, entity)
ARCHIVES_UNDER_ROOT_RECURSIVELY -> registerArchivesUnderRootRecursively(root.url, registrar, data, kind, entity)
}
}
}
private fun registerArchivesUnderRoot(root: VirtualFileUrl,
registrar: WorkspaceFileSetRegistrar,
data: LibraryRootFileSetData,
kind: WorkspaceFileKind,
entity: LibraryEntity
) {
root.virtualFile?.children?.forEach { file ->
if (!file.isDirectory && FileTypeRegistry.getInstance().getFileTypeByFileName(file.nameSequence) === ArchiveFileType.INSTANCE) {
val jarRoot = StandardFileSystems.jar().findFileByPath(file.path + URLUtil.JAR_SEPARATOR)
if (jarRoot != null) {
registrar.registerFileSet(jarRoot, kind, entity, data)
}
}
}
}
private fun registerArchivesUnderRootRecursively(root: VirtualFileUrl,
registrar: WorkspaceFileSetRegistrar,
data: LibraryRootFileSetData,
kind: WorkspaceFileKind,
entity: LibraryEntity) {
val virtualFile = root.virtualFile ?: return
VfsUtilCore.visitChildrenRecursively(virtualFile, object : VirtualFileVisitor<Void?>() {
override fun visitFile(file: VirtualFile ): Boolean {
if (!file.isDirectory && FileTypeRegistry.getInstance().getFileTypeByFileName(file.nameSequence) === ArchiveFileType.INSTANCE) {
val jarRoot = StandardFileSystems.jar().findFileByPath(file.path + URLUtil.JAR_SEPARATOR)
if (jarRoot != null) {
registrar.registerFileSet(jarRoot, kind, entity, data)
return false
}
}
return true
}
})
}
}
internal class LibrarySourceRootFileSetData(projectLibraryId: LibraryId?) : LibraryRootFileSetData(projectLibraryId), ModuleOrLibrarySourceRootData
internal open class LibraryRootFileSetData(private val projectLibraryId: LibraryId?) : UnloadableFileSetData {
override fun isUnloaded(project: Project): Boolean {
return projectLibraryId != null && !ModuleDependencyIndex.getInstance(project).hasDependencyOn(projectLibraryId)
}
}
/**
* Provides a way to exclude [WorkspaceFileSet] with custom data from [WorkspaceFileIndex] based on some condition. This is a temporary
* solution to exclude project-level libraries which aren't used in modules from the index.
*/
internal interface UnloadableFileSetData : WorkspaceFileSetData {
fun isUnloaded(project: Project): Boolean
}

View File

@@ -0,0 +1,62 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.workspaceModel.core.fileIndex.impl
import com.intellij.openapi.module.Module
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.workspaceModel.core.fileIndex.*
import com.intellij.workspaceModel.ide.impl.legacyBridge.module.findModule
import com.intellij.workspaceModel.ide.impl.legacyBridge.module.roots.SourceRootTypeRegistry
import com.intellij.workspaceModel.ide.impl.virtualFile
import com.intellij.workspaceModel.storage.EntityStorage
import com.intellij.workspaceModel.storage.bridgeEntities.api.ContentRootEntity
import com.intellij.workspaceModel.storage.bridgeEntities.api.SourceRootEntity
class ContentRootFileIndexContributor : WorkspaceFileIndexContributor<ContentRootEntity> {
override val entityClass: Class<ContentRootEntity>
get() = ContentRootEntity::class.java
override fun registerFileSets(entity: ContentRootEntity, registrar: WorkspaceFileSetRegistrar, storage: EntityStorage) {
val module = entity.module.findModule(storage)
if (module != null) {
registrar.registerFileSet(entity.url, WorkspaceFileKind.CONTENT, entity, ModuleContentRootData(module, null))
registrar.registerExclusionPatterns(entity.url, entity.excludedPatterns, entity)
}
}
}
class SourceRootFileIndexContributor : WorkspaceFileIndexContributor<SourceRootEntity> {
override val entityClass: Class<SourceRootEntity>
get() = SourceRootEntity::class.java
override fun registerFileSets(entity: SourceRootEntity, registrar: WorkspaceFileSetRegistrar, storage: EntityStorage) {
val module = entity.contentRoot.module.findModule(storage)
if (module != null) {
val contentRoot = entity.contentRoot.url.virtualFile
val kind = if (SourceRootTypeRegistry.getInstance().findTypeById(entity.rootType)?.isForTests == true) WorkspaceFileKind.TEST_CONTENT else WorkspaceFileKind.CONTENT
registrar.registerFileSet(entity.url, kind, entity, ModuleSourceRootData(module, contentRoot, entity.rootType))
//todo update index when parent ContentRootEntity is modified
registrar.registerExclusionPatterns(entity.url, entity.contentRoot.excludedPatterns, entity)
}
}
}
/**
* Implement this interface in custom data stored in [WorkspaceFileSet] to associate it with [Module] instance and specify 'content root' for it.
* This information will be used by [com.intellij.openapi.roots.ProjectFileIndex.getModuleForFile]
* and [com.intellij.openapi.roots.ProjectFileIndex.getContentRootForFile] methods.
*/
internal interface ModuleContentOrSourceRootData: WorkspaceFileSetData {
val module: Module
val customContentRoot: VirtualFile?
}
/**
* Implement this interface in custom data stored in [WorkspaceFileSet] to mark it as a 'source root'.
* This information will be use by [com.intellij.openapi.roots.ProjectFileIndex.isInSource] and
* [com.intellij.openapi.roots.ProjectFileIndex.getSourceRootForFile] methods.
*/
internal interface ModuleOrLibrarySourceRootData: WorkspaceFileSetData
internal data class ModuleContentRootData(override val module: Module, override val customContentRoot: VirtualFile?): ModuleContentOrSourceRootData
internal data class ModuleSourceRootData(override val module: Module, override val customContentRoot: VirtualFile?, val rootType: String): ModuleContentOrSourceRootData, ModuleOrLibrarySourceRootData

View File

@@ -0,0 +1,5 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
@ApiStatus.Internal
package com.intellij.workspaceModel.core.fileIndex.impl;
import org.jetbrains.annotations.ApiStatus;

View File

@@ -0,0 +1,17 @@
// Copyright 2000-2022 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
/**
* Classes in this package provide a way to associate files and directories with entities from {@link com.intellij.workspaceModel.ide.WorkspaceModel}.
* <p>
* Use {@link com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndexContributor} to include files referenced from entities to the
* workspace, and use {@link com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndex} to get status of a file or directory in
* the workspace.
* </p>
*
* <p>
* All classes in this package <strong>are experimental</strong> and their API will change in future versions.
* </p>
*/
@ApiStatus.Experimental
package com.intellij.workspaceModel.core.fileIndex;
import org.jetbrains.annotations.ApiStatus;

View File

@@ -4,6 +4,7 @@ package com.intellij.workspaceModel.ide.impl
import com.intellij.diagnostic.StartUpMeasurer.startActivity
import com.intellij.openapi.Disposable
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.components.serviceIfCreated
import com.intellij.openapi.components.serviceOrNull
import com.intellij.openapi.diagnostic.debug
import com.intellij.openapi.diagnostic.logger
@@ -12,6 +13,8 @@ import com.intellij.openapi.module.ModuleManager
import com.intellij.openapi.project.Project
import com.intellij.openapi.roots.impl.libraries.ProjectLibraryTable
import com.intellij.openapi.util.registry.Registry
import com.intellij.workspaceModel.core.fileIndex.WorkspaceFileIndex
import com.intellij.workspaceModel.core.fileIndex.impl.WorkspaceFileIndexImpl
import com.intellij.workspaceModel.ide.*
import com.intellij.workspaceModel.ide.impl.legacyBridge.library.ProjectLibraryTableBridgeImpl
import com.intellij.workspaceModel.ide.impl.legacyBridge.module.ModuleManagerBridgeImpl
@@ -175,6 +178,10 @@ open class WorkspaceModelImpl(private val project: Project) : WorkspaceModel, Di
private fun onChanged(change: VersionedStorageChange) {
ApplicationManager.getApplication().assertWriteAccessAllowed()
if (project.isDisposed) return
//it is important to update WorkspaceFileIndex before other listeners are called because they may rely on it
logErrorOnEventHandling {
(project.serviceIfCreated<WorkspaceFileIndex>() as? WorkspaceFileIndexImpl)?.onEntitiesChanged(change)
}
logErrorOnEventHandling {
project.messageBus.syncPublisher(WorkspaceModelTopics.CHANGED).changed(change)

View File

@@ -112,7 +112,7 @@ class ModuleDependencyIndexImpl(private val project: Project): ModuleDependencyI
val libraryTablesRegistrar = LibraryTablesRegistrar.getInstance()
moduleEntity.dependencies.forEach {
when {
it is ModuleDependencyItem.Exportable.LibraryDependency && it.library.tableId is LibraryTableId.GlobalLibraryTableId -> {
it is ModuleDependencyItem.Exportable.LibraryDependency && it.library.tableId !is LibraryTableId.ModuleLibraryTableId -> {
val libraryName = it.library.name
val libraryLevel = it.library.tableId.level
val libraryTable = libraryTablesRegistrar.getLibraryTableByLevel(libraryLevel, project) ?: return@forEach
@@ -243,6 +243,9 @@ class ModuleDependencyIndexImpl(private val project: Project): ModuleDependencyI
override fun jdkAdded(jdk: Sdk) {
if (hasDependencyOn(jdk)) {
if (watchedSdks.isEmpty()) {
eventDispatcher.multicaster.firstDependencyOnSdkAdded()
}
eventDispatcher.multicaster.referencedSdkAdded(jdk)
if (watchedSdks.add(jdk.rootProvider)) {
eventDispatcher.multicaster.addedDependencyOn(jdk)
@@ -280,13 +283,21 @@ class ModuleDependencyIndexImpl(private val project: Project): ModuleDependencyI
if (hasDependencyOn(jdk)) {
eventDispatcher.multicaster.referencedSdkRemoved(jdk)
}
if (watchedSdks.isEmpty()) {
eventDispatcher.multicaster.lastDependencyOnSdkRemoved()
}
}
fun addTrackedJdk(sdkDependency: ModuleDependencyItem, moduleEntity: ModuleEntity) {
val sdk = findSdk(sdkDependency)
if (sdk != null && watchedSdks.add(sdk.rootProvider)) {
eventDispatcher.multicaster.addedDependencyOn(sdk)
sdk.rootProvider.addRootSetChangedListener(rootSetChangeListener)
if (sdk != null) {
if (watchedSdks.isEmpty()) {
eventDispatcher.multicaster.firstDependencyOnSdkAdded()
}
if (watchedSdks.add(sdk.rootProvider)) {
eventDispatcher.multicaster.addedDependencyOn(sdk)
sdk.rootProvider.addRootSetChangedListener(rootSetChangeListener)
}
}
sdkDependencies.putValue(sdkDependency, moduleEntity.persistentId)
}
@@ -297,6 +308,9 @@ class ModuleDependencyIndexImpl(private val project: Project): ModuleDependencyI
if (sdk != null && !hasDependencyOn(sdk) && watchedSdks.remove(sdk.rootProvider)) {
sdk.rootProvider.removeRootSetChangedListener(rootSetChangeListener)
eventDispatcher.multicaster.removedDependencyOn(sdk)
if (watchedSdks.isEmpty()) {
eventDispatcher.multicaster.lastDependencyOnSdkRemoved()
}
}
}

View File

@@ -127,4 +127,12 @@ interface ModuleDependencyListener : EventListener {
@JvmDefault
fun referencedSdkRemoved(sdk: Sdk) {
}
@JvmDefault
fun firstDependencyOnSdkAdded() {
}
@JvmDefault
fun lastDependencyOnSdkRemoved() {
}
}