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