mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-04 23:39:07 +07:00
[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:
committed by
intellij-monorepo-bot
parent
b4a45be687
commit
056aca7669
@@ -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));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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`() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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) }
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
@@ -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;
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -127,4 +127,12 @@ interface ModuleDependencyListener : EventListener {
|
||||
@JvmDefault
|
||||
fun referencedSdkRemoved(sdk: Sdk) {
|
||||
}
|
||||
|
||||
@JvmDefault
|
||||
fun firstDependencyOnSdkAdded() {
|
||||
}
|
||||
|
||||
@JvmDefault
|
||||
fun lastDependencyOnSdkRemoved() {
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user