diff --git a/platform/lang-impl/src/com/intellij/util/indexing/BackgroundCacheUpdaterRunner.java b/platform/lang-impl/src/com/intellij/util/indexing/BackgroundCacheUpdaterRunner.java index f33cc4528522..8b8f6754ed86 100644 --- a/platform/lang-impl/src/com/intellij/util/indexing/BackgroundCacheUpdaterRunner.java +++ b/platform/lang-impl/src/com/intellij/util/indexing/BackgroundCacheUpdaterRunner.java @@ -77,7 +77,7 @@ public class BackgroundCacheUpdaterRunner { final MyFileContentQueue queue = new MyFileContentQueue(); try { - final double count = files.size(); + final double total = files.size(); queue.queue(files, indicator); final Consumer uiUpdater = new Consumer() { @@ -87,7 +87,7 @@ public class BackgroundCacheUpdaterRunner { public void consume(VirtualFile virtualFile) { indicator.checkCanceled(); - indicator.setFraction(processed.size() / count); + indicator.setFraction(processed.size() / total); processed.add(virtualFile); indicator.setText2(virtualFile.getPresentableUrl()); } @@ -110,7 +110,7 @@ public class BackgroundCacheUpdaterRunner { } }; if (project != null) { - DumbServiceImpl.getInstance(project).queueIndexUpdate(action); + DumbServiceImpl.getInstance(project).queueIndexUpdate(action, files.size()); } else { final ProgressIndicator currentIndicator = ProgressManager.getInstance().getProgressIndicator(); diff --git a/platform/platform-impl/src/com/intellij/internal/ToggleDumbModeAction.java b/platform/platform-impl/src/com/intellij/internal/ToggleDumbModeAction.java index 350e05bb67f6..24657cdbdd53 100644 --- a/platform/platform-impl/src/com/intellij/internal/ToggleDumbModeAction.java +++ b/platform/platform-impl/src/com/intellij/internal/ToggleDumbModeAction.java @@ -15,7 +15,10 @@ */ package com.intellij.internal; -import com.intellij.openapi.actionSystem.*; +import com.intellij.openapi.actionSystem.AnAction; +import com.intellij.openapi.actionSystem.AnActionEvent; +import com.intellij.openapi.actionSystem.PlatformDataKeys; +import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbServiceImpl; @@ -46,7 +49,7 @@ public class ToggleDumbModeAction extends AnAction implements DumbAware { } } } - }); + }, 0); } } 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 92ec65421b27..5e237b6eb682 100644 --- a/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java +++ b/platform/platform-impl/src/com/intellij/openapi/project/DumbServiceImpl.java @@ -15,9 +15,13 @@ */ package com.intellij.openapi.project; -import com.intellij.ide.startup.impl.StartupManagerImpl; import com.intellij.ide.startup.StartupManagerEx; -import com.intellij.openapi.application.*; +import com.intellij.ide.startup.impl.StartupManagerImpl; +import com.intellij.openapi.application.Application; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.application.ApplicationNamesInfo; +import com.intellij.openapi.application.ModalityState; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.progress.Task; @@ -35,15 +39,23 @@ import org.jetbrains.annotations.NotNull; import javax.swing.event.HyperlinkEvent; import javax.swing.event.HyperlinkListener; +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; /** * @author peter */ public class DumbServiceImpl extends DumbService { + private static final Logger LOG = Logger.getInstance("#com.intellij.openapi.project.DumbServiceImpl"); private final AtomicBoolean myDumb = new AtomicBoolean(); private final DumbModeListener myPublisher; - private final Queue myUpdateQueue = new Queue(5); + private final Queue myUpdateQueue = new Queue(5); + /* to be accessed from EDT only! */ + private IndexUpdateRunnable myCurrentUpdateRunnable = null; private final Queue myAfterUpdateQueue = new Queue(5); @NonNls public static final String FILE_INDEX_BACKGROUND = "fileIndex.background"; private final StartupManagerImpl myStartupManager; @@ -73,40 +85,16 @@ public class DumbServiceImpl extends DumbService { public void runWhenSmart(Runnable runnable) { if (!isDumb()) { runnable.run(); - } else { + } + else { synchronized (myAfterUpdateQueue) { myAfterUpdateQueue.addLast(runnable); } } } - public void queueIndexUpdate(@NotNull final Consumer action) { - final Runnable update = new Runnable() { - public void run() { - if (myProject.isDisposed()) { - updateFinished(); - return; - } - - ProgressManager.getInstance().run(new Task.Backgroundable(myProject, "Updating indices", false) { - @Override - public void run(@NotNull final ProgressIndicator indicator) { - indicator.setIndeterminate(false); - indicator.setText("Indexing..."); - try { - action.consume(indicator); - } - finally { - ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() { - public void run() { - updateFinished(); - } - }); - } - } - }); - } - }; + public void queueIndexUpdate(@NotNull final Consumer action, final int itemsCount) { + final IndexUpdateRunnable update = new IndexUpdateRunnable(action, itemsCount); //todo always go dumb immediately after those who request indices in startup activity are gone (e.g. JS indices) final boolean goDumbImmediately = ((StartupManagerEx)StartupManager.getInstance(myProject)).startupActivityPassed(); @@ -114,7 +102,9 @@ public class DumbServiceImpl extends DumbService { invokeOnEDT(new DumbAwareRunnable() { public void run() { - if (myProject.isDisposed()) return; + if (myProject.isDisposed()) { + return; + } boolean wasDumbEx = wasDumb; if (!goDumbImmediately) { @@ -124,7 +114,12 @@ public class DumbServiceImpl extends DumbService { if (!wasDumbEx) { myPublisher.enteredDumbMode(); update.run(); - } else { + } + else { + final IndexUpdateRunnable currentUpdateRunnable = myCurrentUpdateRunnable; + if (currentUpdateRunnable != null) { + currentUpdateRunnable.myTotalItems += itemsCount; + } myUpdateQueue.addLast(update); } } @@ -148,14 +143,7 @@ public class DumbServiceImpl extends DumbService { } private void updateFinished() { - if (myProject.isDisposed()) return; - - if (!myUpdateQueue.isEmpty()) { - //run next dumb action - myUpdateQueue.pullFirst().run(); - return; - } - + myCurrentUpdateRunnable = null; myDumb.set(false); myPublisher.exitDumbMode(); @@ -193,4 +181,102 @@ public class DumbServiceImpl extends DumbService { return statusBar.notifyProgressByBalloon(MessageType.WARNING, message, null, listener); } + private static final Consumer NULL_ACTION = new Consumer() { + public void consume(ProgressIndicator progressIndicator) { + } + }; + + private class IndexUpdateRunnable implements Runnable { + private final Consumer myAction; + private double myProcessedItems; + private volatile int myTotalItems; + private double myCurrentBaseTotal; + + public IndexUpdateRunnable(Consumer action, int itemsCount) { + myAction = action; + myTotalItems = itemsCount; + myCurrentBaseTotal = itemsCount; + } + + public void run() { + if (myProject.isDisposed()) { + return; + } + myCurrentUpdateRunnable = this; + + ProgressManager.getInstance().run(new Task.Backgroundable(myProject, "Updating indices", false) { + + private final ArrayBlockingQueue> myActionQueue = new ArrayBlockingQueue>(1); + + @Override + public void run(@NotNull final ProgressIndicator indicator) { + final ProgressIndicator proxy = + (ProgressIndicator)Proxy.newProxyInstance(indicator.getClass().getClassLoader(), new Class[]{ProgressIndicator.class}, new InvocationHandler() { + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("setFraction".equals(method.getName())) { + final double fraction = (Double)args[0]; + args[0] = new Double((myProcessedItems + fraction * myCurrentBaseTotal) / myTotalItems); + } + return method.invoke(indicator, args); + } + }); + runAction(proxy, myAction); + } + + private void runAction(ProgressIndicator indicator, Consumer action) { + indicator.setIndeterminate(false); + do { + indicator.setText("Indexing..."); + try { + action.consume(indicator); + } + finally { + myProcessedItems += myCurrentBaseTotal; + ApplicationManager.getApplication().invokeLater(new DumbAwareRunnable() { + public void run() { + if (myProject.isDisposed()) { + return; + } + if (myUpdateQueue.isEmpty()) { + // really terminate the tesk + myActionQueue.offer(NULL_ACTION); + updateFinished(); + } + else { + //run next dumb action + final IndexUpdateRunnable nextUpdateRunnable = myUpdateQueue.pullFirst(); + myCurrentBaseTotal = nextUpdateRunnable.myTotalItems; + // run next action under already existing progress indicator + if (!myActionQueue.offer(nextUpdateRunnable.myAction)) { + nextUpdateRunnable.run(); + } + } + } + }); + + // try to obtain the next action or terminate if no actions left + try { + do { + action = myActionQueue.poll(500, TimeUnit.MILLISECONDS); + if (myProject.isDisposed()) { + // just terminate the progress task + action = NULL_ACTION; + } + } + while (action == null); + } + catch (InterruptedException ignored) { + LOG.info(ignored); + break; + } + } + } + while (action != NULL_ACTION); + // make it impossible to add actions to the queue anymore + myActionQueue.offer(NULL_ACTION); + } + + }); + } + } }