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 # Do 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
)
308 # Allow the normal event handling to take place.
311 def OnKeyDown(self
, event
):
312 """Key down event handler.
314 Prevents modification of previously submitted commands/responses."""
315 key
= event
.KeyCode()
316 controlDown
= event
.ControlDown()
317 altDown
= event
.AltDown()
318 shiftDown
= event
.ShiftDown()
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 # Let Ctrl-Alt-* get handled normally.
330 elif controlDown
and altDown
:
332 # Cut to the clipboard.
333 elif controlDown
and key
in (ord('X'), ord('x')):
335 # Copy to the clipboard.
336 elif controlDown
and not shiftDown
and key
in (ord('C'), ord('c')):
338 # Copy to the clipboard, including prompts.
339 elif controlDown
and shiftDown
and key
in (ord('C'), ord('c')):
340 self
.CopyWithPrompts()
341 # Paste from the clipboard.
342 elif controlDown
and key
in (ord('V'), ord('v')):
344 # Retrieve the previous command from the history buffer.
345 elif (controlDown
and key
== WXK_UP
) \
346 or (altDown
and key
in (ord('P'), ord('p'))):
347 self
.OnHistoryRetrieve(step
=+1)
348 # Retrieve the next command from the history buffer.
349 elif (controlDown
and key
== WXK_DOWN
) \
350 or (altDown
and key
in (ord('N'), ord('n'))):
351 self
.OnHistoryRetrieve(step
=-1)
352 # Search up the history for the text in front of the cursor.
354 self
.OnHistorySearch()
355 # Home needs to be aware of the prompt.
356 elif key
== WXK_HOME
:
357 if currpos
>= stoppos
:
358 if event
.ShiftDown():
359 # Select text from current position to end of prompt.
360 self
.SetSelection(self
.GetCurrentPos(), stoppos
)
362 self
.SetCurrentPos(stoppos
)
363 self
.SetAnchor(stoppos
)
366 # Basic navigation keys should work anywhere.
367 elif key
in (WXK_END
, WXK_LEFT
, WXK_RIGHT
, WXK_UP
, WXK_DOWN
, \
368 WXK_PRIOR
, WXK_NEXT
):
370 # Don't backspace over the latest prompt.
371 elif key
== WXK_BACK
:
372 if currpos
> self
.prompt1Pos
[1]:
374 # Only allow these keys after the latest prompt.
375 elif key
in (WXK_TAB
, WXK_DELETE
):
378 # Don't toggle between insert mode and overwrite mode.
379 elif key
== WXK_INSERT
:
381 # Don't allow line deletion.
382 elif controlDown
and key
in (ord('L'), ord('l')):
384 # Don't allow line transposition.
385 elif controlDown
and key
in (ord('T'), ord('t')):
387 # Protect the readonly portion of the shell.
388 elif not self
.CanEdit():
393 def OnHistoryRetrieve(self
, step
):
394 """Retrieve the previous/next command from the history buffer."""
395 if not self
.CanEdit():
397 startpos
= self
.GetCurrentPos()
398 newindex
= self
.historyIndex
+ step
399 if not (-1 <= newindex
< len(self
.history
)):
401 self
.historyIndex
= newindex
403 self
.ReplaceSelection('')
405 self
.ReplaceSelection('')
406 command
= self
.history
[self
.historyIndex
]
407 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
408 self
.ReplaceSelection(command
)
409 endpos
= self
.GetCurrentPos()
410 self
.SetSelection(endpos
, startpos
)
412 def OnHistorySearch(self
):
413 """Search up the history buffer for the text in front of the cursor."""
414 if not self
.CanEdit():
416 startpos
= self
.GetCurrentPos()
417 # The text up to the cursor is what we search for.
418 numCharsAfterCursor
= self
.GetTextLength() - startpos
419 searchText
= self
.getCommand(rstrip
=0)
420 if numCharsAfterCursor
> 0:
421 searchText
= searchText
[:-numCharsAfterCursor
]
424 # Search upwards from the current history position and loop back
425 # to the beginning if we don't find anything.
426 if (self
.historyIndex
<= -1) \
427 or (self
.historyIndex
>= len(self
.history
)-2):
428 searchOrder
= range(len(self
.history
))
430 searchOrder
= range(self
.historyIndex
+1, len(self
.history
)) + \
431 range(self
.historyIndex
)
432 for i
in searchOrder
:
433 command
= self
.history
[i
]
434 if command
[:len(searchText
)] == searchText
:
435 # Replace the current selection with the one we've found.
436 self
.ReplaceSelection(command
[len(searchText
):])
437 endpos
= self
.GetCurrentPos()
438 self
.SetSelection(endpos
, startpos
)
439 # We've now warped into middle of the history.
440 self
.historyIndex
= i
443 def setStatusText(self
, text
):
444 """Display status information."""
446 # This method will most likely be replaced by the enclosing app
447 # to do something more interesting, like write to a status bar.
450 def processLine(self
):
451 """Process the line of text at which the user hit Enter."""
453 # The user hit ENTER and we need to decide what to do. They could be
454 # sitting on any line in the shell.
456 thepos
= self
.GetCurrentPos()
457 endpos
= self
.GetTextLength()
458 # If they hit RETURN at the very bottom, execute the command.
461 if self
.getCommand():
462 command
= self
.GetTextRange(self
.prompt1Pos
[1], endpos
)
464 # This is a hack, now that we allow editing of previous
465 # lines, which throws off our promptPos values.
466 newend
= endpos
- len(self
.getCommand(rstrip
=0))
467 command
= self
.GetTextRange(self
.prompt1Pos
[1], newend
)
468 command
= command
.replace(os
.linesep
+ sys
.ps2
, '\n')
470 # Or replace the current command with the other command.
471 elif thepos
< self
.prompt1Pos
[0]:
472 theline
= self
.GetCurrentLine()
473 command
= self
.getCommand(rstrip
=0)
474 # If the new line contains a command (even an invalid one).
476 command
= self
.getMultilineCommand()
477 self
.SetCurrentPos(endpos
)
478 startpos
= self
.prompt1Pos
[1]
479 self
.SetSelection(startpos
, endpos
)
480 self
.ReplaceSelection('')
483 # Otherwise, put the cursor back where we started.
485 self
.SetCurrentPos(thepos
)
486 self
.SetAnchor(thepos
)
487 # Or add a new line to the current single or multi-line command.
488 elif thepos
> self
.prompt1Pos
[1]:
489 self
.write(os
.linesep
)
493 def getMultilineCommand(self
, rstrip
=1):
494 """Extract a multi-line command from the editor.
496 The command may not necessarily be valid Python syntax."""
497 # XXX Need to extract real prompts here. Need to keep track of the
498 # prompt every time a command is issued.
503 # This is a total hack job, but it works.
504 text
= self
.GetCurLine()[0]
505 line
= self
.GetCurrentLine()
506 while text
[:ps2size
] == ps2
and line
> 0:
509 text
= self
.GetCurLine()[0]
510 if text
[:ps1size
] == ps1
:
511 line
= self
.GetCurrentLine()
513 startpos
= self
.GetCurrentPos() + ps1size
516 while self
.GetCurLine()[0][:ps2size
] == ps2
:
519 stoppos
= self
.GetCurrentPos()
520 command
= self
.GetTextRange(startpos
, stoppos
)
521 command
= command
.replace(os
.linesep
+ sys
.ps2
, '\n')
522 command
= command
.rstrip()
523 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
527 command
= command
.rstrip()
530 def getCommand(self
, text
=None, rstrip
=1):
531 """Extract a command from text which may include a shell prompt.
533 The command may not necessarily be valid Python syntax."""
535 text
= self
.GetCurLine()[0]
536 # Strip the prompt off the front of text leaving just the command.
537 command
= self
.lstripPrompt(text
)
539 command
= '' # Real commands have prompts.
541 command
= command
.rstrip()
544 def lstripPrompt(self
, text
):
545 """Return text without a leading prompt."""
550 # Strip the prompt off the front of text.
551 if text
[:ps1size
] == ps1
:
552 text
= text
[ps1size
:]
553 elif text
[:ps2size
] == ps2
:
554 text
= text
[ps2size
:]
557 def push(self
, command
):
558 """Send command to the interpreter for execution."""
559 self
.write(os
.linesep
)
560 self
.more
= self
.interp
.push(command
)
562 self
.addHistory(command
.rstrip())
565 def addHistory(self
, command
):
566 """Add command to the command history."""
567 # Reset the history position.
568 self
.historyIndex
= -1
569 # Insert this command into the history, unless it's a blank
570 # line or the same as the last command.
572 and (len(self
.history
) == 0 or command
!= self
.history
[0]):
573 self
.history
.insert(0, command
)
575 def write(self
, text
):
576 """Display text in the shell.
578 Replace line endings with OS-specific endings."""
579 text
= self
.fixLineEndings(text
)
581 self
.EnsureCaretVisible()
583 def fixLineEndings(self
, text
):
584 """Return text with line endings replaced by OS-specific endings."""
585 lines
= text
.split('\r\n')
586 for l
in range(len(lines
)):
587 chunks
= lines
[l
].split('\r')
588 for c
in range(len(chunks
)):
589 chunks
[c
] = os
.linesep
.join(chunks
[c
].split('\n'))
590 lines
[l
] = os
.linesep
.join(chunks
)
591 text
= os
.linesep
.join(lines
)
595 """Display appropriate prompt for the context, either ps1 or ps2.
597 If this is a continuation line, autoindent as necessary."""
599 prompt
= str(sys
.ps2
)
601 prompt
= str(sys
.ps1
)
602 pos
= self
.GetCurLine()[1]
603 if pos
> 0: self
.write(os
.linesep
)
604 self
.promptPos
[0] = self
.GetCurrentPos()
605 if not self
.more
: self
.prompt1Pos
[0] = self
.GetCurrentPos()
607 self
.promptPos
[1] = self
.GetCurrentPos()
609 self
.prompt1Pos
[1] = self
.GetCurrentPos()
610 # Keep the undo feature from undoing previous responses.
611 self
.EmptyUndoBuffer()
612 # XXX Add some autoindent magic here if more.
614 self
.write(' '*4) # Temporary hack indentation.
615 self
.EnsureCaretVisible()
616 self
.ScrollToColumn(0)
619 """Replacement for stdin."""
620 prompt
= 'Please enter your response:'
621 dialog
= wxTextEntryDialog(None, prompt
, \
622 'Input Dialog (Standard)', '')
624 if dialog
.ShowModal() == wxID_OK
:
625 text
= dialog
.GetValue()
626 self
.write(text
+ os
.linesep
)
632 def readRaw(self
, prompt
='Please enter your response:'):
633 """Replacement for raw_input."""
634 dialog
= wxTextEntryDialog(None, prompt
, \
635 'Input Dialog (Raw)', '')
637 if dialog
.ShowModal() == wxID_OK
:
638 text
= dialog
.GetValue()
644 def ask(self
, prompt
='Please enter your response:'):
645 """Get response from the user."""
646 return raw_input(prompt
=prompt
)
649 """Halt execution pending a response from the user."""
650 self
.ask('Press enter to continue:')
653 """Delete all text from the shell."""
656 def run(self
, command
, prompt
=1, verbose
=1):
657 """Execute command within the shell as if it was typed in directly.
658 >>> shell.run('print "this"')
663 # Go to the very bottom of the text.
664 endpos
= self
.GetTextLength()
665 self
.SetCurrentPos(endpos
)
666 command
= command
.rstrip()
667 if prompt
: self
.prompt()
668 if verbose
: self
.write(command
)
671 def runfile(self
, filename
):
672 """Execute all commands in file as if they were typed into the shell."""
673 file = open(filename
)
676 for command
in file.readlines():
677 if command
[:6] == 'shell.': # Run shell methods silently.
678 self
.run(command
, prompt
=0, verbose
=0)
680 self
.run(command
, prompt
=0, verbose
=1)
684 def autoCompleteShow(self
, command
):
685 """Display auto-completion popup list."""
686 list = self
.interp
.getAutoCompleteList(command
, \
687 includeMagic
=self
.autoCompleteIncludeMagic
, \
688 includeSingle
=self
.autoCompleteIncludeSingle
, \
689 includeDouble
=self
.autoCompleteIncludeDouble
)
691 options
= ' '.join(list)
693 self
.AutoCompShow(offset
, options
)
695 def autoCallTipShow(self
, command
):
696 """Display argument spec and docstring in a popup bubble thingie."""
697 if self
.CallTipActive
: self
.CallTipCancel()
698 tip
= self
.interp
.getCallTip(command
)
700 offset
= self
.GetCurrentPos()
701 self
.CallTipShow(offset
, tip
)
703 def writeOut(self
, text
):
704 """Replacement for stdout."""
707 def writeErr(self
, text
):
708 """Replacement for stderr."""
711 def redirectStdin(self
, redirect
=1):
712 """If redirect is true then sys.stdin will come from the shell."""
714 sys
.stdin
= PseudoFileIn(self
.readIn
)
716 sys
.stdin
= self
.stdin
718 def redirectStdout(self
, redirect
=1):
719 """If redirect is true then sys.stdout will go to the shell."""
721 sys
.stdout
= PseudoFileOut(self
.writeOut
)
723 sys
.stdout
= self
.stdout
725 def redirectStderr(self
, redirect
=1):
726 """If redirect is true then sys.stderr will go to the shell."""
728 sys
.stderr
= PseudoFileErr(self
.writeErr
)
730 sys
.stderr
= self
.stderr
733 """Return true if text is selected and can be cut."""
734 if self
.GetSelectionStart() != self
.GetSelectionEnd() \
735 and self
.GetSelectionStart() >= self
.prompt1Pos
[1] \
736 and self
.GetSelectionEnd() >= self
.prompt1Pos
[1]:
742 """Return true if text is selected and can be copied."""
743 return self
.GetSelectionStart() != self
.GetSelectionEnd()
746 """Return true if a paste should succeed."""
747 if self
.CanEdit() and wxStyledTextCtrl
.CanPaste(self
):
753 """Return true if editing should succeed."""
754 return self
.GetCurrentPos() >= self
.prompt1Pos
[1]
757 """Remove selection and place it on the clipboard."""
758 if self
.CanCut() and self
.CanCopy():
759 if self
.AutoCompActive(): self
.AutoCompCancel()
760 if self
.CallTipActive
: self
.CallTipCancel()
762 self
.ReplaceSelection('')
765 """Copy selection and place it on the clipboard."""
767 command
= self
.GetSelectedText()
768 command
= command
.replace(os
.linesep
+ sys
.ps2
, os
.linesep
)
769 command
= command
.replace(os
.linesep
+ sys
.ps1
, os
.linesep
)
770 command
= self
.lstripPrompt(text
=command
)
771 data
= wxTextDataObject(command
)
772 if wxTheClipboard
.Open():
773 wxTheClipboard
.SetData(data
)
774 wxTheClipboard
.Close()
776 def CopyWithPrompts(self
):
777 """Copy selection, including prompts, and place it on the clipboard."""
779 command
= self
.GetSelectedText()
780 data
= wxTextDataObject(command
)
781 if wxTheClipboard
.Open():
782 wxTheClipboard
.SetData(data
)
783 wxTheClipboard
.Close()
786 """Replace selection with clipboard contents."""
788 if wxTheClipboard
.Open():
789 if wxTheClipboard
.IsSupported(wxDataFormat(wxDF_TEXT
)):
790 data
= wxTextDataObject()
791 if wxTheClipboard
.GetData(data
):
792 command
= data
.GetText()
793 command
= command
.rstrip()
794 command
= self
.fixLineEndings(command
)
795 command
= self
.lstripPrompt(text
=command
)
796 command
= command
.replace(os
.linesep
+ sys
.ps2
, '\n')
797 command
= command
.replace(os
.linesep
, '\n')
798 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
799 self
.ReplaceSelection('')
801 wxTheClipboard
.Close()
804 wxID_SELECTALL
= NewId() # This *should* be defined by wxPython.
805 ID_AUTOCOMP
= NewId()
806 ID_AUTOCOMP_SHOW
= NewId()
807 ID_AUTOCOMP_INCLUDE_MAGIC
= NewId()
808 ID_AUTOCOMP_INCLUDE_SINGLE
= NewId()
809 ID_AUTOCOMP_INCLUDE_DOUBLE
= NewId()
810 ID_CALLTIPS
= NewId()
811 ID_CALLTIPS_SHOW
= NewId()
815 """Mixin class to add standard menu items."""
817 def createMenus(self
):
818 m
= self
.fileMenu
= wxMenu()
820 m
.Append(wxID_EXIT
, 'E&xit', 'Exit PyCrust')
822 m
= self
.editMenu
= wxMenu()
823 m
.Append(wxID_UNDO
, '&Undo \tCtrl+Z', 'Undo the last action')
824 m
.Append(wxID_REDO
, '&Redo \tCtrl+Y', 'Redo the last undone action')
826 m
.Append(wxID_CUT
, 'Cu&t \tCtrl+X', 'Cut the selection')
827 m
.Append(wxID_COPY
, '&Copy \tCtrl+C', 'Copy the selection')
828 m
.Append(wxID_PASTE
, '&Paste \tCtrl+V', 'Paste')
830 m
.Append(wxID_CLEAR
, 'Cle&ar', 'Delete the selection')
831 m
.Append(wxID_SELECTALL
, 'Select A&ll', 'Select all text')
833 m
= self
.autocompMenu
= wxMenu()
834 m
.Append(ID_AUTOCOMP_SHOW
, 'Show Auto Completion', \
835 'Show auto completion during dot syntax', \
837 m
.Append(ID_AUTOCOMP_INCLUDE_MAGIC
, 'Include Magic Attributes', \
838 'Include attributes visible to __getattr__ and __setattr__', \
840 m
.Append(ID_AUTOCOMP_INCLUDE_SINGLE
, 'Include Single Underscores', \
841 'Include attibutes prefixed by a single underscore', \
843 m
.Append(ID_AUTOCOMP_INCLUDE_DOUBLE
, 'Include Double Underscores', \
844 'Include attibutes prefixed by a double underscore', \
847 m
= self
.calltipsMenu
= wxMenu()
848 m
.Append(ID_CALLTIPS_SHOW
, 'Show Call Tips', \
849 'Show call tips with argument specifications', checkable
=1)
851 m
= self
.optionsMenu
= wxMenu()
852 m
.AppendMenu(ID_AUTOCOMP
, '&Auto Completion', self
.autocompMenu
, \
853 'Auto Completion Options')
854 m
.AppendMenu(ID_CALLTIPS
, '&Call Tips', self
.calltipsMenu
, \
857 m
= self
.helpMenu
= wxMenu()
859 m
.Append(wxID_ABOUT
, '&About...', 'About PyCrust')
861 b
= self
.menuBar
= wxMenuBar()
862 b
.Append(self
.fileMenu
, '&File')
863 b
.Append(self
.editMenu
, '&Edit')
864 b
.Append(self
.optionsMenu
, '&Options')
865 b
.Append(self
.helpMenu
, '&Help')
868 EVT_MENU(self
, wxID_EXIT
, self
.OnExit
)
869 EVT_MENU(self
, wxID_UNDO
, self
.OnUndo
)
870 EVT_MENU(self
, wxID_REDO
, self
.OnRedo
)
871 EVT_MENU(self
, wxID_CUT
, self
.OnCut
)
872 EVT_MENU(self
, wxID_COPY
, self
.OnCopy
)
873 EVT_MENU(self
, wxID_PASTE
, self
.OnPaste
)
874 EVT_MENU(self
, wxID_CLEAR
, self
.OnClear
)
875 EVT_MENU(self
, wxID_SELECTALL
, self
.OnSelectAll
)
876 EVT_MENU(self
, wxID_ABOUT
, self
.OnAbout
)
877 EVT_MENU(self
, ID_AUTOCOMP_SHOW
, \
878 self
.OnAutoCompleteShow
)
879 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_MAGIC
, \
880 self
.OnAutoCompleteIncludeMagic
)
881 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_SINGLE
, \
882 self
.OnAutoCompleteIncludeSingle
)
883 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_DOUBLE
, \
884 self
.OnAutoCompleteIncludeDouble
)
885 EVT_MENU(self
, ID_CALLTIPS_SHOW
, \
888 EVT_UPDATE_UI(self
, wxID_UNDO
, self
.OnUpdateMenu
)
889 EVT_UPDATE_UI(self
, wxID_REDO
, self
.OnUpdateMenu
)
890 EVT_UPDATE_UI(self
, wxID_CUT
, self
.OnUpdateMenu
)
891 EVT_UPDATE_UI(self
, wxID_COPY
, self
.OnUpdateMenu
)
892 EVT_UPDATE_UI(self
, wxID_PASTE
, self
.OnUpdateMenu
)
893 EVT_UPDATE_UI(self
, wxID_CLEAR
, self
.OnUpdateMenu
)
894 EVT_UPDATE_UI(self
, ID_AUTOCOMP_SHOW
, self
.OnUpdateMenu
)
895 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_MAGIC
, self
.OnUpdateMenu
)
896 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_SINGLE
, self
.OnUpdateMenu
)
897 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_DOUBLE
, self
.OnUpdateMenu
)
898 EVT_UPDATE_UI(self
, ID_CALLTIPS_SHOW
, self
.OnUpdateMenu
)
900 def OnExit(self
, event
):
903 def OnUndo(self
, event
):
906 def OnRedo(self
, event
):
909 def OnCut(self
, event
):
912 def OnCopy(self
, event
):
915 def OnPaste(self
, event
):
918 def OnClear(self
, event
):
921 def OnSelectAll(self
, event
):
922 self
.shell
.SelectAll()
924 def OnAbout(self
, event
):
925 """Display an About PyCrust window."""
927 title
= 'About PyCrust'
928 text
= 'PyCrust %s\n\n' % VERSION
+ \
929 'Yet another Python shell, only flakier.\n\n' + \
930 'Half-baked by Patrick K. O\'Brien,\n' + \
931 'the other half is still in the oven.\n\n' + \
932 'Shell Revision: %s\n' % self
.shell
.revision
+ \
933 'Interpreter Revision: %s\n\n' % self
.shell
.interp
.revision
+ \
934 'Python Version: %s\n' % sys
.version
.split()[0] + \
935 'wxPython Version: %s\n' % wx
.__version
__ + \
936 'Platform: %s\n' % sys
.platform
937 dialog
= wxMessageDialog(self
, text
, title
, wxOK | wxICON_INFORMATION
)
941 def OnAutoCompleteShow(self
, event
):
942 self
.shell
.autoComplete
= event
.IsChecked()
944 def OnAutoCompleteIncludeMagic(self
, event
):
945 self
.shell
.autoCompleteIncludeMagic
= event
.IsChecked()
947 def OnAutoCompleteIncludeSingle(self
, event
):
948 self
.shell
.autoCompleteIncludeSingle
= event
.IsChecked()
950 def OnAutoCompleteIncludeDouble(self
, event
):
951 self
.shell
.autoCompleteIncludeDouble
= event
.IsChecked()
953 def OnCallTipsShow(self
, event
):
954 self
.shell
.autoCallTip
= event
.IsChecked()
956 def OnUpdateMenu(self
, event
):
957 """Update menu items based on current status."""
960 event
.Enable(self
.shell
.CanUndo())
961 elif id == wxID_REDO
:
962 event
.Enable(self
.shell
.CanRedo())
964 event
.Enable(self
.shell
.CanCut())
965 elif id == wxID_COPY
:
966 event
.Enable(self
.shell
.CanCopy())
967 elif id == wxID_PASTE
:
968 event
.Enable(self
.shell
.CanPaste())
969 elif id == wxID_CLEAR
:
970 event
.Enable(self
.shell
.CanCut())
971 elif id == ID_AUTOCOMP_SHOW
:
972 event
.Check(self
.shell
.autoComplete
)
973 elif id == ID_AUTOCOMP_INCLUDE_MAGIC
:
974 event
.Check(self
.shell
.autoCompleteIncludeMagic
)
975 elif id == ID_AUTOCOMP_INCLUDE_SINGLE
:
976 event
.Check(self
.shell
.autoCompleteIncludeSingle
)
977 elif id == ID_AUTOCOMP_INCLUDE_DOUBLE
:
978 event
.Check(self
.shell
.autoCompleteIncludeDouble
)
979 elif id == ID_CALLTIPS_SHOW
:
980 event
.Check(self
.shell
.autoCallTip
)
983 class ShellFrame(wxFrame
, ShellMenu
):
984 """Frame containing the PyCrust shell component."""
986 name
= 'PyCrust Shell Frame'
987 revision
= __version__
989 def __init__(self
, parent
=None, id=-1, title
='PyShell', \
990 pos
=wxDefaultPosition
, size
=wxDefaultSize
, \
991 style
=wxDEFAULT_FRAME_STYLE
, locals=None, \
992 InterpClass
=None, *args
, **kwds
):
993 """Create a PyCrust ShellFrame instance."""
994 wxFrame
.__init
__(self
, parent
, id, title
, pos
, size
, style
)
995 intro
= 'Welcome To PyCrust %s - The Flakiest Python Shell' % VERSION
996 intro
+= '\nSponsored by Orbtech.com - Your Source For Python Development Services'
997 self
.CreateStatusBar()
998 self
.SetStatusText(intro
.replace('\n', ', '))
999 if wxPlatform
== '__WXMSW__':
1001 filename
= os
.path
.join(os
.path
.dirname(__file__
), 'PyCrust.ico')
1002 icon
= wxIcon(filename
, wxBITMAP_TYPE_ICO
)
1004 self
.shell
= Shell(parent
=self
, id=-1, introText
=intro
, \
1005 locals=locals, InterpClass
=InterpClass
, \
1007 # Override the shell so that status messages go to the status bar.
1008 self
.shell
.setStatusText
= self
.SetStatusText