From daa09d11837644aaaaa86fb57ba232adbcd9d43c Mon Sep 17 00:00:00 2001 From: Sergey Simonchik Date: Sat, 23 Mar 2019 14:44:29 +0300 Subject: [PATCH] terminal: show confirmation dialog on attempt to close it if there are running processes (IDEA-205690) --- plugins/terminal/intellij.terminal.iml | 1 + .../terminal/TerminalTabCloseListener.kt | 16 +++++ .../plugins/terminal/TerminalUtil.java | 69 +++++++++++++++++++ 3 files changed, 86 insertions(+) diff --git a/plugins/terminal/intellij.terminal.iml b/plugins/terminal/intellij.terminal.iml index ed89719334cd..01b0d3b35bb5 100644 --- a/plugins/terminal/intellij.terminal.iml +++ b/plugins/terminal/intellij.terminal.iml @@ -22,5 +22,6 @@ + \ No newline at end of file diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/TerminalTabCloseListener.kt b/plugins/terminal/src/org/jetbrains/plugins/terminal/TerminalTabCloseListener.kt index b7794ac3837d..146b08d38948 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/TerminalTabCloseListener.kt +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/TerminalTabCloseListener.kt @@ -5,8 +5,13 @@ import com.intellij.execution.TerminateRemoteProcessDialog import com.intellij.execution.process.NopProcessHandler import com.intellij.execution.ui.BaseContentCloseListener import com.intellij.execution.ui.RunContentManagerImpl +import com.intellij.openapi.diagnostic.Logger +import com.intellij.openapi.diagnostic.logger import com.intellij.openapi.project.Project import com.intellij.ui.content.Content +import com.intellij.util.ObjectUtils +import com.jediterm.terminal.ProcessTtyConnector +import org.jetbrains.plugins.terminal.arrangement.ProcessInfoUtil class TerminalTabCloseListener(val content: Content, val project: Project) : BaseContentCloseListener(content, project) { @@ -21,6 +26,15 @@ class TerminalTabCloseListener(val content: Content, if (widget == null || !widget.isSessionRunning) { return true } + val connector = widget.ttyConnector as? ProcessTtyConnector + try { + if (connector != null && !TerminalUtil.hasRunningCommands(connector)) { + return true + } + } + catch (e: Exception) { + LOG.error(e) + } val proxy = NopProcessHandler().apply { startNotify() } // don't show 'disconnect' button proxy.putUserData(RunContentManagerImpl.ALWAYS_USE_DEFAULT_STOPPING_BEHAVIOUR_KEY, true) @@ -32,3 +46,5 @@ class TerminalTabCloseListener(val content: Content, return project === this.project && closeQuery(this.content, true) } } + +private val LOG = logger() diff --git a/plugins/terminal/src/org/jetbrains/plugins/terminal/TerminalUtil.java b/plugins/terminal/src/org/jetbrains/plugins/terminal/TerminalUtil.java index a43a44966812..5a4d85a16b48 100644 --- a/plugins/terminal/src/org/jetbrains/plugins/terminal/TerminalUtil.java +++ b/plugins/terminal/src/org/jetbrains/plugins/terminal/TerminalUtil.java @@ -1,15 +1,36 @@ // Copyright 2000-2019 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 org.jetbrains.plugins.terminal; +import com.intellij.execution.process.OSProcessUtil; +import com.intellij.execution.process.UnixProcessManager; import com.intellij.openapi.Disposable; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Disposer; +import com.intellij.openapi.util.SystemInfo; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.LocalFileSystem; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.terminal.JBTerminalWidget; +import com.intellij.util.Processor; +import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.containers.MultiMap; +import com.intellij.util.execution.ParametersListUtil; +import com.jediterm.terminal.ProcessTtyConnector; +import com.pty4j.windows.WinPtyProcess; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jvnet.winp.WinProcess; +import org.jvnet.winp.WinpException; + +import java.io.IOException; +import java.util.List; +import java.util.StringTokenizer; public class TerminalUtil { + + private static final Logger LOG = Logger.getInstance(TerminalUtil.class); + private TerminalUtil() {} @NotNull @@ -33,4 +54,52 @@ public class TerminalUtil { return result; } + public static boolean hasRunningCommands(@NotNull ProcessTtyConnector connector) throws IllegalStateException { + Process process = connector.getProcess(); + if (!process.isAlive()) return false; + if (SystemInfo.isUnix) { + int shellPid = OSProcessUtil.getProcessID(process); + MultiMap pidToChildPidsMap = MultiMap.create(); + UnixProcessManager.processPSOutput(UnixProcessManager.getPSCmd(false, false), new Processor() { + @Override + public boolean process(String s) { + StringTokenizer st = new StringTokenizer(s, " "); + int parentPid = Integer.parseInt(st.nextToken()); + int pid = Integer.parseInt(st.nextToken()); + pidToChildPidsMap.putValue(parentPid, pid); + return false; + } + }); + return !pidToChildPidsMap.get(shellPid).isEmpty(); + } + else if (SystemInfo.isWindows) { + WinPtyProcess winPty = (WinPtyProcess)process; + try { + String executable = FileUtil.toSystemIndependentName(StringUtil.notNullize(getExecutable(winPty.getChildProcessId()))); + int consoleProcessCount = winPty.getConsoleProcessCount(); + if (executable.endsWith("/Git/bin/bash.exe")) { + return consoleProcessCount > 3; + } + return consoleProcessCount > 2; + } + catch (IOException e) { + throw new IllegalStateException(e); + } + } + throw new IllegalStateException("Unknown OS: " + SystemInfo.OS_NAME); + } + + @Nullable + private static String getExecutable(int pid) { + WinProcess winProcess = new WinProcess(pid); + String commandLine; + try { + commandLine = winProcess.getCommandLine(); + } + catch (WinpException e) { + LOG.error(e); + return null; + } + return ContainerUtil.getFirstItem(ParametersListUtil.parse(commandLine)); + } }