]> granicus.if.org Git - python/commitdiff
GvR's rpc patch
authorChui Tey <chui.tey@advdata.com.au>
Sun, 26 May 2002 13:36:41 +0000 (13:36 +0000)
committerChui Tey <chui.tey@advdata.com.au>
Sun, 26 May 2002 13:36:41 +0000 (13:36 +0000)
Lib/idlelib/Debugger.py
Lib/idlelib/PyShell.py
Lib/idlelib/RemoteDebugger.py [new file with mode: 0644]
Lib/idlelib/RemoteObjectBrowser.py [new file with mode: 0644]
Lib/idlelib/ScriptBinding.py
Lib/idlelib/rpc.py [new file with mode: 0644]
Lib/idlelib/run.py [new file with mode: 0644]

index 949a0f86f68693f6710c8b0472d94a93baca76a0..a4831685d0af3d0755b1b12e7f5e8a35d6806f65 100644 (file)
@@ -1,5 +1,6 @@
 import os
 import bdb
+import types
 import traceback
 from Tkinter import *
 from WindowList import ListedToplevel
@@ -7,20 +8,66 @@ from WindowList import ListedToplevel
 import StackViewer
 
 
-class Debugger(bdb.Bdb):
+class Idb(bdb.Bdb):
+
+    def __init__(self, gui):
+        self.gui = gui
+        bdb.Bdb.__init__(self)
+
+    def user_line(self, frame):
+        # get the currently executing function
+        co_filename = frame.f_code.co_filename
+        co_name = frame.f_code.co_name 
+        try:
+            func = frame.f_locals[co_name]
+            if getattr(func, "DebuggerStepThrough", 0):
+                print "XXXX DEBUGGER STEPPING THROUGH"
+                self.set_step()
+                return
+        except:
+            pass
+        if co_filename in ('rpc.py', '<string>'):
+            self.set_step()
+            return
+        if co_filename.endswith('threading.py'):
+            self.set_step()
+            return
+        message = self.__frame2message(frame)
+        self.gui.interaction(message, frame)
+
+    def user_exception(self, frame, info):
+        message = self.__frame2message(frame)
+        self.gui.interaction(message, frame, info)
+
+    def __frame2message(self, frame):
+        code = frame.f_code
+        filename = code.co_filename
+        lineno = frame.f_lineno
+        basename = os.path.basename(filename)
+        message = "%s:%s" % (basename, lineno)
+        if code.co_name != "?":
+            message = "%s: %s()" % (message, code.co_name)
+        return message
 
-    interacting = 0
 
+class Debugger:
+
+    interacting = 0
     vstack = vsource = vlocals = vglobals = None
 
-    def __init__(self, pyshell):
-        bdb.Bdb.__init__(self)
+    def __init__(self, pyshell, idb=None):
+        if idb is None:
+            idb = Idb(self)
         self.pyshell = pyshell
+        self.idb = idb
         self.make_gui()
 
-    def canonic(self, filename):
-        # Canonicalize filename -- called by Bdb
-        return os.path.normcase(os.path.abspath(filename))
+    def run(self, *args):
+        try:
+            self.interacting = 1
+            return self.idb.run(*args)
+        finally:
+            self.interacting = 0
 
     def close(self, event=None):
         if self.interacting:
@@ -31,24 +78,6 @@ class Debugger(bdb.Bdb):
         self.pyshell.close_debugger()
         self.top.destroy()
 
-    def run(self, *args):
-        try:
-            self.interacting = 1
-            return apply(bdb.Bdb.run, (self,) + args)
-        finally:
-            self.interacting = 0
-
-    def user_line(self, frame):
-        self.interaction(frame)
-
-    def user_return(self, frame, rv):
-        # XXX show rv?
-        ##self.interaction(frame)
-        pass
-
-    def user_exception(self, frame, info):
-        self.interaction(frame, info)
-
     def make_gui(self):
         pyshell = self.pyshell
         self.flist = pyshell.flist
@@ -128,16 +157,8 @@ class Debugger(bdb.Bdb):
 
     frame = None
 
-    def interaction(self, frame, info=None):
+    def interaction(self, message, frame, info=None):
         self.frame = frame
-        code = frame.f_code
-        file = code.co_filename
-        base = os.path.basename(file)
-        lineno = frame.f_lineno
-        #
-        message = "%s:%s" % (base, lineno)
-        if code.co_name != "?":
-            message = "%s: %s()" % (message, code.co_name)
         self.status.configure(text=message)
         #
         if info:
@@ -160,7 +181,7 @@ class Debugger(bdb.Bdb):
         #
         sv = self.stackviewer
         if sv:
-            stack, i = self.get_stack(self.frame, tb)
+            stack, i = self.idb.get_stack(self.frame, tb)
             sv.load_stack(stack, i)
         #
         self.show_variables(1)
@@ -184,32 +205,34 @@ class Debugger(bdb.Bdb):
         frame = self.frame
         if not frame:
             return
+        filename, lineno = self.__frame2fileline(frame)
+        if filename[:1] + filename[-1:] != "<>" and os.path.exists(filename):
+            self.flist.gotofileline(filename, lineno)
+
+    def __frame2fileline(self, frame):
         code = frame.f_code
-        file = code.co_filename
+        filename = code.co_filename
         lineno = frame.f_lineno
-        if file[:1] + file[-1:] != "<>" and os.path.exists(file):
-            edit = self.flist.open(file)
-            if edit:
-                edit.gotoline(lineno)
+        return filename, lineno
 
     def cont(self):
-        self.set_continue()
+        self.idb.set_continue()
         self.root.quit()
 
     def step(self):
-        self.set_step()
+        self.idb.set_step()
         self.root.quit()
 
     def next(self):
-        self.set_next(self.frame)
+        self.idb.set_next(self.frame)
         self.root.quit()
 
     def ret(self):
-        self.set_return(self.frame)
+        self.idb.set_return(self.frame)
         self.root.quit()
 
     def quit(self):
-        self.set_quit()
+        self.idb.set_quit()
         self.root.quit()
 
     stackviewer = None
@@ -219,7 +242,7 @@ class Debugger(bdb.Bdb):
             self.stackviewer = sv = StackViewer.StackViewer(
                 self.fstack, self.flist, self)
             if self.frame:
-                stack, i = self.get_stack(self.frame, None)
+                stack, i = self.idb.get_stack(self.frame, None)
                 sv.load_stack(stack, i)
         else:
             sv = self.stackviewer
@@ -233,6 +256,7 @@ class Debugger(bdb.Bdb):
             self.sync_source_line()
 
     def show_frame(self, (frame, lineno)):
+        # Called from OldStackViewer
         self.frame = frame
         self.show_variables()
 
@@ -295,15 +319,15 @@ class Debugger(bdb.Bdb):
         text.tag_add("BREAK", "insert linestart", "insert lineend +1char")
 
     # A literal copy of Bdb.set_break() without the print statement at the end
-    def set_break(self, filename, lineno, temporary=0, cond = None):
-        import linecache # Import as late as possible
-        filename = self.canonic(filename)
-        line = linecache.getline(filename, lineno)
-        if not line:
-            return 'That line does not exist!'
-        if not self.breaks.has_key(filename):
-            self.breaks[filename] = []
-        list = self.breaks[filename]
-        if not lineno in list:
-            list.append(lineno)
-        bp = bdb.Breakpoint(filename, lineno, temporary, cond)
+    #def set_break(self, filename, lineno, temporary=0, cond = None):
+    #    import linecache # Import as late as possible
+    #    filename = self.canonic(filename)
+    #    line = linecache.getline(filename, lineno)
+    #    if not line:
+    #        return 'That line does not exist!'
+    #    if not self.breaks.has_key(filename):
+    #        self.breaks[filename] = []
+    #    list = self.breaks[filename]
+    #    if not lineno in list:
+    #        list.append(lineno)
+    #    bp = bdb.Breakpoint(filename, lineno, temporary, cond)
index 9616e35585225baa56bac8c9c2e49400bac37cd6..3e512e3067f396ed7cd56460339348c37685c610 100644 (file)
@@ -29,7 +29,10 @@ import string
 import getopt
 import re
 import protocol
+import socket
+import time
 import warnings
+import traceback
 
 import linecache
 from code import InteractiveInterpreter
@@ -45,6 +48,21 @@ from OutputWindow import OutputWindow, OnDemandOutputWindow
 from configHandler import idleConf
 import idlever
 
+import rpc
+
+use_subprocess = 0 # Set to 1 to spawn subprocess for command execution
+
+# Change warnings module to write to sys.__stderr__
+try:
+    import warnings
+except ImportError:
+    pass
+else:
+    def idle_showwarning(message, category, filename, lineno):
+        file = sys.__stderr__
+        file.write(warnings.formatwarning(message, category, filename, lineno))
+    warnings.showwarning = idle_showwarning
+
 # We need to patch linecache.checkcache, because we don't want it
 # to throw away our <pyshell#...> entries.
 # Rather than repeating its code here, we save those entries,
@@ -186,6 +204,99 @@ class ModifiedInterpreter(InteractiveInterpreter):
         InteractiveInterpreter.__init__(self, locals=locals)
         self.save_warnings_filters = None
 
+        global flist
+        self.output = OnDemandOutputWindow(flist)
+
+    rpcclt = None
+    rpcpid = None
+
+    def spawn_subprocess(self):
+        port = 8833
+        addr = ("localhost", port)
+        w = ['-W' + s for s in sys.warnoptions]
+        args = [sys.executable] + w + ["-c", "__import__('run').main()",
+                                       str(port)]
+        self.rpcpid = os.spawnv(os.P_NOWAIT, args[0], args)
+        for i in range(5):
+            time.sleep(i)
+            try:
+                self.rpcclt = rpc.RPCClient(addr)
+                break
+            except socket.error, err:
+                if i > 3:
+                    print >>sys.__stderr__, "Socket error:", err, "; retry..."
+        else:
+            # XXX Make this a dialog?
+            print >>sys.__stderr__, "Can't spawn subprocess!"
+            return
+        self.output.stdout=PseudoFile(self.output, "stdout")
+        self.output.stderr=PseudoFile(self.output, "stderr")
+        self.rpcclt.register("stdin", self.output)
+        self.rpcclt.register("stdout", self.output.stdout)
+        self.rpcclt.register("stderr", self.output.stderr)
+        self.rpcclt.register("flist", self.tkconsole.flist)
+        self.poll_subprocess()
+
+    active_seq = None
+
+    def poll_subprocess(self):
+        clt = self.rpcclt
+        if clt is None:
+            return
+        response = clt.pollresponse(self.active_seq)
+        self.tkconsole.text.after(50, self.poll_subprocess)
+        if response:
+            self.tkconsole.resetoutput()
+            self.active_seq = None
+            how, what = response
+            file = self.tkconsole.console
+            if how == "OK":
+                if what is not None:
+                    print >>file, `what`
+            elif how == "EXCEPTION":
+                mod, name, args, tb = what
+                print >>file, 'Traceback (most recent call last):'
+                while tb and tb[0][0] in ("run.py", "rpc.py"):
+                    del tb[0]
+                while tb and tb[-1][0] in ("run.py", "rpc.py"):
+                    del tb[-1]
+                for i in range(len(tb)):
+                    fn, ln, nm, line = tb[i]
+                    if not line and fn.startswith("<pyshell#"):
+                        line = linecache.getline(fn, ln)
+                        tb[i] = fn, ln, nm, line
+                traceback.print_list(tb, file=file)
+                if mod and mod != "exceptions":
+                    name = mod + "." + name
+                print >>file, name + ":", " ".join(map(str, args))
+                if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
+                    self.remote_stack_viewer()
+            elif how == "ERROR":
+                print >>sys.__stderr__, "Oops:", how, what
+                print >>file, "Oops:", how, what
+            self.tkconsole.endexecuting()
+
+    def kill_subprocess(self):
+        clt = self.rpcclt
+        self.rpcclt = None
+        if clt is not None:
+            clt.close()
+
+    def remote_stack_viewer(self):
+        import RemoteObjectBrowser
+        oid = self.rpcclt.remotecall("exec", "stackviewer", ("flist",), {})
+        if oid is None:
+            self.tkconsole.root.bell()
+            return
+        item = RemoteObjectBrowser.StubObjectTreeItem(self.rpcclt, oid)
+        from TreeWidget import ScrolledCanvas, TreeNode
+        top = Toplevel(self.tkconsole.root)
+        sc = ScrolledCanvas(top, bg="white", highlightthickness=0)
+        sc.frame.pack(expand=1, fill="both")
+        node = TreeNode(sc.canvas, None, item)
+        node.expand()
+        # XXX Should GC the remote tree when closing the window
+
     gid = 0
 
     def execsource(self, source):
@@ -264,10 +375,11 @@ class ModifiedInterpreter(InteractiveInterpreter):
 
     def showtraceback(self):
         # Extend base class method to reset output properly
-        text = self.tkconsole.text
         self.tkconsole.resetoutput()
         self.checklinecache()
         InteractiveInterpreter.showtraceback(self)
+        if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
+            self.tkconsole.open_stack_viewer()
 
     def checklinecache(self):
         c = linecache.cache
@@ -283,12 +395,43 @@ class ModifiedInterpreter(InteractiveInterpreter):
     def getdebugger(self):
         return self.debugger
 
+    def runcommand(self, code):
+        # This runs the code without invoking the debugger.
+        # The code better not raise an exception!
+        if self.tkconsole.executing:
+            tkMessageBox.showerror(
+                "Already executing",
+                "The Python Shell window is already executing a command; "
+                "please wait until it is finished.",
+                master=self.tkconsole.text)
+            return 0
+        if self.rpcclt:
+            self.rpcclt.remotecall("exec", "runcode", (code,), {})
+        else:
+            exec code in self.locals
+        return 1
+
     def runcode(self, code):
         # Override base class method
+        if self.tkconsole.executing:
+            tkMessageBox.showerror(
+                "Already executing",
+                "The Python Shell window is already executing a command; "
+                "please wait until it is finished.",
+                master=self.tkconsole.text)
+            return
+
+        self.checklinecache()
         if self.save_warnings_filters is not None:
             warnings.filters[:] = self.save_warnings_filters
             self.save_warnings_filters = None
         debugger = self.debugger
+        if not debugger and self.rpcclt is not None:
+            self.tkconsole.beginexecuting()
+            self.active_seq = self.rpcclt.asynccall("exec", "runcode",
+                                                    (code,), {})
+            return
+
         try:
             self.tkconsole.beginexecuting()
             try:
@@ -305,12 +448,8 @@ class ModifiedInterpreter(InteractiveInterpreter):
                     raise
                 else:
                     self.showtraceback()
-                    if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
-                        self.tkconsole.open_stack_viewer()
             except:
                 self.showtraceback()
-                if self.tkconsole.getvar("<<toggle-jit-stack-viewer>>"):
-                    self.tkconsole.open_stack_viewer()
 
         finally:
             self.tkconsole.endexecuting()
@@ -319,7 +458,6 @@ class ModifiedInterpreter(InteractiveInterpreter):
         # Override base class write
         self.tkconsole.console.write(s)
 
-
 class PyShell(OutputWindow):
 
     shell_title = "Python Shell"
@@ -366,13 +504,19 @@ class PyShell(OutputWindow):
         self.save_stdout = sys.stdout
         self.save_stderr = sys.stderr
         self.save_stdin = sys.stdin
-        sys.stdout = PseudoFile(self, "stdout")
-        sys.stderr = PseudoFile(self, "stderr")
-        sys.stdin = self
+        self.stdout = PseudoFile(self, "stdout")
+        self.stderr = PseudoFile(self, "stderr")
         self.console = PseudoFile(self, "console")
+        if not use_subprocess:
+            sys.stdout = self.stdout
+            sys.stderr = self.stderr
+            sys.stdin = self
 
         self.history = self.History(self.text)
 
+        if use_subprocess:
+            self.interp.spawn_subprocess()
+
     reading = 0
     executing = 0
     canceled = 0
@@ -411,12 +555,22 @@ class PyShell(OutputWindow):
         self.set_debugger_indicator()
 
     def open_debugger(self):
+        if self.interp.rpcclt:
+            return self.open_remote_debugger()
         import Debugger
         self.interp.setdebugger(Debugger.Debugger(self))
         sys.ps1 = "[DEBUG ON]\n>>> "
         self.showprompt()
         self.set_debugger_indicator()
 
+    def open_remote_debugger(self):
+        import RemoteDebugger
+        gui = RemoteDebugger.start_remote_debugger(self.interp.rpcclt, self)
+        self.interp.setdebugger(gui)
+        sys.ps1 = "[DEBUG ON]\n>>> "
+        self.showprompt()
+        self.set_debugger_indicator()
+
     def beginexecuting(self):
         # Helper for ModifiedInterpreter
         self.resetoutput()
@@ -430,6 +584,7 @@ class PyShell(OutputWindow):
         ##self._cancel_check = None
         self.executing = 0
         self.canceled = 0
+        self.showprompt()
 
     def close(self):
         # Extend base class method
@@ -449,6 +604,7 @@ class PyShell(OutputWindow):
 
     def _close(self):
         self.close_debugger()
+        self.interp.kill_subprocess()
         # Restore std streams
         sys.stdout = self.save_stdout
         sys.stderr = self.save_stderr
@@ -520,9 +676,18 @@ class PyShell(OutputWindow):
             self.showprompt()
             return "break"
         self.endoffile = 0
-        self.canceled = 1
         if self.reading:
+            self.canceled = 1
             self.top.quit()
+        elif (self.executing and self.interp.rpcclt and
+              self.interp.rpcpid and hasattr(os, "kill")):
+            try:
+                from signal import SIGINT
+            except ImportError:
+                SIGINT = 2
+            os.kill(self.interp.rpcpid, SIGINT)
+        else:
+            self.canceled = 1
         return "break"
 
     def eof_callback(self, event):
@@ -532,11 +697,6 @@ class PyShell(OutputWindow):
                 self.text.compare("insert", "==", "end-1c")):
             return # Let the default binding (delete next char) take over
         if not self.executing:
-##             if not tkMessageBox.askokcancel(
-##                 "Exit?",
-##                 "Are you sure you want to exit?",
-##                 default="ok", master=self.text):
-##                 return "break"
             self.resetoutput()
             self.close()
         else:
@@ -656,6 +816,8 @@ class PyShell(OutputWindow):
         return self._cancel_check
 
     def open_stack_viewer(self, event=None):
+        if self.interp.rpcclt:
+            return self.interp.remote_stack_viewer()
         try:
             sys.last_traceback
         except:
@@ -675,6 +837,7 @@ class PyShell(OutputWindow):
             s = ""
         self.console.write(s)
         self.text.mark_set("insert", "end-1c")
+        self.set_line_and_column()
 
     def resetoutput(self):
         source = self.text.get("iomark", "end-1c")
@@ -683,6 +846,7 @@ class PyShell(OutputWindow):
         if self.text.get("end-2c") != "\n":
             self.text.insert("end-1c", "\n")
         self.text.mark_set("iomark", "end-1c")
+        self.set_line_and_column()
         sys.stdout.softspace = 0
 
     def write(self, s, tags=()):
@@ -698,6 +862,7 @@ class PseudoFile:
     def __init__(self, shell, tags):
         self.shell = shell
         self.tags = tags
+        self.softspace = 0
 
     def write(self, s):
         self.shell.write(s, self.tags)
@@ -718,9 +883,10 @@ idle file(s)    (without options) edit the file(s)
 
 -c cmd     run the command in a shell
 -d         enable the debugger
+-e         edit mode; arguments are files to be edited
 -i         open an interactive shell
 -i file(s) open a shell and also an editor window for each file
--r script  run a file as a script in a shell
+-r script  use experimental remote (subprocess) execution feature 
 -s         run $IDLESTARTUP or $PYTHONSTARTUP before anything else
 -t title   set title of shell window
 
@@ -822,9 +988,10 @@ be a security risk on a single-user machine.
         interactive = 0
         script = None
         startup = 0
+        global use_subprocess
     
         try:
-            opts, args = getopt.getopt(argv, "c:dir:st:")
+            opts, args = getopt.getopt(sys.argv[1:], "c:deir:st:")
         except getopt.error, msg:
             sys.stderr.write("Error: %s\n" % str(msg))
             sys.stderr.write(usage_msg)
@@ -836,10 +1003,14 @@ be a security risk on a single-user machine.
                 cmd = a
             if o == '-d':
                 debug = 1
+            if o == '-e':
+                edit = 1
             if o == '-i':
                 interactive = 1
             if o == '-r':
+                edit = 1
                 script = a
+                use_subprocess = 1
             if o == '-s':
                 startup = 1
             if o == '-t':
diff --git a/Lib/idlelib/RemoteDebugger.py b/Lib/idlelib/RemoteDebugger.py
new file mode 100644 (file)
index 0000000..0f8eac5
--- /dev/null
@@ -0,0 +1,287 @@
+"""Support for remote Python debugging.
+
+Some ASCII art to describe the structure:
+
+       IN PYTHON SUBPROCESS          #             IN IDLE PROCESS
+                                     #
+                                     #        oid='gui_adapter'
+                 +----------+        #       +------------+          +-----+
+                 | GUIProxy |--remote#call-->| GUIAdapter |--calls-->| GUI |
++-----+--calls-->+----------+        #       +------------+          +-----+
+| Idb |                               #                             /
++-----+<-calls--+------------+         #      +----------+<--calls-/
+                | IdbAdapter |<--remote#call--| IdbProxy |
+                +------------+         #      +----------+
+                oid='idb_adapter'      #
+
+The purpose of the Proxy and Adapter classes is to translate certain
+arguments and return values that cannot be transported through the RPC
+barrier, in particular frame and traceback objects.
+
+"""
+
+import sys
+import rpc
+import Debugger
+
+# In the PYTHON subprocess
+
+frametable = {}
+dicttable = {}
+codetable = {}
+
+def wrap_frame(frame):
+    fid = id(frame)
+    frametable[fid] = frame
+    return fid
+
+def wrap_info(info):
+    if info is None:
+        return None
+    else:
+        return None # XXX for now
+
+class GUIProxy:
+
+    def __init__(self, conn, oid):
+        self.conn = conn
+        self.oid = oid
+
+    def interaction(self, message, frame, info=None):
+        self.conn.remotecall(self.oid, "interaction",
+                             (message, wrap_frame(frame), wrap_info(info)),
+                             {})
+
+class IdbAdapter:
+
+    def __init__(self, idb):
+        self.idb = idb
+
+    def set_step(self):
+        self.idb.set_step()
+
+    def set_quit(self):
+        self.idb.set_quit()
+
+    def set_continue(self):
+        self.idb.set_continue()
+
+    def set_next(self, fid):
+        frame = frametable[fid]
+        self.idb.set_next(frame)
+
+    def set_return(self, fid):
+        frame = frametable[fid]
+        self.idb.set_return(frame)
+
+    def get_stack(self, fid, tbid):
+        ##print >>sys.__stderr__, "get_stack(%s, %s)" % (`fid`, `tbid`)
+        frame = frametable[fid]
+        tb = None # XXX for now
+        stack, i = self.idb.get_stack(frame, tb)
+        ##print >>sys.__stderr__, "get_stack() ->", stack
+        stack = [(wrap_frame(frame), k) for frame, k in stack]
+        ##print >>sys.__stderr__, "get_stack() ->", stack
+        return stack, i
+
+    def run(self, cmd):
+        import __main__
+        self.idb.run(cmd, __main__.__dict__)
+
+    def frame_attr(self, fid, name):
+        frame = frametable[fid]
+        return getattr(frame, name)
+
+    def frame_globals(self, fid):
+        frame = frametable[fid]
+        dict = frame.f_globals
+        did = id(dict)
+        dicttable[did] = dict
+        return did
+
+    def frame_locals(self, fid):
+        frame = frametable[fid]
+        dict = frame.f_locals
+        did = id(dict)
+        dicttable[did] = dict
+        return did
+
+    def frame_code(self, fid):
+        frame = frametable[fid]
+        code = frame.f_code
+        cid = id(code)
+        codetable[cid] = code
+        return cid
+
+    def code_name(self, cid):
+        code = codetable[cid]
+        return code.co_name
+
+    def code_filename(self, cid):
+        code = codetable[cid]
+        return code.co_filename
+
+    def dict_keys(self, did):
+        dict = dicttable[did]
+        return dict.keys()
+
+    def dict_item(self, did, key):
+        dict = dicttable[did]
+        value = dict[key]
+        try:
+            # Test for picklability
+            import cPickle
+            cPickle.dumps(value)
+        except:
+            value = None
+        return value
+
+def start_debugger(conn, gui_oid):
+    #
+    # launched in the python subprocess
+    #
+    gui = GUIProxy(conn, gui_oid)
+    idb = Debugger.Idb(gui)
+    ada = IdbAdapter(idb)
+    ada_oid = "idb_adapter"
+    conn.register(ada_oid, ada)
+    return ada_oid
+
+# In the IDLE process
+
+class FrameProxy:
+
+    def __init__(self, conn, fid):
+        self._conn = conn
+        self._fid = fid
+        self._oid = "idb_adapter"
+        self._dictcache = {}
+
+    def __getattr__(self, name):
+        if name[:1] == "_":
+            raise AttributeError, name
+        if name == "f_code":
+            return self._get_f_code()
+        if name == "f_globals":
+            return self._get_f_globals()
+        if name == "f_locals":
+            return self._get_f_locals()
+        return self._conn.remotecall(self._oid, "frame_attr",
+                                     (self._fid, name), {})
+
+    def _get_f_code(self):
+        cid = self._conn.remotecall(self._oid, "frame_code", (self._fid,), {})
+        return CodeProxy(self._conn, self._oid, cid)
+
+    def _get_f_globals(self):
+        did = self._conn.remotecall(self._oid, "frame_globals",
+                                    (self._fid,), {})
+        return self._get_dict_proxy(did)
+
+    def _get_f_locals(self):
+        did = self._conn.remotecall(self._oid, "frame_locals",
+                                    (self._fid,), {})
+        return self._get_dict_proxy(did)
+
+    def _get_dict_proxy(self, did):
+        if self._dictcache.has_key(did):
+            return self._dictcache[did]
+        dp = DictProxy(self._conn, self._oid, did)
+        self._dictcache[did] = dp
+        return dp
+
+class CodeProxy:
+
+    def __init__(self, conn, oid, cid):
+        self._conn = conn
+        self._oid = oid
+        self._cid = cid
+
+    def __getattr__(self, name):
+        if name == "co_name":
+            return self._conn.remotecall(self._oid, "code_name",
+                                         (self._cid,), {})
+        if name == "co_filename":
+            return self._conn.remotecall(self._oid, "code_filename",
+                                         (self._cid,), {})
+
+class DictProxy:
+
+    def __init__(self, conn, oid, did):
+        self._conn = conn
+        self._oid = oid
+        self._did = did
+
+    def keys(self):
+        return self._conn.remotecall(self._oid, "dict_keys", (self._did,), {})
+
+    def __getitem__(self, key):
+        return self._conn.remotecall(self._oid, "dict_item",
+                                     (self._did, key), {})
+
+    def __getattr__(self, name):
+        ##print >>sys.__stderr__, "failed DictProxy.__getattr__:", name
+        raise AttributeError, name
+
+class GUIAdaper:
+
+    def __init__(self, conn, gui):
+        self.conn = conn
+        self.gui = gui
+
+    def interaction(self, message, fid, iid):
+        print "interaction(%s, %s, %s)" % (`message`, `fid`, `iid`)
+        frame = FrameProxy(self.conn, fid)
+        info = None # XXX for now
+        self.gui.interaction(message, frame, info)
+
+class IdbProxy:
+
+    def __init__(self, conn, oid):
+        self.oid = oid
+        self.conn = conn
+
+    def call(self, methodname, *args, **kwargs):
+        ##print "call %s %s %s" % (methodname, args, kwargs)
+        value = self.conn.remotecall(self.oid, methodname, args, kwargs)
+        ##print "return %s" % `value`
+        return value
+
+    def run(self, cmd, locals):
+        # Ignores locals on purpose!
+        self.call("run", cmd)
+
+    def get_stack(self, frame, tb):
+        stack, i = self.call("get_stack", frame._fid, None)
+        stack = [(FrameProxy(self.conn, fid), k) for fid, k in stack]
+        return stack, i
+
+    def set_continue(self):
+        self.call("set_continue")
+
+    def set_step(self):
+        self.call("set_step")
+
+    def set_next(self, frame):
+        self.call("set_next", frame._fid)
+
+    def set_return(self, frame):
+        self.call("set_return", frame._fid)
+
+    def set_quit(self):
+        self.call("set_quit")
+
+def start_remote_debugger(conn, pyshell):
+    #
+    # instruct the (remote) subprocess to create
+    # a debugger instance, and lets it know that 
+    # the local GUIAdapter called "gui_adapter"
+    # is waiting notification of debugging events
+    #
+    ada_oid = "gui_adapter"
+    idb_oid = conn.remotecall("exec", "start_debugger", (ada_oid,), {})
+    idb = IdbProxy(conn, idb_oid)
+    gui = Debugger.Debugger(pyshell, idb)
+    ada = GUIAdaper(conn, gui)
+    conn.register(ada_oid, ada)
+    return gui
diff --git a/Lib/idlelib/RemoteObjectBrowser.py b/Lib/idlelib/RemoteObjectBrowser.py
new file mode 100644 (file)
index 0000000..6ba3391
--- /dev/null
@@ -0,0 +1,36 @@
+import rpc
+
+def remote_object_tree_item(item):
+    wrapper = WrappedObjectTreeItem(item)
+    oid = id(wrapper)
+    rpc.objecttable[oid] = wrapper
+    return oid
+
+class WrappedObjectTreeItem:
+    # Lives in PYTHON subprocess
+
+    def __init__(self, item):
+        self.__item = item
+
+    def __getattr__(self, name):
+        value = getattr(self.__item, name)
+        return value
+
+    def _GetSubList(self):
+        list = self.__item._GetSubList()
+        return map(remote_object_tree_item, list)
+
+class StubObjectTreeItem:
+    # Lives in IDLE process
+
+    def __init__(self, sockio, oid):
+        self.sockio = sockio
+        self.oid = oid
+
+    def __getattr__(self, name):
+        value = rpc.MethodProxy(self.sockio, self.oid, name)
+        return value
+
+    def _GetSubList(self):
+        list = self.sockio.remotecall(self.oid, "_GetSubList", (), {})
+        return [StubObjectTreeItem(self.sockio, oid) for oid in list]
index 2b3bf99267bea715a59830293a447676f9eecabf..906b4f2052688c22ce9941e011b6851e1788f9d8 100644 (file)
@@ -14,6 +14,14 @@ namespace.  Output goes to the shell window.
 - Run module (Control-F5) does the same but executes the module's
 code in the __main__ namespace.
 
+XXX Redesign this interface (yet again) as follows:
+
+- Present a dialog box for ``Run script''
+
+- Allow specify command line arguments in the dialog box
+
+- Restart the interpreter when running a script
+
 """
 
 import sys
@@ -25,9 +33,9 @@ indent_message = """Error: Inconsistent indentation detected!
 
 This means that either:
 
-(1) your indentation is outright incorrect (easy to fix), or
+1) your indentation is outright incorrect (easy to fix), or
 
-(2) your indentation mixes tabs and spaces in a way that depends on \
+2) your indentation mixes tabs and spaces in a way that depends on \
 how many spaces a tab is worth.
 
 To fix case 2, change all tabs to spaces by using Select All followed \
@@ -105,28 +113,31 @@ class ScriptBinding:
         return 1
 
     def import_module_event(self, event):
+        flist = self.editwin.flist
+        shell = flist.open_shell()
+        interp = shell.interp
+
         filename = self.getfilename()
         if not filename:
             return
 
         modname, ext = os.path.splitext(os.path.basename(filename))
-        if sys.modules.has_key(modname):
-            mod = sys.modules[modname]
-        else:
-            mod = imp.new_module(modname)
-            sys.modules[modname] = mod
-        mod.__file__ = filename
-        setattr(sys.modules['__main__'], modname, mod)
 
         dir = os.path.dirname(filename)
         dir = os.path.normpath(os.path.abspath(dir))
-        if dir not in sys.path:
-            sys.path.insert(0, dir)
 
-        flist = self.editwin.flist
-        shell = flist.open_shell()
-        interp = shell.interp
-        interp.runcode("reload(%s)" % modname)
+        interp.runcode("""if 1:
+            import sys as _sys
+            if %s not in _sys.path:
+                _sys.path.insert(0, %s)
+            if _sys.modules.get(%s):
+                del _sys
+                import %s
+                reload(%s)
+            else:
+                del _sys
+                import %s
+                \n""" % (`dir`, `dir`, `modname`, modname, modname, modname))
 
     def run_script_event(self, event):
         filename = self.getfilename()
@@ -136,10 +147,16 @@ class ScriptBinding:
         flist = self.editwin.flist
         shell = flist.open_shell()
         interp = shell.interp
-        if (not sys.argv or
-            os.path.basename(sys.argv[0]) != os.path.basename(filename)):
-            # XXX Too often this discards arguments the user just set...
-            sys.argv = [filename]
+        # XXX Too often this discards arguments the user just set...
+        interp.runcommand("""if 1:
+            _filename = %s
+            import sys as _sys
+            from os.path import basename as _basename
+            if (not _sys.argv or
+                _basename(_sys.argv[0]) != _basename(_filename)):
+                _sys.argv = [_filename]
+            del _filename, _sys, _basename
+                \n""" % `filename`)
         interp.execfile(filename)
 
     def getfilename(self):
diff --git a/Lib/idlelib/rpc.py b/Lib/idlelib/rpc.py
new file mode 100644 (file)
index 0000000..a4d2705
--- /dev/null
@@ -0,0 +1,530 @@
+# ASCII-art documentation
+#
+#  +---------------------------------+ +----------+
+#  | SocketServer.BaseRequestHandler | | SocketIO |
+#  +---------------------------------+ +----------+
+#                  ^                      ^  ^
+#                  |                      |  |
+#                  | + -------------------+  |
+#                  | |                       |
+#  +-------------------------+        +-----------------+
+#  | RPCHandler              |        | RPCClient       |
+#  |-------------------------|        |-----------------|
+#  | register()              |        | remotecall()    |
+#  | unregister()            |        | register()      | 
+#  |                         |        | unregister()    |
+#  |                         |        | get_remote_proxy|
+#  +-------------------------+        +-----------------+
+#
+import sys 
+import socket
+import select
+import SocketServer
+import struct
+import cPickle as pickle
+import threading
+import traceback
+import copy_reg
+import types
+import marshal
+
+def unpickle_code(ms):
+    co = marshal.loads(ms)
+    assert isinstance(co, types.CodeType)
+    return co
+
+def pickle_code(co):
+    assert isinstance(co, types.CodeType)
+    ms = marshal.dumps(co)
+    return unpickle_code, (ms,)
+
+def unpickle_function(ms):
+    return ms
+
+def pickle_function(fn):
+    assert isinstance(fn, type.FunctionType)
+    return `fn`
+copy_reg.pickle(types.CodeType, pickle_code, unpickle_code)
+copy_reg.pickle(types.FunctionType, pickle_function, unpickle_function)
+
+BUFSIZE = 8*1024
+
+class RPCServer(SocketServer.TCPServer):
+
+    def __init__(self, addr, handlerclass=None):
+        if handlerclass is None:
+            handlerclass = RPCHandler
+        self.objtable = objecttable 
+        SocketServer.TCPServer.__init__(self, addr, handlerclass)
+
+    def verify_request(self, request, client_address):
+        host, port = client_address
+        if host != "127.0.0.1":
+            print "Disallowed host:", host
+            return 0
+        else:
+            return 1
+
+    def register(self, oid, object):
+        self.objtable[oid] = object
+
+    def unregister(self, oid):
+        try:
+            del self.objtable[oid]
+        except KeyError:
+            pass
+
+
+objecttable = {}
+
+class SocketIO:
+
+    debugging = 0
+
+    def __init__(self, sock, objtable=None, debugging=None):
+        self.mainthread = threading.currentThread()
+        if debugging is not None:
+            self.debugging = debugging
+        self.sock = sock
+        if objtable is None:
+            objtable = objecttable
+        self.objtable = objtable
+        self.statelock = threading.Lock()
+        self.responses = {}
+        self.cvars = {}
+
+    def close(self):
+        sock = self.sock
+        self.sock = None
+        if sock is not None:
+            sock.close()
+
+    def debug(self, *args):
+        if not self.debugging:
+            return
+        s = str(threading.currentThread().getName())
+        for a in args:
+            s = s + " " + str(a)
+        s = s + "\n"
+        sys.__stderr__.write(s)
+
+    def register(self, oid, object):
+        self.objtable[oid] = object
+
+    def unregister(self, oid):
+        try:
+            del self.objtable[oid]
+        except KeyError:
+            pass
+
+    def localcall(self, request):
+        ##self.debug("localcall:", request) 
+        try:
+            how, (oid, methodname, args, kwargs) = request
+        except TypeError:
+            return ("ERROR", "Bad request format")
+        assert how == "call"
+        if not self.objtable.has_key(oid):
+            return ("ERROR", "Unknown object id: %s" % `oid`)
+        obj = self.objtable[oid]
+        if methodname == "__methods__":
+            methods = {}
+            _getmethods(obj, methods)
+            return ("OK", methods)
+        if methodname == "__attributes__":
+            attributes = {}
+            _getattributes(obj, attributes)
+            return ("OK", attributes)
+        if not hasattr(obj, methodname):
+            return ("ERROR", "Unsupported method name: %s" % `methodname`)
+        method = getattr(obj, methodname)
+        try:
+            ret = method(*args, **kwargs)
+            if isinstance(ret, RemoteObject):
+                ret = remoteref(ret)
+            return ("OK", ret)
+        except:
+            ##traceback.print_exc(file=sys.__stderr__)
+            typ, val, tb = info = sys.exc_info()
+            sys.last_type, sys.last_value, sys.last_traceback = info
+            if isinstance(typ, type(Exception)):
+                # Class exceptions
+                mod = typ.__module__
+                name = typ.__name__
+                if issubclass(typ, Exception):
+                    args = val.args
+                else:
+                    args = (str(val),)
+            else:
+                # String exceptions
+                mod = None
+                name = typ
+                args = (str(val),)
+            tb = traceback.extract_tb(tb)
+            return ("EXCEPTION", (mod, name, args, tb))
+
+    def remotecall(self, oid, methodname, args, kwargs):
+        seq = self.asynccall(oid, methodname, args, kwargs)
+        return self.asyncreturn(seq)
+
+    def asynccall(self, oid, methodname, args, kwargs):
+        request = ("call", (oid, methodname, args, kwargs))
+        seq = self.putrequest(request)
+        return seq
+
+    def asyncreturn(self, seq):
+        response = self.getresponse(seq)
+        return self.decoderesponse(response)
+
+    def decoderesponse(self, response):
+        how, what = response
+        if how == "OK":
+            return what
+        if how == "EXCEPTION":
+            mod, name, args, tb = what
+            self.traceback = tb
+            if mod:
+                try:
+                    __import__(mod)
+                    module = sys.modules[mod]
+                except ImportError:
+                    pass
+                else:
+                    try:
+                        cls = getattr(module, name)
+                    except AttributeError:
+                        pass
+                    else:
+                        raise getattr(__import__(mod), name)(*args)
+            else:
+                if mod:
+                    name = mod + "." + name
+                raise name, args
+        if how == "ERROR":
+            raise RuntimeError, what
+        raise SystemError, (how, what)
+
+    def mainloop(self):
+        try:
+            self.getresponse(None)
+        except EOFError:
+            pass
+
+    def getresponse(self, myseq):
+        response = self._getresponse(myseq)
+        if response is not None:
+            how, what = response
+            if how == "OK":
+                response = how, self._proxify(what)
+        return response
+
+    def _proxify(self, obj):
+        if isinstance(obj, RemoteProxy):
+            return RPCProxy(self, obj.oid)
+        if isinstance(obj, types.ListType):
+            return map(self._proxify, obj)
+        # XXX Check for other types -- not currently needed
+        return obj
+
+    def _getresponse(self, myseq):
+        if threading.currentThread() is self.mainthread:
+            # Main thread: does all reading of requests and responses
+            while 1:
+                response = self.pollresponse(myseq, None)
+                if response is not None:
+                    return response
+        else:
+            # Auxiliary thread: wait for notification from main thread
+            cvar = threading.Condition(self.statelock)
+            self.statelock.acquire()
+            self.cvars[myseq] = cvar
+            while not self.responses.has_key(myseq):
+                cvar.wait()
+            response = self.responses[myseq]
+            del self.responses[myseq]
+            del self.cvars[myseq]
+            self.statelock.release()
+            return response
+
+    def putrequest(self, request):
+        seq = self.newseq()
+        self.putmessage((seq, request))
+        return seq
+
+    nextseq = 0
+
+    def newseq(self):
+        self.nextseq = seq = self.nextseq + 2
+        return seq
+
+    def putmessage(self, message):
+        try:
+            s = pickle.dumps(message)
+        except:
+            print >>sys.__stderr__, "Cannot pickle:", `message`
+            raise
+        s = struct.pack("<i", len(s)) + s
+        while len(s) > 0:
+            n = self.sock.send(s)
+            s = s[n:]
+
+    def ioready(self, wait=0.0):
+        r, w, x = select.select([self.sock.fileno()], [], [], wait)
+        return len(r)
+
+    buffer = ""
+    bufneed = 4
+    bufstate = 0 # meaning: 0 => reading count; 1 => reading data
+
+    def pollpacket(self, wait=0.0):
+        self._stage0()
+        if len(self.buffer) < self.bufneed:
+            if not self.ioready(wait):
+                return None
+            try:
+                s = self.sock.recv(BUFSIZE)
+            except socket.error:
+                raise EOFError
+            if len(s) == 0:
+                raise EOFError
+            self.buffer += s
+            self._stage0()
+        return self._stage1()
+
+    def _stage0(self):
+        if self.bufstate == 0 and len(self.buffer) >= 4:
+            s = self.buffer[:4]
+            self.buffer = self.buffer[4:]
+            self.bufneed = struct.unpack("<i", s)[0]
+            self.bufstate = 1
+
+    def _stage1(self):
+        if self.bufstate == 1 and len(self.buffer) >= self.bufneed:
+            packet = self.buffer[:self.bufneed]
+            self.buffer = self.buffer[self.bufneed:]
+            self.bufneed = 4
+            self.bufstate = 0
+            return packet
+
+    def pollmessage(self, wait=0.0):
+        packet = self.pollpacket(wait)
+        if packet is None:
+            return None
+        try:
+            message = pickle.loads(packet)
+        except:
+            print >>sys.__stderr__, "-----------------------"
+            print >>sys.__stderr__, "cannot unpickle packet:", `packet`
+            traceback.print_stack(file=sys.__stderr__)
+            print >>sys.__stderr__, "-----------------------"
+            raise
+        return message
+
+    def pollresponse(self, myseq, wait=0.0):
+        # Loop while there's no more buffered input or until specific response
+        while 1:
+            message = self.pollmessage(wait)
+            if message is None:
+                return None
+            wait = 0.0
+            seq, resq = message
+            if resq[0] == "call":
+                response = self.localcall(resq)
+                self.putmessage((seq, response))
+                continue
+            elif seq == myseq:
+                return resq
+            else:
+                self.statelock.acquire()
+                self.responses[seq] = resq
+                cv = self.cvars.get(seq)
+                if cv is not None:
+                    cv.notify()
+                self.statelock.release()
+                continue
+
+class RemoteObject:
+    # Token mix-in class
+    pass
+
+def remoteref(obj):
+    oid = id(obj)
+    objecttable[oid] = obj
+    return RemoteProxy(oid)
+
+class RemoteProxy:
+
+    def __init__(self, oid):
+        self.oid = oid
+
+class RPCHandler(SocketServer.BaseRequestHandler, SocketIO):
+
+    debugging = 0
+
+    def __init__(self, sock, addr, svr):
+        svr.current_handler = self ## cgt xxx
+        SocketIO.__init__(self, sock)
+        SocketServer.BaseRequestHandler.__init__(self, sock, addr, svr)
+
+    def setup(self):
+        SocketServer.BaseRequestHandler.setup(self)
+        print >>sys.__stderr__, "Connection from", self.client_address
+
+    def finish(self):
+        print >>sys.__stderr__, "End connection from", self.client_address
+        SocketServer.BaseRequestHandler.finish(self)
+
+    def handle(self):
+        self.mainloop()
+
+    def get_remote_proxy(self, oid):
+        return RPCProxy(self, oid)
+
+class RPCClient(SocketIO):
+
+    nextseq = 1 # Requests coming from the client are odd
+
+    def __init__(self, address, family=socket.AF_INET, type=socket.SOCK_STREAM):
+        sock = socket.socket(family, type)
+        sock.connect(address)
+        SocketIO.__init__(self, sock)
+
+    def get_remote_proxy(self, oid):
+        return RPCProxy(self, oid)
+
+class RPCProxy:
+
+    __methods = None
+    __attributes = None
+
+    def __init__(self, sockio, oid):
+        self.sockio = sockio
+        self.oid = oid
+
+    def __getattr__(self, name):
+        if self.__methods is None:
+            self.__getmethods()
+        if self.__methods.get(name):
+            return MethodProxy(self.sockio, self.oid, name)
+        if self.__attributes is None:
+            self.__getattributes()
+        if not self.__attributes.has_key(name):
+            raise AttributeError, name
+    __getattr__.DebuggerStepThrough=1
+
+    def __getattributes(self):
+        self.__attributes = self.sockio.remotecall(self.oid,
+                                                "__attributes__", (), {})
+
+    def __getmethods(self):
+        self.__methods = self.sockio.remotecall(self.oid,
+                                                "__methods__", (), {})
+
+def _getmethods(obj, methods):
+    # Helper to get a list of methods from an object
+    # Adds names to dictionary argument 'methods'
+    for name in dir(obj):
+        attr = getattr(obj, name)
+        if callable(attr):
+            methods[name] = 1
+    if type(obj) == types.InstanceType:
+        _getmethods(obj.__class__, methods)
+    if type(obj) == types.ClassType:
+        for super in obj.__bases__:
+            _getmethods(super, methods)
+
+def _getattributes(obj, attributes):
+    for name in dir(obj):
+        attr = getattr(obj, name)
+        if not callable(attr):
+           attributes[name] = 1
+
+class MethodProxy:
+
+    def __init__(self, sockio, oid, name):
+        self.sockio = sockio
+        self.oid = oid
+        self.name = name
+
+    def __call__(self, *args, **kwargs):
+        value = self.sockio.remotecall(self.oid, self.name, args, kwargs)
+        return value
+
+#
+# Self Test
+#
+
+def testServer(addr):
+    class RemotePerson:
+        def __init__(self,name):
+            self.name = name 
+        def greet(self, name):
+            print "(someone called greet)"
+            print "Hello %s, I am %s." % (name, self.name)
+            print
+        def getName(self):
+            print "(someone called getName)"
+            print 
+            return self.name
+        def greet_this_guy(self, name):
+            print "(someone called greet_this_guy)"
+            print "About to greet %s ..." % name
+            remote_guy = self.server.current_handler.get_remote_proxy(name)
+            remote_guy.greet("Thomas Edison")
+            print "Done."
+            print
+            
+    person = RemotePerson("Thomas Edison")
+    svr = RPCServer(addr)
+    svr.register('thomas', person)
+    person.server = svr # only required if callbacks are used
+
+    # svr.serve_forever()
+    svr.handle_request()  # process once only
+
+def testClient(addr):
+
+    #
+    # demonstrates RPC Client
+    #
+    import time
+    clt=RPCClient(addr)
+    thomas = clt.get_remote_proxy("thomas")
+    print "The remote person's name is ..."
+    print thomas.getName()
+    # print clt.remotecall("thomas", "getName", (), {})
+    print
+    time.sleep(1)
+    print "Getting remote thomas to say hi..."
+    thomas.greet("Alexander Bell")
+    #clt.remotecall("thomas","greet",("Alexander Bell",), {})
+    print "Done."
+    print 
+    time.sleep(2)
+    
+    # demonstrates remote server calling local instance
+    class LocalPerson:
+        def __init__(self,name):
+            self.name = name 
+        def greet(self, name):
+            print "You've greeted me!"
+        def getName(self):
+            return self.name
+    person = LocalPerson("Alexander Bell")
+    clt.register("alexander",person)
+    thomas.greet_this_guy("alexander")
+    # clt.remotecall("thomas","greet_this_guy",("alexander",), {})
+
+def test():
+    addr=("localhost",8833)
+    if len(sys.argv) == 2:
+        if sys.argv[1]=='-server':
+            testServer(addr)
+            return
+    testClient(addr)
+
+if __name__ == '__main__':
+    test()
+
+        
diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py
new file mode 100644 (file)
index 0000000..1b84d4d
--- /dev/null
@@ -0,0 +1,49 @@
+import sys
+import rpc
+
+def main():
+    port = 8833
+    if sys.argv[1:]:
+        port = int(sys.argv[1])
+    sys.argv[:] = [""]
+    addr = ("localhost", port)
+    svr = rpc.RPCServer(addr, MyHandler)
+    svr.handle_request() # A single request only
+
+class MyHandler(rpc.RPCHandler):
+
+    def handle(self):
+        executive = Executive(self)
+        self.register("exec", executive)
+        sys.stdin = self.get_remote_proxy("stdin")
+        sys.stdout = self.get_remote_proxy("stdout")
+        sys.stderr = self.get_remote_proxy("stderr")
+        rpc.RPCHandler.handle(self)
+
+class Executive:
+
+    def __init__(self, rpchandler):
+        self.conn = rpchandler
+        import __main__
+        self.locals = __main__.__dict__
+
+    def runcode(self, code):
+        exec code in self.locals
+
+    def start_debugger(self, gui_oid):
+        import RemoteDebugger
+        return RemoteDebugger.start_debugger(self.conn, gui_oid)
+
+    def stackviewer(self, flist_oid=None):
+        if not hasattr(sys, "last_traceback"):
+            return None
+        flist = None
+        if flist_oid is not None:
+            flist = self.conn.get_remote_proxy(flist_oid)
+        import RemoteObjectBrowser
+        import StackViewer
+        tb = sys.last_traceback
+        while tb and tb.tb_frame.f_globals["__name__"] in ["rpc", "run"]:
+            tb = tb.tb_next
+        item = StackViewer.StackTreeItem(flist, tb)
+        return RemoteObjectBrowser.remote_object_tree_item(item)