Snapshot idea/138.1696 from git://git.jetbrains.org/idea/community.git

Change-Id: I50c97b83a815ce635e49a38380ba5b8765e4b16a
diff --git a/python/helpers/pydev/pydevd_console.py b/python/helpers/pydev/pydevd_console.py
new file mode 100644
index 0000000..52b18bb
--- /dev/null
+++ b/python/helpers/pydev/pydevd_console.py
@@ -0,0 +1,212 @@
+'''An helper file for the pydev debugger (REPL) console
+'''
+from code import InteractiveConsole
+import sys
+import traceback
+
+import _pydev_completer
+from pydevd_tracing import GetExceptionTracebackStr
+from pydevd_vars import makeValidXmlValue
+from pydev_imports import Exec
+from pydevd_io import IOBuf
+from pydev_console_utils import BaseInterpreterInterface, BaseStdIn
+from pydev_override import overrides
+import pydevd_save_locals
+
+CONSOLE_OUTPUT = "output"
+CONSOLE_ERROR = "error"
+
+
+#=======================================================================================================================
+# ConsoleMessage
+#=======================================================================================================================
+class ConsoleMessage:
+    """Console Messages
+    """
+    def __init__(self):
+        self.more = False
+        # List of tuple [('error', 'error_message'), ('message_list', 'output_message')]
+        self.console_messages = []
+
+    def add_console_message(self, message_type, message):
+        """add messages in the console_messages list
+        """
+        for m in message.split("\n"):
+            if m.strip():
+                self.console_messages.append((message_type, m))
+
+    def update_more(self, more):
+        """more is set to true if further input is required from the user
+        else more is set to false
+        """
+        self.more = more
+
+    def toXML(self):
+        """Create an XML for console message_list, error and more (true/false)
+        <xml>
+            <message_list>console message_list</message_list>
+            <error>console error</error>
+            <more>true/false</more>
+        </xml>
+        """
+        makeValid = makeValidXmlValue
+
+        xml = '<xml><more>%s</more>' % (self.more)
+
+        for message_type, message in self.console_messages:
+            xml += '<%s message="%s"></%s>' % (message_type, makeValid(message), message_type)
+
+        xml += '</xml>'
+
+        return xml
+
+
+#=======================================================================================================================
+# DebugConsoleStdIn
+#=======================================================================================================================
+class DebugConsoleStdIn(BaseStdIn):
+
+    overrides(BaseStdIn.readline)
+    def readline(self, *args, **kwargs):
+        sys.stderr.write('Warning: Reading from stdin is still not supported in this console.\n')
+        return '\n'
+
+#=======================================================================================================================
+# DebugConsole
+#=======================================================================================================================
+class DebugConsole(InteractiveConsole, BaseInterpreterInterface):
+    """Wrapper around code.InteractiveConsole, in order to send
+    errors and outputs to the debug console
+    """
+
+    overrides(BaseInterpreterInterface.createStdIn)
+    def createStdIn(self):
+        return DebugConsoleStdIn() #For now, raw_input is not supported in this console.
+
+
+    overrides(InteractiveConsole.push)
+    def push(self, line, frame):
+        """Change built-in stdout and stderr methods by the
+        new custom StdMessage.
+        execute the InteractiveConsole.push.
+        Change the stdout and stderr back be the original built-ins
+
+        Return boolean (True if more input is required else False),
+        output_messages and input_messages
+        """
+        more = False
+        original_stdout = sys.stdout
+        original_stderr = sys.stderr
+        try:
+            try:
+                self.frame = frame
+                out = sys.stdout = IOBuf()
+                err = sys.stderr = IOBuf()
+                more = self.addExec(line)
+            except Exception:
+                exc = GetExceptionTracebackStr()
+                err.buflist.append("Internal Error: %s" % (exc,))
+        finally:
+            #Remove frame references.
+            self.frame = None
+            frame = None
+            sys.stdout = original_stdout
+            sys.stderr = original_stderr
+
+        return more, out.buflist, err.buflist
+
+
+    overrides(BaseInterpreterInterface.doAddExec)
+    def doAddExec(self, line):
+        return InteractiveConsole.push(self, line)
+
+
+    overrides(InteractiveConsole.runcode)
+    def runcode(self, code):
+        """Execute a code object.
+
+        When an exception occurs, self.showtraceback() is called to
+        display a traceback.  All exceptions are caught except
+        SystemExit, which is reraised.
+
+        A note about KeyboardInterrupt: this exception may occur
+        elsewhere in this code, and may not always be caught.  The
+        caller should be prepared to deal with it.
+
+        """
+        try:
+            Exec(code, self.frame.f_globals, self.frame.f_locals)
+            pydevd_save_locals.save_locals(self.frame)
+        except SystemExit:
+            raise
+        except:
+            self.showtraceback()
+
+
+#=======================================================================================================================
+# InteractiveConsoleCache
+#=======================================================================================================================
+class InteractiveConsoleCache:
+
+    thread_id = None
+    frame_id = None
+    interactive_console_instance = None
+
+
+#Note: On Jython 2.1 we can't use classmethod or staticmethod, so, just make the functions below free-functions.
+def get_interactive_console(thread_id, frame_id, frame, console_message):
+    """returns the global interactive console.
+    interactive console should have been initialized by this time
+    """
+    if InteractiveConsoleCache.thread_id == thread_id and InteractiveConsoleCache.frame_id == frame_id:
+        return InteractiveConsoleCache.interactive_console_instance
+
+    InteractiveConsoleCache.interactive_console_instance = DebugConsole()
+    InteractiveConsoleCache.thread_id = thread_id
+    InteractiveConsoleCache.frame_id = frame_id
+
+    console_stacktrace = traceback.extract_stack(frame, limit=1)
+    if console_stacktrace:
+        current_context = console_stacktrace[0] # top entry from stacktrace
+        context_message = 'File "%s", line %s, in %s' % (current_context[0], current_context[1], current_context[2])
+        console_message.add_console_message(CONSOLE_OUTPUT, "[Current context]: %s" % (context_message,))
+    return InteractiveConsoleCache.interactive_console_instance
+
+
+def clear_interactive_console():
+    InteractiveConsoleCache.thread_id = None
+    InteractiveConsoleCache.frame_id = None
+    InteractiveConsoleCache.interactive_console_instance = None
+
+
+def execute_console_command(frame, thread_id, frame_id, line):
+    """fetch an interactive console instance from the cache and
+    push the received command to the console.
+
+    create and return an instance of console_message
+    """
+    console_message = ConsoleMessage()
+
+    interpreter = get_interactive_console(thread_id, frame_id, frame, console_message)
+    more, output_messages, error_messages = interpreter.push(line, frame)
+    console_message.update_more(more)
+
+    for message in output_messages:
+        console_message.add_console_message(CONSOLE_OUTPUT, message)
+
+    for message in error_messages:
+        console_message.add_console_message(CONSOLE_ERROR, message)
+
+    return console_message
+
+
+def get_completions(frame, act_tok):
+    """ fetch all completions, create xml for the same
+    return the completions xml
+    """
+    return _pydev_completer.GenerateCompletionsAsXML(frame, act_tok)
+
+
+
+
+