# shell.py #---------------------------------------------------------------------- # 12/10/2003 - Jeff Grimmett (grimmtooth@softhome.net) # # o 2.5 compatability update. # o Added deprecation warning. # """wxPython interactive shell Copyright (c) 1999 SIA "ANK" this module is free software. it may be used under same terms as Python itself Notes: i would like to use command completion (see rlcompleter library module), but i cannot load it because i don't have readline... History: 03-oct-1999 [als] created 04-oct-1999 [als] PyShellOutput.intro moved from __init__ parameters to class attributes; html debug disabled 04-oct-1999 [als] fixed bug with class attributes input prompts and output styles added to customized demo some html cleanups 04-oct-1999 [rpd] Changed to use the new sizers 05-oct-1999 [als] changes inspired by code.InteractiveInterpreter() from Python Library. if i knew about this class earlier, i would rather inherit from it. renamed to wxPyShell.py since i've renounced the 8.3 scheme 8-10-2001 THIS MODULE IS NOW DEPRECATED. Please see the most excellent PyCrust package instead. """ __version__ ="$Revision$" # $RCSfile$ import code import sys import traceback import warnings import wx import wx.html warningmsg = r"""\ ########################################\ # THIS MODULE IS NOW DEPRECATED | # | # Please see the most excellent PyCrust | # package instead. | ########################################/ """ warnings.warn(warningmsg, DeprecationWarning, stacklevel=2) #---------------------------------------------------------------------- class PyShellInput(wx.Panel): """PyShell input window """ PS1 =" Enter Command:" PS2 ="... continue:" def __init__(self, parent, shell, id=-1): """Create input window shell must be a PyShell object. it is used for exception handling, eval() namespaces, and shell.output is used for output (print's go to overridden stdout) """ wx.Panel.__init__(self, parent, id) self.shell =shell # make a private copy of class attrs self.PS1 =PyShellInput.PS1 self.PS2 =PyShellInput.PS2 # create controls self.label =wx.StaticText(self, -1, self.PS1) tid =wx.NewId() self.entry =wx.TextCtrl(self, tid, style = wx.TE_MULTILINE) self.entry.Bind(wx.EVT_CHAR, self.OnChar) self.entry.SetFont(wx.Font(9, wx.MODERN, wx.NORMAL, wx.NORMAL, False)) sizer =wx.BoxSizer(wx.VERTICAL) sizer.AddMany([(self.label, 0, wx.EXPAND), (self.entry, 1, wx.EXPAND)]) self.SetSizer(sizer) self.SetAutoLayout(True) self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) # when in "continuation" mode, # two consecutive newlines are required # to avoid execution of unfinished block self.first_line =1 def OnSetFocus(self, event): self.entry.SetFocus() def Clear(self, event=None): """reset input state""" self.label.SetLabel(self.PS1) self.label.Refresh() self.entry.SetSelection(0, self.entry.GetLastPosition()) self.first_line =1 # self.entry.SetFocus() def OnChar(self, event): """called on CHARevent. executes input on newline""" # print "On Char:", event.__dict__.keys() if event.KeyCode() !=wx.WXK_RETURN: # not of our business event.Skip() return text =self.entry.GetValue() # weird CRLF thingy text = text.replace("\r\n", "\n") # see if we've finished if (not (self.first_line or text[-1] =="\n") # in continuation mode or (text[-1] =="\\") # escaped newline ): # XXX should escaped newline put myself i "continuation" mode? event.Skip() return # ok, we can try to execute this rc =self.shell.TryExec(text) if rc: # code is incomplete; continue input if self.first_line: self.label.SetLabel(self.PS2) self.label.Refresh() self.first_line =0 event.Skip() else: self.Clear() class PyShellOutput(wx.Panel): """PyShell output window for now, it is based on simple wxTextCtrl, but i'm looking at HTML classes to provide colorized output """ # attributes for for different (input, output, exception) display styles: # begin tag, end tag, newline in_style =(" >>> ", "
\n", "
\n... ") out_style =("", "\n", "
\n") exc_style =("", "\n", "
\n") intro ="

wxPython Interactive Shell

\n" html_debug =0 # entity references erefs =(("&", "&"), (">", ">"), ("<", "<"), (" ", "  ")) def __init__(self, parent, id=-1): wx.Panel.__init__(self, parent, id) # make a private copy of class attrs self.in_style =PyShellOutput.in_style self.out_style =PyShellOutput.out_style self.exc_style =PyShellOutput.exc_style self.intro =PyShellOutput.intro self.html_debug =PyShellOutput.html_debug # create windows if self.html_debug: # this was used in html debugging, # but i don't want to delete it; it's funny splitter =wx.SplitterWindow(self, -1) self.view =wx.TextCtrl(splitter, -1, style = wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL) self.html =wx.html.HtmlWindow(splitter) splitter.SplitVertically(self.view, self.html) splitter.SetSashPosition(40) splitter.SetMinimumPaneSize(3) self.client =splitter else: self.view =None self.html =wx.html.HtmlWindow(self) self.client =self.html # used in OnSize() self.text =self.intro self.html.SetPage(self.text) self.html.SetAutoLayout(True) self.line_buffer ="" # refreshes are annoying self.in_batch =0 self.dirty =0 self.Bind(wx.EVT_SIZE, self.OnSize) self.Bind(wx.EVT_IDLE, self.OnIdle) def OnSize(self, event): self.client.SetSize(self.GetClientSize()) def OnIdle(self, event): """when there's nothing to do, we can update display""" if self.in_batch and self.dirty: self.UpdWindow() def BeginBatch(self): """do not refresh display till EndBatch()""" self.in_batch =1 def EndBatch(self): """end batch; start updating display immediately""" self.in_batch =0 if self.dirty: self.UpdWindow() def UpdWindow(self): """sync display with text buffer""" html =self.html html.SetPage(self.text) self.dirty =0 # scroll to the end (x,y) =html.GetVirtualSize() html.Scroll(0, y) def AddText(self, text, style=None): """write text to output window""" # a trick needed to defer default from compile-time to execute-time if style ==None: style =self.out_style if 0 and __debug__: sys.__stdout__.write(text) # handle entities for (symbol, eref) in self.erefs: text = text.replace(symbol, eref) # replace newlines text = text.replace("\n", style[2]) # add to contents self.text =self.text +style[0] +text +style[1] if not self.in_batch: self.UpdWindow() else: self.dirty =1 if self.html_debug: # html debug output needn't to be too large self.view.SetValue(self.text[-4096:]) def write(self, str, style=None): """stdout-like interface""" if style ==None: style =self.out_style # do not process incomplete lines if len(str) <1: # hm... what was i supposed to do? return elif str[-1] !="\n": self.line_buffer =self.line_buffer +str else: self.AddText(self.line_buffer +str, style) self.line_buffer ="" def flush(self, style=None): """write out all that was left in line buffer""" if style ==None: style =self.out_style self.AddText(self.line_buffer +"\n", style) def write_in(self, str, style=None): """write text in "input" style""" if style ==None: style =self.in_style self.AddText(str, style) def write_exc(self, str, style=None): """write text in "exception" style""" if style ==None: style =self.exc_style self.AddText(str, style) class PyShell(wx.Panel): """interactive Python shell with wxPython interface """ def __init__(self, parent, globals=globals(), locals={}, id=-1, pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.TAB_TRAVERSAL, name="shell"): """create PyShell window""" wx.Panel.__init__(self, parent, id, pos, size, style, name) self.globals =globals self.locals =locals splitter =wx.SplitterWindow(self, -1) self.output =PyShellOutput(splitter) self.input =PyShellInput(splitter, self) self.input.SetFocus() splitter.SplitHorizontally(self.input, self.output) splitter.SetSashPosition(100) splitter.SetMinimumPaneSize(20) self.splitter =splitter self.Bind(wx.EVT_SET_FOCUS, self.OnSetFocus) self.Bind(wx.EVT_SIZE, self.OnSize) def OnSetFocus(self, event): self.input.SetFocus() def TryExec(self, source, symbol="single"): """Compile and run some source in the interpreter. borrowed from code.InteractiveInterpreter().runsource() as i said above, i would rather like to inherit from that class returns 1 if more input is required, or 0, otherwise """ try: cc = code.compile_command(source, symbol=symbol) except (OverflowError, SyntaxError): # [als] hm... never seen anything of that kind self.ShowSyntaxError() return 0 if cc is None: # source is incomplete return 1 # source is sucessfully compiled out =self.output # redirect system stdout to the output window prev_out =sys.stdout sys.stdout =out # begin printout batch (html updates are deferred until EndBatch()) out.BeginBatch() out.write_in(source) try: exec cc in self.globals, self.locals except SystemExit: # SystemExit is not handled and has to be re-raised raise except: # all other exceptions produce traceback output self.ShowException() # switch back to saved stdout sys.stdout =prev_out # commit printout out.flush() out.EndBatch() return 0 def ShowException(self): """display the traceback for the latest exception""" (etype, value, tb) =sys.exc_info() # remove myself from traceback tblist =traceback.extract_tb(tb)[1:] msg = ' '.join(traceback.format_exception_only(etype, value) +traceback.format_list(tblist)) self.output.write_exc(msg) def ShowSyntaxError(self): """display message about syntax error (no traceback here)""" (etype, value, tb) =sys.exc_info() msg = ' '.join(traceback.format_exception_only(etype, value)) self.output.write_exc(msg) def OnSize(self, event): self.splitter.SetSize(self.GetClientSize()) #---------------------------------------------------------------------- if __name__ == '__main__': class MyFrame(wx.Frame): """Very standard Frame class. Nothing special here!""" def __init__(self, parent=None, id =-1, title="wxPython Interactive Shell"): wx.Frame.__init__(self, parent, id, title) self.shell =PyShell(self) class MyApp(wx.App): """Demonstrates usage of both default and customized shells""" def OnInit(self): frame = MyFrame() frame.Show(True) self.SetTopWindow(frame) ## PyShellInput.PS1 =" let's get some work done..." ## PyShellInput.PS2 =" ok, what do you really mean?" ## PyShellOutput.in_style =( ## ">>> ", ## "
\n", "
\n... ") ## PyShellOutput.out_style =( ## "", ## "
\n", "
\n") ## PyShellOutput.exc_style =("", ## "\n", "
\n") ## PyShellOutput.intro ="Customized wxPython Shell" \ ## "
<-- move this sash to see html debug output

\n" ## PyShellOutput.html_debug =1 ## frame = MyFrame(title="Customized wxPython Shell") ## frame.Show(True) return True app = MyApp(0) app.MainLoop()