Merge remote-tracking branch 'origin/master' into develar/is
[idea/community.git] / platform / lang-impl / src / com / intellij / execution / ui / RunContentManagerImpl.java
1 /*
2  * Copyright 2000-2016 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.ExecutionUtil;
24 import com.intellij.execution.ui.layout.impl.DockableGridContainerFactory;
25 import com.intellij.ide.DataManager;
26 import com.intellij.ide.GeneralSettings;
27 import com.intellij.ide.impl.ContentManagerWatcher;
28 import com.intellij.openapi.Disposable;
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.util.Comparing;
40 import com.intellij.openapi.util.Disposer;
41 import com.intellij.openapi.util.IconLoader;
42 import com.intellij.openapi.util.Key;
43 import com.intellij.openapi.wm.ToolWindow;
44 import com.intellij.openapi.wm.ToolWindowAnchor;
45 import com.intellij.openapi.wm.ToolWindowManager;
46 import com.intellij.openapi.wm.ex.ToolWindowManagerAdapter;
47 import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
48 import com.intellij.ui.AppUIUtil;
49 import com.intellij.ui.content.*;
50 import com.intellij.ui.docking.DockManager;
51 import com.intellij.util.SmartList;
52 import com.intellij.util.concurrency.Semaphore;
53 import com.intellij.util.containers.ContainerUtil;
54 import com.intellij.util.ui.UIUtil;
55 import gnu.trove.THashMap;
56 import gnu.trove.THashSet;
57 import org.jetbrains.annotations.NotNull;
58 import org.jetbrains.annotations.Nullable;
59
60 import javax.swing.*;
61 import java.util.*;
62
63 public class RunContentManagerImpl implements RunContentManager, Disposable {
64   public static final Key<Boolean> ALWAYS_USE_DEFAULT_STOPPING_BEHAVIOUR_KEY = Key.create("ALWAYS_USE_DEFAULT_STOPPING_BEHAVIOUR_KEY");
65   private static final Logger LOG = Logger.getInstance(RunContentManagerImpl.class);
66   private static final Key<RunContentDescriptor> DESCRIPTOR_KEY = Key.create("Descriptor");
67
68   private final Project myProject;
69   private final Map<String, ContentManager> myToolwindowIdToContentManagerMap = new THashMap<String, ContentManager>();
70   private final Map<String, Icon> myToolwindowIdToBaseIconMap = new THashMap<String, Icon>();
71   private final LinkedList<String> myToolwindowIdZBuffer = new LinkedList<String>();
72
73   public RunContentManagerImpl(@NotNull Project project, @NotNull DockManager dockManager) {
74     myProject = project;
75     DockableGridContainerFactory containerFactory = new DockableGridContainerFactory();
76     dockManager.register(DockableGridContainerFactory.TYPE, containerFactory);
77     Disposer.register(myProject, containerFactory);
78
79     AppUIUtil.invokeOnEdt(() -> init(), myProject.getDisposed());
80   }
81
82   // must be called on EDT
83   private void init() {
84     ToolWindowManagerEx toolWindowManager = ToolWindowManagerEx.getInstanceEx(myProject);
85     if (toolWindowManager == null) {
86       return;
87     }
88
89     for (Executor executor : ExecutorRegistry.getInstance().getRegisteredExecutors()) {
90       registerToolwindow(executor, toolWindowManager);
91     }
92
93     toolWindowManager.addToolWindowManagerListener(new ToolWindowManagerAdapter() {
94       @Override
95       public void stateChanged() {
96         if (myProject.isDisposed()) {
97           return;
98         }
99
100         ToolWindowManager toolWindowManager = ToolWindowManager.getInstance(myProject);
101         Set<String> currentWindows = new THashSet<String>();
102         ContainerUtil.addAll(currentWindows, toolWindowManager.getToolWindowIds());
103         myToolwindowIdZBuffer.retainAll(currentWindows);
104
105         final String activeToolWindowId = toolWindowManager.getActiveToolWindowId();
106         if (activeToolWindowId != null) {
107           if (myToolwindowIdZBuffer.remove(activeToolWindowId)) {
108             myToolwindowIdZBuffer.addFirst(activeToolWindowId);
109           }
110         }
111       }
112     });
113   }
114
115   @Override
116   public void dispose() {
117   }
118
119   private void registerToolwindow(@NotNull final Executor executor, @NotNull ToolWindowManagerEx toolWindowManager) {
120     final String toolWindowId = executor.getToolWindowId();
121     if (toolWindowManager.getToolWindow(toolWindowId) != null) {
122       return;
123     }
124
125     final ToolWindow toolWindow = toolWindowManager.registerToolWindow(toolWindowId, true, ToolWindowAnchor.BOTTOM, this, true);
126     final ContentManager contentManager = toolWindow.getContentManager();
127     contentManager.addDataProvider(new DataProvider() {
128       private int myInsideGetData = 0;
129
130       @Override
131       public Object getData(String dataId) {
132         myInsideGetData++;
133         try {
134           if (PlatformDataKeys.HELP_ID.is(dataId)) {
135             return executor.getHelpId();
136           }
137           else {
138             return myInsideGetData == 1 ? DataManager.getInstance().getDataContext(contentManager.getComponent()).getData(dataId) : null;
139           }
140         }
141         finally {
142           myInsideGetData--;
143         }
144       }
145     });
146
147     toolWindow.setIcon(executor.getToolWindowIcon());
148     myToolwindowIdToBaseIconMap.put(toolWindowId, executor.getToolWindowIcon());
149     new ContentManagerWatcher(toolWindow, contentManager);
150     contentManager.addContentManagerListener(new ContentManagerAdapter() {
151       @Override
152       public void selectionChanged(final ContentManagerEvent event) {
153         if (event.getOperation() == ContentManagerEvent.ContentOperation.add) {
154           Content content = event.getContent();
155           getSyncPublisher().contentSelected(content == null ? null : getRunContentDescriptorByContent(content), executor);
156         }
157       }
158     });
159     myToolwindowIdToContentManagerMap.put(toolWindowId, contentManager);
160     Disposer.register(contentManager, new Disposable() {
161       @Override
162       public void dispose() {
163         myToolwindowIdToContentManagerMap.remove(toolWindowId).removeAllContents(true);
164         myToolwindowIdZBuffer.remove(toolWindowId);
165         myToolwindowIdToBaseIconMap.remove(toolWindowId);
166       }
167     });
168     myToolwindowIdZBuffer.addLast(toolWindowId);
169   }
170
171   private RunContentWithExecutorListener getSyncPublisher() {
172     return myProject.getMessageBus().syncPublisher(TOPIC);
173   }
174
175   @Override
176   public void toFrontRunContent(final Executor requestor, final ProcessHandler handler) {
177     final RunContentDescriptor descriptor = getDescriptorBy(handler, requestor);
178     if (descriptor == null) {
179       return;
180     }
181     toFrontRunContent(requestor, descriptor);
182   }
183
184   @Override
185   public void toFrontRunContent(final Executor requestor, final RunContentDescriptor descriptor) {
186     ApplicationManager.getApplication().invokeLater(() -> {
187       ContentManager contentManager = getContentManagerForRunner(requestor);
188       Content content = getRunContentByDescriptor(contentManager, descriptor);
189       if (content != null) {
190         contentManager.setSelectedContent(content);
191         ToolWindowManager.getInstance(myProject).getToolWindow(requestor.getToolWindowId()).show(null);
192       }
193     }, myProject.getDisposed());
194   }
195
196   @Override
197   public void hideRunContent(@NotNull final Executor executor, final RunContentDescriptor descriptor) {
198     ApplicationManager.getApplication().invokeLater(() -> {
199       ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(executor.getToolWindowId());
200       if (toolWindow != null) {
201         toolWindow.hide(null);
202       }
203     }, myProject.getDisposed());
204   }
205
206   @Override
207   @Nullable
208   public RunContentDescriptor getSelectedContent(final Executor executor) {
209     final Content selectedContent = getContentManagerForRunner(executor).getSelectedContent();
210     return selectedContent != null ? getRunContentDescriptorByContent(selectedContent) : null;
211   }
212
213   @Override
214   @Nullable
215   public RunContentDescriptor getSelectedContent() {
216     for (String activeWindow : myToolwindowIdZBuffer) {
217       final ContentManager contentManager = myToolwindowIdToContentManagerMap.get(activeWindow);
218       if (contentManager == null) {
219         continue;
220       }
221
222       final Content selectedContent = contentManager.getSelectedContent();
223       if (selectedContent == null) {
224         if (contentManager.getContentCount() == 0) {
225           // continue to the next window if the content manager is empty
226           continue;
227         }
228         else {
229           // stop iteration over windows because there is some content in the window and the window is the last used one
230           break;
231         }
232       }
233       // here we have selected content
234       return getRunContentDescriptorByContent(selectedContent);
235     }
236
237     return null;
238   }
239
240   @Override
241   public boolean removeRunContent(@NotNull final Executor executor, final RunContentDescriptor descriptor) {
242     final ContentManager contentManager = getContentManagerForRunner(executor);
243     final Content content = getRunContentByDescriptor(contentManager, descriptor);
244     return content != null && contentManager.removeContent(content, true);
245   }
246
247   @Override
248   public void showRunContent(@NotNull Executor executor, @NotNull RunContentDescriptor descriptor) {
249     showRunContent(executor, descriptor, descriptor.getExecutionId());
250   }
251
252   public void showRunContent(@NotNull final Executor executor, @NotNull final RunContentDescriptor descriptor, final long executionId) {
253     if (ApplicationManager.getApplication().isUnitTestMode()) {
254       return;
255     }
256
257     final ContentManager contentManager = getContentManagerForRunner(executor);
258     RunContentDescriptor oldDescriptor = chooseReuseContentForDescriptor(contentManager, descriptor, executionId, descriptor.getDisplayName());
259     final Content content;
260     if (oldDescriptor == null) {
261       content = createNewContent(contentManager, descriptor, executor);
262       Icon icon = descriptor.getIcon();
263       content.setIcon(icon == null ? executor.getToolWindowIcon() : icon);
264     }
265     else {
266       content = oldDescriptor.getAttachedContent();
267       LOG.assertTrue(content != null);
268       getSyncPublisher().contentRemoved(oldDescriptor, executor);
269       Disposer.dispose(oldDescriptor); // is of the same category, can be reused
270     }
271
272     content.setExecutionId(executionId);
273     content.setComponent(descriptor.getComponent());
274     content.setPreferredFocusedComponent(descriptor.getPreferredFocusComputable());
275     content.putUserData(DESCRIPTOR_KEY, descriptor);
276     final ToolWindow toolWindow = ToolWindowManager.getInstance(myProject).getToolWindow(executor.getToolWindowId());
277     final ProcessHandler processHandler = descriptor.getProcessHandler();
278     if (processHandler != null) {
279       final ProcessAdapter processAdapter = new ProcessAdapter() {
280         @Override
281         public void startNotified(final ProcessEvent event) {
282           UIUtil.invokeLaterIfNeeded(() -> {
283             content.setIcon(ExecutionUtil.getLiveIndicator(descriptor.getIcon()));
284             toolWindow.setIcon(ExecutionUtil.getLiveIndicator(myToolwindowIdToBaseIconMap.get(executor.getToolWindowId())));
285           });
286         }
287
288         @Override
289         public void processTerminated(final ProcessEvent event) {
290           ApplicationManager.getApplication().invokeLater(() -> {
291             boolean alive = false;
292             String toolWindowId = executor.getToolWindowId();
293             ContentManager manager = myToolwindowIdToContentManagerMap.get(toolWindowId);
294             if (manager == null) return;
295             for (Content content1 : manager.getContents()) {
296               RunContentDescriptor descriptor1 = getRunContentDescriptorByContent(content1);
297               if (descriptor1 != null) {
298                 ProcessHandler handler = descriptor1.getProcessHandler();
299                 if (handler != null && !handler.isProcessTerminated()) {
300                   alive = true;
301                   break;
302                 }
303               }
304             }
305             Icon base = myToolwindowIdToBaseIconMap.get(toolWindowId);
306             toolWindow.setIcon(alive ? ExecutionUtil.getLiveIndicator(base) : base);
307
308             Icon icon = descriptor.getIcon();
309             content.setIcon(icon == null ? executor.getDisabledIcon() : IconLoader.getTransparentIcon(icon));
310           });
311         }
312       };
313       processHandler.addProcessListener(processAdapter);
314       final Disposable disposer = content.getDisposer();
315       if (disposer != null) {
316         Disposer.register(disposer, new Disposable() {
317           @Override
318           public void dispose() {
319             processHandler.removeProcessListener(processAdapter);
320           }
321         });
322       }
323     }
324     content.setDisplayName(descriptor.getDisplayName());
325     descriptor.setAttachedContent(content);
326     content.getManager().setSelectedContent(content);
327
328     if (!descriptor.isActivateToolWindowWhenAdded()) {
329       return;
330     }
331
332     ApplicationManager.getApplication().invokeLater(() -> {
333       ToolWindow window = ToolWindowManager.getInstance(myProject).getToolWindow(executor.getToolWindowId());
334       // let's activate tool window, but don't move focus
335       //
336       // window.show() isn't valid here, because it will not
337       // mark the window as "last activated" windows and thus
338       // some action like navigation up/down in stacktrace wont
339       // work correctly
340       descriptor.getPreferredFocusComputable();
341       window.activate(descriptor.getActivationCallback(), descriptor.isAutoFocusContent(), descriptor.isAutoFocusContent());
342     }, myProject.getDisposed());
343   }
344
345
346   @Nullable
347   @Override
348   public RunContentDescriptor getReuseContent(@NotNull ExecutionEnvironment executionEnvironment) {
349     if (ApplicationManager.getApplication().isUnitTestMode()) return null;
350     RunContentDescriptor contentToReuse = executionEnvironment.getContentToReuse();
351     if (contentToReuse != null) {
352       return contentToReuse;
353     }
354
355     final ContentManager contentManager = getContentManagerForRunner(executionEnvironment.getExecutor());
356     return chooseReuseContentForDescriptor(contentManager, null, executionEnvironment.getExecutionId(),
357                                            executionEnvironment.toString());
358   }
359
360   @Override
361   public RunContentDescriptor findContentDescriptor(final Executor requestor, final ProcessHandler handler) {
362     return getDescriptorBy(handler, requestor);
363   }
364
365   @Override
366   public void showRunContent(@NotNull Executor info, @NotNull RunContentDescriptor descriptor, @Nullable RunContentDescriptor contentToReuse) {
367     copyContentAndBehavior(descriptor, contentToReuse);
368     showRunContent(info, descriptor, descriptor.getExecutionId());
369   }
370
371   public static void copyContentAndBehavior(@NotNull RunContentDescriptor descriptor, @Nullable RunContentDescriptor contentToReuse) {
372     if (contentToReuse != null) {
373       Content attachedContent = contentToReuse.getAttachedContent();
374       if (attachedContent != null && attachedContent.isValid()) {
375         descriptor.setAttachedContent(attachedContent);
376       }
377       if (contentToReuse.isReuseToolWindowActivation()) {
378         descriptor.setActivateToolWindowWhenAdded(contentToReuse.isActivateToolWindowWhenAdded());
379       }
380     }
381   }
382
383   @Nullable
384   private static RunContentDescriptor chooseReuseContentForDescriptor(@NotNull ContentManager contentManager,
385                                                                       @Nullable RunContentDescriptor descriptor,
386                                                                       long executionId,
387                                                                       @Nullable String preferredName) {
388     Content content = null;
389     if (descriptor != null) {
390       //Stage one: some specific descriptors (like AnalyzeStacktrace) cannot be reused at all
391       if (descriptor.isContentReuseProhibited()) {
392         return null;
393       }
394       //Stage two: try to get content from descriptor itself
395       final Content attachedContent = descriptor.getAttachedContent();
396
397       if (attachedContent != null
398           && attachedContent.isValid()
399           && contentManager.getIndexOfContent(attachedContent) != -1
400           && (Comparing.equal(descriptor.getDisplayName(), attachedContent.getDisplayName()) || !attachedContent.isPinned())) {
401         content = attachedContent;
402       }
403     }
404     //Stage three: choose the content with name we prefer
405     if (content == null) {
406       content = getContentFromManager(contentManager, preferredName, executionId);
407     }
408     if (content == null || !isTerminated(content) || (content.getExecutionId() == executionId && executionId != 0)) {
409       return null;
410     }
411     final RunContentDescriptor oldDescriptor = getRunContentDescriptorByContent(content);
412     if (oldDescriptor != null && !oldDescriptor.isContentReuseProhibited() ) {
413       //content.setExecutionId(executionId);
414       return oldDescriptor;
415     }
416
417     return null;
418   }
419
420   @Nullable
421   private static Content getContentFromManager(ContentManager contentManager, @Nullable String preferredName, long executionId) {
422     ArrayList<Content> contents = new ArrayList<Content>(Arrays.asList(contentManager.getContents()));
423     Content first = contentManager.getSelectedContent();
424     if (first != null && contents.remove(first)) {//selected content should be checked first
425       contents.add(0, first);
426     }
427     if (preferredName != null) {//try to match content with specified preferred name
428       for (Content c : contents) {
429         if (canReuseContent(c, executionId) && preferredName.equals(c.getDisplayName())) {
430           return c;
431         }
432       }
433     }
434     for (Content c : contents) {//return first "good" content
435       if (canReuseContent(c, executionId)) {
436         return c;
437       }
438     }
439     return null;
440   }
441
442   private static boolean canReuseContent(Content c, long executionId) {
443     return c != null && !c.isPinned() && isTerminated(c) && !(c.getExecutionId() == executionId && executionId != 0);
444   }
445
446   @NotNull
447   private ContentManager getContentManagerForRunner(final Executor executor) {
448     final ContentManager contentManager = myToolwindowIdToContentManagerMap.get(executor.getToolWindowId());
449     if (contentManager == null) {
450       LOG.error("Runner " + executor.getId() + " is not registered");
451     }
452     //noinspection ConstantConditions
453     return contentManager;
454   }
455
456   private Content createNewContent(final ContentManager contentManager, final RunContentDescriptor descriptor, Executor executor) {
457     final String processDisplayName = descriptor.getDisplayName();
458     final Content content = ContentFactory.SERVICE.getInstance().createContent(descriptor.getComponent(), processDisplayName, true);
459     content.putUserData(DESCRIPTOR_KEY, descriptor);
460     content.putUserData(ToolWindow.SHOW_CONTENT_ICON, Boolean.TRUE);
461     contentManager.addContent(content);
462     new CloseListener(content, executor);
463     return content;
464   }
465
466   private static boolean isTerminated(@NotNull Content content) {
467     RunContentDescriptor descriptor = getRunContentDescriptorByContent(content);
468     ProcessHandler processHandler = descriptor == null ? null : descriptor.getProcessHandler();
469     return processHandler == null || processHandler.isProcessTerminated();
470   }
471
472   @Nullable
473   private static RunContentDescriptor getRunContentDescriptorByContent(@NotNull Content content) {
474     return content.getUserData(DESCRIPTOR_KEY);
475   }
476
477   @Override
478   @Nullable
479   public ToolWindow getToolWindowByDescriptor(@NotNull RunContentDescriptor descriptor) {
480     for (Map.Entry<String, ContentManager> entry : myToolwindowIdToContentManagerMap.entrySet()) {
481       if (getRunContentByDescriptor(entry.getValue(), descriptor) != null) {
482         return ToolWindowManager.getInstance(myProject).getToolWindow(entry.getKey());
483       }
484     }
485     return null;
486   }
487
488   @Nullable
489   private static Content getRunContentByDescriptor(@NotNull ContentManager contentManager, @NotNull RunContentDescriptor descriptor) {
490     for (Content content : contentManager.getContents()) {
491       if (descriptor.equals(content.getUserData(DESCRIPTOR_KEY))) {
492         return content;
493       }
494     }
495     return null;
496   }
497
498   @Override
499   @NotNull
500   public List<RunContentDescriptor> getAllDescriptors() {
501     if (myToolwindowIdToContentManagerMap.isEmpty()) {
502       return Collections.emptyList();
503     }
504
505     List<RunContentDescriptor> descriptors = new SmartList<RunContentDescriptor>();
506     for (String id : myToolwindowIdToContentManagerMap.keySet()) {
507       for (Content content : myToolwindowIdToContentManagerMap.get(id).getContents()) {
508         RunContentDescriptor descriptor = getRunContentDescriptorByContent(content);
509         if (descriptor != null) {
510           descriptors.add(descriptor);
511         }
512       }
513     }
514     return descriptors;
515   }
516
517   @Nullable
518   private RunContentDescriptor getDescriptorBy(ProcessHandler handler, Executor runnerInfo) {
519     for (Content content : getContentManagerForRunner(runnerInfo).getContents()) {
520       RunContentDescriptor runContentDescriptor = getRunContentDescriptorByContent(content);
521       assert runContentDescriptor != null;
522       if (runContentDescriptor.getProcessHandler() == handler) {
523         return runContentDescriptor;
524       }
525     }
526     return null;
527   }
528
529   private class CloseListener extends ContentManagerAdapter implements ProjectManagerListener {
530     private Content myContent;
531     private final Executor myExecutor;
532
533     private CloseListener(@NotNull final Content content, @NotNull Executor executor) {
534       myContent = content;
535       content.getManager().addContentManagerListener(this);
536       ProjectManager.getInstance().addProjectManagerListener(this);
537       myExecutor = executor;
538     }
539
540     @Override
541     public void contentRemoved(final ContentManagerEvent event) {
542       final Content content = event.getContent();
543       if (content == myContent) {
544         dispose();
545       }
546     }
547
548     private void dispose() {
549       if (myContent == null) return;
550
551       final Content content = myContent;
552       try {
553         RunContentDescriptor descriptor = getRunContentDescriptorByContent(content);
554         getSyncPublisher().contentRemoved(descriptor, myExecutor);
555         if (descriptor != null) {
556           Disposer.dispose(descriptor);
557         }
558       }
559       finally {
560         content.getManager().removeContentManagerListener(this);
561         ProjectManager.getInstance().removeProjectManagerListener(this);
562         content.release(); // don't invoke myContent.release() because myContent becomes null after destroyProcess()
563         myContent = null;
564       }
565     }
566
567     @Override
568     public void contentRemoveQuery(final ContentManagerEvent event) {
569       if (event.getContent() == myContent) {
570         final boolean canClose = closeQuery(false);
571         if (!canClose) {
572           event.consume();
573         }
574       }
575     }
576
577     @Override
578     public void projectClosed(final Project project) {
579       if (myContent != null && project == myProject) {
580         myContent.getManager().removeContent(myContent, true);
581         dispose(); // Dispose content even if content manager refused to.
582       }
583     }
584
585     @Override
586     public boolean canCloseProject(final Project project) {
587       if (project != myProject) return true;
588
589       if (myContent == null) return true;
590
591       final boolean canClose = closeQuery(true);
592       if (canClose) {
593         myContent.getManager().removeContent(myContent, true);
594         myContent = null;
595       }
596       return canClose;
597     }
598
599     private boolean closeQuery(boolean modal) {
600       final RunContentDescriptor descriptor = getRunContentDescriptorByContent(myContent);
601       if (descriptor == null) {
602         return true;
603       }
604
605       final ProcessHandler processHandler = descriptor.getProcessHandler();
606       if (processHandler == null || processHandler.isProcessTerminated() || processHandler.isProcessTerminating()) {
607         return true;
608       }
609       final boolean destroyProcess;
610       //noinspection deprecation
611       if (processHandler.isSilentlyDestroyOnClose() || Boolean.TRUE.equals(processHandler.getUserData(ProcessHandler.SILENTLY_DESTROY_ON_CLOSE))) {
612         destroyProcess = true;
613       }
614       else {
615         //todo[nik] this is a temporary solution for the following problem: some configurations should not allow user to choose between 'terminating' and 'detaching'
616         boolean canDisconnect = !Boolean.TRUE.equals(processHandler.getUserData(ALWAYS_USE_DEFAULT_STOPPING_BEHAVIOUR_KEY));
617         GeneralSettings.ProcessCloseConfirmation rc =
618           TerminateRemoteProcessDialog.show(myProject, descriptor.getDisplayName(), canDisconnect, processHandler.detachIsDefault());
619         if (rc == null) { // cancel
620           return false;
621         }
622         destroyProcess = rc == GeneralSettings.ProcessCloseConfirmation.TERMINATE;
623       }
624       if (destroyProcess) {
625         processHandler.destroyProcess();
626       }
627       else {
628         processHandler.detachProcess();
629       }
630       waitForProcess(descriptor, modal);
631       return true;
632     }
633   }
634
635   private void waitForProcess(final RunContentDescriptor descriptor, final boolean modal) {
636     final ProcessHandler processHandler = descriptor.getProcessHandler();
637     final boolean killable = !modal && (processHandler instanceof KillableProcess) && ((KillableProcess)processHandler).canKillProcess();
638
639     String title = ExecutionBundle.message("terminating.process.progress.title", descriptor.getDisplayName());
640     ProgressManager.getInstance().run(new Task.Backgroundable(myProject, title, true) {
641
642       {
643         if (killable) {
644           String cancelText= ExecutionBundle.message("terminating.process.progress.kill");
645           setCancelText(cancelText);
646           setCancelTooltipText(cancelText);
647         }
648       }
649
650       @Override
651       public boolean isConditionalModal() {
652         return modal;
653       }
654
655       @Override
656       public boolean shouldStartInBackground() {
657         return !modal;
658       }
659
660       @Override
661       public void run(@NotNull final ProgressIndicator progressIndicator) {
662         final Semaphore semaphore = new Semaphore();
663         semaphore.down();
664
665         ApplicationManager.getApplication().executeOnPooledThread(() -> {
666           final ProcessHandler processHandler1 = descriptor.getProcessHandler();
667           try {
668             if (processHandler1 != null) {
669               processHandler1.waitFor();
670             }
671           }
672           finally {
673             semaphore.up();
674           }
675         });
676
677         progressIndicator.setText(ExecutionBundle.message("waiting.for.vm.detach.progress.text"));
678         ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
679           @Override
680           public void run() {
681             while (true) {
682               if (progressIndicator.isCanceled() || !progressIndicator.isRunning()) {
683                 semaphore.up();
684                 break;
685               }
686               try {
687                 //noinspection SynchronizeOnThis
688                 synchronized (this) {
689                   //noinspection SynchronizeOnThis
690                   wait(2000L);
691                 }
692               }
693               catch (InterruptedException ignore) {
694               }
695             }
696           }
697         });
698
699         semaphore.waitFor();
700       }
701
702       @Override
703       public void onCancel() {
704         if (killable && !processHandler.isProcessTerminated()) {
705           ((KillableProcess)processHandler).killProcess();
706         }
707       }
708     });
709   }
710 }