diff --git a/platform/core-api/src/com/intellij/openapi/project/DumbModeTask.java b/platform/core-api/src/com/intellij/openapi/project/DumbModeTask.java index 360e7626669b..56fab1229f48 100644 --- a/platform/core-api/src/com/intellij/openapi/project/DumbModeTask.java +++ b/platform/core-api/src/com/intellij/openapi/project/DumbModeTask.java @@ -15,14 +15,18 @@ */ package com.intellij.openapi.project; +import com.intellij.openapi.Disposable; import com.intellij.openapi.progress.ProgressIndicator; import org.jetbrains.annotations.NotNull; /** * @author peter */ -public abstract class DumbModeTask { +public abstract class DumbModeTask implements Disposable { public abstract void performInDumbMode(@NotNull ProgressIndicator indicator); + @Override + public void dispose() { + } } diff --git a/platform/core-api/src/com/intellij/openapi/project/DumbService.java b/platform/core-api/src/com/intellij/openapi/project/DumbService.java index a690091518e9..ad5c36a5d3e2 100644 --- a/platform/core-api/src/com/intellij/openapi/project/DumbService.java +++ b/platform/core-api/src/com/intellij/openapi/project/DumbService.java @@ -176,7 +176,9 @@ public abstract class DumbService { return new ArrayList(collection); } - public abstract void queueTask(DumbModeTask task); + public abstract void queueTask(@NotNull DumbModeTask task); + + public abstract void cancelTask(@NotNull DumbModeTask task); public abstract JComponent wrapGently(@NotNull JComponent dumbUnawareContent, @NotNull Disposable parentDisposable); diff --git a/platform/core-impl/src/com/intellij/mock/MockDumbService.java b/platform/core-impl/src/com/intellij/mock/MockDumbService.java index fa922fbe21ba..1e1179cc5ee8 100644 --- a/platform/core-impl/src/com/intellij/mock/MockDumbService.java +++ b/platform/core-impl/src/com/intellij/mock/MockDumbService.java @@ -21,6 +21,7 @@ import com.intellij.openapi.progress.EmptyProgressIndicator; import com.intellij.openapi.project.DumbModeTask; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; import org.jetbrains.annotations.NotNull; import javax.swing.*; @@ -50,10 +51,14 @@ public class MockDumbService extends DumbService { } @Override - public void queueTask(DumbModeTask task) { + public void queueTask(@NotNull DumbModeTask task) { task.performInDumbMode(new EmptyProgressIndicator()); + Disposer.dispose(task); } + @Override + public void cancelTask(@NotNull DumbModeTask task) { } + @Override public JComponent wrapGently(@NotNull JComponent dumbUnawareContent, @NotNull Disposable parentDisposable) { throw new UnsupportedOperationException(); diff --git a/platform/lang-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdater.java b/platform/lang-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdater.java index 1247b582b10e..b1959c2ae74c 100644 --- a/platform/lang-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdater.java +++ b/platform/lang-impl/src/com/intellij/openapi/roots/impl/PushedFilePropertiesUpdater.java @@ -164,14 +164,21 @@ public class PushedFilePropertiesUpdater { }); } - private void queueTask(Runnable task) { - myTasks.offer(task); - DumbService.getInstance(myProject).queueTask(new DumbModeTask() { + private void queueTask(Runnable action) { + myTasks.offer(action); + final DumbModeTask task = new DumbModeTask() { @Override public void performInDumbMode(@NotNull ProgressIndicator indicator) { performPushTasks(); } + }; + myProject.getMessageBus().connect(task).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() { + @Override + public void rootsChanged(ModuleRootEvent event) { + DumbService.getInstance(myProject).cancelTask(task); + } }); + DumbService.getInstance(myProject).queueTask(task); } private void performPushTasks() { diff --git a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexProjectHandler.java b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexProjectHandler.java index 2ece1842ecac..9a5c46a99983 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexProjectHandler.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/FileBasedIndexProjectHandler.java @@ -19,6 +19,7 @@ */ package com.intellij.util.indexing; +import com.intellij.ProjectTopics; import com.intellij.ide.IdeBundle; import com.intellij.ide.caches.FileContent; import com.intellij.ide.startup.StartupManagerEx; @@ -29,6 +30,8 @@ import com.intellij.openapi.fileTypes.FileTypeManager; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.*; import com.intellij.openapi.roots.ContentIterator; +import com.intellij.openapi.roots.ModuleRootAdapter; +import com.intellij.openapi.roots.ModuleRootEvent; import com.intellij.openapi.roots.ProjectFileIndex; import com.intellij.openapi.roots.ex.ProjectRootManagerEx; import com.intellij.openapi.roots.impl.ProjectRootManagerComponent; @@ -128,7 +131,7 @@ public class FileBasedIndexProjectHandler extends AbstractProjectComponent imple @Override public void disposeComponent() { - // done mostly for tests. In real life this is noop, because the set was removed on project closing + // done mostly for tests. In real life this is no-op, because the set was removed on project closing myIndex.removeIndexableSet(this); } @@ -140,7 +143,7 @@ public class FileBasedIndexProjectHandler extends AbstractProjectComponent imple return null; } - return new DumbModeTask() { + final DumbModeTask task = new DumbModeTask() { @Override public void performInDumbMode(@NotNull ProgressIndicator indicator) { final Collection files = index.getFilesToUpdate(project); @@ -149,6 +152,13 @@ public class FileBasedIndexProjectHandler extends AbstractProjectComponent imple reindexRefreshedFiles(indicator, files, project, index); } }; + project.getMessageBus().connect(task).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() { + @Override + public void rootsChanged(ModuleRootEvent event) { + DumbService.getInstance(project).cancelTask(task); + } + }); + return task; } private static void reindexRefreshedFiles(ProgressIndicator indicator, diff --git a/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java b/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java index 8647b951407e..f500e5bc10e2 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/UnindexedFilesUpdater.java @@ -15,6 +15,7 @@ */ package com.intellij.util.indexing; +import com.intellij.ProjectTopics; import com.intellij.ide.IdeBundle; import com.intellij.ide.caches.FileContent; import com.intellij.ide.startup.impl.StartupManagerImpl; @@ -24,8 +25,11 @@ import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.CacheUpdateRunner; import com.intellij.openapi.project.DumbModeTask; +import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.CollectingContentIterator; +import com.intellij.openapi.roots.ModuleRootAdapter; +import com.intellij.openapi.roots.ModuleRootEvent; import com.intellij.openapi.roots.impl.PushedFilePropertiesUpdater; import com.intellij.openapi.startup.StartupManager; import com.intellij.openapi.vfs.VirtualFile; @@ -48,6 +52,12 @@ public class UnindexedFilesUpdater extends DumbModeTask { public UnindexedFilesUpdater(final Project project, boolean onStartup) { myProject = project; myOnStartup = onStartup; + project.getMessageBus().connect(this).subscribe(ProjectTopics.PROJECT_ROOTS, new ModuleRootAdapter() { + @Override + public void rootsChanged(ModuleRootEvent event) { + DumbService.getInstance(project).cancelTask(UnindexedFilesUpdater.this); + } + }); } private void updateUnindexedFiles(ProgressIndicator indicator) { diff --git a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java index b72a7e0cc70e..f3e7da762f91 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java @@ -17,7 +17,6 @@ package com.intellij.openapi.project; import com.intellij.ide.IdeBundle; import com.intellij.ide.caches.CacheUpdater; -import com.intellij.ide.util.DelegatingProgressIndicator; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; @@ -28,6 +27,7 @@ import com.intellij.openapi.progress.*; import com.intellij.openapi.progress.util.ProgressIndicatorBase; import com.intellij.openapi.ui.MessageType; import com.intellij.openapi.util.Computable; +import com.intellij.openapi.util.Disposer; import com.intellij.openapi.util.Ref; import com.intellij.openapi.util.ShutDownTracker; import com.intellij.openapi.wm.AppIconScheme; @@ -37,9 +37,9 @@ import com.intellij.openapi.wm.ex.ProgressIndicatorEx; import com.intellij.openapi.wm.ex.StatusBarEx; import com.intellij.ui.AppIcon; import com.intellij.util.concurrency.Semaphore; +import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.Queue; import com.intellij.util.io.storage.HeavyProcessLatch; -import com.intellij.util.messages.MessageBus; import com.intellij.util.ui.UIUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -48,28 +48,55 @@ import org.jetbrains.annotations.TestOnly; import javax.swing.*; import java.util.ArrayList; import java.util.Collection; +import java.util.Map; -public class DumbServiceImpl extends DumbService { +public class DumbServiceImpl extends DumbService implements Disposable { private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.project.DumbServiceImpl"); private volatile boolean myDumb = false; private final DumbModeListener myPublisher; private final Queue myUpdatesQueue = new Queue(5); + + /** + * Per-task progress indicators. Modified from EDT only. + * The task is removed from this map after it's finished or when the project is disposed. + */ + private final Map myProgresses = ContainerUtil.newConcurrentMap(); + private final Queue myRunWhenSmartQueue = new Queue(5); private final Project myProject; + public DumbServiceImpl(Project project) { + myProject = project; + myPublisher = project.getMessageBus().syncPublisher(DUMB_MODE); + } + @SuppressWarnings({"MethodOverridesStaticMethodOfSuperclass"}) public static DumbServiceImpl getInstance(@NotNull Project project) { return (DumbServiceImpl)DumbService.getInstance(project); } @Override - public void queueTask(final DumbModeTask task) { + public void queueTask(@NotNull final DumbModeTask task) { scheduleCacheUpdate(task, true); } - public DumbServiceImpl(Project project, MessageBus bus) { - myProject = project; - myPublisher = bus.syncPublisher(DUMB_MODE); + @Override + public void cancelTask(@NotNull DumbModeTask task) { + ProgressIndicatorEx indicator = myProgresses.get(task); + if (indicator != null) { + indicator.cancel(); + } + } + + @Override + public void dispose() { + ApplicationManager.getApplication().assertIsDispatchThread(); + myUpdatesQueue.clear(); + myRunWhenSmartQueue.clear(); + for (DumbModeTask task : new ArrayList(myProgresses.keySet())) { + cancelTask(task); + Disposer.dispose(task); + } } @Override @@ -134,6 +161,7 @@ public class DumbServiceImpl extends DumbService { if (indicator != null) { indicator.popState(); } + Disposer.dispose(task); } return; } @@ -145,6 +173,14 @@ public class DumbServiceImpl extends DumbService { return; } myUpdatesQueue.addLast(task); + myProgresses.put(task, new ProgressIndicatorBase()); + Disposer.register(task, new Disposable() { + @Override + public void dispose() { + application.assertIsDispatchThread(); + myProgresses.remove(task); + } + }); // ok to test and set the flag like this, because the change is always done from dispatch thread if (!myDumb) { // always change dumb status inside write action. @@ -294,14 +330,12 @@ public class DumbServiceImpl extends DumbService { ((ProgressIndicatorEx)indicator).addStateDelegate(new AppIconProgress()); } - final ProgressIndicator proxy = new DelegatingProgressIndicator(indicator); - final ShutDownTracker shutdownTracker = ShutDownTracker.getInstance(); final Thread self = Thread.currentThread(); try { HeavyProcessLatch.INSTANCE.processStarted(); shutdownTracker.registerStopperThread(self); - runTasks(proxy); + runTasks(indicator); } catch (RuntimeException e) { LOG.error(e); @@ -313,16 +347,26 @@ public class DumbServiceImpl extends DumbService { } } - private void runTasks(ProgressIndicator indicator) { + private void runTasks(ProgressIndicator visibleIndicator) { + DumbModeTask prevTask = null; while (true) { - DumbModeTask task = getNextTask(); + DumbModeTask task = getNextTask(prevTask); if (task == null) break; try { if (ApplicationManager.getApplication().isInternal()) LOG.info("Running dumb mode task: " + task); + + final ProgressIndicatorEx indicator = myProgresses.get(task); + assert indicator != null; indicator.checkCanceled(); + + if (visibleIndicator instanceof ProgressIndicatorEx) { + indicator.addStateDelegate((ProgressIndicatorEx)visibleIndicator); + } + indicator.setIndeterminate(true); indicator.setText(IdeBundle.message("progress.indexing.scanning")); + task.performInDumbMode(indicator); } catch (ProcessCanceledException ignored) { @@ -330,6 +374,7 @@ public class DumbServiceImpl extends DumbService { catch (Throwable unexpected) { LOG.error(unexpected); } + prevTask = task; } } @@ -337,16 +382,30 @@ public class DumbServiceImpl extends DumbService { } @Nullable - private DumbModeTask getNextTask() { + private DumbModeTask getNextTask(@Nullable final DumbModeTask prevTask) { final Ref nextTask = Ref.create(); UIUtil.invokeAndWaitIfNeeded(new Runnable() { @Override public void run() { if (myProject.isDisposed()) return; - if (myUpdatesQueue.isEmpty()) { - updateFinished(); - } else { - nextTask.set(myUpdatesQueue.pullFirst()); + if (prevTask != null) { + Disposer.dispose(prevTask); + } + + while (true) { + if (myUpdatesQueue.isEmpty()) { + updateFinished(); + return; + } + + DumbModeTask queuedTask = myUpdatesQueue.pullFirst(); + if (myProgresses.get(queuedTask).isCanceled()) { + Disposer.dispose(queuedTask); + continue; + } + + nextTask.set(queuedTask); + return; } } });