From 6ffb0069d6421f36009979bf05b0aeb6d2e2d197 Mon Sep 17 00:00:00 2001 From: Kirill Timofeev Date: Mon, 12 Nov 2018 17:16:51 +0300 Subject: [PATCH] platform: extract BaseContentCloseListener --- .../build/BuildContentManagerImpl.java | 177 ++-------------- .../ui/BaseContentCloseListener.java | 182 ++++++++++++++++ .../execution/ui/RunContentManagerImpl.java | 197 ++++-------------- 3 files changed, 238 insertions(+), 318 deletions(-) create mode 100644 platform/lang-impl/src/com/intellij/execution/ui/BaseContentCloseListener.java diff --git a/platform/lang-impl/src/com/intellij/build/BuildContentManagerImpl.java b/platform/lang-impl/src/com/intellij/build/BuildContentManagerImpl.java index b6ea25dce22f..20411310788b 100644 --- a/platform/lang-impl/src/com/intellij/build/BuildContentManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/build/BuildContentManagerImpl.java @@ -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 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(); - } - }); - } } diff --git a/platform/lang-impl/src/com/intellij/execution/ui/BaseContentCloseListener.java b/platform/lang-impl/src/com/intellij/execution/ui/BaseContentCloseListener.java new file mode 100644 index 000000000000..8140f58fb87c --- /dev/null +++ b/platform/lang-impl/src/com/intellij/execution/ui/BaseContentCloseListener.java @@ -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 + } +} diff --git a/platform/lang-impl/src/com/intellij/execution/ui/RunContentManagerImpl.java b/platform/lang-impl/src/com/intellij/execution/ui/RunContentManagerImpl.java index 7697b57418cc..408697a957df 100644 --- a/platform/lang-impl/src/com/intellij/execution/ui/RunContentManagerImpl.java +++ b/platform/lang-impl/src/com/intellij/execution/ui/RunContentManagerImpl.java @@ -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(); - } - } - }); - } }