diff --git a/java/java-tests/testSrc/com/intellij/java/openapi/roots/impl/DirectoryIndexTest.java b/java/java-tests/testSrc/com/intellij/java/openapi/roots/impl/DirectoryIndexTest.java index 301286e81cc5..087158551527 100644 --- a/java/java-tests/testSrc/com/intellij/java/openapi/roots/impl/DirectoryIndexTest.java +++ b/java/java-tests/testSrc/com/intellij/java/openapi/roots/impl/DirectoryIndexTest.java @@ -2,6 +2,7 @@ package com.intellij.java.openapi.roots.impl; import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.command.WriteCommandAction; import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.fileTypes.ex.FileTypeManagerEx; @@ -988,4 +989,24 @@ public class DirectoryIndexTest extends DirectoryIndexTestCase { assertEquals("Invalid package name for dir " + dir + ": " + packageName, packageName, actualName); } } + + public void testUnrelatedDirectoriesCreationMustNotLeadToDirectoryIndexRebuildToImproveCheckoutPerformance() { + VirtualFile root = ModuleRootManager.getInstance(myModule).getContentRoots()[0]; + WriteAction.run(()->ModuleRootModificationUtil.updateModel(myModule, model -> { + ContentEntry rootEntry = model.getContentEntries()[0]; + for (int i=0;i<10_000;i++) { + VirtualFile src = createChildDirectory(root, "extsrc" + i); + rootEntry.addSourceFolder(src, false); + } + })); + + ProjectFileIndex fileIndex = ProjectFileIndex.getInstance(getProject()); + PlatformTestUtil.startPerformanceTest("dir creation must not lead to dirindex rebuild", 30_000, ()->{ + for (int i=0; i<5_000; i++) { + VirtualFile xxx = createChildDirectory(root, "xxx"); + assertFalse(fileIndex.isInSource(xxx)); + delete(xxx); + } + }).assertTiming(); + } } \ No newline at end of file diff --git a/platform/core-impl/src/com/intellij/openapi/roots/impl/PackageDirectoryCache.java b/platform/core-impl/src/com/intellij/openapi/roots/impl/PackageDirectoryCache.java index cbbb8e5457cb..fccf3af32748 100644 --- a/platform/core-impl/src/com/intellij/openapi/roots/impl/PackageDirectoryCache.java +++ b/platform/core-impl/src/com/intellij/openapi/roots/impl/PackageDirectoryCache.java @@ -52,6 +52,11 @@ public class PackageDirectoryCache { } } + void clear() { + myNonExistentPackages.clear(); + myDirectoriesByPackageNameCache.clear(); + } + public void onLowMemory() { myNonExistentPackages.clear(); } @@ -86,7 +91,7 @@ public class PackageDirectoryCache { } for (VirtualFile file : myRootsByPackagePrefix.get(packageName)) { - if (file.isDirectory()) { + if (file.isDirectory() && file.isValid()) { result.add(file); } } diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java index c407ebe6fa66..29bcaf526b6c 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/DirectoryIndexImpl.java @@ -6,6 +6,7 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.fileTypes.FileTypeEvent; import com.intellij.openapi.fileTypes.FileTypeListener; import com.intellij.openapi.fileTypes.FileTypeManager; +import com.intellij.openapi.fileTypes.FileTypeRegistry; import com.intellij.openapi.module.Module; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.Project; @@ -17,9 +18,12 @@ import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.LowMemoryWatcher; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFileManager; +import com.intellij.openapi.vfs.ex.temp.TempFileSystem; import com.intellij.openapi.vfs.newvfs.BulkFileListener; import com.intellij.openapi.vfs.newvfs.NewVirtualFile; import com.intellij.openapi.vfs.newvfs.events.VFileEvent; +import com.intellij.openapi.vfs.newvfs.events.VFileMoveEvent; +import com.intellij.openapi.vfs.newvfs.events.VFilePropertyChangeEvent; import com.intellij.util.Query; import com.intellij.util.messages.MessageBusConnection; import org.jetbrains.annotations.NotNull; @@ -64,6 +68,11 @@ public class DirectoryIndexImpl extends DirectoryIndex { }); myConnection.subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootListener() { + @Override + public void beforeRootsChange(@NotNull ModuleRootEvent event) { + myRootIndex = null; + } + @Override public void rootsChanged(@NotNull ModuleRootEvent event) { myRootIndex = null; @@ -75,7 +84,15 @@ public class DirectoryIndexImpl extends DirectoryIndex { public void after(@NotNull List events) { RootIndex rootIndex = myRootIndex; if (rootIndex != null && shouldResetOnEvents(events)) { - myRootIndex = null; + rootIndex.myPackageDirectoryCache.clear(); + for (VFileEvent event : events) { + // TempFileSystem doesn't properly support virtual file pointers, so reset unconditionally + if (event.getFileSystem() instanceof TempFileSystem || + isIgnoredFileCreated(event)) { + myRootIndex = null; + break; + } + } } } }); @@ -91,6 +108,12 @@ public class DirectoryIndexImpl extends DirectoryIndex { return false; } + private static boolean isIgnoredFileCreated(@NotNull VFileEvent event) { + return event instanceof VFileMoveEvent && FileTypeRegistry.getInstance().isFileIgnored(((VFileMoveEvent)event).getNewParent()) || + event instanceof VFilePropertyChangeEvent && + ((VFilePropertyChangeEvent)event).getPropertyName().equals(VirtualFile.PROP_NAME) && + FileTypeRegistry.getInstance().isFileIgnored(((VFilePropertyChangeEvent)event).getFile()); + } private void dispatchPendingEvents() { myConnection.deliverImmediately(); diff --git a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java index a3aef4c24890..20c45d023d05 100644 --- a/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java +++ b/platform/projectModel-impl/src/com/intellij/openapi/roots/impl/RootIndex.java @@ -49,7 +49,7 @@ public class RootIndex { private final Map myRootInfos = ContainerUtil.newHashMap(); private final ConcurrentBitSet myNonInterestingIds = new ConcurrentBitSet(); @NotNull private final Project myProject; - private final PackageDirectoryCache myPackageDirectoryCache; + final PackageDirectoryCache myPackageDirectoryCache; private OrderEntryGraph myOrderEntryGraph; public RootIndex(@NotNull Project project) {