From 9a9cfcdbfa6ce71f3ad391df01447588a864fe43 Mon Sep 17 00:00:00 2001 From: Elizaveta Shashkova Date: Tue, 21 Jun 2016 13:03:06 +0300 Subject: [PATCH] Cannot do keyboard input in debug mode with interactive console turned on (PY-16825 ) In debug console we should handle input calls on Python side and notify Java side, like we do it in Interactive Python Console. After handling input we should send it back to Python process. --- .../_pydev_bundle/pydev_console_utils.py | 25 ++++++++ .../pydev/_pydevd_bundle/pydevd_comm.py | 9 +++ python/helpers/pydev/pydevd.py | 8 +++ .../python/debugger/IPyDebugProcess.java | 2 + .../debugger/pydev/AbstractCommand.java | 5 ++ .../python/debugger/pydev/RemoteDebugger.java | 3 + .../python/console/PythonConsoleView.java | 9 +++ .../PythonDebugConsoleCommunication.java | 57 +++++++++++++------ .../python/debugger/PyDebugProcess.java | 7 +++ 9 files changed, 109 insertions(+), 16 deletions(-) diff --git a/python/helpers/pydev/_pydev_bundle/pydev_console_utils.py b/python/helpers/pydev/_pydev_bundle/pydev_console_utils.py index 19be9971bda1..112d0ae36de9 100644 --- a/python/helpers/pydev/_pydev_bundle/pydev_console_utils.py +++ b/python/helpers/pydev/_pydev_bundle/pydev_console_utils.py @@ -114,6 +114,31 @@ class StdIn(BaseStdIn): return '\n' +#======================================================================================================================= +# DebugConsoleStdIn +#======================================================================================================================= +class DebugConsoleStdIn(BaseStdIn): + ''' + Object to be added to stdin (to emulate it as non-blocking while the next line arrives) + ''' + + def __init__(self, dbg, original_stdin): + BaseStdIn.__init__(self) + self.debugger = dbg + self.original_stdin = original_stdin + + def readline(self, *args, **kwargs): + # Notify Java side about input and call original function + try: + cmd = self.debugger.cmd_factory.make_input_requested_message() + self.debugger.writer.add_command(cmd) + return self.original_stdin.readline(*args, **kwargs) + except Exception: + import traceback + traceback.print_exc() + return '\n' + + class CodeFragment: def __init__(self, text, is_single_line=True): self.text = text diff --git a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py index babdc3571d0a..ec0c13d692b5 100644 --- a/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py +++ b/python/helpers/pydev/_pydevd_bundle/pydevd_comm.py @@ -138,6 +138,7 @@ CMD_GET_ARRAY = 143 CMD_STEP_INTO_MY_CODE = 144 CMD_GET_CONCURRENCY_EVENT = 145 CMD_SHOW_RETURN_VALUES = 146 +CMD_INPUT_REQUESTED = 147 CMD_VERSION = 501 CMD_RETURN = 502 @@ -191,6 +192,7 @@ ID_TO_MEANING = { '144': 'CMD_STEP_INTO_MY_CODE', '145': 'CMD_GET_CONCURRENCY_EVENT', '146': 'CMD_SHOW_RETURN_VALUES', + '147': 'CMD_INPUT_REQUESTED', '501': 'CMD_VERSION', '502': 'CMD_RETURN', @@ -779,6 +781,13 @@ class NetCommandFactory: except: return self.make_error_message(0, get_exception_traceback_str()) + def make_input_requested_message(self): + try: + return NetCommand(CMD_INPUT_REQUESTED, 0, '') + except: + return self.make_error_message(0, get_exception_traceback_str()) + + def make_exit_message(self): try: net = NetCommand(CMD_EXIT, 0, '') diff --git a/python/helpers/pydev/pydevd.py b/python/helpers/pydev/pydevd.py index b27daef92c57..e333f90df1c7 100644 --- a/python/helpers/pydev/pydevd.py +++ b/python/helpers/pydev/pydevd.py @@ -1180,6 +1180,7 @@ def _locked_settrace( if bufferStdErrToServer: init_stderr_redirect() + patch_stdin(debugger) debugger.set_trace_for_frame_and_parents(get_frame(), False, overwrite_prev_trace=overwrite_prev_trace) @@ -1382,6 +1383,12 @@ def apply_debugger_options(setup_options): enable_qt_support() +def patch_stdin(debugger): + from _pydev_bundle.pydev_console_utils import DebugConsoleStdIn + orig_stdin = sys.stdin + sys.stdin = DebugConsoleStdIn(debugger, orig_stdin) + + #======================================================================================================================= # main #======================================================================================================================= @@ -1512,6 +1519,7 @@ if __name__ == '__main__': pass # It's ok not having stackless there... is_module = setup['module'] + patch_stdin(debugger) if fix_app_engine_debug: sys.stderr.write("pydev debugger: google app engine integration enabled\n") diff --git a/python/pydevSrc/com/jetbrains/python/debugger/IPyDebugProcess.java b/python/pydevSrc/com/jetbrains/python/debugger/IPyDebugProcess.java index 82155bacd88f..ce55d8e87e83 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/IPyDebugProcess.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/IPyDebugProcess.java @@ -37,5 +37,7 @@ public interface IPyDebugProcess extends PyFrameAccessor { boolean isSuspendedOnAllThreadsPolicy(); + void consoleInputRequested(); + XDebugSession getSession(); } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java index 4ed466cacbc6..941e39343ba4 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/AbstractCommand.java @@ -50,6 +50,7 @@ public abstract class AbstractCommand { public static final int STEP_INTO_MY_CODE = 144; public static final int LOG_CONCURRENCY_EVENT = 145; public static final int SHOW_RETURN_VALUES = 146; + public static final int INPUT_REQUESTED = 147; public static final int ERROR = 901; @@ -197,6 +198,10 @@ public abstract class AbstractCommand { return command == WRITE_TO_CONSOLE; } + public static boolean isInputRequested(final int command) { + return command == INPUT_REQUESTED; + } + public static boolean isExitEvent(final int command) { return command == EXIT; } diff --git a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java index 7b15821aef8b..08b93e8fbfa1 100644 --- a/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java +++ b/python/pydevSrc/com/jetbrains/python/debugger/pydev/RemoteDebugger.java @@ -547,6 +547,9 @@ public class RemoteDebugger implements ProcessDebugger { else if (AbstractCommand.isConcurrencyEvent(frame.getCommand())) { recordConcurrencyEvent(ProtocolParser.parseConcurrencyEvent(frame.getPayload(), myDebugProcess.getPositionConverter())); } + else if (AbstractCommand.isInputRequested(frame.getCommand())) { + myDebugProcess.consoleInputRequested(); + } else { placeResponse(frame.getSequence(), frame); } diff --git a/python/src/com/jetbrains/python/console/PythonConsoleView.java b/python/src/com/jetbrains/python/console/PythonConsoleView.java index e3e17b295f02..d6f4f63f0834 100644 --- a/python/src/com/jetbrains/python/console/PythonConsoleView.java +++ b/python/src/com/jetbrains/python/console/PythonConsoleView.java @@ -95,6 +95,15 @@ public class PythonConsoleView extends LanguageConsoleImpl implements Observable myExecuteActionHandler = consoleExecuteActionHandler; } + public void inputRequested() { + final ConsoleCommunication consoleCommunication = myExecuteActionHandler.getConsoleCommunication(); + if (consoleCommunication instanceof PythonDebugConsoleCommunication) { + ((PythonDebugConsoleCommunication)consoleCommunication).waitingForInput = true; + myExecuteActionHandler.inputRequested(); + myExecuteActionHandler.setEnabled(true); + } + } + @Override public void requestFocus() { IdeFocusManager.findInstance().requestFocus(getConsoleEditor().getContentComponent(), true); diff --git a/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java b/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java index 25eb30cc0552..e357c4dd89ab 100644 --- a/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java +++ b/python/src/com/jetbrains/python/console/PythonDebugConsoleCommunication.java @@ -15,8 +15,10 @@ */ package com.jetbrains.python.console; +import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Pair; +import com.intellij.openapi.vfs.encoding.EncodingProjectManager; import com.intellij.util.Function; import com.jetbrains.python.console.pydev.AbstractConsoleCommunication; import com.jetbrains.python.console.pydev.InterpreterResponse; @@ -26,12 +28,16 @@ import com.jetbrains.python.debugger.PyDebuggerException; import com.jetbrains.python.debugger.pydev.PyDebugCallback; import org.jetbrains.annotations.NotNull; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; import java.util.List; /** * @author traff */ public class PythonDebugConsoleCommunication extends AbstractConsoleCommunication { + private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.console.pydev.PythonDebugConsoleCommunication"); private final PyDebugProcess myDebugProcess; private final StringBuilder myExpression = new StringBuilder(); @@ -55,7 +61,7 @@ public class PythonDebugConsoleCommunication extends AbstractConsoleCommunicatio @Override public boolean isWaitingForInput() { - return false; + return waitingForInput; } @Override @@ -78,24 +84,43 @@ public class PythonDebugConsoleCommunication extends AbstractConsoleCommunicatio } public void execInterpreter(ConsoleCodeFragment code, final Function callback) { - myExpression.append(code.getText()); - exec(new ConsoleCodeFragment(myExpression.toString(), false), new PyDebugCallback>() { - @Override - public void ok(Pair executed) { - boolean more = executed.second; - - if (!more) { - myExpression.setLength(0); + if (waitingForInput) { + final OutputStream processInput = myDebugProcess.getProcessHandler().getProcessInput(); + if (processInput != null) { + try { + final Charset defaultCharset = EncodingProjectManager.getInstance(myDebugProcess.getProject()).getDefaultCharset(); + String text = code.getText(); + // we save previous command text!!! + processInput.write((text + "\n").getBytes(defaultCharset)); + processInput.flush(); + } + catch (IOException e) { + LOG.error(e.getMessage()); } - callback.fun(new InterpreterResponse(more, isWaitingForInput())); } + waitingForInput = false; + } + else { - @Override - public void error(PyDebuggerException exception) { - myExpression.setLength(0); - callback.fun(new InterpreterResponse(false, isWaitingForInput())); - } - }); + myExpression.append(code.getText()); + exec(new ConsoleCodeFragment(myExpression.toString(), false), new PyDebugCallback>() { + @Override + public void ok(Pair executed) { + boolean more = executed.second; + + if (!more) { + myExpression.setLength(0); + } + callback.fun(new InterpreterResponse(more, isWaitingForInput())); + } + + @Override + public void error(PyDebuggerException exception) { + myExpression.setLength(0); + callback.fun(new InterpreterResponse(false, isWaitingForInput())); + } + }); + } } @Override diff --git a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java index ea7c3afccd9c..3ab7886b6237 100644 --- a/python/src/com/jetbrains/python/debugger/PyDebugProcess.java +++ b/python/src/com/jetbrains/python/debugger/PyDebugProcess.java @@ -346,6 +346,13 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr } } + @Override + public void consoleInputRequested() { + if (myExecutionConsole instanceof PythonDebugLanguageConsoleView) { + ((PythonDebugLanguageConsoleView)myExecutionConsole).getPydevConsoleView().inputRequested(); + } + } + protected void afterConnect() { }