#@+leo-ver=4-thin
#@+node:ekr.20060517102054:@thin leo_Shell.py
#@@language python
#@@tabwidth -4
#@<< imports >>
#@+node:ekr.20060517102458:<< imports >>
import leoGlobals as g
import leo_RemoteDebugger
from code import InteractiveInterpreter
import idlelib.rpc as rpc
import linecache
import os
import sys
import time
#@nonl
#@-node:ekr.20060517102458:<< imports >>
#@nl
LOCALHOST = '127.0.0.1'
#@+others
#@+node:ekr.20060516135654.97:class ModifiedInterpreter
class ModifiedInterpreter(InteractiveInterpreter):
#@ @+others
#@+node:ekr.20060516135654.98:__init__
def __init__(self, tkconsole):
self.tkconsole = tkconsole # Required by later rpc registrations.
self.active_seq = None
self.port = 8833
self.rpcclt = None
self.rpcpid = None
locals = sys.modules['__main__'].__dict__
InteractiveInterpreter.__init__(self, locals=locals)
self.save_warnings_filters = None
self.restarting = False
self.subprocess_arglist = self.build_subprocess_arglist()
#@nonl
#@-node:ekr.20060516135654.98:__init__
#@+node:ekr.20060516135654.99:spawn_subprocess (sets self.rpcpid)
def spawn_subprocess(self):
args = self.subprocess_arglist
self.rpcpid = os.spawnv(os.P_NOWAIT, sys.executable, args)
g.trace('os.spawnv returns rpcpid',self.rpcpid)
#@nonl
#@-node:ekr.20060516135654.99:spawn_subprocess (sets self.rpcpid)
#@+node:ekr.20060516135654.100:leo_Debugger.build_subprocess_arglist
def build_subprocess_arglist(self):
w = ['-W' + s for s in sys.warnoptions]
# Maybe IDLE is installed and is being accessed via sys.path,
# or maybe it's not installed and the idle.py script is being
# run from the IDLE source directory.
if 1: # EKR
del_exitf = False
else:
del_exitf = idleConf.GetOption(
'main', 'General', 'delete-exitfunc',
default=False, type='bool')
###if __name__ == 'idlelib.PyShell':
if 1: # Works only if leo/src is put in sys.path in sitecustomize or in the Python PATH variable.
command = "__import__('leo_run').main(%r)" % (del_exitf,)
elif 1: # EKR: Works using idlelib.run.
command = "__import__('idlelib.run').run.main(%r)" % (del_exitf,)
else:
command = "__import__('run').main(%r)" % (del_exitf,)
if sys.platform[:3] == 'win' and ' ' in sys.executable:
# handle embedded space in path by quoting the argument
decorated_exec = '"%s"' % sys.executable
else:
decorated_exec = sys.executable
return [decorated_exec] + w + ["-c", command, str(self.port)]
#@nonl
#@-node:ekr.20060516135654.100:leo_Debugger.build_subprocess_arglist
#@+node:ekr.20060516135654.101:start_subprocess
def start_subprocess(self):
# spawning first avoids passing a listening socket to the subprocess
self.spawn_subprocess()
#time.sleep(20) # test to simulate GUI not accepting connection
addr = (LOCALHOST, self.port)
# Idle starts listening for connection on localhost
for i in range(3):
time.sleep(i)
try:
self.rpcclt = MyRPCClient(addr)
g.trace(self.rpcclt)
break
except socket.error, err:
pass
else:
self.display_port_binding_error()
return None
# Accept the connection from the Python execution server
self.rpcclt.listening_sock.settimeout(10)
try:
self.rpcclt.accept()
except socket.timeout, err:
self.display_no_subprocess_error()
return None
self.rpcclt.register("stdin", self.tkconsole)
self.rpcclt.register("stdout", self.tkconsole.stdout)
self.rpcclt.register("stderr", self.tkconsole.stderr)
self.rpcclt.register("flist", self.tkconsole.flist)
self.rpcclt.register("linecache", linecache)
self.rpcclt.register("interp", self)
self.transfer_path()
self.poll_subprocess()
return self.rpcclt
#@-node:ekr.20060516135654.101:start_subprocess
#@+node:ekr.20060516135654.102:restart_subprocess
def restart_subprocess(self):
if self.restarting:
return self.rpcclt
self.restarting = True
# close only the subprocess debugger
debug = self.getdebugger()
if debug:
try:
# Only close subprocess debugger, don't unregister gui_adap!
RemoteDebugger.close_subprocess_debugger(self.rpcclt)
except:
pass
# Kill subprocess, spawn a new one, accept connection.
self.rpcclt.close()
self.unix_terminate()
console = self.tkconsole
was_executing = console.executing
console.executing = False
self.spawn_subprocess()
try:
self.rpcclt.accept()
except socket.timeout, err:
self.display_no_subprocess_error()
return None
self.transfer_path()
# annotate restart in shell window and mark it
console.text.delete("iomark", "end-1c")
if was_executing:
console.write('\n')
console.showprompt()
halfbar = ((int(console.width) - 16) // 2) * '='
console.write(halfbar + ' RESTART ' + halfbar)
console.text.mark_set("restart", "end-1c")
console.text.mark_gravity("restart", "left")
console.showprompt()
# restart subprocess debugger
if debug:
# Restarted debugger connects to current instance of debug GUI
gui = RemoteDebugger.restart_subprocess_debugger(self.rpcclt)
# reload remote debugger breakpoints for all PyShellEditWindows
debug.load_breakpoints()
self.restarting = False
return self.rpcclt
#@-node:ekr.20060516135654.102:restart_subprocess
#@+node:ekr.20060516135654.103:__request_interrupt
def __request_interrupt(self):
self.rpcclt.remotecall("exec", "interrupt_the_server", (), {})
#@-node:ekr.20060516135654.103:__request_interrupt
#@+node:ekr.20060516135654.104:interrupt_subprocess
def interrupt_subprocess(self):
threading.Thread(target=self.__request_interrupt).start()
#@-node:ekr.20060516135654.104:interrupt_subprocess
#@+node:ekr.20060516135654.105:kill_subprocess
def kill_subprocess(self):
try:
self.rpcclt.close()
except AttributeError: # no socket
pass
self.unix_terminate()
self.tkconsole.executing = False
self.rpcclt = None
#@-node:ekr.20060516135654.105:kill_subprocess
#@+node:ekr.20060516135654.106:unix_terminate
def unix_terminate(self):
"UNIX: make sure subprocess is terminated and collect status"
if hasattr(os, 'kill'):
try:
os.kill(self.rpcpid, SIGTERM)
except OSError:
# process already terminated:
return
else:
try:
os.waitpid(self.rpcpid, 0)
except OSError:
return
#@-node:ekr.20060516135654.106:unix_terminate
#@+node:ekr.20060516135654.107:transfer_path
def transfer_path(self):
self.runcommand("""if 1:
import sys as _sys
_sys.path = %r
del _sys
_msg = 'Use File/Exit or your end-of-file key to quit Leo'
__builtins__.quit = __builtins__.exit = _msg
del _msg
\n""" % (sys.path,))
#@-node:ekr.20060516135654.107:transfer_path
#@+node:ekr.20060516135654.108:poll_subprocess
def poll_subprocess(self):
clt = self.rpcclt
if clt is None:
return
try:
response = clt.pollresponse(self.active_seq, wait=0.05)
except (EOFError, IOError, KeyboardInterrupt):
# lost connection or subprocess terminated itself, restart
# [the KBI is from rpc.SocketIO.handle_EOF()]
if self.tkconsole.closing:
return
response = None
self.restart_subprocess()
if response:
self.tkconsole.resetoutput()
self.active_seq = None
how, what = response
console = self.tkconsole.console
if how == "OK":
if what is not None:
print >>console, repr(what)
elif how == "EXCEPTION":
if self.tkconsole.getvar(virtual_event_name('toggle-jit-stack-viewer')):
self.remote_stack_viewer()
elif how == "ERROR":
errmsg = "PyShell.ModifiedInterpreter: Subprocess ERROR:\n"
print >>sys.__stderr__, errmsg, what
print >>console, errmsg, what
# we received a response to the currently active seq number:
self.tkconsole.endexecuting()
# Reschedule myself
if not self.tkconsole.closing:
self.tkconsole.text.after(self.tkconsole.pollinterval,
self.poll_subprocess)
#@-node:ekr.20060516135654.108:poll_subprocess
#@+node:ekr.20060516135654.109:setdebugger
debugger = None
def setdebugger(self, debugger):
self.debugger = debugger
#@-node:ekr.20060516135654.109:setdebugger
#@+node:ekr.20060516135654.110:getdebugger
def getdebugger(self):
return self.debugger
#@-node:ekr.20060516135654.110:getdebugger
#@+node:ekr.20060516135654.111:open_remote_stack_viewer
def open_remote_stack_viewer(self):
"""Initiate the remote stack viewer from a separate thread.
This method is called from the subprocess, and by returning from this
method we allow the subprocess to unblock. After a bit the shell
requests the subprocess to open the remote stack viewer which returns a
static object looking at the last exceptiopn. It is queried through
the RPC mechanism.
"""
self.tkconsole.text.after(300, self.remote_stack_viewer)
return
#@-node:ekr.20060516135654.111:open_remote_stack_viewer
#@+node:ekr.20060516135654.112:remote_stack_viewer
def remote_stack_viewer(self):
import RemoteObjectBrowser
oid = self.rpcclt.remotequeue("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)
theme = idleConf.GetOption('main','Theme','name')
background = idleConf.GetHighlight(theme, 'normal')['background']
sc = ScrolledCanvas(top, bg=background, highlightthickness=0)
sc.frame.pack(expand=1, fill="both")
node = TreeNode(sc.canvas, None, item)
node.expand()
#@-node:ekr.20060516135654.112:remote_stack_viewer
#@+node:ekr.20060516135654.113:execsource
# XXX Should GC the remote tree when closing the window
gid = 0
def execsource(self, source):
"Like runsource() but assumes complete exec source"
filename = self.stuffsource(source)
self.execfile(filename, source)
#@-node:ekr.20060516135654.113:execsource
#@+node:ekr.20060516135654.114:execfile
def execfile(self, filename, source=None):
g.trace(filename)
"Execute an existing file"
if source is None:
source = open(filename, "r").read()
try:
code = compile(source, filename, "exec")
except (OverflowError, SyntaxError):
self.tkconsole.resetoutput()
tkerr = self.tkconsole.stderr
print>>tkerr, '*** Error in script or command!\n'
print>>tkerr, 'Traceback (most recent call last):'
InteractiveInterpreter.showsyntaxerror(self, filename)
self.tkconsole.showprompt()
else:
self.runcode(code)
#@-node:ekr.20060516135654.114:execfile
#@+node:ekr.20060516135654.115:runsource
def runsource(self, source):
"Extend base class method: Stuff the source in the line cache first"
filename = self.stuffsource(source)
self.more = 0
self.save_warnings_filters = warnings.filters[:]
warnings.filterwarnings(action="error", category=SyntaxWarning)
if isinstance(source, types.UnicodeType):
import IOBinding
try:
source = source.encode(IOBinding.encoding)
except UnicodeError:
self.tkconsole.resetoutput()
self.write("Unsupported characters in input")
return
try:
return InteractiveInterpreter.runsource(self, source, filename)
finally:
if self.save_warnings_filters is not None:
warnings.filters[:] = self.save_warnings_filters
self.save_warnings_filters = None
#@-node:ekr.20060516135654.115:runsource
#@+node:ekr.20060516135654.116:stuffsource
def stuffsource(self, source):
"Stuff source in the filename cache"
filename = "<pyshell#%d>" % self.gid
self.gid = self.gid + 1
lines = source.split("\n")
linecache.cache[filename] = len(source)+1, 0, lines, filename
return filename
#@-node:ekr.20060516135654.116:stuffsource
#@+node:ekr.20060516135654.117:prepend_syspath
def prepend_syspath(self, filename):
"Prepend sys.path with file's directory if not already included"
self.runcommand("""if 1:
_filename = %r
import sys as _sys
from os.path import dirname as _dirname
_dir = _dirname(_filename)
if not _dir in _sys.path:
_sys.path.insert(0, _dir)
del _filename, _sys, _dirname, _dir
\n""" % (filename,))
#@-node:ekr.20060516135654.117:prepend_syspath
#@+node:ekr.20060516135654.118:showsyntaxerror
def showsyntaxerror(self, filename=None):
"""Extend base class method: Add Colorizing
Color the offending position instead of printing it and pointing at it
with a caret.
"""
text = self.tkconsole.text
stuff = self.unpackerror()
if stuff:
msg, lineno, offset, line = stuff
if lineno == 1:
pos = "iomark + %d chars" % (offset-1)
else:
pos = "iomark linestart + %d lines + %d chars" % \
(lineno-1, offset-1)
text.tag_add("ERROR", pos)
text.see(pos)
char = text.get(pos)
if char and char in IDENTCHARS:
text.tag_add("ERROR", pos + " wordstart", pos)
self.tkconsole.resetoutput()
self.write("SyntaxError: %s\n" % str(msg))
else:
self.tkconsole.resetoutput()
InteractiveInterpreter.showsyntaxerror(self, filename)
self.tkconsole.showprompt()
#@-node:ekr.20060516135654.118:showsyntaxerror
#@+node:ekr.20060516135654.119:unpackerror
def unpackerror(self):
type, value, tb = sys.exc_info()
ok = type is SyntaxError
if ok:
try:
msg, (dummy_filename, lineno, offset, line) = value
if not offset:
offset = 0
except:
ok = 0
if ok:
return msg, lineno, offset, line
else:
return None
#@-node:ekr.20060516135654.119:unpackerror
#@+node:ekr.20060516135654.120:showtraceback
def showtraceback(self):
"Extend base class method to reset output properly"
self.tkconsole.resetoutput()
self.checklinecache()
InteractiveInterpreter.showtraceback(self)
if self.tkconsole.getvar(g.virtual_event_name('toggle-jit-stack-viewer')):
self.tkconsole.open_stack_viewer()
#@nonl
#@-node:ekr.20060516135654.120:showtraceback
#@+node:ekr.20060516135654.121:checklinecache
def checklinecache(self):
c = linecache.cache
for key in c.keys():
if key[:1] + key[-1:] != "<>":
del c[key]
#@-node:ekr.20060516135654.121:checklinecache
#@+node:ekr.20060516135654.122:runcommand
def runcommand(self, code):
"Run the code without invoking the debugger"
# The code better not raise an exception!
if self.tkconsole.executing:
self.display_executing_dialog()
return 0
if self.rpcclt:
self.rpcclt.remotequeue("exec", "runcode", (code,), {})
else:
exec code in self.locals
return 1
#@-node:ekr.20060516135654.122:runcommand
#@+node:ekr.20060516135654.123:runcode
def runcode(self, code):
"Override base class method"
if self.tkconsole.executing:
self.interp.restart_subprocess()
self.checklinecache()
if self.save_warnings_filters is not None:
warnings.filters[:] = self.save_warnings_filters
self.save_warnings_filters = None
debugger = self.debugger
try:
self.tkconsole.beginexecuting()
try:
if not debugger and self.rpcclt is not None:
self.active_seq = self.rpcclt.asyncqueue("exec", "runcode",
(code,), {})
elif debugger:
debugger.run(code, self.locals)
else:
exec code in self.locals
except SystemExit:
if tkMessageBox.askyesno(
"Exit?",
"Do you want to exit altogether?",
default="yes",
master=self.tkconsole.text):
raise
else:
self.showtraceback()
except:
self.showtraceback()
finally:
if not use_subprocess:
self.tkconsole.endexecuting()
#@-node:ekr.20060516135654.123:runcode
#@+node:ekr.20060516135654.124:write
def write(self, s):
"Override base class method"
self.tkconsole.stderr.write(s)
#@-node:ekr.20060516135654.124:write
#@+node:ekr.20060516135654.125:display_port_binding_error
def display_port_binding_error(self):
tkMessageBox.showerror(
"Port Binding Error",
"Leo can't bind TCP/IP port 8833, which is necessary to "
"communicate with its Python execution server. Either "
"no networking is installed on this computer or another "
"process (another Leo?) is using the port. Run Leo with the -n "
"command line switch to start without a subprocess and refer to "
"Help/Leo Help 'Running without a subprocess' for further "
"details.",
master=self.tkconsole.text)
#@-node:ekr.20060516135654.125:display_port_binding_error
#@+node:ekr.20060516135654.126:display_no_subprocess_error
def display_no_subprocess_error(self):
tkMessageBox.showerror(
"Subprocess Startup Error",
"Leo's subprocess didn't make connection. Either Leo can't "
"start a subprocess or personal firewall software is blocking "
"the connection.",
master=self.tkconsole.text)
#@-node:ekr.20060516135654.126:display_no_subprocess_error
#@+node:ekr.20060516135654.127:display_executing_dialog
def display_executing_dialog(self):
if 1: ### EKR
g.trace('Executing a command. Please wait until it is finished')
else:
tkMessageBox.showerror(
"Already executing",
"The Python Shell window is already executing a command; "
"please wait until it is finished.",
master=self.tkconsole.text)
#@nonl
#@-node:ekr.20060516135654.127:display_executing_dialog
#@+node:ekr.20060516135654.128:close_debugger OVERRIDE
def close_debugger(self):
interp = self
db = interp.getdebugger()
if db:
interp.setdebugger(None)
db.close()
if interp.rpcclt:
leo_RemoteDebugger.close_remote_debugger(interp.rpcclt)
if 0: # original code
db = self.interp.getdebugger()
if db:
self.interp.setdebugger(None)
db.close()
if self.interp.rpcclt:
RemoteDebugger.close_remote_debugger(self.interp.rpcclt)
self.resetoutput()
self.console.write("[DEBUG OFF]\n")
sys.ps1 = ">>> "
self.showprompt()
self.set_debugger_indicator()
#@nonl
#@-node:ekr.20060516135654.128:close_debugger OVERRIDE
#@-others
#@nonl
#@-node:ekr.20060516135654.97:class ModifiedInterpreter
#@+node:ekr.20060516135654.129:class MyRPCClient
class MyRPCClient(rpc.RPCClient):
#@ @+others
#@+node:ekr.20060516135654.130:handle_EOF
def handle_EOF(self):
"Override the base class - just re-raise EOFError"
raise EOFError
#@nonl
#@-node:ekr.20060516135654.130:handle_EOF
#@-others
#@nonl
#@-node:ekr.20060516135654.129:class MyRPCClient
#@-others
#@nonl
#@-node:ekr.20060517102054:@thin leo_Shell.py
#@-leo
|