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.com - 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
) == (2, 3) and wxRELEASE_NUMBER 
< 2) \
 
  35     or (wxMAJOR_VERSION 
<= 2 and wxMINOR_VERSION 
<= 2): 
  39     faces 
= { 'times'  : 'Times', 
  42               'other'  : 'new century schoolbook', 
  50     """Simplified interface to all shell-related functionality. 
  52     This is a semi-transparent facade, in that all attributes of other are  
  53     still accessible, even though only some are visible to the user.""" 
  55     name 
= 'PyCrust Shell Interface' 
  56     revision 
= __version__
 
  58     def __init__(self
, other
): 
  59         """Create a ShellFacade instance.""" 
  71         for method 
in methods
: 
  72             self
.__dict
__[method
] = getattr(other
, method
) 
  75         d
['help'] = 'There is no help available, yet.' 
  78     def __getattr__(self
, name
): 
  79         if hasattr(self
.other
, name
): 
  80             return getattr(self
.other
, name
) 
  82             raise AttributeError, name
 
  84     def __setattr__(self
, name
, value
): 
  85         if self
.__dict
__.has_key(name
): 
  86             self
.__dict
__[name
] = value
 
  87         elif hasattr(self
.other
, name
): 
  88             return setattr(self
.other
, name
, value
) 
  90             raise AttributeError, name
 
  92     def _getAttributeNames(self
): 
  93         """Return list of magic attributes to extend introspection.""" 
  94         list = ['autoCallTip', 
  96                 'autoCompleteCaseInsensitive', 
  97                 'autoCompleteIncludeDouble', 
  98                 'autoCompleteIncludeMagic', 
  99                 'autoCompleteIncludeSingle', 
 105 class Shell(wxStyledTextCtrl
): 
 106     """PyCrust Shell based on wxStyledTextCtrl.""" 
 108     name 
= 'PyCrust Shell' 
 109     revision 
= __version__
 
 111     def __init__(self
, parent
, id=-1, pos
=wxDefaultPosition
, \
 
 112                  size
=wxDefaultSize
, style
=wxCLIP_CHILDREN
, introText
='', \
 
 113                  locals=None, InterpClass
=None, *args
, **kwds
): 
 114         """Create a PyCrust Shell instance.""" 
 115         wxStyledTextCtrl
.__init
__(self
, parent
, id, pos
, size
, style
) 
 116         # Grab these so they can be restored by self.redirect* methods. 
 117         self
.stdin 
= sys
.stdin
 
 118         self
.stdout 
= sys
.stdout
 
 119         self
.stderr 
= sys
.stderr
 
 120         # Add the current working directory "." to the search path. 
 121         sys
.path
.insert(0, os
.curdir
) 
 122         # Import a default interpreter class if one isn't provided. 
 123         if InterpClass 
== None: 
 124             from interpreter 
import Interpreter
 
 126             Interpreter 
= InterpClass
 
 127         # Create default locals so we have something interesting. 
 128         shellLocals 
= {'__name__': 'PyCrust-Shell',  
 129                        '__doc__': 'PyCrust-Shell, The PyCrust Python Shell.', 
 130                        '__version__': VERSION
, 
 132         # Add the dictionary that was passed in. 
 134             shellLocals
.update(locals) 
 135         self
.interp 
= Interpreter(locals=shellLocals
, \
 
 136                                   rawin
=self
.readRaw
, \
 
 137                                   stdin
=PseudoFileIn(self
.readIn
), \
 
 138                                   stdout
=PseudoFileOut(self
.writeOut
), \
 
 139                                   stderr
=PseudoFileErr(self
.writeErr
), \
 
 141         # Keep track of the most recent prompt starting and ending positions. 
 142         self
.promptPos 
= [0, 0] 
 143         # Keep track of the most recent non-continuation prompt. 
 144         self
.prompt1Pos 
= [0, 0] 
 145         # Keep track of multi-line commands. 
 147         # Create the command history.  Commands are added into the front of 
 148         # the list (ie. at index 0) as they are entered. self.historyIndex 
 149         # is the current position in the history; it gets incremented as you 
 150         # retrieve the previous command, decremented as you retrieve the 
 151         # next, and reset when you hit Enter. self.historyIndex == -1 means 
 152         # you're on the current command, not in the history. 
 154         self
.historyIndex 
= -1 
 155         # Assign handlers for keyboard events. 
 156         EVT_KEY_DOWN(self
, self
.OnKeyDown
) 
 157         EVT_CHAR(self
, self
.OnChar
) 
 158         # Configure various defaults and user preferences. 
 160         # Display the introductory banner information. 
 161         try: self
.showIntro(introText
) 
 163         # Assign some pseudo keywords to the interpreter's namespace. 
 164         try: self
.setBuiltinKeywords() 
 166         # Add 'shell' to the interpreter's local namespace. 
 167         try: self
.setLocalShell() 
 169         # Do this last so the user has complete control over their 
 170         # environment. They can override anything they want. 
 171         try: self
.execStartupScript(self
.interp
.startupScript
) 
 178         """Configure shell based on user preferences.""" 
 179         self
.SetMarginType(1, wxSTC_MARGIN_NUMBER
) 
 180         self
.SetMarginWidth(1, 40) 
 182         self
.SetLexer(wxSTC_LEX_PYTHON
) 
 183         self
.SetKeyWords(0, ' '.join(keyword
.kwlist
)) 
 185         self
.setStyles(faces
) 
 186         self
.SetViewWhiteSpace(0) 
 189         # Do we want to automatically pop up command completion options? 
 190         self
.autoComplete 
= 1 
 191         self
.autoCompleteIncludeMagic 
= 1 
 192         self
.autoCompleteIncludeSingle 
= 1 
 193         self
.autoCompleteIncludeDouble 
= 1 
 194         self
.autoCompleteCaseInsensitive 
= 1 
 195         self
.AutoCompSetIgnoreCase(self
.autoCompleteCaseInsensitive
) 
 196         # De we want to automatically pop up command argument help? 
 198         self
.CallTipSetBackground(wxColour(255, 255, 232)) 
 200     def showIntro(self
, text
=''): 
 201         """Display introductory text in the shell.""" 
 203             if not text
.endswith(os
.linesep
): text 
+= os
.linesep
 
 206             self
.write(self
.interp
.introText
) 
 207         except AttributeError: 
 210     def setBuiltinKeywords(self
): 
 211         """Create pseudo keywords as part of builtins. 
 213         This is a rather clever hack that sets "close", "exit" and "quit"  
 214         to a PseudoKeyword object so that we can make them do what we want. 
 215         In this case what we want is to call our self.quit() method. 
 216         The user can type "close", "exit" or "quit" without the final parens. 
 218 ## POB: This is having some weird side-effects so I'm taking it out. 
 219 ##        import __builtin__ 
 220 ##        from pseudo import PseudoKeyword 
 221 ##        __builtin__.close = __builtin__.exit = __builtin__.quit = \ 
 222 ##            PseudoKeyword(self.quit) 
 224         from pseudo 
import PseudoKeyword
 
 225         __builtin__
.close 
= __builtin__
.exit 
= __builtin__
.quit 
= \
 
 226             'Click on the close button to leave the application.' 
 229         """Quit the application.""" 
 231         # XXX Good enough for now but later we want to send a close event. 
 233         # In the close event handler we can make sure they want to quit. 
 234         # Other applications, like PythonCard, may choose to hide rather than 
 235         # quit so we should just post the event and let the surrounding app 
 236         # decide what it wants to do. 
 237         self
.write('Click on the close button to leave the application.') 
 239     def setLocalShell(self
): 
 240         """Add 'shell' to locals as reference to ShellFacade instance.""" 
 241         self
.interp
.locals['shell'] = ShellFacade(other
=self
) 
 243     def execStartupScript(self
, startupScript
): 
 244         """Execute the user's PYTHONSTARTUP script if they have one.""" 
 245         if startupScript 
and os
.path
.isfile(startupScript
): 
 246             startupText 
= 'Startup script executed: ' + startupScript
 
 247             self
.push('print %s;execfile(%s)' % \
 
 248                       (`startupText`
, `startupScript`
)) 
 252     def setStyles(self
, faces
): 
 253         """Configure font size, typeface and color for lexer.""" 
 256         self
.StyleSetSpec(wxSTC_STYLE_DEFAULT
, "face:%(mono)s,size:%(size)d" % faces
) 
 261         self
.StyleSetSpec(wxSTC_STYLE_LINENUMBER
, "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % faces
) 
 262         self
.StyleSetSpec(wxSTC_STYLE_CONTROLCHAR
, "face:%(mono)s" % faces
) 
 263         self
.StyleSetSpec(wxSTC_STYLE_BRACELIGHT
, "fore:#0000FF,back:#FFFF88") 
 264         self
.StyleSetSpec(wxSTC_STYLE_BRACEBAD
, "fore:#FF0000,back:#FFFF88") 
 267         self
.StyleSetSpec(wxSTC_P_DEFAULT
, "face:%(mono)s" % faces
) 
 268         self
.StyleSetSpec(wxSTC_P_COMMENTLINE
, "fore:#007F00,face:%(mono)s" % faces
) 
 269         self
.StyleSetSpec(wxSTC_P_NUMBER
, "") 
 270         self
.StyleSetSpec(wxSTC_P_STRING
, "fore:#7F007F,face:%(mono)s" % faces
) 
 271         self
.StyleSetSpec(wxSTC_P_CHARACTER
, "fore:#7F007F,face:%(mono)s" % faces
) 
 272         self
.StyleSetSpec(wxSTC_P_WORD
, "fore:#00007F,bold") 
 273         self
.StyleSetSpec(wxSTC_P_TRIPLE
, "fore:#7F0000") 
 274         self
.StyleSetSpec(wxSTC_P_TRIPLEDOUBLE
, "fore:#000033,back:#FFFFE8") 
 275         self
.StyleSetSpec(wxSTC_P_CLASSNAME
, "fore:#0000FF,bold") 
 276         self
.StyleSetSpec(wxSTC_P_DEFNAME
, "fore:#007F7F,bold") 
 277         self
.StyleSetSpec(wxSTC_P_OPERATOR
, "") 
 278         self
.StyleSetSpec(wxSTC_P_IDENTIFIER
, "") 
 279         self
.StyleSetSpec(wxSTC_P_COMMENTBLOCK
, "fore:#7F7F7F") 
 280         self
.StyleSetSpec(wxSTC_P_STRINGEOL
, "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces
) 
 282     def OnChar(self
, event
): 
 283         """Keypress event handler. 
 285         Prevents modification of previously submitted commands/responses.""" 
 286         if not self
.CanEdit(): 
 288         key 
= event
.KeyCode() 
 289         currpos 
= self
.GetCurrentPos() 
 290         stoppos 
= self
.promptPos
[1] 
 292             # The dot or period key activates auto completion. 
 293             # Get the command between the prompt and the cursor. 
 294             # Add a dot to the end of the command. 
 295             command 
= self
.GetTextRange(stoppos
, currpos
) + '.' 
 297             if self
.autoComplete
: self
.autoCompleteShow(command
) 
 298         elif key 
== ord('('): 
 299             # The left paren activates a call tip and cancels 
 300             # an active auto completion. 
 301             if self
.AutoCompActive(): self
.AutoCompCancel() 
 302             # Get the command between the prompt and the cursor. 
 303             # Add the '(' to the end of the command. 
 304             command 
= self
.GetTextRange(stoppos
, currpos
) + '(' 
 306             if self
.autoCallTip
: self
.autoCallTipShow(command
) 
 307         # Hack to keep characters from entering when Alt or Control are down. 
 308         elif event
.ControlDown() or event
.AltDown(): 
 311             # Allow the normal event handling to take place. 
 314     def OnKeyDown(self
, event
): 
 315         """Key down event handler. 
 317         Prevents modification of previously submitted commands/responses.""" 
 318         key 
= event
.KeyCode() 
 319         currpos 
= self
.GetCurrentPos() 
 320         stoppos 
= self
.promptPos
[1] 
 321         # Return is used to submit a command to the interpreter. 
 322         if key 
== WXK_RETURN
: 
 323             if self
.AutoCompActive(): self
.AutoCompCancel() 
 324             if self
.CallTipActive
: self
.CallTipCancel() 
 326         # If the auto-complete window is up let it do its thing. 
 327         elif self
.AutoCompActive(): 
 329         # Cut to the clipboard. 
 330         elif event
.ControlDown() and key 
in (ord('X'), ord('x')): 
 332         # Copy to the clipboard. 
 333         elif event
.ControlDown() and not event
.ShiftDown() \
 
 334         and key 
in (ord('C'), ord('c')): 
 336         # Copy to the clipboard, including prompts. 
 337         elif event
.ControlDown() and event
.ShiftDown() \
 
 338         and key 
in (ord('C'), ord('c')): 
 339             self
.CopyWithPrompts() 
 340         # Paste from the clipboard. 
 341         elif event
.ControlDown() and key 
in (ord('V'), ord('v')): 
 343         # Retrieve the previous command from the history buffer. 
 344         elif (event
.ControlDown() and key 
== WXK_UP
) \
 
 345         or (event
.AltDown() and key 
in (ord('P'), ord('p'))): 
 346             self
.OnHistoryRetrieve(step
=+1) 
 347         # Retrieve the next command from the history buffer. 
 348         elif (event
.ControlDown() and key 
== WXK_DOWN
) \
 
 349         or (event
.AltDown() and key 
in (ord('N'), ord('n'))): 
 350             self
.OnHistoryRetrieve(step
=-1) 
 351         # Search up the history for the text in front of the cursor. 
 353             self
.OnHistorySearch() 
 354         # Home needs to be aware of the prompt. 
 355         elif key 
== WXK_HOME
: 
 356             if currpos 
>= stoppos
: 
 357                 if event
.ShiftDown(): 
 358                     # Select text from current position to end of prompt. 
 359                     self
.SetSelection(self
.GetCurrentPos(), stoppos
) 
 361                     self
.SetCurrentPos(stoppos
) 
 362                     self
.SetAnchor(stoppos
) 
 365         # Basic navigation keys should work anywhere. 
 366         elif key 
in (WXK_END
, WXK_LEFT
, WXK_RIGHT
, WXK_UP
, WXK_DOWN
, \
 
 367                      WXK_PRIOR
, WXK_NEXT
): 
 369         # Don't backspace over the latest prompt. 
 370         elif key 
== WXK_BACK
: 
 371             if currpos 
> self
.prompt1Pos
[1]: 
 373         # Only allow these keys after the latest prompt. 
 374         elif key 
in (WXK_TAB
, WXK_DELETE
): 
 377         # Don't toggle between insert mode and overwrite mode. 
 378         elif key 
== WXK_INSERT
: 
 380         # Protect the readonly portion of the shell. 
 381         elif not self
.CanEdit(): 
 386     def OnHistoryRetrieve(self
, step
): 
 387         """Retrieve the previous/next command from the history buffer.""" 
 388         if not self
.CanEdit(): 
 390         startpos 
= self
.GetCurrentPos() 
 391         newindex 
= self
.historyIndex 
+ step
 
 392         if not (-1 <= newindex 
< len(self
.history
)): 
 394         self
.historyIndex 
= newindex
 
 396             self
.ReplaceSelection('') 
 398             self
.ReplaceSelection('') 
 399             command 
= self
.history
[self
.historyIndex
] 
 400             command 
= command
.replace('\n', os
.linesep 
+ sys
.ps2
) 
 401             self
.ReplaceSelection(command
) 
 402         endpos 
= self
.GetCurrentPos() 
 403         self
.SetSelection(endpos
, startpos
) 
 405     def OnHistorySearch(self
): 
 406         """Search up the history buffer for the text in front of the cursor.""" 
 407         if not self
.CanEdit(): 
 409         startpos 
= self
.GetCurrentPos() 
 410         # The text up to the cursor is what we search for. 
 411         numCharsAfterCursor 
= self
.GetTextLength() - startpos
 
 412         searchText 
= self
.getCommand(rstrip
=0) 
 413         if numCharsAfterCursor 
> 0: 
 414             searchText 
= searchText
[:-numCharsAfterCursor
] 
 417         # Search upwards from the current history position and loop back 
 418         # to the beginning if we don't find anything. 
 419         if (self
.historyIndex 
<= -1) \
 
 420         or (self
.historyIndex 
>= len(self
.history
)-2): 
 421             searchOrder 
= range(len(self
.history
)) 
 423             searchOrder 
= range(self
.historyIndex
+1, len(self
.history
)) + \
 
 424                           range(self
.historyIndex
) 
 425         for i 
in searchOrder
: 
 426             command 
= self
.history
[i
] 
 427             if command
[:len(searchText
)] == searchText
: 
 428                 # Replace the current selection with the one we've found. 
 429                 self
.ReplaceSelection(command
[len(searchText
):]) 
 430                 endpos 
= self
.GetCurrentPos() 
 431                 self
.SetSelection(endpos
, startpos
) 
 432                 # We've now warped into middle of the history. 
 433                 self
.historyIndex 
= i
 
 436     def setStatusText(self
, text
): 
 437         """Display status information.""" 
 439         # This method will most likely be replaced by the enclosing app 
 440         # to do something more interesting, like write to a status bar. 
 443     def processLine(self
): 
 444         """Process the line of text at which the user hit Enter.""" 
 446         # The user hit ENTER and we need to decide what to do. They could be 
 447         # sitting on any line in the shell. 
 449         thepos 
= self
.GetCurrentPos() 
 450         endpos 
= self
.GetTextLength() 
 451         # If they hit RETURN at the very bottom, execute the command. 
 454             if self
.getCommand(): 
 455                 command 
= self
.GetTextRange(self
.prompt1Pos
[1], endpos
) 
 457                 # This is a hack, now that we allow editing of previous 
 458                 # lines, which throws off our promptPos values. 
 459                 newend 
= endpos 
- len(self
.getCommand(rstrip
=0)) 
 460                 command 
= self
.GetTextRange(self
.prompt1Pos
[1], newend
) 
 461             command 
= command
.replace(os
.linesep 
+ sys
.ps2
, '\n') 
 463         # Or replace the current command with the other command. 
 464         elif thepos 
< self
.prompt1Pos
[0]: 
 465             theline 
= self
.GetCurrentLine() 
 466             command 
= self
.getCommand(rstrip
=0) 
 467             # If the new line contains a command (even an invalid one). 
 469                 command 
= self
.getMultilineCommand() 
 470                 self
.SetCurrentPos(endpos
) 
 471                 startpos 
= self
.prompt1Pos
[1] 
 472                 self
.SetSelection(startpos
, endpos
) 
 473                 self
.ReplaceSelection('') 
 476             # Otherwise, put the cursor back where we started. 
 478                 self
.SetCurrentPos(thepos
) 
 479                 self
.SetAnchor(thepos
) 
 480         # Or add a new line to the current single or multi-line command. 
 481         elif thepos 
> self
.prompt1Pos
[1]: 
 482             self
.write(os
.linesep
) 
 486     def getMultilineCommand(self
, rstrip
=1): 
 487         """Extract a multi-line command from the editor. 
 489         The command may not necessarily be valid Python syntax.""" 
 490         # XXX Need to extract real prompts here. Need to keep track of the 
 491         # prompt every time a command is issued. 
 496         # This is a total hack job, but it works. 
 497         text 
= self
.GetCurLine()[0] 
 498         line 
= self
.GetCurrentLine() 
 499         while text
[:ps2size
] == ps2 
and line 
> 0: 
 502             text 
= self
.GetCurLine()[0] 
 503         if text
[:ps1size
] == ps1
: 
 504             line 
= self
.GetCurrentLine() 
 506             startpos 
= self
.GetCurrentPos() + ps1size
 
 509             while self
.GetCurLine()[0][:ps2size
] == ps2
: 
 512             stoppos 
= self
.GetCurrentPos() 
 513             command 
= self
.GetTextRange(startpos
, stoppos
) 
 514             command 
= command
.replace(os
.linesep 
+ sys
.ps2
, '\n') 
 515             command 
= command
.rstrip() 
 516             command 
= command
.replace('\n', os
.linesep 
+ sys
.ps2
) 
 520             command 
= command
.rstrip() 
 523     def getCommand(self
, text
=None, rstrip
=1): 
 524         """Extract a command from text which may include a shell prompt. 
 526         The command may not necessarily be valid Python syntax.""" 
 528             text 
= self
.GetCurLine()[0] 
 529         # Strip the prompt off the front of text leaving just the command. 
 530         command 
= self
.lstripPrompt(text
) 
 532             command 
= ''  # Real commands have prompts. 
 534             command 
= command
.rstrip() 
 537     def lstripPrompt(self
, text
): 
 538         """Return text without a leading prompt.""" 
 543         # Strip the prompt off the front of text. 
 544         if text
[:ps1size
] == ps1
: 
 545             text 
= text
[ps1size
:] 
 546         elif text
[:ps2size
] == ps2
: 
 547             text 
= text
[ps2size
:] 
 550     def push(self
, command
): 
 551         """Send command to the interpreter for execution.""" 
 552         self
.write(os
.linesep
) 
 553         self
.more 
= self
.interp
.push(command
) 
 555             self
.addHistory(command
.rstrip()) 
 558     def addHistory(self
, command
): 
 559         """Add command to the command history.""" 
 560         # Reset the history position. 
 561         self
.historyIndex 
= -1 
 562         # Insert this command into the history, unless it's a blank 
 563         # line or the same as the last command. 
 565         and (len(self
.history
) == 0 or command 
!= self
.history
[0]): 
 566             self
.history
.insert(0, command
) 
 568     def write(self
, text
): 
 569         """Display text in the shell. 
 571         Replace line endings with OS-specific endings.""" 
 572         text 
= self
.fixLineEndings(text
) 
 574         self
.EnsureCaretVisible() 
 576     def fixLineEndings(self
, text
): 
 577         """Return text with line endings replaced by OS-specific endings.""" 
 578         lines 
= text
.split('\r\n') 
 579         for l 
in range(len(lines
)): 
 580             chunks 
= lines
[l
].split('\r') 
 581             for c 
in range(len(chunks
)): 
 582                 chunks
[c
] = os
.linesep
.join(chunks
[c
].split('\n')) 
 583             lines
[l
] = os
.linesep
.join(chunks
) 
 584         text 
= os
.linesep
.join(lines
) 
 588         """Display appropriate prompt for the context, either ps1 or ps2. 
 590         If this is a continuation line, autoindent as necessary.""" 
 592             prompt 
= str(sys
.ps2
) 
 594             prompt 
= str(sys
.ps1
) 
 595         pos 
= self
.GetCurLine()[1] 
 596         if pos 
> 0: self
.write(os
.linesep
) 
 597         self
.promptPos
[0] = self
.GetCurrentPos() 
 598         if not self
.more
: self
.prompt1Pos
[0] = self
.GetCurrentPos() 
 600         self
.promptPos
[1] = self
.GetCurrentPos() 
 602             self
.prompt1Pos
[1] = self
.GetCurrentPos() 
 603             # Keep the undo feature from undoing previous responses. 
 604             self
.EmptyUndoBuffer() 
 605         # XXX Add some autoindent magic here if more. 
 607             self
.write(' '*4)  # Temporary hack indentation. 
 608         self
.EnsureCaretVisible() 
 609         self
.ScrollToColumn(0) 
 612         """Replacement for stdin.""" 
 613         prompt 
= 'Please enter your response:' 
 614         dialog 
= wxTextEntryDialog(None, prompt
, \
 
 615                                    'Input Dialog (Standard)', '') 
 617             if dialog
.ShowModal() == wxID_OK
: 
 618                 text 
= dialog
.GetValue() 
 619                 self
.write(text 
+ os
.linesep
) 
 625     def readRaw(self
, prompt
='Please enter your response:'): 
 626         """Replacement for raw_input.""" 
 627         dialog 
= wxTextEntryDialog(None, prompt
, \
 
 628                                    'Input Dialog (Raw)', '') 
 630             if dialog
.ShowModal() == wxID_OK
: 
 631                 text 
= dialog
.GetValue() 
 637     def ask(self
, prompt
='Please enter your response:'): 
 638         """Get response from the user.""" 
 639         return raw_input(prompt
=prompt
) 
 642         """Halt execution pending a response from the user.""" 
 643         self
.ask('Press enter to continue:') 
 646         """Delete all text from the shell.""" 
 649     def run(self
, command
, prompt
=1, verbose
=1): 
 650         """Execute command within the shell as if it was typed in directly. 
 651         >>> shell.run('print "this"') 
 656         # Go to the very bottom of the text. 
 657         endpos 
= self
.GetTextLength() 
 658         self
.SetCurrentPos(endpos
)         
 659         command 
= command
.rstrip() 
 660         if prompt
: self
.prompt() 
 661         if verbose
: self
.write(command
) 
 664     def runfile(self
, filename
): 
 665         """Execute all commands in file as if they were typed into the shell.""" 
 666         file = open(filename
) 
 669             for command 
in file.readlines(): 
 670                 if command
[:6] == 'shell.':  # Run shell methods silently. 
 671                     self
.run(command
, prompt
=0, verbose
=0) 
 673                     self
.run(command
, prompt
=0, verbose
=1) 
 677     def autoCompleteShow(self
, command
): 
 678         """Display auto-completion popup list.""" 
 679         list = self
.interp
.getAutoCompleteList(command
, \
 
 680                     includeMagic
=self
.autoCompleteIncludeMagic
, \
 
 681                     includeSingle
=self
.autoCompleteIncludeSingle
, \
 
 682                     includeDouble
=self
.autoCompleteIncludeDouble
) 
 684             options 
= ' '.join(list) 
 686             self
.AutoCompShow(offset
, options
) 
 688     def autoCallTipShow(self
, command
): 
 689         """Display argument spec and docstring in a popup bubble thingie.""" 
 690         if self
.CallTipActive
: self
.CallTipCancel() 
 691         tip 
= self
.interp
.getCallTip(command
) 
 693             offset 
= self
.GetCurrentPos() 
 694             self
.CallTipShow(offset
, tip
) 
 696     def writeOut(self
, text
): 
 697         """Replacement for stdout.""" 
 700     def writeErr(self
, text
): 
 701         """Replacement for stderr.""" 
 704     def redirectStdin(self
, redirect
=1): 
 705         """If redirect is true then sys.stdin will come from the shell.""" 
 707             sys
.stdin 
= PseudoFileIn(self
.readIn
) 
 709             sys
.stdin 
= self
.stdin
 
 711     def redirectStdout(self
, redirect
=1): 
 712         """If redirect is true then sys.stdout will go to the shell.""" 
 714             sys
.stdout 
= PseudoFileOut(self
.writeOut
) 
 716             sys
.stdout 
= self
.stdout
 
 718     def redirectStderr(self
, redirect
=1): 
 719         """If redirect is true then sys.stderr will go to the shell.""" 
 721             sys
.stderr 
= PseudoFileErr(self
.writeErr
) 
 723             sys
.stderr 
= self
.stderr
 
 726         """Return true if text is selected and can be cut.""" 
 727         if self
.GetSelectionStart() != self
.GetSelectionEnd() \
 
 728         and self
.GetSelectionStart() >= self
.prompt1Pos
[1] \
 
 729         and self
.GetSelectionEnd() >= self
.prompt1Pos
[1]: 
 735         """Return true if text is selected and can be copied.""" 
 736         return self
.GetSelectionStart() != self
.GetSelectionEnd() 
 739         """Return true if a paste should succeed.""" 
 740         if self
.CanEdit() and wxStyledTextCtrl
.CanPaste(self
): 
 746         """Return true if editing should succeed.""" 
 747         return self
.GetCurrentPos() >= self
.prompt1Pos
[1] 
 750         """Remove selection and place it on the clipboard.""" 
 751         if self
.CanCut() and self
.CanCopy(): 
 752             if self
.AutoCompActive(): self
.AutoCompCancel() 
 753             if self
.CallTipActive
: self
.CallTipCancel() 
 755             self
.ReplaceSelection('') 
 758         """Copy selection and place it on the clipboard.""" 
 760             command 
= self
.GetSelectedText() 
 761             command 
= command
.replace(os
.linesep 
+ sys
.ps2
, os
.linesep
) 
 762             command 
= command
.replace(os
.linesep 
+ sys
.ps1
, os
.linesep
) 
 763             command 
= self
.lstripPrompt(text
=command
) 
 764             data 
= wxTextDataObject(command
) 
 765             if wxTheClipboard
.Open(): 
 766                 wxTheClipboard
.SetData(data
) 
 767                 wxTheClipboard
.Close() 
 769     def CopyWithPrompts(self
): 
 770         """Copy selection, including prompts, and place it on the clipboard.""" 
 772             command 
= self
.GetSelectedText() 
 773             data 
= wxTextDataObject(command
) 
 774             if wxTheClipboard
.Open(): 
 775                 wxTheClipboard
.SetData(data
) 
 776                 wxTheClipboard
.Close() 
 779         """Replace selection with clipboard contents.""" 
 781             if wxTheClipboard
.Open(): 
 782                 if wxTheClipboard
.IsSupported(wxDataFormat(wxDF_TEXT
)): 
 783                     data 
= wxTextDataObject() 
 784                     if wxTheClipboard
.GetData(data
): 
 785                         command 
= data
.GetText() 
 786                         command 
= command
.rstrip() 
 787                         command 
= self
.fixLineEndings(command
) 
 788                         command 
= self
.lstripPrompt(text
=command
) 
 789                         command 
= command
.replace(os
.linesep 
+ sys
.ps2
, '\n') 
 790                         command 
= command
.replace(os
.linesep
, '\n') 
 791                         command 
= command
.replace('\n', os
.linesep 
+ sys
.ps2
) 
 792                         self
.ReplaceSelection('') 
 794                 wxTheClipboard
.Close() 
 797 wxID_SELECTALL 
= NewId()  # This *should* be defined by wxPython. 
 798 ID_AUTOCOMP 
= NewId() 
 799 ID_AUTOCOMP_SHOW 
= NewId() 
 800 ID_AUTOCOMP_INCLUDE_MAGIC 
= NewId() 
 801 ID_AUTOCOMP_INCLUDE_SINGLE 
= NewId() 
 802 ID_AUTOCOMP_INCLUDE_DOUBLE 
= NewId() 
 803 ID_CALLTIPS 
= NewId() 
 804 ID_CALLTIPS_SHOW 
= NewId() 
 808     """Mixin class to add standard menu items.""" 
 810     def createMenus(self
): 
 811         m 
= self
.fileMenu 
= wxMenu() 
 813         m
.Append(wxID_EXIT
, 'E&xit', 'Exit PyCrust') 
 815         m 
= self
.editMenu 
= wxMenu() 
 816         m
.Append(wxID_UNDO
, '&Undo \tCtrl+Z', 'Undo the last action') 
 817         m
.Append(wxID_REDO
, '&Redo \tCtrl+Y', 'Redo the last undone action') 
 819         m
.Append(wxID_CUT
, 'Cu&t \tCtrl+X', 'Cut the selection') 
 820         m
.Append(wxID_COPY
, '&Copy \tCtrl+C', 'Copy the selection') 
 821         m
.Append(wxID_PASTE
, '&Paste \tCtrl+V', 'Paste') 
 823         m
.Append(wxID_CLEAR
, 'Cle&ar', 'Delete the selection') 
 824         m
.Append(wxID_SELECTALL
, 'Select A&ll', 'Select all text') 
 826         m 
= self
.autocompMenu 
= wxMenu() 
 827         m
.Append(ID_AUTOCOMP_SHOW
, 'Show Auto Completion', \
 
 828                  'Show auto completion during dot syntax', \
 
 830         m
.Append(ID_AUTOCOMP_INCLUDE_MAGIC
, 'Include Magic Attributes', \
 
 831                  'Include attributes visible to __getattr__ and __setattr__', \
 
 833         m
.Append(ID_AUTOCOMP_INCLUDE_SINGLE
, 'Include Single Underscores', \
 
 834                  'Include attibutes prefixed by a single underscore', \
 
 836         m
.Append(ID_AUTOCOMP_INCLUDE_DOUBLE
, 'Include Double Underscores', \
 
 837                  'Include attibutes prefixed by a double underscore', \
 
 840         m 
= self
.calltipsMenu 
= wxMenu() 
 841         m
.Append(ID_CALLTIPS_SHOW
, 'Show Call Tips', \
 
 842                  'Show call tips with argument specifications', checkable
=1) 
 844         m 
= self
.optionsMenu 
= wxMenu() 
 845         m
.AppendMenu(ID_AUTOCOMP
, '&Auto Completion', self
.autocompMenu
, \
 
 846                      'Auto Completion Options') 
 847         m
.AppendMenu(ID_CALLTIPS
, '&Call Tips', self
.calltipsMenu
, \
 
 850         m 
= self
.helpMenu 
= wxMenu() 
 852         m
.Append(wxID_ABOUT
, '&About...', 'About PyCrust') 
 854         b 
= self
.menuBar 
= wxMenuBar() 
 855         b
.Append(self
.fileMenu
, '&File') 
 856         b
.Append(self
.editMenu
, '&Edit') 
 857         b
.Append(self
.optionsMenu
, '&Options') 
 858         b
.Append(self
.helpMenu
, '&Help') 
 861         EVT_MENU(self
, wxID_EXIT
, self
.OnExit
) 
 862         EVT_MENU(self
, wxID_UNDO
, self
.OnUndo
) 
 863         EVT_MENU(self
, wxID_REDO
, self
.OnRedo
) 
 864         EVT_MENU(self
, wxID_CUT
, self
.OnCut
) 
 865         EVT_MENU(self
, wxID_COPY
, self
.OnCopy
) 
 866         EVT_MENU(self
, wxID_PASTE
, self
.OnPaste
) 
 867         EVT_MENU(self
, wxID_CLEAR
, self
.OnClear
) 
 868         EVT_MENU(self
, wxID_SELECTALL
, self
.OnSelectAll
) 
 869         EVT_MENU(self
, wxID_ABOUT
, self
.OnAbout
) 
 870         EVT_MENU(self
, ID_AUTOCOMP_SHOW
, \
 
 871                  self
.OnAutoCompleteShow
) 
 872         EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_MAGIC
, \
 
 873                  self
.OnAutoCompleteIncludeMagic
) 
 874         EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_SINGLE
, \
 
 875                  self
.OnAutoCompleteIncludeSingle
) 
 876         EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_DOUBLE
, \
 
 877                  self
.OnAutoCompleteIncludeDouble
) 
 878         EVT_MENU(self
, ID_CALLTIPS_SHOW
, \
 
 881         EVT_UPDATE_UI(self
, wxID_UNDO
, self
.OnUpdateMenu
) 
 882         EVT_UPDATE_UI(self
, wxID_REDO
, self
.OnUpdateMenu
) 
 883         EVT_UPDATE_UI(self
, wxID_CUT
, self
.OnUpdateMenu
) 
 884         EVT_UPDATE_UI(self
, wxID_COPY
, self
.OnUpdateMenu
) 
 885         EVT_UPDATE_UI(self
, wxID_PASTE
, self
.OnUpdateMenu
) 
 886         EVT_UPDATE_UI(self
, wxID_CLEAR
, self
.OnUpdateMenu
) 
 887         EVT_UPDATE_UI(self
, ID_AUTOCOMP_SHOW
, self
.OnUpdateMenu
) 
 888         EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_MAGIC
, self
.OnUpdateMenu
) 
 889         EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_SINGLE
, self
.OnUpdateMenu
) 
 890         EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_DOUBLE
, self
.OnUpdateMenu
) 
 891         EVT_UPDATE_UI(self
, ID_CALLTIPS_SHOW
, self
.OnUpdateMenu
) 
 893     def OnExit(self
, event
): 
 896     def OnUndo(self
, event
): 
 899     def OnRedo(self
, event
): 
 902     def OnCut(self
, event
): 
 905     def OnCopy(self
, event
): 
 908     def OnPaste(self
, event
): 
 911     def OnClear(self
, event
): 
 914     def OnSelectAll(self
, event
): 
 915         self
.shell
.SelectAll() 
 917     def OnAbout(self
, event
): 
 918         """Display an About PyCrust window.""" 
 920         title 
= 'About PyCrust' 
 921         text 
= 'PyCrust %s\n\n' % VERSION 
+ \
 
 922                'Yet another Python shell, only flakier.\n\n' + \
 
 923                'Half-baked by Patrick K. O\'Brien,\n' + \
 
 924                'the other half is still in the oven.\n\n' + \
 
 925                'Shell Revision: %s\n' % self
.shell
.revision 
+ \
 
 926                'Interpreter Revision: %s\n\n' % self
.shell
.interp
.revision 
+ \
 
 927                'Python Version: %s\n' % sys
.version
.split()[0] + \
 
 928                'wxPython Version: %s\n' % wx
.__version
__ + \
 
 929                'Platform: %s\n' % sys
.platform
 
 930         dialog 
= wxMessageDialog(self
, text
, title
, wxOK | wxICON_INFORMATION
) 
 934     def OnAutoCompleteShow(self
, event
): 
 935         self
.shell
.autoComplete 
= event
.IsChecked() 
 937     def OnAutoCompleteIncludeMagic(self
, event
): 
 938         self
.shell
.autoCompleteIncludeMagic 
= event
.IsChecked() 
 940     def OnAutoCompleteIncludeSingle(self
, event
): 
 941         self
.shell
.autoCompleteIncludeSingle 
= event
.IsChecked() 
 943     def OnAutoCompleteIncludeDouble(self
, event
): 
 944         self
.shell
.autoCompleteIncludeDouble 
= event
.IsChecked() 
 946     def OnCallTipsShow(self
, event
): 
 947         self
.shell
.autoCallTip 
= event
.IsChecked() 
 949     def OnUpdateMenu(self
, event
): 
 950         """Update menu items based on current status.""" 
 953             event
.Enable(self
.shell
.CanUndo()) 
 954         elif id == wxID_REDO
: 
 955             event
.Enable(self
.shell
.CanRedo()) 
 957             event
.Enable(self
.shell
.CanCut()) 
 958         elif id == wxID_COPY
: 
 959             event
.Enable(self
.shell
.CanCopy()) 
 960         elif id == wxID_PASTE
: 
 961             event
.Enable(self
.shell
.CanPaste()) 
 962         elif id == wxID_CLEAR
: 
 963             event
.Enable(self
.shell
.CanCut()) 
 964         elif id == ID_AUTOCOMP_SHOW
: 
 965             event
.Check(self
.shell
.autoComplete
) 
 966         elif id == ID_AUTOCOMP_INCLUDE_MAGIC
: 
 967             event
.Check(self
.shell
.autoCompleteIncludeMagic
) 
 968         elif id == ID_AUTOCOMP_INCLUDE_SINGLE
: 
 969             event
.Check(self
.shell
.autoCompleteIncludeSingle
) 
 970         elif id == ID_AUTOCOMP_INCLUDE_DOUBLE
: 
 971             event
.Check(self
.shell
.autoCompleteIncludeDouble
) 
 972         elif id == ID_CALLTIPS_SHOW
: 
 973             event
.Check(self
.shell
.autoCallTip
) 
 976 class ShellFrame(wxFrame
, ShellMenu
): 
 977     """Frame containing the PyCrust shell component.""" 
 979     name 
= 'PyCrust Shell Frame' 
 980     revision 
= __version__
 
 982     def __init__(self
, parent
=None, id=-1, title
='PyShell', \
 
 983                  pos
=wxDefaultPosition
, size
=wxDefaultSize
, \
 
 984                  style
=wxDEFAULT_FRAME_STYLE
, locals=None, \
 
 985                  InterpClass
=None, *args
, **kwds
): 
 986         """Create a PyCrust ShellFrame instance.""" 
 987         wxFrame
.__init
__(self
, parent
, id, title
, pos
, size
, style
) 
 988         intro 
= 'Welcome To PyCrust %s - The Flakiest Python Shell' % VERSION
 
 989         intro 
+= '\nSponsored by Orbtech.com - Your Source For Python Development Services' 
 990         self
.CreateStatusBar() 
 991         self
.SetStatusText(intro
.replace('\n', ', ')) 
 992         if wxPlatform 
== '__WXMSW__': 
 994             filename 
= os
.path
.join(os
.path
.dirname(__file__
), 'PyCrust.ico') 
 995             icon 
= wxIcon(filename
, wxBITMAP_TYPE_ICO
) 
 997         self
.shell 
= Shell(parent
=self
, id=-1, introText
=intro
, \
 
 998                            locals=locals, InterpClass
=InterpClass
, \
 
1000         # Override the shell so that status messages go to the status bar. 
1001         self
.shell
.setStatusText 
= self
.SetStatusText