cleanup
[idea/community.git] / platform / core-api / src / com / intellij / openapi / project / DumbService.java
1 // Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
2 package com.intellij.openapi.project;
3
4 import com.intellij.openapi.Disposable;
5 import com.intellij.openapi.application.ApplicationManager;
6 import com.intellij.openapi.application.ModalityState;
7 import com.intellij.openapi.application.ReadAction;
8 import com.intellij.openapi.components.ServiceManager;
9 import com.intellij.openapi.extensions.ExtensionPointName;
10 import com.intellij.openapi.extensions.ProjectExtensionPointName;
11 import com.intellij.openapi.progress.ProcessCanceledException;
12 import com.intellij.openapi.progress.ProgressManager;
13 import com.intellij.openapi.util.*;
14 import com.intellij.openapi.util.NlsContexts.PopupContent;
15 import com.intellij.util.ThrowableRunnable;
16 import com.intellij.util.messages.Topic;
17 import org.jetbrains.annotations.*;
18
19 import javax.swing.*;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Collection;
23 import java.util.List;
24
25 /**
26  * A service managing the IDE's 'dumb' mode: when indexes are updated in the background, and the functionality is very much limited.
27  * Only the explicitly allowed functionality is available. Usually, it's allowed by implementing {@link DumbAware} interface.<p></p>
28  * <p>
29  * "Dumb" mode starts and ends in a {@link com.intellij.openapi.application.WriteAction}, so if you're inside a {@link ReadAction}
30  * on a background thread, it won't suddenly begin in the middle of your operation. But note that whenever you start
31  * a top-level read action on a background thread, you should be prepared to anything being changed, including "dumb"
32  * mode being suddenly on and off. To avoid executing a read action in "dumb" mode, please use {@link #runReadActionInSmartMode} or
33  * {@link com.intellij.openapi.application.NonBlockingReadAction#inSmartMode}.
34  * <p>
35  * More information about dumb mode could be found here: {@link IndexNotReadyException}
36  *
37  * @author peter
38  */
39 public abstract class DumbService {
40   /**
41    * @see Project#getMessageBus()
42    */
43   public static final Topic<DumbModeListener> DUMB_MODE = new Topic<>("dumb mode", DumbModeListener.class);
44
45   /**
46    * The tracker is advanced each time we enter/exit from dumb mode.
47    */
48   public abstract ModificationTracker getModificationTracker();
49
50   /**
51    * To avoid race conditions use it only in EDT thread or inside read-action. See documentation for this class {@link DumbService}
52    *
53    * @return whether the IDE is in dumb mode, which means that right now indexes are updated in the background.
54    * The IDE offers only limited functionality at such times, e.g., plain text file editing and version control operations.
55    */
56   public abstract boolean isDumb();
57
58   public static boolean isDumb(@NotNull Project project) {
59     return getInstance(project).isDumb();
60   }
61
62   public static @NotNull <T> List<T> getDumbAwareExtensions(@NotNull Project project, @NotNull ExtensionPointName<T> extensionPoint) {
63     List<T> list = extensionPoint.getExtensionList();
64     if (list.isEmpty()) {
65       return list;
66     }
67
68     DumbService dumbService = getInstance(project);
69     return dumbService.filterByDumbAwareness(list);
70   }
71
72   public static @NotNull <T> List<T> getDumbAwareExtensions(@NotNull Project project, @NotNull ProjectExtensionPointName<T> extensionPoint) {
73     DumbService dumbService = getInstance(project);
74     return dumbService.filterByDumbAwareness(extensionPoint.getExtensions(project));
75   }
76
77   /**
78    * Executes the runnable as soon as possible on AWT Event Dispatch when:
79    * <ul>
80    * <li>project is initialized</li>
81    * <li>and there's no dumb mode in progress</li>
82    * </ul>
83    * This may also happen immediately if these conditions are already met.<p/>
84    * Note that it's not guaranteed that the dumb mode won't start again during this runnable execution, it should manage that situation explicitly.
85    */
86   public abstract void runWhenSmart(@NotNull Runnable runnable);
87
88   /**
89    * Pause the current thread until dumb mode ends and then continue execution.
90    * NOTE: there are no guarantees that a new dumb mode won't begin before the next statement.
91    * Hence: use with care. Consider using {@link #runWhenSmart(Runnable)} or {@link #runReadActionInSmartMode(Runnable)} instead
92    */
93   public abstract void waitForSmartMode();
94
95   /**
96    * Pause the current thread until dumb mode ends, and then run the read action. Indexes are guaranteed to be available inside that read action,
97    * unless this method is already called with read access allowed.
98    *
99    * @throws ProcessCanceledException if the project is closed during dumb mode
100    */
101   public <T> T runReadActionInSmartMode(final @NotNull Computable<T> r) {
102     final Ref<T> result = new Ref<>();
103     runReadActionInSmartMode(() -> result.set(r.compute()));
104     return result.get();
105   }
106
107   public @Nullable <T> T tryRunReadActionInSmartMode(@NotNull Computable<T> task, @Nullable String notification) {
108     if (ApplicationManager.getApplication().isReadAccessAllowed()) {
109       try {
110         return task.compute();
111       }
112       catch (IndexNotReadyException e) {
113         if (notification != null) {
114           showDumbModeNotification(notification);
115         }
116         return null;
117       }
118     }
119     else {
120       return runReadActionInSmartMode(task);
121     }
122   }
123
124   /**
125    * Pause the current thread until dumb mode ends, and then run the read action. Indexes are guaranteed to be available inside that read action,
126    * unless this method is already called with read access allowed.
127    *
128    * @throws ProcessCanceledException if the project is closed during dumb mode
129    */
130   public void runReadActionInSmartMode(@NotNull Runnable r) {
131     if (ApplicationManager.getApplication().isReadAccessAllowed()) {
132       // we can't wait for smart mode to begin (it'd result in a deadlock),
133       // so let's just pretend it's already smart and fail with IndexNotReadyException if not
134       r.run();
135       return;
136     }
137
138     while (true) {
139       waitForSmartMode();
140       boolean success = ReadAction.compute(() -> {
141         if (getProject().isDisposed()) {
142           throw new ProcessCanceledException();
143         }
144         if (isDumb()) {
145           return false;
146         }
147         r.run();
148         return true;
149       });
150       if (success) break;
151     }
152   }
153
154   /**
155    * Pause the current thread until dumb mode ends, and then attempt to execute the runnable. If it fails due to another dumb mode having started,
156    * try again until the runnable can complete successfully.
157    *
158    * @deprecated This method provides no guarantees and should be avoided, please use {@link #runReadActionInSmartMode} instead.
159    */
160   @Deprecated
161   public void repeatUntilPassesInSmartMode(final @NotNull Runnable r) {
162     while (true) {
163       waitForSmartMode();
164       try {
165         r.run();
166         return;
167       }
168       catch (IndexNotReadyException ignored) {
169       }
170     }
171   }
172
173   /**
174    * Invoke the runnable later on EventDispatchThread AND when IDE isn't in dumb mode.
175    * The runnable won't be invoked if the project is disposed during dumb mode.
176    */
177   public abstract void smartInvokeLater(@NotNull Runnable runnable);
178
179   /**
180    * Invoke the runnable later on EventDispatchThread with the given modality state AND when IDE isn't in dumb mode.
181    * The runnable won't be invoked if the project is disposed during dumb mode.
182    */
183   public abstract void smartInvokeLater(@NotNull Runnable runnable, @NotNull ModalityState modalityState);
184
185   private static final NotNullLazyKey<DumbService, Project> INSTANCE_KEY = ServiceManager.createLazyKey(DumbService.class);
186
187   public static DumbService getInstance(@NotNull Project project) {
188     return INSTANCE_KEY.getValue(project);
189   }
190
191   /**
192    * @return all the elements of the given array if there's no dumb mode currently, or the dumb-aware ones if {@link #isDumb()} is true.
193    * @see #isDumbAware(Object)
194    */
195   public @NotNull <T> List<T> filterByDumbAwareness(T @NotNull [] array) {
196     return filterByDumbAwareness(Arrays.asList(array));
197   }
198
199   /**
200    * @return all the elements of the given collection if there's no dumb mode currently, or the dumb-aware ones if {@link #isDumb()} is true.
201    * @see #isDumbAware(Object)
202    */
203   @Contract(pure = true)
204   public @NotNull <T> List<T> filterByDumbAwareness(@NotNull Collection<? extends T> collection) {
205     if (isDumb()) {
206       final ArrayList<T> result = new ArrayList<>(collection.size());
207       for (T element : collection) {
208         if (isDumbAware(element)) {
209           result.add(element);
210         }
211       }
212       return result;
213     }
214
215     if (collection instanceof List) {
216       return (List<T>)collection;
217     }
218
219     return new ArrayList<>(collection);
220   }
221
222   /**
223    * Queues a task to be executed in "dumb mode", where access to indexes is forbidden. Tasks are executed sequentially
224    * in background unless {@link #completeJustSubmittedTasks()} is called in the same dispatch thread activity.<p/>
225    * <p>
226    * Tasks can specify custom "equality" policy via their constructor. Calling this method has no effect if an "equal" task is already enqueued (but not yet running).
227    */
228   public abstract void queueTask(@NotNull DumbModeTask task);
229
230   /**
231    * Cancels the given task. If it's in the queue, it won't be executed. If it's already running, its {@link com.intellij.openapi.progress.ProgressIndicator} is canceled, so the next {@link ProgressManager#checkCanceled()} call
232    * will throw {@link ProcessCanceledException}.
233    */
234   public abstract void cancelTask(@NotNull DumbModeTask task);
235
236   /**
237    * Cancels all tasks and wait when their execution is finished. Should be called on write thread.
238    */
239   @ApiStatus.Internal
240   public abstract void cancelAllTasksAndWait();
241
242   /**
243    * Runs the "just submitted" tasks under a modal dialog. "Just submitted" means that tasks were queued for execution
244    * earlier within the same Swing event dispatch thread event processing, and there were no other tasks already running at that moment. Otherwise, this method does nothing.<p/>
245    * <p>
246    * This functionality can be useful in refactorings (invoked in "smart mode"), when after VFS or root changes
247    * (which could start "dumb mode") some reference resolve is required (which again requires "smart mode").<p/>
248    * <p>
249    * Should be invoked on dispatch thread.
250    * It's the caller's responsibility to invoke this method only when the model is in internally consistent state,
251    * so that background threads with read actions don't see half-baked PSI/VFS/etc.
252    */
253   public abstract void completeJustSubmittedTasks();
254
255   /**
256    * Replaces given component temporarily with "Not available until indices are built" label during dumb mode.
257    *
258    * @return Wrapped component.
259    */
260   public abstract JComponent wrapGently(@NotNull JComponent dumbUnawareContent, @NotNull Disposable parentDisposable);
261
262   /**
263    * Adds a "Results might be incomplete while indexing." decorator to a given component during dumb mode.
264    *
265    * @param dumbAwareContent - a component to wrap
266    * @param updateRunnable - an action to execute when dumb mode state changed or user explicitly request reload panel
267    *
268    * @return Wrapped component.
269    */
270   public abstract JComponent wrapWithSpoiler(@NotNull JComponent dumbAwareContent, @NotNull Runnable updateRunnable, @NotNull Disposable parentDisposable);
271
272   /**
273    * Disables given component temporarily during dumb mode.
274    */
275   public void makeDumbAware(final @NotNull JComponent componentToDisable, @NotNull Disposable parentDisposable) {
276     componentToDisable.setEnabled(!isDumb());
277     getProject().getMessageBus().connect(parentDisposable).subscribe(DUMB_MODE, new DumbModeListener() {
278       @Override
279       public void enteredDumbMode() {
280         componentToDisable.setEnabled(false);
281       }
282
283       @Override
284       public void exitDumbMode() {
285         componentToDisable.setEnabled(true);
286       }
287     });
288   }
289
290   /**
291    * Show a notification when given action is not available during dumb mode.
292    */
293   public abstract void showDumbModeNotification(@NotNull @PopupContent String message);
294
295   /**
296    * Shows balloon about indexing blocking those actions until it is hidden (by key input, mouse event, etc.) or indexing stops.
297    * @param balloonText
298    * @param runWhenSmartAndBalloonStillShowing — will be executed in smart mode on EDT, balloon won't be dismissed by user's actions
299    */
300   public abstract void showDumbModeActionBalloon(@NotNull @PopupContent String balloonText,
301                                                  @NotNull Runnable runWhenSmartAndBalloonStillShowing);
302
303   public abstract Project getProject();
304
305   @Contract(value = "null -> false", pure = true)
306   public static boolean isDumbAware(Object o) {
307     if (o instanceof PossiblyDumbAware) {
308       return ((PossiblyDumbAware)o).isDumbAware();
309     }
310     //noinspection SSBasedInspection
311     return o instanceof DumbAware;
312   }
313
314   /**
315    * Enables or disables alternative resolve strategies for the current thread.<p/>
316    * <p>
317    * Normally reference resolution uses indexes, and hence is not available in dumb mode. In some cases, alternative ways
318    * of performing resolve are available, although much slower. It's impractical to always use these ways because it'll
319    * lead to overloaded CPU (especially given there's also indexing in progress). But for some explicit user actions
320    * (e.g., explicit Goto Declaration) turning on these slower methods is beneficial.<p/>
321    * <p>
322    * NOTE: even with alternative resolution enabled, methods like resolve(), findClass() etc may still throw
323    * {@link IndexNotReadyException}. So alternative resolve is not a panacea, it might help provide navigation in some cases
324    * but not in all.<p/>
325    * <p>
326    * A typical usage would involve {@code try-finally}, where the alternative resolution is first enabled, then an action is performed,
327    * and then alternative resolution is turned off in the {@code finally} block.
328    * @deprecated Use {@link #runWithAlternativeResolveEnabled(ThrowableRunnable)} or {@link #computeWithAlternativeResolveEnabled(ThrowableComputable)} or {@link #withAlternativeResolveEnabled(Runnable)} instead
329    */
330   @Deprecated
331   public abstract void setAlternativeResolveEnabled(boolean enabled);
332
333   /**
334    * Invokes the given runnable with alternative resolve set to true.
335    *
336    * @see #setAlternativeResolveEnabled(boolean)
337    */
338   public void withAlternativeResolveEnabled(@NotNull Runnable runnable) {
339     setAlternativeResolveEnabled(true);
340     try {
341       runnable.run();
342     }
343     finally {
344       setAlternativeResolveEnabled(false);
345     }
346   }
347
348   /**
349    * Invokes the given computable with alternative resolve set to true.
350    *
351    * @see #setAlternativeResolveEnabled(boolean)
352    */
353   public <T, E extends Throwable> T computeWithAlternativeResolveEnabled(@NotNull ThrowableComputable<T, E> runnable) throws E {
354     setAlternativeResolveEnabled(true);
355     try {
356       return runnable.compute();
357     }
358     finally {
359       setAlternativeResolveEnabled(false);
360     }
361   }
362
363   /**
364    * Invokes the given runnable with alternative resolve set to true.
365    *
366    * @see #setAlternativeResolveEnabled(boolean)
367    */
368   public <E extends Throwable> void runWithAlternativeResolveEnabled(@NotNull ThrowableRunnable<E> runnable) throws E {
369     setAlternativeResolveEnabled(true);
370     try {
371       runnable.run();
372     }
373     finally {
374       setAlternativeResolveEnabled(false);
375     }
376   }
377
378   /**
379    * @return whether alternative resolution is enabled for the current thread.
380    * @see #setAlternativeResolveEnabled(boolean)
381    */
382   public abstract boolean isAlternativeResolveEnabled();
383
384   /**
385    * @see #completeJustSubmittedTasks()
386    * @deprecated Obsolete, does nothing, just executes the passed runnable.
387    */
388   @Deprecated
389   public static void allowStartingDumbModeInside(@NotNull DumbModePermission permission, @NotNull Runnable runnable) {
390     runnable.run();
391   }
392
393   /**
394    * Runs a heavy activity and suspends indexing (if any) for this time. The user still can manually pause and resume the indexing. In that case, indexing won't be resumed automatically after the activity finishes.
395    *
396    * @param activityName the text (a noun phrase) to display as a reason for the indexing being paused
397    */
398   public abstract void suspendIndexingAndRun(@NotNull @Nls(capitalization = Nls.Capitalization.Sentence) String activityName,
399                                              @NotNull Runnable activity);
400
401   /**
402    * Checks whether {@link #isDumb()} is true for the current project and if it's currently suspended by user or a {@link #suspendIndexingAndRun} call.
403    * This should be called inside read action. The momentary system state is returned: there are no guarantees that the result won't change
404    * in the next line of the calling code.
405    */
406   public abstract boolean isSuspendedDumbMode();
407
408   /**
409    * @see #DUMB_MODE
410    */
411   public interface DumbModeListener {
412     /**
413      * The event arrives on EDT.
414      */
415     default void enteredDumbMode() {}
416
417     /**
418      * The event arrives on EDT.
419      */
420     default void exitDumbMode() {}
421   }
422
423   @ApiStatus.Internal
424   public abstract void unsafeRunWhenSmart(@NotNull Runnable runnable);
425 }