1 """The PyCrust Shell is an interactive text control in which a user types in 
   2 commands to be sent to the interpreter. This particular shell is based on 
   3 wxPython's wxStyledTextCtrl. The latest files are always available at the 
   4 SourceForge project page at http://sourceforge.net/projects/pycrust/. 
   5 Sponsored by Orbtech - Your Source For Python Development Services""" 
   7 __author__ 
= "Patrick K. O'Brien <pobrien@orbtech.com>" 
   9 __version__ 
= "$Revision$"[11:-2] 
  11 from wxPython
.wx 
import * 
  12 from wxPython
.stc 
import * 
  16 from pseudo 
import PseudoFileIn
 
  17 from pseudo 
import PseudoFileOut
 
  18 from pseudo 
import PseudoFileErr
 
  19 from version 
import VERSION
 
  22 if wxPlatform 
== '__WXMSW__': 
  23     faces 
= { 'times'  : 'Times New Roman', 
  24               'mono'   : 'Courier New', 
  25               'helv'   : 'Lucida Console', 
  26               'lucida' : 'Lucida Console', 
  27               'other'  : 'Comic Sans MS', 
  32     # Versions of wxPython prior to 2.3.2 had a sizing bug on Win platform. 
  33     # The font was 2 points too large. So we need to reduce the font size. 
  34     if (wxMAJOR_VERSION
, wxMINOR_VERSION
, wxRELEASE_NUMBER
) < (2, 3, 2): 
  38     faces 
= { 'times'  : 'Times', 
  41               'other'  : 'new century schoolbook', 
  49     """Simplified interface to all shell-related functionality. 
  51     This is a semi-transparent facade, in that all attributes of other are  
  52     still accessible, even though only some are visible to the user.""" 
  54     name 
= 'PyCrust Shell Interface' 
  55     revision 
= __version__
 
  57     def __init__(self
, other
): 
  58         """Create a ShellFacade instance.""" 
  70         for method 
in methods
: 
  71             self
.__dict
__[method
] = getattr(other
, method
) 
  77 Home              Go to the beginning of the command or line. 
  78 Shift+Home        Select to the beginning of the command or line. 
  79 Shift+End         Select to the end of the line. 
  80 End               Go to the end of the line. 
  81 Ctrl+C            Copy selected text, removing prompts. 
  82 Ctrl+Shift+C      Copy selected text, retaining prompts. 
  83 Ctrl+X            Cut selected text. 
  84 Ctrl+V            Paste from clipboard. 
  85 Ctrl+Shift+V      Paste and run multiple commands from clipboard. 
  86 Ctrl+Up Arrow     Retrieve Previous History item. 
  87 Alt+P             Retrieve Previous History item. 
  88 Ctrl+Down Arrow   Retrieve Next History item. 
  89 Alt+N             Retrieve Next History item. 
  90 Shift+Up Arrow    Insert Previous History item. 
  91 Shift+Down Arrow  Insert Next History item. 
  92 F8                Command-completion of History item. 
  93                   (Type a few characters of a previous command and then press F8.) 
  97         """Display some useful information about how to use the shell.""" 
  98         self
.write(self
.helpText
) 
 100     def __getattr__(self
, name
): 
 101         if hasattr(self
.other
, name
): 
 102             return getattr(self
.other
, name
) 
 104             raise AttributeError, name
 
 106     def __setattr__(self
, name
, value
): 
 107         if self
.__dict
__.has_key(name
): 
 108             self
.__dict
__[name
] = value
 
 109         elif hasattr(self
.other
, name
): 
 110             return setattr(self
.other
, name
, value
) 
 112             raise AttributeError, name
 
 114     def _getAttributeNames(self
): 
 115         """Return list of magic attributes to extend introspection.""" 
 116         list = ['autoCallTip', 
 118                 'autoCompleteCaseInsensitive', 
 119                 'autoCompleteIncludeDouble', 
 120                 'autoCompleteIncludeMagic', 
 121                 'autoCompleteIncludeSingle', 
 127 class Shell(wxStyledTextCtrl
): 
 128     """PyCrust Shell based on wxStyledTextCtrl.""" 
 130     name 
= 'PyCrust Shell' 
 131     revision 
= __version__
 
 133     def __init__(self
, parent
, id=-1, pos
=wxDefaultPosition
, \
 
 134                  size
=wxDefaultSize
, style
=wxCLIP_CHILDREN
, introText
='', \
 
 135                  locals=None, InterpClass
=None, *args
, **kwds
): 
 136         """Create a PyCrust Shell instance.""" 
 137         wxStyledTextCtrl
.__init
__(self
, parent
, id, pos
, size
, style
) 
 138         # Grab these so they can be restored by self.redirect* methods. 
 139         self
.stdin 
= sys
.stdin
 
 140         self
.stdout 
= sys
.stdout
 
 141         self
.stderr 
= sys
.stderr
 
 142         # Add the current working directory "." to the search path. 
 143         sys
.path
.insert(0, os
.curdir
) 
 144         # Import a default interpreter class if one isn't provided. 
 145         if InterpClass 
== None: 
 146             from interpreter 
import Interpreter
 
 148             Interpreter 
= InterpClass
 
 149         # Create default locals so we have something interesting. 
 150         shellLocals 
= {'__name__': 'PyCrust-Shell',  
 151                        '__doc__': 'PyCrust-Shell, The PyCrust Python Shell.', 
 152                        '__version__': VERSION
, 
 154         # Add the dictionary that was passed in. 
 156             shellLocals
.update(locals) 
 157         self
.interp 
= Interpreter(locals=shellLocals
, \
 
 158                                   rawin
=self
.readRaw
, \
 
 159                                   stdin
=PseudoFileIn(self
.readIn
), \
 
 160                                   stdout
=PseudoFileOut(self
.writeOut
), \
 
 161                                   stderr
=PseudoFileErr(self
.writeErr
), \
 
 163         # Keep track of the last non-continuation prompt positions. 
 164         self
.promptPosStart 
= 0 
 165         self
.promptPosEnd 
= 0 
 166         # Keep track of multi-line commands. 
 168         # Create the command history.  Commands are added into the front of 
 169         # the list (ie. at index 0) as they are entered. self.historyIndex 
 170         # is the current position in the history; it gets incremented as you 
 171         # retrieve the previous command, decremented as you retrieve the 
 172         # next, and reset when you hit Enter. self.historyIndex == -1 means 
 173         # you're on the current command, not in the history. 
 175         self
.historyIndex 
= -1 
 176         # Assign handlers for keyboard events. 
 177         EVT_KEY_DOWN(self
, self
.OnKeyDown
) 
 178         EVT_CHAR(self
, self
.OnChar
) 
 179         # Configure various defaults and user preferences. 
 181         # Display the introductory banner information. 
 182         try: self
.showIntro(introText
) 
 184         # Assign some pseudo keywords to the interpreter's namespace. 
 185         try: self
.setBuiltinKeywords() 
 187         # Add 'shell' to the interpreter's local namespace. 
 188         try: self
.setLocalShell() 
 190         # Do this last so the user has complete control over their 
 191         # environment. They can override anything they want. 
 192         try: self
.execStartupScript(self
.interp
.startupScript
) 
 199         """Configure shell based on user preferences.""" 
 200         self
.SetMarginType(1, wxSTC_MARGIN_NUMBER
) 
 201         self
.SetMarginWidth(1, 40) 
 203         self
.SetLexer(wxSTC_LEX_PYTHON
) 
 204         self
.SetKeyWords(0, ' '.join(keyword
.kwlist
)) 
 206         self
.setStyles(faces
) 
 207         self
.SetViewWhiteSpace(0) 
 210         # Do we want to automatically pop up command completion options? 
 211         self
.autoComplete 
= 1 
 212         self
.autoCompleteIncludeMagic 
= 1 
 213         self
.autoCompleteIncludeSingle 
= 1 
 214         self
.autoCompleteIncludeDouble 
= 1 
 215         self
.autoCompleteCaseInsensitive 
= 1 
 216         self
.AutoCompSetIgnoreCase(self
.autoCompleteCaseInsensitive
) 
 217         # Do we want to automatically pop up command argument help? 
 219         self
.CallTipSetBackground(wxColour(255, 255, 232)) 
 221     def showIntro(self
, text
=''): 
 222         """Display introductory text in the shell.""" 
 224             if not text
.endswith(os
.linesep
): text 
+= os
.linesep
 
 227             self
.write(self
.interp
.introText
) 
 228         except AttributeError: 
 231     def setBuiltinKeywords(self
): 
 232         """Create pseudo keywords as part of builtins. 
 234         This is a rather clever hack that sets "close", "exit" and "quit"  
 235         to a PseudoKeyword object so that we can make them do what we want. 
 236         In this case what we want is to call our self.quit() method. 
 237         The user can type "close", "exit" or "quit" without the final parens. 
 239 ## POB: This is having some weird side-effects so I'm taking it out. 
 240 ##        import __builtin__ 
 241 ##        from pseudo import PseudoKeyword 
 242 ##        __builtin__.close = __builtin__.exit = __builtin__.quit = \ 
 243 ##            PseudoKeyword(self.quit) 
 245         from pseudo 
import PseudoKeyword
 
 246         __builtin__
.close 
= __builtin__
.exit 
= __builtin__
.quit 
= \
 
 247             'Click on the close button to leave the application.' 
 250         """Quit the application.""" 
 252         # XXX Good enough for now but later we want to send a close event. 
 254         # In the close event handler we can make sure they want to quit. 
 255         # Other applications, like PythonCard, may choose to hide rather than 
 256         # quit so we should just post the event and let the surrounding app 
 257         # decide what it wants to do. 
 258         self
.write('Click on the close button to leave the application.') 
 260     def setLocalShell(self
): 
 261         """Add 'shell' to locals as reference to ShellFacade instance.""" 
 262         self
.interp
.locals['shell'] = ShellFacade(other
=self
) 
 264     def execStartupScript(self
, startupScript
): 
 265         """Execute the user's PYTHONSTARTUP script if they have one.""" 
 266         if startupScript 
and os
.path
.isfile(startupScript
): 
 267             startupText 
= 'Startup script executed: ' + startupScript
 
 268             self
.push('print %s;execfile(%s)' % \
 
 269                       (`startupText`
, `startupScript`
)) 
 273     def setStyles(self
, faces
): 
 274         """Configure font size, typeface and color for lexer.""" 
 277         self
.StyleSetSpec(wxSTC_STYLE_DEFAULT
, "face:%(mono)s,size:%(size)d,back:%(backcol)s" % faces
) 
 282         self
.StyleSetSpec(wxSTC_STYLE_LINENUMBER
, "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % faces
) 
 283         self
.StyleSetSpec(wxSTC_STYLE_CONTROLCHAR
, "face:%(mono)s" % faces
) 
 284         self
.StyleSetSpec(wxSTC_STYLE_BRACELIGHT
, "fore:#0000FF,back:#FFFF88") 
 285         self
.StyleSetSpec(wxSTC_STYLE_BRACEBAD
, "fore:#FF0000,back:#FFFF88") 
 288         self
.StyleSetSpec(wxSTC_P_DEFAULT
, "face:%(mono)s" % faces
) 
 289         self
.StyleSetSpec(wxSTC_P_COMMENTLINE
, "fore:#007F00,face:%(mono)s" % faces
) 
 290         self
.StyleSetSpec(wxSTC_P_NUMBER
, "") 
 291         self
.StyleSetSpec(wxSTC_P_STRING
, "fore:#7F007F,face:%(mono)s" % faces
) 
 292         self
.StyleSetSpec(wxSTC_P_CHARACTER
, "fore:#7F007F,face:%(mono)s" % faces
) 
 293         self
.StyleSetSpec(wxSTC_P_WORD
, "fore:#00007F,bold") 
 294         self
.StyleSetSpec(wxSTC_P_TRIPLE
, "fore:#7F0000") 
 295         self
.StyleSetSpec(wxSTC_P_TRIPLEDOUBLE
, "fore:#000033,back:#FFFFE8") 
 296         self
.StyleSetSpec(wxSTC_P_CLASSNAME
, "fore:#0000FF,bold") 
 297         self
.StyleSetSpec(wxSTC_P_DEFNAME
, "fore:#007F7F,bold") 
 298         self
.StyleSetSpec(wxSTC_P_OPERATOR
, "") 
 299         self
.StyleSetSpec(wxSTC_P_IDENTIFIER
, "") 
 300         self
.StyleSetSpec(wxSTC_P_COMMENTBLOCK
, "fore:#7F7F7F") 
 301         self
.StyleSetSpec(wxSTC_P_STRINGEOL
, "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces
) 
 303     def OnChar(self
, event
): 
 304         """Keypress event handler. 
 306         Prevents modification of previously submitted commands/responses.""" 
 307         if not self
.CanEdit(): 
 309         key 
= event
.KeyCode() 
 310         currpos 
= self
.GetCurrentPos() 
 311         stoppos 
= self
.promptPosEnd
 
 313             # The dot or period key activates auto completion. 
 314             # Get the command between the prompt and the cursor. 
 315             # Add a dot to the end of the command. 
 316             command 
= self
.GetTextRange(stoppos
, currpos
) + '.' 
 318             if self
.autoComplete
: self
.autoCompleteShow(command
) 
 319         elif key 
== ord('('): 
 320             # The left paren activates a call tip and cancels 
 321             # an active auto completion. 
 322             if self
.AutoCompActive(): self
.AutoCompCancel() 
 323             # Get the command between the prompt and the cursor. 
 324             # Add the '(' to the end of the command. 
 325             command 
= self
.GetTextRange(stoppos
, currpos
) + '(' 
 327             if self
.autoCallTip
: self
.autoCallTipShow(command
) 
 329             # Allow the normal event handling to take place. 
 332     def OnKeyDown(self
, event
): 
 333         """Key down event handler. 
 335         Prevents modification of previously submitted commands/responses.""" 
 336         key 
= event
.KeyCode() 
 337         controlDown 
= event
.ControlDown() 
 338         altDown 
= event
.AltDown() 
 339         shiftDown 
= event
.ShiftDown() 
 340         currpos 
= self
.GetCurrentPos() 
 341         endpos 
= self
.GetTextLength() 
 342         # Return (Enter) is used to submit a command to the interpreter. 
 343         if not controlDown 
and key 
== WXK_RETURN
: 
 344             if self
.AutoCompActive(): self
.AutoCompCancel() 
 345             if self
.CallTipActive(): self
.CallTipCancel() 
 347         # Ctrl+Return (Cntrl+Enter) is used to insert a line break. 
 348         elif controlDown 
and key 
== WXK_RETURN
: 
 349             if self
.AutoCompActive(): self
.AutoCompCancel() 
 350             if self
.CallTipActive(): self
.CallTipCancel() 
 351             if currpos 
== endpos
: 
 354                 self
.insertLineBreak() 
 355         # If the auto-complete window is up let it do its thing. 
 356         elif self
.AutoCompActive(): 
 358         # Let Ctrl-Alt-* get handled normally. 
 359         elif controlDown 
and altDown
: 
 361         # Clear the current, unexecuted command. 
 362         elif key 
== WXK_ESCAPE
: 
 363             if self
.CallTipActive(): 
 367         # Cut to the clipboard. 
 368         elif (controlDown 
and key 
in (ord('X'), ord('x'))) \
 
 369         or (shiftDown 
and key 
== WXK_DELETE
): 
 371         # Copy to the clipboard. 
 372         elif controlDown 
and not shiftDown \
 
 373             and key 
in (ord('C'), ord('c'), WXK_INSERT
): 
 375         # Copy to the clipboard, including prompts. 
 376         elif controlDown 
and shiftDown \
 
 377             and key 
in (ord('C'), ord('c'), WXK_INSERT
): 
 378             self
.CopyWithPrompts() 
 379         # Paste from the clipboard. 
 380         elif (controlDown 
and not shiftDown \
 
 381             and key 
in (ord('V'), ord('v'))) \
 
 382         or (shiftDown 
and not controlDown 
and key 
== WXK_INSERT
): 
 384         # Paste from the clipboard, run commands. 
 385         elif controlDown 
and shiftDown \
 
 386             and key 
in (ord('V'), ord('v')): 
 388         # Replace with the previous command from the history buffer. 
 389         elif (controlDown 
and key 
== WXK_UP
) \
 
 390         or (altDown 
and key 
in (ord('P'), ord('p'))): 
 391             self
.OnHistoryReplace(step
=+1) 
 392         # Replace with the next command from the history buffer. 
 393         elif (controlDown 
and key 
== WXK_DOWN
) \
 
 394         or (altDown 
and key 
in (ord('N'), ord('n'))): 
 395             self
.OnHistoryReplace(step
=-1) 
 396         # Insert the previous command from the history buffer. 
 397         elif (shiftDown 
and key 
== WXK_UP
): 
 398             self
.OnHistoryInsert(step
=+1) 
 399         # Insert the next command from the history buffer. 
 400         elif (shiftDown 
and key 
== WXK_DOWN
): 
 401             self
.OnHistoryInsert(step
=-1) 
 402         # Search up the history for the text in front of the cursor. 
 404             self
.OnHistorySearch() 
 405         # Home needs to be aware of the prompt. 
 406         elif key 
== WXK_HOME
: 
 407             home 
= self
.promptPosEnd
 
 409                 if event
.ShiftDown(): 
 410                     # Select text from current position to end of prompt. 
 411                     self
.SetSelection(self
.GetCurrentPos(), home
) 
 413                     self
.SetCurrentPos(home
) 
 415                     self
.EnsureCaretVisible() 
 418         # Basic navigation keys should work anywhere. 
 419         elif key 
in (WXK_END
, WXK_LEFT
, WXK_RIGHT
, WXK_UP
, WXK_DOWN
, \
 
 420                      WXK_PRIOR
, WXK_NEXT
): 
 422         # Don't backspace over the latest non-continuation prompt. 
 423         elif key 
== WXK_BACK
: 
 424             if currpos 
> self
.promptPosEnd
: 
 426         # Only allow these keys after the latest prompt. 
 427         elif key 
in (WXK_TAB
, WXK_DELETE
): 
 430         # Don't toggle between insert mode and overwrite mode. 
 431         elif key 
== WXK_INSERT
: 
 433         # Don't allow line deletion. 
 434         elif controlDown 
and key 
in (ord('L'), ord('l')): 
 436         # Don't allow line transposition. 
 437         elif controlDown 
and key 
in (ord('T'), ord('t')): 
 439         # Protect the readonly portion of the shell. 
 440         elif not self
.CanEdit(): 
 445     def clearCommand(self
): 
 446         """Delete the current, unexecuted command.""" 
 447         startpos 
= self
.promptPosEnd
 
 448         endpos 
= self
.GetTextLength() 
 449         self
.SetSelection(startpos
, endpos
) 
 450         self
.ReplaceSelection('') 
 453     def OnHistoryReplace(self
, step
): 
 454         """Replace with the previous/next command from the history buffer.""" 
 456         self
.replaceFromHistory(step
) 
 458     def replaceFromHistory(self
, step
): 
 459         """Replace selection with command from the history buffer.""" 
 460         self
.ReplaceSelection('') 
 461         newindex 
= self
.historyIndex 
+ step
 
 462         if -1 <= newindex 
<= len(self
.history
): 
 463             self
.historyIndex 
= newindex
 
 464         if 0 <= newindex 
<= len(self
.history
)-1: 
 465             command 
= self
.history
[self
.historyIndex
] 
 466             command 
= command
.replace('\n', os
.linesep 
+ sys
.ps2
) 
 467             self
.ReplaceSelection(command
) 
 469     def OnHistoryInsert(self
, step
): 
 470         """Insert the previous/next command from the history buffer.""" 
 471         if not self
.CanEdit(): 
 473         startpos 
= self
.GetCurrentPos() 
 474         self
.replaceFromHistory(step
) 
 475         endpos 
= self
.GetCurrentPos() 
 476         self
.SetSelection(endpos
, startpos
) 
 478     def OnHistorySearch(self
): 
 479         """Search up the history buffer for the text in front of the cursor.""" 
 480         if not self
.CanEdit(): 
 482         startpos 
= self
.GetCurrentPos() 
 483         # The text up to the cursor is what we search for. 
 484         numCharsAfterCursor 
= self
.GetTextLength() - startpos
 
 485         searchText 
= self
.getCommand(rstrip
=0) 
 486         if numCharsAfterCursor 
> 0: 
 487             searchText 
= searchText
[:-numCharsAfterCursor
] 
 490         # Search upwards from the current history position and loop back 
 491         # to the beginning if we don't find anything. 
 492         if (self
.historyIndex 
<= -1) \
 
 493         or (self
.historyIndex 
>= len(self
.history
)-2): 
 494             searchOrder 
= range(len(self
.history
)) 
 496             searchOrder 
= range(self
.historyIndex
+1, len(self
.history
)) + \
 
 497                           range(self
.historyIndex
) 
 498         for i 
in searchOrder
: 
 499             command 
= self
.history
[i
] 
 500             if command
[:len(searchText
)] == searchText
: 
 501                 # Replace the current selection with the one we've found. 
 502                 self
.ReplaceSelection(command
[len(searchText
):]) 
 503                 endpos 
= self
.GetCurrentPos() 
 504                 self
.SetSelection(endpos
, startpos
) 
 505                 # We've now warped into middle of the history. 
 506                 self
.historyIndex 
= i
 
 509     def setStatusText(self
, text
): 
 510         """Display status information.""" 
 512         # This method will most likely be replaced by the enclosing app 
 513         # to do something more interesting, like write to a status bar. 
 516     def insertLineBreak(self
): 
 517         """Insert a new line break.""" 
 519             self
.write(os
.linesep
) 
 523     def processLine(self
): 
 524         """Process the line of text at which the user hit Enter.""" 
 526         # The user hit ENTER and we need to decide what to do. They could be 
 527         # sitting on any line in the shell. 
 529         thepos 
= self
.GetCurrentPos()         
 530         startpos 
= self
.promptPosEnd
 
 531         endpos 
= self
.GetTextLength() 
 532         # If they hit RETURN inside the current command, execute the command. 
 534             self
.SetCurrentPos(endpos
) 
 536             command 
= self
.GetTextRange(startpos
, endpos
) 
 537             lines 
= command
.split(os
.linesep 
+ sys
.ps2
) 
 538             lines 
= [line
.rstrip() for line 
in lines
] 
 539             command 
= '\n'.join(lines
) 
 541         # Or replace the current command with the other command. 
 543             # If the line contains a command (even an invalid one). 
 544             if self
.getCommand(rstrip
=0): 
 545                 command 
= self
.getMultilineCommand() 
 548             # Otherwise, put the cursor back where we started. 
 550                 self
.SetCurrentPos(thepos
) 
 551                 self
.SetAnchor(thepos
) 
 553     def getMultilineCommand(self
, rstrip
=1): 
 554         """Extract a multi-line command from the editor. 
 556         The command may not necessarily be valid Python syntax.""" 
 557         # XXX Need to extract real prompts here. Need to keep track of the 
 558         # prompt every time a command is issued. 
 563         # This is a total hack job, but it works. 
 564         text 
= self
.GetCurLine()[0] 
 565         line 
= self
.GetCurrentLine() 
 566         while text
[:ps2size
] == ps2 
and line 
> 0: 
 569             text 
= self
.GetCurLine()[0] 
 570         if text
[:ps1size
] == ps1
: 
 571             line 
= self
.GetCurrentLine() 
 573             startpos 
= self
.GetCurrentPos() + ps1size
 
 576             while self
.GetCurLine()[0][:ps2size
] == ps2
: 
 579             stoppos 
= self
.GetCurrentPos() 
 580             command 
= self
.GetTextRange(startpos
, stoppos
) 
 581             command 
= command
.replace(os
.linesep 
+ sys
.ps2
, '\n') 
 582             command 
= command
.rstrip() 
 583             command 
= command
.replace('\n', os
.linesep 
+ sys
.ps2
) 
 587             command 
= command
.rstrip() 
 590     def getCommand(self
, text
=None, rstrip
=1): 
 591         """Extract a command from text which may include a shell prompt. 
 593         The command may not necessarily be valid Python syntax.""" 
 595             text 
= self
.GetCurLine()[0] 
 596         # Strip the prompt off the front of text leaving just the command. 
 597         command 
= self
.lstripPrompt(text
) 
 599             command 
= ''  # Real commands have prompts. 
 601             command 
= command
.rstrip() 
 604     def lstripPrompt(self
, text
): 
 605         """Return text without a leading prompt.""" 
 610         # Strip the prompt off the front of text. 
 611         if text
[:ps1size
] == ps1
: 
 612             text 
= text
[ps1size
:] 
 613         elif text
[:ps2size
] == ps2
: 
 614             text 
= text
[ps2size
:] 
 617     def push(self
, command
): 
 618         """Send command to the interpreter for execution.""" 
 619         self
.write(os
.linesep
) 
 620         self
.more 
= self
.interp
.push(command
) 
 622             self
.addHistory(command
.rstrip()) 
 625     def addHistory(self
, command
): 
 626         """Add command to the command history.""" 
 627         # Reset the history position. 
 628         self
.historyIndex 
= -1 
 629         # Insert this command into the history, unless it's a blank 
 630         # line or the same as the last command. 
 632         and (len(self
.history
) == 0 or command 
!= self
.history
[0]): 
 633             self
.history
.insert(0, command
) 
 635     def write(self
, text
): 
 636         """Display text in the shell. 
 638         Replace line endings with OS-specific endings.""" 
 639         text 
= self
.fixLineEndings(text
) 
 641         self
.EnsureCaretVisible() 
 643     def fixLineEndings(self
, text
): 
 644         """Return text with line endings replaced by OS-specific endings.""" 
 645         lines 
= text
.split('\r\n') 
 646         for l 
in range(len(lines
)): 
 647             chunks 
= lines
[l
].split('\r') 
 648             for c 
in range(len(chunks
)): 
 649                 chunks
[c
] = os
.linesep
.join(chunks
[c
].split('\n')) 
 650             lines
[l
] = os
.linesep
.join(chunks
) 
 651         text 
= os
.linesep
.join(lines
) 
 655         """Display appropriate prompt for the context, either ps1 or ps2. 
 657         If this is a continuation line, autoindent as necessary.""" 
 659             prompt 
= str(sys
.ps2
) 
 661             prompt 
= str(sys
.ps1
) 
 662         pos 
= self
.GetCurLine()[1] 
 663         if pos 
> 0: self
.write(os
.linesep
) 
 665             self
.promptPosStart 
= self
.GetCurrentPos() 
 668             self
.promptPosEnd 
= self
.GetCurrentPos() 
 669             # Keep the undo feature from undoing previous responses. 
 670             self
.EmptyUndoBuffer() 
 671         # XXX Add some autoindent magic here if more. 
 673             self
.write(' '*4)  # Temporary hack indentation. 
 674         self
.EnsureCaretVisible() 
 675         self
.ScrollToColumn(0) 
 678         """Replacement for stdin.""" 
 679         prompt 
= 'Please enter your response:' 
 680         dialog 
= wxTextEntryDialog(None, prompt
, \
 
 681                                    'Input Dialog (Standard)', '') 
 683             if dialog
.ShowModal() == wxID_OK
: 
 684                 text 
= dialog
.GetValue() 
 685                 self
.write(text 
+ os
.linesep
) 
 691     def readRaw(self
, prompt
='Please enter your response:'): 
 692         """Replacement for raw_input.""" 
 693         dialog 
= wxTextEntryDialog(None, prompt
, \
 
 694                                    'Input Dialog (Raw)', '') 
 696             if dialog
.ShowModal() == wxID_OK
: 
 697                 text 
= dialog
.GetValue() 
 703     def ask(self
, prompt
='Please enter your response:'): 
 704         """Get response from the user.""" 
 705         return raw_input(prompt
=prompt
) 
 708         """Halt execution pending a response from the user.""" 
 709         self
.ask('Press enter to continue:') 
 712         """Delete all text from the shell.""" 
 715     def run(self
, command
, prompt
=1, verbose
=1): 
 716         """Execute command within the shell as if it was typed in directly. 
 717         >>> shell.run('print "this"') 
 722         # Go to the very bottom of the text. 
 723         endpos 
= self
.GetTextLength() 
 724         self
.SetCurrentPos(endpos
)         
 725         command 
= command
.rstrip() 
 726         if prompt
: self
.prompt() 
 727         if verbose
: self
.write(command
) 
 730     def runfile(self
, filename
): 
 731         """Execute all commands in file as if they were typed into the shell.""" 
 732         file = open(filename
) 
 735             for command 
in file.readlines(): 
 736                 if command
[:6] == 'shell.':  # Run shell methods silently. 
 737                     self
.run(command
, prompt
=0, verbose
=0) 
 739                     self
.run(command
, prompt
=0, verbose
=1) 
 743     def autoCompleteShow(self
, command
): 
 744         """Display auto-completion popup list.""" 
 745         list = self
.interp
.getAutoCompleteList(command
, \
 
 746                     includeMagic
=self
.autoCompleteIncludeMagic
, \
 
 747                     includeSingle
=self
.autoCompleteIncludeSingle
, \
 
 748                     includeDouble
=self
.autoCompleteIncludeDouble
) 
 750             options 
= ' '.join(list) 
 752             self
.AutoCompShow(offset
, options
) 
 754     def autoCallTipShow(self
, command
): 
 755         """Display argument spec and docstring in a popup bubble thingie.""" 
 756         if self
.CallTipActive
: self
.CallTipCancel() 
 757         (name
, argspec
, tip
) = self
.interp
.getCallTip(command
) 
 759             startpos 
= self
.GetCurrentPos() 
 760             self
.write(argspec 
+ ')') 
 761             endpos 
= self
.GetCurrentPos() 
 762             self
.SetSelection(endpos
, startpos
) 
 764             curpos 
= self
.GetCurrentPos() 
 765             tippos 
= curpos 
- (len(name
) + 1) 
 766             fallback 
= curpos 
- self
.GetColumn(curpos
) 
 767             # In case there isn't enough room, only go back to the fallback. 
 768             tippos 
= max(tippos
, fallback
) 
 769             self
.CallTipShow(tippos
, tip
) 
 771     def writeOut(self
, text
): 
 772         """Replacement for stdout.""" 
 775     def writeErr(self
, text
): 
 776         """Replacement for stderr.""" 
 779     def redirectStdin(self
, redirect
=1): 
 780         """If redirect is true then sys.stdin will come from the shell.""" 
 782             sys
.stdin 
= PseudoFileIn(self
.readIn
) 
 784             sys
.stdin 
= self
.stdin
 
 786     def redirectStdout(self
, redirect
=1): 
 787         """If redirect is true then sys.stdout will go to the shell.""" 
 789             sys
.stdout 
= PseudoFileOut(self
.writeOut
) 
 791             sys
.stdout 
= self
.stdout
 
 793     def redirectStderr(self
, redirect
=1): 
 794         """If redirect is true then sys.stderr will go to the shell.""" 
 796             sys
.stderr 
= PseudoFileErr(self
.writeErr
) 
 798             sys
.stderr 
= self
.stderr
 
 801         """Return true if text is selected and can be cut.""" 
 802         if self
.GetSelectionStart() != self
.GetSelectionEnd() \
 
 803         and self
.GetSelectionStart() >= self
.promptPosEnd \
 
 804         and self
.GetSelectionEnd() >= self
.promptPosEnd
: 
 810         """Return true if text is selected and can be copied.""" 
 811         return self
.GetSelectionStart() != self
.GetSelectionEnd() 
 814         """Return true if a paste should succeed.""" 
 815         if self
.CanEdit() and wxStyledTextCtrl
.CanPaste(self
): 
 821         """Return true if editing should succeed.""" 
 822         return self
.GetCurrentPos() >= self
.promptPosEnd
 
 825         """Remove selection and place it on the clipboard.""" 
 826         if self
.CanCut() and self
.CanCopy(): 
 827             if self
.AutoCompActive(): self
.AutoCompCancel() 
 828             if self
.CallTipActive
: self
.CallTipCancel() 
 830             self
.ReplaceSelection('') 
 833         """Copy selection and place it on the clipboard.""" 
 835             command 
= self
.GetSelectedText() 
 836             command 
= command
.replace(os
.linesep 
+ sys
.ps2
, os
.linesep
) 
 837             command 
= command
.replace(os
.linesep 
+ sys
.ps1
, os
.linesep
) 
 838             command 
= self
.lstripPrompt(text
=command
) 
 839             data 
= wxTextDataObject(command
) 
 840             if wxTheClipboard
.Open(): 
 841                 wxTheClipboard
.SetData(data
) 
 842                 wxTheClipboard
.Close() 
 844     def CopyWithPrompts(self
): 
 845         """Copy selection, including prompts, and place it on the clipboard.""" 
 847             command 
= self
.GetSelectedText() 
 848             data 
= wxTextDataObject(command
) 
 849             if wxTheClipboard
.Open(): 
 850                 wxTheClipboard
.SetData(data
) 
 851                 wxTheClipboard
.Close() 
 854         """Replace selection with clipboard contents.""" 
 855         if self
.CanPaste() and wxTheClipboard
.Open(): 
 856             if wxTheClipboard
.IsSupported(wxDataFormat(wxDF_TEXT
)): 
 857                 data 
= wxTextDataObject() 
 858                 if wxTheClipboard
.GetData(data
): 
 859                     self
.ReplaceSelection('') 
 860                     command 
= data
.GetText() 
 861                     command 
= command
.rstrip() 
 862                     command 
= self
.fixLineEndings(command
) 
 863                     command 
= self
.lstripPrompt(text
=command
) 
 864                     command 
= command
.replace(os
.linesep 
+ sys
.ps2
, '\n') 
 865                     command 
= command
.replace(os
.linesep
, '\n') 
 866                     command 
= command
.replace('\n', os
.linesep 
+ sys
.ps2
) 
 868             wxTheClipboard
.Close() 
 870     def PasteAndRun(self
): 
 871         """Replace selection with clipboard contents, run commands.""" 
 872         if wxTheClipboard
.Open(): 
 873             if wxTheClipboard
.IsSupported(wxDataFormat(wxDF_TEXT
)): 
 874                 data 
= wxTextDataObject() 
 875                 if wxTheClipboard
.GetData(data
): 
 876                     endpos 
= self
.GetTextLength() 
 877                     self
.SetCurrentPos(endpos
) 
 878                     startpos 
= self
.promptPosEnd
 
 879                     self
.SetSelection(startpos
, endpos
) 
 880                     self
.ReplaceSelection('') 
 881                     text 
= data
.GetText() 
 883                     text 
= self
.fixLineEndings(text
) 
 884                     text 
= self
.lstripPrompt(text
=text
) 
 885                     text 
= text
.replace(os
.linesep 
+ sys
.ps1
, '\n') 
 886                     text 
= text
.replace(os
.linesep 
+ sys
.ps2
, '\n') 
 887                     text 
= text
.replace(os
.linesep
, '\n') 
 888                     lines 
= text
.split('\n') 
 892                         if line
.strip() != '' and line
.lstrip() == line
: 
 895                                 # Add the previous command to the list. 
 896                                 commands
.append(command
) 
 897                             # Start a new command, which may be multiline. 
 900                             # Multiline command. Add to the command. 
 903                     commands
.append(command
) 
 904                     for command 
in commands
:     
 905                         command 
= command
.replace('\n', os
.linesep 
+ sys
.ps2
) 
 908             wxTheClipboard
.Close() 
 911 wxID_SELECTALL 
= NewId()  # This *should* be defined by wxPython. 
 912 ID_AUTOCOMP 
= NewId() 
 913 ID_AUTOCOMP_SHOW 
= NewId() 
 914 ID_AUTOCOMP_INCLUDE_MAGIC 
= NewId() 
 915 ID_AUTOCOMP_INCLUDE_SINGLE 
= NewId() 
 916 ID_AUTOCOMP_INCLUDE_DOUBLE 
= NewId() 
 917 ID_CALLTIPS 
= NewId() 
 918 ID_CALLTIPS_SHOW 
= NewId() 
 922     """Mixin class to add standard menu items.""" 
 924     def createMenus(self
): 
 925         m 
= self
.fileMenu 
= wxMenu() 
 927         m
.Append(wxID_EXIT
, 'E&xit', 'Exit PyCrust') 
 929         m 
= self
.editMenu 
= wxMenu() 
 930         m
.Append(wxID_UNDO
, '&Undo \tCtrl+Z', 'Undo the last action') 
 931         m
.Append(wxID_REDO
, '&Redo \tCtrl+Y', 'Redo the last undone action') 
 933         m
.Append(wxID_CUT
, 'Cu&t \tCtrl+X', 'Cut the selection') 
 934         m
.Append(wxID_COPY
, '&Copy \tCtrl+C', 'Copy the selection') 
 935         m
.Append(wxID_PASTE
, '&Paste \tCtrl+V', 'Paste') 
 937         m
.Append(wxID_CLEAR
, 'Cle&ar', 'Delete the selection') 
 938         m
.Append(wxID_SELECTALL
, 'Select A&ll', 'Select all text') 
 940         m 
= self
.autocompMenu 
= wxMenu() 
 941         m
.Append(ID_AUTOCOMP_SHOW
, 'Show Auto Completion', \
 
 942                  'Show auto completion during dot syntax', \
 
 944         m
.Append(ID_AUTOCOMP_INCLUDE_MAGIC
, 'Include Magic Attributes', \
 
 945                  'Include attributes visible to __getattr__ and __setattr__', \
 
 947         m
.Append(ID_AUTOCOMP_INCLUDE_SINGLE
, 'Include Single Underscores', \
 
 948                  'Include attibutes prefixed by a single underscore', \
 
 950         m
.Append(ID_AUTOCOMP_INCLUDE_DOUBLE
, 'Include Double Underscores', \
 
 951                  'Include attibutes prefixed by a double underscore', \
 
 954         m 
= self
.calltipsMenu 
= wxMenu() 
 955         m
.Append(ID_CALLTIPS_SHOW
, 'Show Call Tips', \
 
 956                  'Show call tips with argument specifications', checkable
=1) 
 958         m 
= self
.optionsMenu 
= wxMenu() 
 959         m
.AppendMenu(ID_AUTOCOMP
, '&Auto Completion', self
.autocompMenu
, \
 
 960                      'Auto Completion Options') 
 961         m
.AppendMenu(ID_CALLTIPS
, '&Call Tips', self
.calltipsMenu
, \
 
 964         m 
= self
.helpMenu 
= wxMenu() 
 966         m
.Append(wxID_ABOUT
, '&About...', 'About PyCrust') 
 968         b 
= self
.menuBar 
= wxMenuBar() 
 969         b
.Append(self
.fileMenu
, '&File') 
 970         b
.Append(self
.editMenu
, '&Edit') 
 971         b
.Append(self
.optionsMenu
, '&Options') 
 972         b
.Append(self
.helpMenu
, '&Help') 
 975         EVT_MENU(self
, wxID_EXIT
, self
.OnExit
) 
 976         EVT_MENU(self
, wxID_UNDO
, self
.OnUndo
) 
 977         EVT_MENU(self
, wxID_REDO
, self
.OnRedo
) 
 978         EVT_MENU(self
, wxID_CUT
, self
.OnCut
) 
 979         EVT_MENU(self
, wxID_COPY
, self
.OnCopy
) 
 980         EVT_MENU(self
, wxID_PASTE
, self
.OnPaste
) 
 981         EVT_MENU(self
, wxID_CLEAR
, self
.OnClear
) 
 982         EVT_MENU(self
, wxID_SELECTALL
, self
.OnSelectAll
) 
 983         EVT_MENU(self
, wxID_ABOUT
, self
.OnAbout
) 
 984         EVT_MENU(self
, ID_AUTOCOMP_SHOW
, \
 
 985                  self
.OnAutoCompleteShow
) 
 986         EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_MAGIC
, \
 
 987                  self
.OnAutoCompleteIncludeMagic
) 
 988         EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_SINGLE
, \
 
 989                  self
.OnAutoCompleteIncludeSingle
) 
 990         EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_DOUBLE
, \
 
 991                  self
.OnAutoCompleteIncludeDouble
) 
 992         EVT_MENU(self
, ID_CALLTIPS_SHOW
, \
 
 995         EVT_UPDATE_UI(self
, wxID_UNDO
, self
.OnUpdateMenu
) 
 996         EVT_UPDATE_UI(self
, wxID_REDO
, self
.OnUpdateMenu
) 
 997         EVT_UPDATE_UI(self
, wxID_CUT
, self
.OnUpdateMenu
) 
 998         EVT_UPDATE_UI(self
, wxID_COPY
, self
.OnUpdateMenu
) 
 999         EVT_UPDATE_UI(self
, wxID_PASTE
, self
.OnUpdateMenu
) 
1000         EVT_UPDATE_UI(self
, wxID_CLEAR
, self
.OnUpdateMenu
) 
1001         EVT_UPDATE_UI(self
, ID_AUTOCOMP_SHOW
, self
.OnUpdateMenu
) 
1002         EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_MAGIC
, self
.OnUpdateMenu
) 
1003         EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_SINGLE
, self
.OnUpdateMenu
) 
1004         EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_DOUBLE
, self
.OnUpdateMenu
) 
1005         EVT_UPDATE_UI(self
, ID_CALLTIPS_SHOW
, self
.OnUpdateMenu
) 
1007     def OnExit(self
, event
): 
1010     def OnUndo(self
, event
): 
1013     def OnRedo(self
, event
): 
1016     def OnCut(self
, event
): 
1019     def OnCopy(self
, event
): 
1022     def OnPaste(self
, event
): 
1025     def OnClear(self
, event
): 
1028     def OnSelectAll(self
, event
): 
1029         self
.shell
.SelectAll() 
1031     def OnAbout(self
, event
): 
1032         """Display an About PyCrust window.""" 
1034         title 
= 'About PyCrust' 
1035         text 
= 'PyCrust %s\n\n' % VERSION 
+ \
 
1036                'Yet another Python shell, only flakier.\n\n' + \
 
1037                'Half-baked by Patrick K. O\'Brien,\n' + \
 
1038                'the other half is still in the oven.\n\n' + \
 
1039                'Shell Revision: %s\n' % self
.shell
.revision 
+ \
 
1040                'Interpreter Revision: %s\n\n' % self
.shell
.interp
.revision 
+ \
 
1041                'Python Version: %s\n' % sys
.version
.split()[0] + \
 
1042                'wxPython Version: %s\n' % wx
.__version
__ + \
 
1043                'Platform: %s\n' % sys
.platform
 
1044         dialog 
= wxMessageDialog(self
, text
, title
, wxOK | wxICON_INFORMATION
) 
1048     def OnAutoCompleteShow(self
, event
): 
1049         self
.shell
.autoComplete 
= event
.IsChecked() 
1051     def OnAutoCompleteIncludeMagic(self
, event
): 
1052         self
.shell
.autoCompleteIncludeMagic 
= event
.IsChecked() 
1054     def OnAutoCompleteIncludeSingle(self
, event
): 
1055         self
.shell
.autoCompleteIncludeSingle 
= event
.IsChecked() 
1057     def OnAutoCompleteIncludeDouble(self
, event
): 
1058         self
.shell
.autoCompleteIncludeDouble 
= event
.IsChecked() 
1060     def OnCallTipsShow(self
, event
): 
1061         self
.shell
.autoCallTip 
= event
.IsChecked() 
1063     def OnUpdateMenu(self
, event
): 
1064         """Update menu items based on current status.""" 
1067             event
.Enable(self
.shell
.CanUndo()) 
1068         elif id == wxID_REDO
: 
1069             event
.Enable(self
.shell
.CanRedo()) 
1070         elif id == wxID_CUT
: 
1071             event
.Enable(self
.shell
.CanCut()) 
1072         elif id == wxID_COPY
: 
1073             event
.Enable(self
.shell
.CanCopy()) 
1074         elif id == wxID_PASTE
: 
1075             event
.Enable(self
.shell
.CanPaste()) 
1076         elif id == wxID_CLEAR
: 
1077             event
.Enable(self
.shell
.CanCut()) 
1078         elif id == ID_AUTOCOMP_SHOW
: 
1079             event
.Check(self
.shell
.autoComplete
) 
1080         elif id == ID_AUTOCOMP_INCLUDE_MAGIC
: 
1081             event
.Check(self
.shell
.autoCompleteIncludeMagic
) 
1082         elif id == ID_AUTOCOMP_INCLUDE_SINGLE
: 
1083             event
.Check(self
.shell
.autoCompleteIncludeSingle
) 
1084         elif id == ID_AUTOCOMP_INCLUDE_DOUBLE
: 
1085             event
.Check(self
.shell
.autoCompleteIncludeDouble
) 
1086         elif id == ID_CALLTIPS_SHOW
: 
1087             event
.Check(self
.shell
.autoCallTip
) 
1090 class ShellFrame(wxFrame
, ShellMenu
): 
1091     """Frame containing the PyCrust shell component.""" 
1093     name 
= 'PyCrust Shell Frame' 
1094     revision 
= __version__
 
1096     def __init__(self
, parent
=None, id=-1, title
='PyShell', \
 
1097                  pos
=wxDefaultPosition
, size
=wxDefaultSize
, \
 
1098                  style
=wxDEFAULT_FRAME_STYLE
, locals=None, \
 
1099                  InterpClass
=None, *args
, **kwds
): 
1100         """Create a PyCrust ShellFrame instance.""" 
1101         wxFrame
.__init
__(self
, parent
, id, title
, pos
, size
, style
) 
1102         intro 
= 'Welcome To PyCrust %s - The Flakiest Python Shell' % VERSION
 
1103         intro 
+= '\nSponsored by Orbtech - Your Source For Python Development Services' 
1104         self
.CreateStatusBar() 
1105         self
.SetStatusText(intro
.replace('\n', ', ')) 
1106         if wxPlatform 
== '__WXMSW__': 
1108             filename 
= os
.path
.join(os
.path
.dirname(__file__
), 'PyCrust.ico') 
1109             icon 
= wxIcon(filename
, wxBITMAP_TYPE_ICO
) 
1111         self
.shell 
= Shell(parent
=self
, id=-1, introText
=intro
, \
 
1112                            locals=locals, InterpClass
=InterpClass
, \
 
1114         # Override the shell so that status messages go to the status bar. 
1115         self
.shell
.setStatusText 
= self
.SetStatusText