platform: extract BaseContentCloseListener

This commit is contained in:
Kirill Timofeev
2018-11-12 18:24:22 +03:00
parent 0547f560fe
commit 6ffb0069d6
3 changed files with 238 additions and 318 deletions
@@ -2,23 +2,15 @@
package com.intellij.build;
import com.intellij.build.process.BuildProcessHandler;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.TerminateRemoteProcessDialog;
import com.intellij.execution.runners.ExecutionUtil;
import com.intellij.execution.ui.BaseContentCloseListener;
import com.intellij.execution.ui.RunContentManagerImpl;
import com.intellij.icons.AllIcons;
import com.intellij.ide.DataManager;
import com.intellij.ide.GeneralSettings;
import com.intellij.ide.impl.ContentManagerWatcher;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.VetoableProjectManagerListener;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.util.ActionCallback;
import com.intellij.openapi.util.Disposer;
@@ -29,9 +21,10 @@ import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
import com.intellij.openapi.wm.ToolWindowId;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.ui.content.*;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
import com.intellij.ui.content.TabbedContent;
import com.intellij.util.ContentUtilEx;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import org.jetbrains.annotations.NotNull;
@@ -77,7 +70,7 @@ public class BuildContentManagerImpl implements BuildContentManager {
StartupManager.getInstance(project).runWhenProjectIsInitialized(() -> {
ToolWindow toolWindow = ToolWindowManager.getInstance(project)
.registerToolWindow(ToolWindowId.BUILD, true, ToolWindowAnchor.BOTTOM, project, true);
.registerToolWindow(ToolWindowId.BUILD, true, ToolWindowAnchor.BOTTOM, project, true);
toolWindow.setIcon(AllIcons.Toolwindows.ToolWindowBuild);
toolWindow.setAvailable(true, null);
toolWindow.hide(null);
@@ -99,7 +92,7 @@ public class BuildContentManagerImpl implements BuildContentManager {
});
new ContentManagerWatcher(toolWindow, contentManager);
for (Runnable postponedRunnable: myPostponedRunnables) {
for (Runnable postponedRunnable : myPostponedRunnables) {
postponedRunnable.run();
}
myPostponedRunnables.clear();
@@ -143,7 +136,7 @@ public class BuildContentManagerImpl implements BuildContentManager {
final Content[] existingContents = contentManager.getContents();
if (idx != -1) {
final MultiMap<String, String> existingCategoriesNames = MultiMap.createSmart();
for (Content existingContent: existingContents) {
for (Content existingContent : existingContents) {
String tabName = existingContent.getTabName();
existingCategoriesNames.putValue(StringUtil.trimEnd(StringUtil.split(tabName, " ").get(0), ':'), tabName);
}
@@ -162,7 +155,7 @@ public class BuildContentManagerImpl implements BuildContentManager {
contentManager.addContent(content);
}
for (Content existingContent: existingContents) {
for (Content existingContent : existingContents) {
existingContent.setDisplayName(existingContent.getTabName());
}
String tabName = content.getTabName();
@@ -284,41 +277,17 @@ public class BuildContentManagerImpl implements BuildContentManager {
});
}
private class CloseListener extends ContentManagerAdapter implements VetoableProjectManagerListener, Disposable {
@Nullable
private Content myContent;
private class CloseListener extends BaseContentCloseListener {
@Nullable
private BuildProcessHandler myProcessHandler;
private CloseListener(@NotNull final Content content, @NotNull BuildProcessHandler processHandler) {
myContent = content;
ContentManager contentManager = content.getManager();
if (contentManager != null) {
contentManager.addContentManagerListener(this);
}
ProjectManager.getInstance().addProjectManagerListener(myProject, this);
super(content, myProject);
myProcessHandler = processHandler;
}
@Override
public void contentRemoved(@NotNull final ContentManagerEvent event) {
final Content content = event.getContent();
if (content == myContent) {
Disposer.dispose(this);
}
}
@Override
public void dispose() {
if (myContent == null) return;
final Content content = myContent;
ContentManager contentManager = content.getManager();
if (contentManager != null) {
contentManager.removeContentManagerListener(this);
}
ProjectManager.getInstance().removeProjectManagerListener(myProject, this);
myContent = null;
protected void disposeContent(@NotNull Content content) {
if (myProcessHandler instanceof Disposable) {
Disposer.dispose((Disposable)myProcessHandler);
}
@@ -326,124 +295,20 @@ public class BuildContentManagerImpl implements BuildContentManager {
}
@Override
public void contentRemoveQuery(@NotNull final ContentManagerEvent event) {
if (event.getContent() == myContent) {
final boolean canClose = closeQuery(false);
if (!canClose) {
event.consume();
}
}
}
@Override
public void projectClosed(@NotNull final Project project) {
if (myContent != null && project == myProject) {
ContentManager contentManager = myContent.getManager();
if (contentManager != null) {
contentManager.removeContent(myContent, true);
}
Disposer.dispose(this); // Dispose content even if content manager refused to.
}
}
@Override
public boolean canClose(@NotNull Project project) {
if (project != myProject) return true;
if (myContent == null) return true;
final boolean canClose = closeQuery(true);
// Content could be removed during close query
if (canClose && myContent != null) {
ContentManager contentManager = myContent.getManager();
if (contentManager != null) contentManager.removeContent(myContent, true);
myContent = null;
}
return canClose;
}
private boolean closeQuery(boolean modal) {
protected boolean closeQuery(@NotNull Content content, boolean modal) {
if (myProcessHandler == null || myProcessHandler.isProcessTerminated() || myProcessHandler.isProcessTerminating()) {
return true;
}
myProcessHandler.putUserData(RunContentManagerImpl.ALWAYS_USE_DEFAULT_STOPPING_BEHAVIOUR_KEY, Boolean.TRUE);
GeneralSettings.ProcessCloseConfirmation rc =
TerminateRemoteProcessDialog.show(myProject, myProcessHandler.getExecutionName(), myProcessHandler);
if(myProcessHandler == null) { // process finished before the dialog close
return true;
}
if (rc == null) { // cancel
return false;
}
boolean destroyProcess = rc == GeneralSettings.ProcessCloseConfirmation.TERMINATE;
if (destroyProcess) {
myProcessHandler.destroyProcess();
}
else {
myProcessHandler.detachProcess();
}
waitForProcess(modal, myProcessHandler);
return true;
final String sessionName = myProcessHandler.getExecutionName();
final WaitForProcessTask task = new WaitForProcessTask(myProcessHandler, sessionName, modal, myProject) {
@Override
public void onCancel() {
// stop waiting for the process
myProcessHandler.forceProcessDetach();
}
};
return askUserAndWait(myProcessHandler, sessionName, task);
}
}
private void waitForProcess(final boolean modal, BuildProcessHandler processHandler) {
String title = ExecutionBundle.message("terminating.process.progress.title", processHandler.getExecutionName());
ProgressManager.getInstance().run(new Task.Backgroundable(myProject, title, true) {
@Override
public boolean isConditionalModal() {
return modal;
}
@Override
public boolean shouldStartInBackground() {
return !modal;
}
@Override
public void run(@NotNull final ProgressIndicator progressIndicator) {
final Semaphore semaphore = new Semaphore();
semaphore.down();
ApplicationManager.getApplication().executeOnPooledThread(() -> {
try {
processHandler.waitFor();
}
finally {
semaphore.up();
}
});
progressIndicator.setText(ExecutionBundle.message("waiting.for.vm.detach.progress.text"));
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
while (true) {
if (progressIndicator.isCanceled() || !progressIndicator.isRunning()) {
semaphore.up();
break;
}
try {
//noinspection SynchronizeOnThis
synchronized (this) {
//noinspection SynchronizeOnThis
wait(2000L);
}
}
catch (InterruptedException ignore) {
}
}
}
});
semaphore.waitFor();
}
@Override
public void onCancel() {
// stop waiting for the process
processHandler.forceProcessDetach();
}
});
}
}
@@ -0,0 +1,182 @@
// Copyright 2000-2018 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.
package com.intellij.execution.ui;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.TerminateRemoteProcessDialog;
import com.intellij.execution.process.ProcessHandler;
import com.intellij.ide.GeneralSettings;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.VetoableProjectManagerListener;
import com.intellij.openapi.util.Disposer;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentManager;
import com.intellij.ui.content.ContentManagerAdapter;
import com.intellij.ui.content.ContentManagerEvent;
import com.intellij.util.concurrency.Semaphore;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public abstract class BaseContentCloseListener extends ContentManagerAdapter implements VetoableProjectManagerListener, Disposable {
private Content myContent;
private final Project myProject;
public BaseContentCloseListener(@NotNull final Content content, @NotNull final Project project) {
myContent = content;
myProject = project;
final ContentManager contentManager = content.getManager();
if (contentManager != null) {
contentManager.addContentManagerListener(this);
}
ProjectManager.getInstance().addProjectManagerListener(myProject, this);
}
@Override
public void contentRemoved(@NotNull final ContentManagerEvent event) {
final Content content = event.getContent();
if (content == myContent) {
Disposer.dispose(this);
}
}
@Override
public void dispose() {
if (myContent == null) return;
final Content content = myContent;
try {
disposeContent(content);
}
finally {
final ContentManager contentManager = content.getManager();
if (contentManager != null) {
contentManager.removeContentManagerListener(this);
}
ProjectManager.getInstance().removeProjectManagerListener(myProject, this);
content.release(); // don't invoke myContent.release() because myContent can becomes null
myContent = null;
}
}
protected abstract void disposeContent(@NotNull Content content);
@Override
public void contentRemoveQuery(@NotNull final ContentManagerEvent event) {
if (event.getContent() == myContent) {
final boolean canClose = closeQuery(myContent, false);
if (!canClose) {
event.consume();
}
}
}
@Override
public void projectClosed(@NotNull final Project project) {
if (myContent == null || project != myProject) return;
ContentManager contentManager = myContent.getManager();
if (contentManager != null) {
contentManager.removeContent(myContent, true);
}
Disposer.dispose(this); // Dispose content even if content manager refused to.
}
@Override
public boolean canClose(@NotNull Project project) {
if (myContent == null || project != myProject) return true;
final boolean canClose = closeQuery(myContent, true);
// Content could be removed during close query
if (canClose && myContent != null) {
myContent.getManager().removeContent(myContent, true);
myContent = null;
}
return canClose;
}
protected boolean askUserAndWait(@NotNull ProcessHandler processHandler, @NotNull String sessionName, @NotNull WaitForProcessTask task) {
GeneralSettings.ProcessCloseConfirmation rc = TerminateRemoteProcessDialog.show(myProject, sessionName, processHandler);
if (rc == null) { // cancel
return false;
}
boolean destroyProcess = rc == GeneralSettings.ProcessCloseConfirmation.TERMINATE;
if (destroyProcess) {
processHandler.destroyProcess();
}
else {
processHandler.detachProcess();
}
ProgressManager.getInstance().run(task);
return true;
}
protected abstract boolean closeQuery(@NotNull Content content, boolean modal);
protected abstract static class WaitForProcessTask extends Task.Backgroundable {
final ProcessHandler myProcessHandler;
final boolean myModal;
protected WaitForProcessTask(@NotNull ProcessHandler processHandler,
@NotNull String processName,
boolean modal,
@Nullable Project project) {
super(project, ExecutionBundle.message("terminating.process.progress.title", processName));
myProcessHandler = processHandler;
myModal = modal;
}
@Override
public boolean isConditionalModal() {
return myModal;
}
@Override
public boolean shouldStartInBackground() {
return !myModal;
}
@Override
public void run(@NotNull ProgressIndicator progressIndicator) {
final Semaphore semaphore = new Semaphore();
semaphore.down();
ApplicationManager.getApplication().executeOnPooledThread(() -> {
try {
myProcessHandler.waitFor();
}
finally {
semaphore.up();
}
});
progressIndicator.setText(ExecutionBundle.message("waiting.for.vm.detach.progress.text"));
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
while (true) {
if (progressIndicator.isCanceled() || !progressIndicator.isRunning()) {
semaphore.up();
break;
}
try {
//noinspection SynchronizeOnThis
synchronized (this) {
//noinspection SynchronizeOnThis
wait(2000L);
}
}
catch (InterruptedException ignore) {
}
}
}
});
semaphore.waitFor();
}
@Override
abstract public void onCancel(); //force user to override
}
}
@@ -1,7 +1,10 @@
// Copyright 2000-2018 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.
package com.intellij.execution.ui;
import com.intellij.execution.*;
import com.intellij.execution.ExecutionBundle;
import com.intellij.execution.Executor;
import com.intellij.execution.ExecutorRegistry;
import com.intellij.execution.KillableProcess;
import com.intellij.execution.configurations.RunConfiguration;
import com.intellij.execution.dashboard.RunDashboardManager;
import com.intellij.execution.process.ProcessAdapter;
@@ -11,19 +14,13 @@ import com.intellij.execution.runners.ExecutionEnvironment;
import com.intellij.execution.runners.ExecutionUtil;
import com.intellij.execution.ui.layout.impl.DockableGridContainerFactory;
import com.intellij.ide.DataManager;
import com.intellij.ide.GeneralSettings;
import com.intellij.ide.impl.ContentManagerWatcher;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.DataProvider;
import com.intellij.openapi.actionSystem.PlatformDataKeys;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.Task;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.VetoableProjectManagerListener;
import com.intellij.openapi.util.*;
import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowAnchor;
@@ -34,7 +31,6 @@ import com.intellij.ui.AppUIUtil;
import com.intellij.ui.content.*;
import com.intellij.ui.docking.DockManager;
import com.intellij.util.SmartList;
import com.intellij.util.concurrency.Semaphore;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.UIUtil;
import gnu.trove.THashMap;
@@ -605,80 +601,26 @@ public class RunContentManagerImpl implements RunContentManager, Disposable {
return null;
}
private class CloseListener extends ContentManagerAdapter implements VetoableProjectManagerListener, Disposable {
private Content myContent;
private class CloseListener extends BaseContentCloseListener {
private final Executor myExecutor;
private CloseListener(@NotNull final Content content, @NotNull Executor executor) {
myContent = content;
content.getManager().addContentManagerListener(this);
ProjectManager.getInstance().addProjectManagerListener(myProject, this);
private CloseListener(@NotNull final Content content, @NotNull final Executor executor) {
super(content, myProject);
myExecutor = executor;
}
@Override
public void contentRemoved(@NotNull final ContentManagerEvent event) {
final Content content = event.getContent();
if (content == myContent) {
Disposer.dispose(this);
protected void disposeContent(@NotNull Content content) {
RunContentDescriptor descriptor = getRunContentDescriptorByContent(content);
getSyncPublisher().contentRemoved(descriptor, myExecutor);
if (descriptor != null) {
Disposer.dispose(descriptor);
}
}
@Override
public void dispose() {
if (myContent == null) return;
final Content content = myContent;
try {
RunContentDescriptor descriptor = getRunContentDescriptorByContent(content);
getSyncPublisher().contentRemoved(descriptor, myExecutor);
if (descriptor != null) {
Disposer.dispose(descriptor);
}
}
finally {
content.getManager().removeContentManagerListener(this);
ProjectManager.getInstance().removeProjectManagerListener(myProject, this);
content.release(); // don't invoke myContent.release() because myContent becomes null after destroyProcess()
myContent = null;
}
}
@Override
public void contentRemoveQuery(@NotNull final ContentManagerEvent event) {
if (event.getContent() == myContent) {
final boolean canClose = closeQuery(false);
if (!canClose) {
event.consume();
}
}
}
@Override
public void projectClosed(@NotNull final Project project) {
if (myContent != null && project == myProject) {
myContent.getManager().removeContent(myContent, true);
Disposer.dispose(this); // Dispose content even if content manager refused to.
}
}
@Override
public boolean canClose(@NotNull Project project) {
if (project != myProject) return true;
if (myContent == null) return true;
final boolean canClose = closeQuery(true);
// Content could be removed during close query
if (canClose && myContent != null) {
myContent.getManager().removeContent(myContent, true);
myContent = null;
}
return canClose;
}
private boolean closeQuery(boolean modal) {
final RunContentDescriptor descriptor = getRunContentDescriptorByContent(myContent);
protected boolean closeQuery(@NotNull Content content, boolean modal) {
final RunContentDescriptor descriptor = getRunContentDescriptorByContent(content);
if (descriptor == null) {
return true;
}
@@ -687,96 +629,27 @@ public class RunContentManagerImpl implements RunContentManager, Disposable {
if (processHandler == null || processHandler.isProcessTerminated() || processHandler.isProcessTerminating()) {
return true;
}
GeneralSettings.ProcessCloseConfirmation rc = TerminateRemoteProcessDialog.show(
myProject, descriptor.getDisplayName(), processHandler);
if (rc == null) { // cancel
return false;
}
boolean destroyProcess = rc == GeneralSettings.ProcessCloseConfirmation.TERMINATE;
if (destroyProcess) {
processHandler.destroyProcess();
}
else {
processHandler.detachProcess();
}
waitForProcess(descriptor, modal);
return true;
final String sessionName = descriptor.getDisplayName();
final WaitForProcessTask task = new WaitForProcessTask(processHandler, sessionName, modal, myProject) {
final boolean killable =
!modal && (processHandler instanceof KillableProcess) && ((KillableProcess)processHandler).canKillProcess();
{
if (killable) {
String cancelText = ExecutionBundle.message("terminating.process.progress.kill");
setCancelText(cancelText);
setCancelTooltipText(cancelText);
}
}
@Override
public void onCancel() {
if (killable && !processHandler.isProcessTerminated()) {
((KillableProcess)processHandler).killProcess();
}
}
};
return askUserAndWait(processHandler, sessionName, task);
}
}
private void waitForProcess(final RunContentDescriptor descriptor, final boolean modal) {
final ProcessHandler processHandler = descriptor.getProcessHandler();
final boolean killable = !modal && (processHandler instanceof KillableProcess) && ((KillableProcess)processHandler).canKillProcess();
String title = ExecutionBundle.message("terminating.process.progress.title", descriptor.getDisplayName());
ProgressManager.getInstance().run(new Task.Backgroundable(myProject, title, true) {
{
if (killable) {
String cancelText= ExecutionBundle.message("terminating.process.progress.kill");
setCancelText(cancelText);
setCancelTooltipText(cancelText);
}
}
@Override
public boolean isConditionalModal() {
return modal;
}
@Override
public boolean shouldStartInBackground() {
return !modal;
}
@Override
public void run(@NotNull final ProgressIndicator progressIndicator) {
final Semaphore semaphore = new Semaphore();
semaphore.down();
ApplicationManager.getApplication().executeOnPooledThread(() -> {
final ProcessHandler processHandler1 = descriptor.getProcessHandler();
try {
if (processHandler1 != null) {
processHandler1.waitFor();
}
}
finally {
semaphore.up();
}
});
progressIndicator.setText(ExecutionBundle.message("waiting.for.vm.detach.progress.text"));
ApplicationManager.getApplication().executeOnPooledThread(new Runnable() {
@Override
public void run() {
while (true) {
if (progressIndicator.isCanceled() || !progressIndicator.isRunning()) {
semaphore.up();
break;
}
try {
//noinspection SynchronizeOnThis
synchronized (this) {
//noinspection SynchronizeOnThis
wait(2000L);
}
}
catch (InterruptedException ignore) {
}
}
}
});
semaphore.waitFor();
}
@Override
public void onCancel() {
if (killable && !processHandler.isProcessTerminated()) {
((KillableProcess)processHandler).killProcess();
}
}
});
}
}