summaryrefslogtreecommitdiffstats
path: root/Lib
diff options
context:
space:
mode:
Diffstat (limited to 'Lib')
-rw-r--r--Lib/idlelib/Debugger.py140
-rw-r--r--Lib/idlelib/PyShell.py205
-rw-r--r--Lib/idlelib/RemoteDebugger.py287
-rw-r--r--Lib/idlelib/RemoteObjectBrowser.py36
-rw-r--r--Lib/idlelib/ScriptBinding.py55
-rw-r--r--Lib/idlelib/rpc.py530
-rw-r--r--Lib/idlelib/run.py49
7 files changed, 1208 insertions, 94 deletions
diff --git a/Lib/idlelib/Debugger.py b/Lib/idlelib/Debugger.py
index 949a0f8..a483168 100644
--- a/Lib/idlelib/Debugger.py
+++ b/Lib/idlelib/Debugger.py
@@ -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)
diff --git a/Lib/idlelib/PyShell.py b/Lib/idlelib/PyShell.py
index 9616e35..3e512e3 100644
--- a/Lib/idlelib/PyShell.py
+++ b/Lib/idlelib/PyShell.py
@@ -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
index 0000000..0f8eac5
--- /dev/null
+++ b/Lib/idlelib/RemoteDebugger.py
@@ -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
index 0000000..6ba3391
--- /dev/null
+++ b/Lib/idlelib/RemoteObjectBrowser.py
@@ -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]
diff --git a/Lib/idlelib/ScriptBinding.py b/Lib/idlelib/ScriptBinding.py
index 2b3bf99..906b4f2 100644
--- a/Lib/idlelib/ScriptBinding.py
+++ b/Lib/idlelib/ScriptBinding.py
@@ -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
index 0000000..a4d2705
--- /dev/null
+++ b/Lib/idlelib/rpc.py
@@ -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
index 0000000..1b84d4d
--- /dev/null
+++ b/Lib/idlelib/run.py
@@ -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)