refactor mapping: made thread-safe, pushed dangerous method down
[idea/community.git] / platform / lang-impl / src / com / intellij / execution / ui / RunContentManagerImpl.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.intellij.execution.ui;
17
18 import com.intellij.execution.*;
19 import com.intellij.execution.process.ProcessAdapter;
20 import com.intellij.execution.process.ProcessEvent;
21 import com.intellij.execution.process.ProcessHandler;
22 import com.intellij.execution.runners.ExecutionEnvironment;
23 import com.intellij.execution.runners.GenericProgramRunner;
24 import com.intellij.execution.ui.layout.impl.DockableGridContainerFactory;
25 import com.intellij.ide.DataManager;
26 import com.intellij.ide.impl.ContentManagerWatcher;
27 import com.intellij.openapi.Disposable;
28 import com.intellij.openapi.actionSystem.DataContext;
29 import com.intellij.openapi.actionSystem.DataProvider;
30 import com.intellij.openapi.actionSystem.PlatformDataKeys;
31 import com.intellij.openapi.application.ApplicationManager;
32 import com.intellij.openapi.diagnostic.Logger;
33 import com.intellij.openapi.progress.ProgressIndicator;
34 import com.intellij.openapi.progress.ProgressManager;
35 import com.intellij.openapi.progress.Task;
36 import com.intellij.openapi.project.Project;
37 import com.intellij.openapi.project.ProjectManager;
38 import com.intellij.openapi.project.ProjectManagerListener;
39 import com.intellij.openapi.ui.DialogWrapper;
40 import com.intellij.openapi.util.Comparing;
41 import com.intellij.openapi.util.Disposer;
42 import com.intellij.openapi.util.IconLoader;
43 import com.intellij.openapi.util.Key;
44 import com.intellij.openapi.wm.ToolWindow;
45 import com.intellij.openapi.wm.ToolWindowAnchor;
46 import com.intellij.openapi.wm.ToolWindowManager;
47 import com.intellij.openapi.wm.ex.ToolWindowManagerAdapter;
48 import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
49 import com.intellij.ui.AppUIUtil;
50 import com.intellij.ui.ColorUtil;
51 import com.intellij.ui.LayeredIcon;
52 import com.intellij.ui.content.*;
53 import com.intellij.ui.docking.DockManager;
54 import com.intellij.util.SmartList;
55 import com.intellij.util.concurrency.Semaphore;
56 import com.intellij.util.containers.ContainerUtil;
57 import com.intellij.util.ui.GraphicsUtil;
58 import com.intellij.util.ui.UIUtil;
59 import gnu.trove.THashMap;
60 import gnu.trove.THashSet;
61 import org.jetbrains.annotations.NotNull;
62 import org.jetbrains.annotations.Nullable;
63
64 import javax.swing.*;
65 import java.awt.*;
66 import java.awt.geom.Ellipse2D;
67 import java.util.*;
68 import java.util.List;
69
70 public class RunContentManagerImpl implements RunContentManager, Disposable {
71   public static final Key<Boolean> ALWAYS_USE_DEFAULT_STOPPING_BEHAVIOUR_KEY = Key.create("ALWAYS_USE_DEFAULT_STOPPING_BEHAVIOUR_KEY");
72   private static final Logger LOG = Logger.getInstance(RunContentManagerImpl.class);
73   private static final Key<RunContentDescriptor> DESCRIPTOR_KEY = Key.create("Descriptor");
74
75   private final Project myProject;
76   private final Map<String, ContentManager> myToolwindowIdToContentManagerMap = new THashMap<String, ContentManager>();
77   private final Map<String, Icon> myToolwindowIdToBaseIconMap = new THashMap<String, Icon>();
78
79   private final Map<RunContentListener, Disposable> myListeners = new THashMap<RunContentListener, Disposable>();
80   private final LinkedList<String> myToolwindowIdZBuffer = new LinkedList<String>();
81
82   public RunContentManagerImpl(@NotNull Project project, @NotNull DockManager dockManager) {
83     myProject = project;
84     DockableGridContainerFactory containerFactory = new DockableGridContainerFactory();
85     dockManager.register(DockableGridContainerFactory.TYPE, containerFactory);
86     Disposer.register(myProject, containerFactory);
87
88     AppUIUtil.invokeOnEdt(new Runnable() {
89       @Override
90       public void run() {
91         init();
92       }
93     }, myProject.getDisposed());
94   }
95
96   // must be called on EDT
97   private void init() {
98     ToolWindowManagerEx toolWindowManager = ToolWindowManagerEx.getInstanceEx(myProject);
99     if (toolWindowManager == null) {
100       return;
101     }
102
103     for (Executor executor : ExecutorRegistry.getInstance().getRegisteredExecutors()) {
104       registerToolwindow(executor, toolWindowManager);
105     }
106
107     toolWindowManager.addToolWindowManagerListener(new ToolWindowManagerAdapter() {
108       @Override
109       public void stateChanged() {
110         if (myProject.isDisposed()) {
111           return;
112         }
113
114         ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
115         Set<String> currentWindows = new THashSet<String>();
116         ContainerUtil.addAll(currentWindows, toolWindowManager.getToolWindowIds());
117         myToolwindowIdZBuffer.retainAll(currentWindows);
118
119         final String activeToolWindowId = toolWindowManager.getActiveToolWindowId();
120         if (activeToolWindowId != null) {
121           if (myToolwindowIdZBuffer.remove(activeToolWindowId)) {
122             myToolwindowIdZBuffer.addFirst(activeToolWindowId);
123           }
124         }
125       }
126     });
127   }
128
129   @Override
130   public void dispose() {
131   }
132
133   private void registerToolwindow(@NotNull final Executor executor, @NotNull ToolWindowManagerEx toolWindowManager) {
134     final String toolWindowId = executor.getToolWindowId();
135     if (toolWindowManager.getToolWindow(toolWindowId) != null) {
136       return;
137     }
138
139     final ToolWindow toolWindow = toolWindowManager.registerToolWindow(toolWindowId, true, ToolWindowAnchor.BOTTOM, this, true);
140     final ContentManager contentManager = toolWindow.getContentManager();
141     contentManager.addDataProvider(new DataProvider() {
142       private int myInsideGetData = 0;
143
144       @Override
145       public Object getData(String dataId) {
146         myInsideGetData++;
147         try {
148           if (PlatformDataKeys.HELP_ID.is(dataId)) {
149             return executor.getHelpId();
150           }
151           else {
152             return myInsideGetData == 1 ? DataManager.getInstance().getDataContext(contentManager.getComponent()).getData(dataId) : null;
153           }
154         }
155         finally {
156           myInsideGetData--;
157         }
158       }
159     });
160
161     toolWindow.setIcon(executor.getToolWindowIcon());
162     myToolwindowIdToBaseIconMap.put(toolWindowId, executor.getToolWindowIcon());
163     new ContentManagerWatcher(toolWindow, contentManager);
164     contentManager.addContentManagerListener(new ContentManagerAdapter() {
165       @Override
166       public void selectionChanged(final ContentManagerEvent event) {
167         Content content = event.getContent();
168         getSyncPublisher().contentSelected(content == null ? null : getRunContentDescriptorByContent(content), executor);
169       }
170     });
171     myToolwindowIdToContentManagerMap.put(toolWindowId, contentManager);
172     Disposer.register(contentManager, new Disposable() {
173       @Override
174       public void dispose() {
175         myToolwindowIdToContentManagerMap.remove(toolWindowId).removeAllContents(true);
176         myToolwindowIdZBuffer.remove(toolWindowId);
177         myToolwindowIdToBaseIconMap.remove(toolWindowId);
178       }
179     });
180     myToolwindowIdZBuffer.addLast(toolWindowId);
181   }
182
183   private RunContentWithExecutorListener getSyncPublisher() {
184     return myProject.getMessageBus().syncPublisher(TOPIC);
185   }
186
187   @Override
188   public void toFrontRunContent(final Executor requestor, final ProcessHandler handler) {
189     final RunContentDescriptor descriptor = getDescriptorBy(handler, requestor);
190     if (descriptor == null) {
191       return;
192     }
193     toFrontRunContent(requestor, descriptor);
194   }
195
196   @Override
197   public void toFrontRunContent(final Executor requestor, final RunContentDescriptor descriptor) {
198     ApplicationManager.getApplication().invokeLater(new Runnable() {
199       @Override
200       public void run() {
201         ContentManager contentManager = getContentManagerForRunner(requestor);
202         Content content = getRunContentByDescriptor(contentManager, descriptor);
203         if (content != null) {
204           contentManager.setSelectedContent(content);
205           ToolWindowManager.getInstance(myProject).getToolWindow(requestor.getToolWindowId()).show(null);
206         }
207       }
208     }, myProject.getDisposed());
209   }
210
211   @Override
212   public void hideRunContent(@NotNull final Executor executor, final RunContentDescriptor descriptor) {
213     ApplicationManager.getApplication().invokeLater(new Runnable() {
214       @Override
215       public void run() {
216         ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(executor.getToolWindowId());
217         if (toolWindow != null) {
218           toolWindow.hide(null);
219         }
220       }
221     }, myProject.getDisposed());
222   }
223
224   @Override
225   @Nullable
226   public RunContentDescriptor getSelectedContent(final Executor executor) {
227     final Content selectedContent = getContentManagerForRunner(executor).getSelectedContent();
228     return selectedContent != null ? getRunContentDescriptorByContent(selectedContent) : null;
229   }
230
231   @Override
232   @Nullable
233   public RunContentDescriptor getSelectedContent() {
234     for (String activeWindow : myToolwindowIdZBuffer) {
235       final ContentManager contentManager = myToolwindowIdToContentManagerMap.get(activeWindow);
236       if (contentManager == null) {
237         continue;
238       }
239
240       final Content selectedContent = contentManager.getSelectedContent();
241       if (selectedContent == null) {
242         if (contentManager.getContentCount() == 0) {
243           // continue to the next window if the content manager is empty
244           continue;
245         }
246         else {
247           // stop iteration over windows because there is some content in the window and the window is the last used one
248           break;
249         }
250       }
251       // here we have selected content
252       return getRunContentDescriptorByContent(selectedContent);
253     }
254
255     return null;
256   }
257
258   @Override
259   public boolean removeRunContent(@NotNull final Executor executor, final RunContentDescriptor descriptor) {
260     final ContentManager contentManager = getContentManagerForRunner(executor);
261     final Content content = getRunContentByDescriptor(contentManager, descriptor);
262     return content != null && contentManager.removeContent(content, true);
263   }
264
265   @Override
266   public void showRunContent(@NotNull Executor executor, @NotNull RunContentDescriptor descriptor) {
267     showRunContent(executor, descriptor, descriptor.getExecutionId());
268   }
269
270   public void showRunContent(@NotNull final Executor executor, @NotNull final RunContentDescriptor descriptor, final long executionId) {
271     if (ApplicationManager.getApplication().isUnitTestMode()) {
272       return;
273     }
274
275     final ContentManager contentManager = getContentManagerForRunner(executor);
276     RunContentDescriptor oldDescriptor = chooseReuseContentForDescriptor(contentManager, descriptor, executionId, descriptor.getDisplayName());
277     final Content content;
278     if (oldDescriptor == null) {
279       content = createNewContent(contentManager, descriptor, executor);
280       Icon icon = descriptor.getIcon();
281       content.setIcon(icon == null ? executor.getToolWindowIcon() : icon);
282     }
283     else {
284       content = oldDescriptor.getAttachedContent();
285       LOG.assertTrue(content != null);
286       getSyncPublisher().contentRemoved(oldDescriptor, executor);
287       Disposer.dispose(oldDescriptor); // is of the same category, can be reused
288     }
289
290     content.setExecutionId(executionId);
291     content.setComponent(descriptor.getComponent());
292     content.setPreferredFocusedComponent(descriptor.getPreferredFocusComputable());
293     content.putUserData(DESCRIPTOR_KEY, descriptor);
294     final ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(executor.getToolWindowId());
295     final ProcessHandler processHandler = descriptor.getProcessHandler();
296     if (processHandler != null) {
297       final ProcessAdapter processAdapter = new ProcessAdapter() {
298         @Override
299         public void startNotified(final ProcessEvent event) {
300           UIUtil.invokeLaterIfNeeded(new Runnable() {
301             @Override
302             public void run() {
303               toolWindow.setIcon(getLiveIndicator(myToolwindowIdToBaseIconMap.get(executor.getToolWindowId())));
304             }
305           });
306         }
307
308         @Override
309         public void processTerminated(final ProcessEvent event) {
310           ApplicationManager.getApplication().invokeLater(new Runnable() {
311             @Override
312             public void run() {
313               final Icon icon = descriptor.getIcon();
314
315               boolean alive = false;
316               String toolWindowId = executor.getToolWindowId();
317               ContentManager manager = myToolwindowIdToContentManagerMap.get(toolWindowId);
318               for (Content content : manager.getContents()) {
319                 RunContentDescriptor descriptor = getRunContentDescriptorByContent(content);
320                 if (descriptor != null) {
321                   ProcessHandler handler = descriptor.getProcessHandler();
322                   if (handler != null && !handler.isProcessTerminated()) {
323                     alive = true;
324                     break;
325                   }
326                 }
327               }
328
329               final boolean finalAlive = alive;
330               UIUtil.invokeLaterIfNeeded(new Runnable() {
331                 @Override
332                 public void run() {
333                   toolWindow.setIcon(finalAlive
334                                      ? getLiveIndicator(myToolwindowIdToBaseIconMap.get(executor.getToolWindowId()))
335                                      : myToolwindowIdToBaseIconMap.get(executor.getToolWindowId()));
336                   content.setIcon(icon == null ? executor.getDisabledIcon() : IconLoader.getTransparentIcon(icon));
337                 }
338               });
339             }
340           });
341         }
342       };
343       processHandler.addProcessListener(processAdapter);
344       final Disposable disposer = content.getDisposer();
345       if (disposer != null) {
346         Disposer.register(disposer, new Disposable() {
347           @Override
348           public void dispose() {
349             processHandler.removeProcessListener(processAdapter);
350           }
351         });
352       }
353     }
354     content.setDisplayName(descriptor.getDisplayName());
355     descriptor.setAttachedContent(content);
356     content.getManager().setSelectedContent(content);
357
358     if (!descriptor.isActivateToolWindowWhenAdded()) {
359       return;
360     }
361
362     ApplicationManager.getApplication().invokeLater(new Runnable() {
363       @Override
364       public void run() {
365         ToolWindow window = ToolWindowManager.getInstance(myProject).getToolWindow(executor.getToolWindowId());
366         // let's activate tool window, but don't move focus
367         //
368         // window.show() isn't valid here, because it will not
369         // mark the window as "last activated" windows and thus
370         // some action like navigation up/down in stacktrace wont
371         // work correctly
372         descriptor.getPreferredFocusComputable();
373         window.activate(descriptor.getActivationCallback(), descriptor.isAutoFocusContent(), descriptor.isAutoFocusContent());
374       }
375     }, myProject.getDisposed());
376   }
377
378   private final static int INDICATOR_SIZE = 4;
379   private static Icon getLiveIndicator(final Icon base) {
380     return new LayeredIcon(base, new Icon() {
381       @Override
382       public void paintIcon(Component c, Graphics g, int x, int y) {
383         Graphics2D g2d = (Graphics2D)g.create();
384         try {
385           GraphicsUtil.setupAAPainting(g2d);
386           g2d.setColor(Color.GREEN);
387           Ellipse2D.Double shape =
388             new Ellipse2D.Double(x + getIconWidth() - INDICATOR_SIZE, y + getIconHeight() - INDICATOR_SIZE, INDICATOR_SIZE, INDICATOR_SIZE);
389           g2d.fill(shape);
390           g2d.setColor(ColorUtil.withAlpha(Color.BLACK, .40));
391           g2d.draw(shape);
392         }
393         finally {
394           g2d.dispose();
395         }
396       }
397
398       @Override
399       public int getIconWidth() {
400         return base != null ? base.getIconWidth() : 13;
401       }
402
403       @Override
404       public int getIconHeight() {
405         return base != null ? base.getIconHeight() : 13;
406       }
407     });
408   }
409
410   @Override
411   @Nullable
412   @Deprecated
413   public RunContentDescriptor getReuseContent(final Executor requestor, DataContext dataContext) {
414     if (ApplicationManager.getApplication().isUnitTestMode()) {
415       return null;
416     }
417     //noinspection deprecation
418     return getReuseContent(requestor, GenericProgramRunner.CONTENT_TO_REUSE_DATA_KEY.getData(dataContext));
419   }
420
421   @Override
422   @Nullable
423   @Deprecated
424   public RunContentDescriptor getReuseContent(Executor requestor, @Nullable RunContentDescriptor contentToReuse) {
425     if (ApplicationManager.getApplication().isUnitTestMode()) {
426       return null;
427     }
428     if (contentToReuse != null) {
429       return contentToReuse;
430     }
431     return chooseReuseContentForDescriptor(getContentManagerForRunner(requestor), null, 0L, null);
432   }
433
434   @Nullable
435   @Override
436   public RunContentDescriptor getReuseContent(Executor requestor, @NotNull ExecutionEnvironment executionEnvironment) {
437     return getReuseContent(executionEnvironment);
438   }
439
440   @Nullable
441   @Override
442   public RunContentDescriptor getReuseContent(@NotNull ExecutionEnvironment executionEnvironment) {
443     if (ApplicationManager.getApplication().isUnitTestMode()) return null;
444     RunContentDescriptor contentToReuse = executionEnvironment.getContentToReuse();
445     if (contentToReuse != null) {
446       return contentToReuse;
447     }
448
449     final ContentManager contentManager = getContentManagerForRunner(executionEnvironment.getExecutor());
450     return chooseReuseContentForDescriptor(contentManager, null, executionEnvironment.getExecutionId(),
451                                            executionEnvironment.toString());
452   }
453
454   @Override
455   public RunContentDescriptor findContentDescriptor(final Executor requestor, final ProcessHandler handler) {
456     return getDescriptorBy(handler, requestor);
457   }
458
459   @Override
460   public void showRunContent(@NotNull Executor info, @NotNull RunContentDescriptor descriptor, @Nullable RunContentDescriptor contentToReuse) {
461     copyContentAndBehavior(descriptor, contentToReuse);
462     showRunContent(info, descriptor, descriptor.getExecutionId());
463   }
464
465   public static void copyContentAndBehavior(@NotNull RunContentDescriptor descriptor, @Nullable RunContentDescriptor contentToReuse) {
466     if (contentToReuse != null) {
467       Content attachedContent = contentToReuse.getAttachedContent();
468       if (attachedContent != null && attachedContent.isValid()) {
469         descriptor.setAttachedContent(attachedContent);
470       }
471       if (contentToReuse.isReuseToolWindowActivation()) {
472         descriptor.setActivateToolWindowWhenAdded(contentToReuse.isActivateToolWindowWhenAdded());
473       }
474     }
475   }
476
477   @Nullable
478   private static RunContentDescriptor chooseReuseContentForDescriptor(@NotNull ContentManager contentManager,
479                                                                       @Nullable RunContentDescriptor descriptor,
480                                                                       long executionId,
481                                                                       @Nullable String preferredName) {
482     Content content = null;
483     if (descriptor != null) {
484       //Stage one: some specific descriptors (like AnalyzeStacktrace) cannot be reused at all
485       if (descriptor.isContentReuseProhibited()) {
486         return null;
487       }
488       //Stage two: try to get content from descriptor itself
489       final Content attachedContent = descriptor.getAttachedContent();
490
491       if (attachedContent != null
492           && attachedContent.isValid()
493           && contentManager.getIndexOfContent(attachedContent) != -1
494           && (Comparing.equal(descriptor.getDisplayName(), attachedContent.getDisplayName()) || !attachedContent.isPinned())) {
495         content = attachedContent;
496       }
497     }
498     //Stage three: choose the content with name we prefer
499     if (content == null) {
500       content = getContentFromManager(contentManager, preferredName, executionId);
501     }
502     if (content == null || !isTerminated(content) || (content.getExecutionId() == executionId && executionId != 0)) {
503       return null;
504     }
505     final RunContentDescriptor oldDescriptor = getRunContentDescriptorByContent(content);
506     if (oldDescriptor != null && !oldDescriptor.isContentReuseProhibited() ) {
507       //content.setExecutionId(executionId);
508       return oldDescriptor;
509     }
510
511     return null;
512   }
513
514   @Nullable
515   private static Content getContentFromManager(ContentManager contentManager, @Nullable String preferredName, long executionId) {
516     ArrayList<Content> contents = new ArrayList<Content>(Arrays.asList(contentManager.getContents()));
517     Content first = contentManager.getSelectedContent();
518     if (first != null && contents.remove(first)) {//selected content should be checked first
519       contents.add(0, first);
520     }
521     if (preferredName != null) {//try to match content with specified preferred name
522       for (Content c : contents) {
523         if (canReuseContent(c, executionId) && preferredName.equals(c.getDisplayName())) {
524           return c;
525         }
526       }
527     }
528     for (Content c : contents) {//return first "good" content
529       if (canReuseContent(c, executionId)) {
530         return c;
531       }
532     }
533     return null;
534   }
535
536   private static boolean canReuseContent(Content c, long executionId) {
537     return c != null && !c.isPinned() && isTerminated(c) && !(c.getExecutionId() == executionId && executionId != 0);
538   }
539
540   @NotNull
541   private ContentManager getContentManagerForRunner(final Executor executor) {
542     final ContentManager contentManager = myToolwindowIdToContentManagerMap.get(executor.getToolWindowId());
543     if (contentManager == null) {
544       LOG.error("Runner " + executor.getId() + " is not registered");
545     }
546     //noinspection ConstantConditions
547     return contentManager;
548   }
549
550   private Content createNewContent(final ContentManager contentManager, final RunContentDescriptor descriptor, Executor executor) {
551     final String processDisplayName = descriptor.getDisplayName();
552     final Content content = ContentFactory.SERVICE.getInstance().createContent(descriptor.getComponent(), processDisplayName, true);
553     content.putUserData(DESCRIPTOR_KEY, descriptor);
554     content.putUserData(ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE);
555     contentManager.addContent(content);
556     new CloseListener(content, executor);
557     return content;
558   }
559
560   private static boolean isTerminated(@NotNull Content content) {
561     RunContentDescriptor descriptor = getRunContentDescriptorByContent(content);
562     ProcessHandler processHandler = descriptor == null ? null : descriptor.getProcessHandler();
563     return processHandler == null || processHandler.isProcessTerminated();
564   }
565
566   @Nullable
567   private static RunContentDescriptor getRunContentDescriptorByContent(@NotNull Content content) {
568     return content.getUserData(DESCRIPTOR_KEY);
569   }
570
571   @Override
572   @Nullable
573   public ToolWindow getToolWindowByDescriptor(@NotNull RunContentDescriptor descriptor) {
574     for (Map.Entry<String, ContentManager> entry : myToolwindowIdToContentManagerMap.entrySet()) {
575       if (getRunContentByDescriptor(entry.getValue(), descriptor) != null) {
576         return ToolWindowManager.getInstance(myProject).getToolWindow(entry.getKey());
577       }
578     }
579     return null;
580   }
581
582   @Nullable
583   private static Content getRunContentByDescriptor(@NotNull ContentManager contentManager, @NotNull RunContentDescriptor descriptor) {
584     for (Content content : contentManager.getContents()) {
585       if (descriptor.equals(content.getUserData(DESCRIPTOR_KEY))) {
586         return content;
587       }
588     }
589     return null;
590   }
591
592   @Override
593   public void addRunContentListener(@NotNull final RunContentListener listener, final Executor executor) {
594     final Disposable disposable = Disposer.newDisposable();
595     myProject.getMessageBus().connect(disposable).subscribe(TOPIC, new RunContentWithExecutorListener() {
596       @Override
597       public void contentSelected(@Nullable RunContentDescriptor descriptor, @NotNull Executor executor2) {
598         if (executor2.equals(executor)) {
599           listener.contentSelected(descriptor);
600         }
601       }
602
603       @Override
604       public void contentRemoved(@Nullable RunContentDescriptor descriptor, @NotNull Executor executor2) {
605         if (executor2.equals(executor)) {
606           listener.contentRemoved(descriptor);
607         }
608       }
609     });
610     myListeners.put(listener, disposable);
611   }
612
613   @Override
614   public void addRunContentListener(@NotNull final RunContentListener listener) {
615     final Disposable disposable = Disposer.newDisposable();
616     myProject.getMessageBus().connect(disposable).subscribe(TOPIC, new RunContentWithExecutorListener() {
617       @Override
618       public void contentSelected(@Nullable RunContentDescriptor descriptor, @NotNull Executor executor) {
619         listener.contentSelected(descriptor);
620       }
621
622       @Override
623       public void contentRemoved(@Nullable RunContentDescriptor descriptor, @NotNull Executor executor) {
624         listener.contentRemoved(descriptor);
625       }
626     });
627     myListeners.put(listener, disposable);
628   }
629
630   @Override
631   @NotNull
632   public List<RunContentDescriptor> getAllDescriptors() {
633     if (myToolwindowIdToContentManagerMap.isEmpty()) {
634       return Collections.emptyList();
635     }
636
637     List<RunContentDescriptor> descriptors = new SmartList<RunContentDescriptor>();
638     for (String id : myToolwindowIdToContentManagerMap.keySet()) {
639       for (Content content : myToolwindowIdToContentManagerMap.get(id).getContents()) {
640         RunContentDescriptor descriptor = getRunContentDescriptorByContent(content);
641         if (descriptor != null) {
642           descriptors.add(descriptor);
643         }
644       }
645     }
646     return descriptors;
647   }
648
649   @Override
650   public void removeRunContentListener(final RunContentListener listener) {
651     Disposable disposable = myListeners.remove(listener);
652     if (disposable != null) {
653       Disposer.dispose(disposable);
654     }
655   }
656
657   @Nullable
658   private RunContentDescriptor getDescriptorBy(ProcessHandler handler, Executor runnerInfo) {
659     for (Content content : getContentManagerForRunner(runnerInfo).getContents()) {
660       RunContentDescriptor runContentDescriptor = getRunContentDescriptorByContent(content);
661       assert runContentDescriptor != null;
662       if (runContentDescriptor.getProcessHandler() == handler) {
663         return runContentDescriptor;
664       }
665     }
666     return null;
667   }
668
669   private class CloseListener extends ContentManagerAdapter implements ProjectManagerListener {
670     private Content myContent;
671     private final Executor myExecutor;
672
673     private CloseListener(@NotNull final Content content, @NotNull Executor executor) {
674       myContent = content;
675       content.getManager().addContentManagerListener(this);
676       ProjectManager.getInstance().addProjectManagerListener(this);
677       myExecutor = executor;
678     }
679
680     @Override
681     public void contentRemoved(final ContentManagerEvent event) {
682       final Content content = event.getContent();
683       if (content == myContent) {
684         dispose();
685       }
686     }
687
688     private void dispose() {
689       if (myContent == null) return;
690
691       final Content content = myContent;
692       try {
693         RunContentDescriptor descriptor = getRunContentDescriptorByContent(content);
694         getSyncPublisher().contentRemoved(descriptor, myExecutor);
695         if (descriptor != null) {
696           Disposer.dispose(descriptor);
697         }
698       }
699       finally {
700         content.getManager().removeContentManagerListener(this);
701         ProjectManager.getInstance().removeProjectManagerListener(this);
702         content.release(); // don't invoke myContent.release() because myContent becomes null after destroyProcess()
703         myContent = null;
704       }
705     }
706
707     @Override
708     public void contentRemoveQuery(final ContentManagerEvent event) {
709       if (event.getContent() == myContent) {
710         final boolean canClose = closeQuery(false);
711         if (!canClose) {
712           event.consume();
713         }
714       }
715     }
716
717     @Override
718     public void projectOpened(final Project project) {
719     }
720
721     @Override
722     public void projectClosed(final Project project) {
723       if (myContent != null && project == myProject) {
724         myContent.getManager().removeContent(myContent, true);
725         dispose(); // Dispose content even if content manager refused to.
726       }
727     }
728
729     @Override
730     public boolean canCloseProject(final Project project) {
731       if (project != myProject) return true;
732
733       if (myContent == null) return true;
734
735       final boolean canClose = closeQuery(true);
736       if (canClose) {
737         myContent.getManager().removeContent(myContent, true);
738         myContent = null;
739       }
740       return canClose;
741     }
742
743     @Override
744     public void projectClosing(final Project project) {
745     }
746
747     private boolean closeQuery(boolean modal) {
748       final RunContentDescriptor descriptor = getRunContentDescriptorByContent(myContent);
749       if (descriptor == null) {
750         return true;
751       }
752
753       final ProcessHandler processHandler = descriptor.getProcessHandler();
754       if (processHandler == null || processHandler.isProcessTerminated() || processHandler.isProcessTerminating()) {
755         return true;
756       }
757       final boolean destroyProcess;
758       //noinspection deprecation
759       if (processHandler.isSilentlyDestroyOnClose() || Boolean.TRUE.equals(processHandler.getUserData(ProcessHandler.SILENTLY_DESTROY_ON_CLOSE))) {
760         destroyProcess = true;
761       }
762       else {
763         //todo[nik] this is a temporary solution for the following problem: some configurations should not allow user to choose between 'terminating' and 'detaching'
764         final boolean useDefault = Boolean.TRUE.equals(processHandler.getUserData(ALWAYS_USE_DEFAULT_STOPPING_BEHAVIOUR_KEY));
765         final TerminateRemoteProcessDialog.TerminateOption option = new TerminateRemoteProcessDialog.TerminateOption(processHandler.detachIsDefault(), useDefault);
766         final int rc = TerminateRemoteProcessDialog.show(myProject, descriptor.getDisplayName(), option);
767         if (rc != DialogWrapper.OK_EXIT_CODE) return false;
768         destroyProcess = !option.isToBeShown();
769       }
770       if (destroyProcess) {
771         processHandler.destroyProcess();
772       }
773       else {
774         processHandler.detachProcess();
775       }
776       waitForProcess(descriptor, modal);
777       return true;
778     }
779   }
780
781   private void waitForProcess(final RunContentDescriptor descriptor, final boolean modal) {
782     final ProcessHandler processHandler = descriptor.getProcessHandler();
783     final boolean killable = !modal && (processHandler instanceof KillableProcess) && ((KillableProcess)processHandler).canKillProcess();
784
785     String title = ExecutionBundle.message("terminating.process.progress.title", descriptor.getDisplayName());
786     ProgressManager.getInstance().run(new Task.Backgroundable(myProject, title, true) {
787
788       {
789         if (killable) {
790           String cancelText= ExecutionBundle.message("terminating.process.progress.kill");
791           setCancelText(cancelText);
792           setCancelTooltipText(cancelText);
793         }
794       }
795
796       @Override
797       public boolean isConditionalModal() {
798         return modal;
799       }
800
801       @Override
802       public boolean shouldStartInBackground() {
803         return !modal;
804       }
805
806       @Override
807       public void run(@NotNull final ProgressIndicator progressIndicator) {
808         final Semaphore semaphore = new Semaphore();
809         semaphore.down();
810
811         ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
812           @Override
813           public void run() {
814             final ProcessHandler processHandler = descriptor.getProcessHandler();
815             try {
816               if (processHandler != null) {
817                 processHandler.waitFor();
818               }
819             }
820             finally {
821               semaphore.up();
822             }
823           }
824         });
825
826         progressIndicator.setText(ExecutionBundle.message("waiting.for.vm.detach.progress.text"));
827         ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
828           @Override
829           public void run() {
830             while (true) {
831               if (progressIndicator.isCanceled() || !progressIndicator.isRunning()) {
832                 semaphore.up();
833                 break;
834               }
835               try {
836                 //noinspection SynchronizeOnThis
837                 synchronized (this) {
838                   //noinspection SynchronizeOnThis
839                   wait(2000L);
840                 }
841               }
842               catch (InterruptedException ignore) {
843               }
844             }
845           }
846         });
847
848         semaphore.waitFor();
849       }
850
851       @Override
852       public void onCancel() {
853         if (killable && !processHandler.isProcessTerminated()) {
854           ((KillableProcess)processHandler).killProcess();
855         }
856       }
857     });
858   }
859 }