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
, 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+Up Arrow Retrieve Previous History item.
86 Alt+P Retrieve Previous History item.
87 Ctrl+Down Arrow Retrieve Next History item.
88 Alt+N Retrieve Next History item.
89 F8 Command-completion of History item.
90 (Type a few characters of a previous command and then press F8.)
94 """Display some useful information about how to use the shell."""
95 self
.write(self
.helpText
)
97 def __getattr__(self
, name
):
98 if hasattr(self
.other
, name
):
99 return getattr(self
.other
, name
)
101 raise AttributeError, name
103 def __setattr__(self
, name
, value
):
104 if self
.__dict
__.has_key(name
):
105 self
.__dict
__[name
] = value
106 elif hasattr(self
.other
, name
):
107 return setattr(self
.other
, name
, value
)
109 raise AttributeError, name
111 def _getAttributeNames(self
):
112 """Return list of magic attributes to extend introspection."""
113 list = ['autoCallTip',
115 'autoCompleteCaseInsensitive',
116 'autoCompleteIncludeDouble',
117 'autoCompleteIncludeMagic',
118 'autoCompleteIncludeSingle',
124 class Shell(wxStyledTextCtrl
):
125 """PyCrust Shell based on wxStyledTextCtrl."""
127 name
= 'PyCrust Shell'
128 revision
= __version__
130 def __init__(self
, parent
, id=-1, pos
=wxDefaultPosition
, \
131 size
=wxDefaultSize
, style
=wxCLIP_CHILDREN
, introText
='', \
132 locals=None, InterpClass
=None, *args
, **kwds
):
133 """Create a PyCrust Shell instance."""
134 wxStyledTextCtrl
.__init
__(self
, parent
, id, pos
, size
, style
)
135 # Grab these so they can be restored by self.redirect* methods.
136 self
.stdin
= sys
.stdin
137 self
.stdout
= sys
.stdout
138 self
.stderr
= sys
.stderr
139 # Add the current working directory "." to the search path.
140 sys
.path
.insert(0, os
.curdir
)
141 # Import a default interpreter class if one isn't provided.
142 if InterpClass
== None:
143 from interpreter
import Interpreter
145 Interpreter
= InterpClass
146 # Create default locals so we have something interesting.
147 shellLocals
= {'__name__': 'PyCrust-Shell',
148 '__doc__': 'PyCrust-Shell, The PyCrust Python Shell.',
149 '__version__': VERSION
,
151 # Add the dictionary that was passed in.
153 shellLocals
.update(locals)
154 self
.interp
= Interpreter(locals=shellLocals
, \
155 rawin
=self
.readRaw
, \
156 stdin
=PseudoFileIn(self
.readIn
), \
157 stdout
=PseudoFileOut(self
.writeOut
), \
158 stderr
=PseudoFileErr(self
.writeErr
), \
160 # Keep track of the last non-continuation prompt positions.
161 self
.promptPosStart
= 0
162 self
.promptPosEnd
= 0
163 # Keep track of multi-line commands.
165 # Create the command history. Commands are added into the front of
166 # the list (ie. at index 0) as they are entered. self.historyIndex
167 # is the current position in the history; it gets incremented as you
168 # retrieve the previous command, decremented as you retrieve the
169 # next, and reset when you hit Enter. self.historyIndex == -1 means
170 # you're on the current command, not in the history.
172 self
.historyIndex
= -1
173 # Assign handlers for keyboard events.
174 EVT_KEY_DOWN(self
, self
.OnKeyDown
)
175 EVT_CHAR(self
, self
.OnChar
)
176 # Configure various defaults and user preferences.
178 # Display the introductory banner information.
179 try: self
.showIntro(introText
)
181 # Assign some pseudo keywords to the interpreter's namespace.
182 try: self
.setBuiltinKeywords()
184 # Add 'shell' to the interpreter's local namespace.
185 try: self
.setLocalShell()
187 # Do this last so the user has complete control over their
188 # environment. They can override anything they want.
189 try: self
.execStartupScript(self
.interp
.startupScript
)
196 """Configure shell based on user preferences."""
197 self
.SetMarginType(1, wxSTC_MARGIN_NUMBER
)
198 self
.SetMarginWidth(1, 40)
200 self
.SetLexer(wxSTC_LEX_PYTHON
)
201 self
.SetKeyWords(0, ' '.join(keyword
.kwlist
))
203 self
.setStyles(faces
)
204 self
.SetViewWhiteSpace(0)
207 # Do we want to automatically pop up command completion options?
208 self
.autoComplete
= 1
209 self
.autoCompleteIncludeMagic
= 1
210 self
.autoCompleteIncludeSingle
= 1
211 self
.autoCompleteIncludeDouble
= 1
212 self
.autoCompleteCaseInsensitive
= 1
213 self
.AutoCompSetIgnoreCase(self
.autoCompleteCaseInsensitive
)
214 # Do we want to automatically pop up command argument help?
216 self
.CallTipSetBackground(wxColour(255, 255, 232))
218 def showIntro(self
, text
=''):
219 """Display introductory text in the shell."""
221 if not text
.endswith(os
.linesep
): text
+= os
.linesep
224 self
.write(self
.interp
.introText
)
225 except AttributeError:
228 def setBuiltinKeywords(self
):
229 """Create pseudo keywords as part of builtins.
231 This is a rather clever hack that sets "close", "exit" and "quit"
232 to a PseudoKeyword object so that we can make them do what we want.
233 In this case what we want is to call our self.quit() method.
234 The user can type "close", "exit" or "quit" without the final parens.
236 ## POB: This is having some weird side-effects so I'm taking it out.
237 ## import __builtin__
238 ## from pseudo import PseudoKeyword
239 ## __builtin__.close = __builtin__.exit = __builtin__.quit = \
240 ## PseudoKeyword(self.quit)
242 from pseudo
import PseudoKeyword
243 __builtin__
.close
= __builtin__
.exit
= __builtin__
.quit
= \
244 'Click on the close button to leave the application.'
247 """Quit the application."""
249 # XXX Good enough for now but later we want to send a close event.
251 # In the close event handler we can make sure they want to quit.
252 # Other applications, like PythonCard, may choose to hide rather than
253 # quit so we should just post the event and let the surrounding app
254 # decide what it wants to do.
255 self
.write('Click on the close button to leave the application.')
257 def setLocalShell(self
):
258 """Add 'shell' to locals as reference to ShellFacade instance."""
259 self
.interp
.locals['shell'] = ShellFacade(other
=self
)
261 def execStartupScript(self
, startupScript
):
262 """Execute the user's PYTHONSTARTUP script if they have one."""
263 if startupScript
and os
.path
.isfile(startupScript
):
264 startupText
= 'Startup script executed: ' + startupScript
265 self
.push('print %s;execfile(%s)' % \
266 (`startupText`
, `startupScript`
))
270 def setStyles(self
, faces
):
271 """Configure font size, typeface and color for lexer."""
274 self
.StyleSetSpec(wxSTC_STYLE_DEFAULT
, "face:%(mono)s,size:%(size)d,back:%(backcol)s" % faces
)
279 self
.StyleSetSpec(wxSTC_STYLE_LINENUMBER
, "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % faces
)
280 self
.StyleSetSpec(wxSTC_STYLE_CONTROLCHAR
, "face:%(mono)s" % faces
)
281 self
.StyleSetSpec(wxSTC_STYLE_BRACELIGHT
, "fore:#0000FF,back:#FFFF88")
282 self
.StyleSetSpec(wxSTC_STYLE_BRACEBAD
, "fore:#FF0000,back:#FFFF88")
285 self
.StyleSetSpec(wxSTC_P_DEFAULT
, "face:%(mono)s" % faces
)
286 self
.StyleSetSpec(wxSTC_P_COMMENTLINE
, "fore:#007F00,face:%(mono)s" % faces
)
287 self
.StyleSetSpec(wxSTC_P_NUMBER
, "")
288 self
.StyleSetSpec(wxSTC_P_STRING
, "fore:#7F007F,face:%(mono)s" % faces
)
289 self
.StyleSetSpec(wxSTC_P_CHARACTER
, "fore:#7F007F,face:%(mono)s" % faces
)
290 self
.StyleSetSpec(wxSTC_P_WORD
, "fore:#00007F,bold")
291 self
.StyleSetSpec(wxSTC_P_TRIPLE
, "fore:#7F0000")
292 self
.StyleSetSpec(wxSTC_P_TRIPLEDOUBLE
, "fore:#000033,back:#FFFFE8")
293 self
.StyleSetSpec(wxSTC_P_CLASSNAME
, "fore:#0000FF,bold")
294 self
.StyleSetSpec(wxSTC_P_DEFNAME
, "fore:#007F7F,bold")
295 self
.StyleSetSpec(wxSTC_P_OPERATOR
, "")
296 self
.StyleSetSpec(wxSTC_P_IDENTIFIER
, "")
297 self
.StyleSetSpec(wxSTC_P_COMMENTBLOCK
, "fore:#7F7F7F")
298 self
.StyleSetSpec(wxSTC_P_STRINGEOL
, "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces
)
300 def OnChar(self
, event
):
301 """Keypress event handler.
303 Prevents modification of previously submitted commands/responses."""
304 if not self
.CanEdit():
306 key
= event
.KeyCode()
307 currpos
= self
.GetCurrentPos()
308 stoppos
= self
.promptPosEnd
310 # The dot or period key activates auto completion.
311 # Get the command between the prompt and the cursor.
312 # Add a dot to the end of the command.
313 command
= self
.GetTextRange(stoppos
, currpos
) + '.'
315 if self
.autoComplete
: self
.autoCompleteShow(command
)
316 elif key
== ord('('):
317 # The left paren activates a call tip and cancels
318 # an active auto completion.
319 if self
.AutoCompActive(): self
.AutoCompCancel()
320 # Get the command between the prompt and the cursor.
321 # Add the '(' to the end of the command.
322 command
= self
.GetTextRange(stoppos
, currpos
) + '('
324 if self
.autoCallTip
: self
.autoCallTipShow(command
)
326 # Allow the normal event handling to take place.
329 def OnKeyDown(self
, event
):
330 """Key down event handler.
332 Prevents modification of previously submitted commands/responses."""
333 key
= event
.KeyCode()
334 controlDown
= event
.ControlDown()
335 altDown
= event
.AltDown()
336 shiftDown
= event
.ShiftDown()
337 currpos
= self
.GetCurrentPos()
338 # Return is used to submit a command to the interpreter.
339 if key
== WXK_RETURN
:
340 if self
.AutoCompActive(): self
.AutoCompCancel()
341 if self
.CallTipActive(): self
.CallTipCancel()
343 # If the auto-complete window is up let it do its thing.
344 elif self
.AutoCompActive():
346 # Let Ctrl-Alt-* get handled normally.
347 elif controlDown
and altDown
:
349 # Cut to the clipboard.
350 elif (controlDown
and key
in (ord('X'), ord('x'))) \
351 or (shiftDown
and key
== WXK_DELETE
):
353 # Copy to the clipboard.
354 elif controlDown
and not shiftDown \
355 and key
in (ord('C'), ord('c'), WXK_INSERT
):
357 # Copy to the clipboard, including prompts.
358 elif controlDown
and shiftDown \
359 and key
in (ord('C'), ord('c'), WXK_INSERT
):
360 self
.CopyWithPrompts()
361 # Paste from the clipboard.
362 elif (controlDown
and key
in (ord('V'), ord('v'), WXK_INSERT
)) \
363 or (shiftDown
and key
== WXK_INSERT
):
365 # Retrieve the previous command from the history buffer.
366 elif (controlDown
and key
== WXK_UP
) \
367 or (altDown
and key
in (ord('P'), ord('p'))):
368 self
.OnHistoryRetrieve(step
=+1)
369 # Retrieve the next command from the history buffer.
370 elif (controlDown
and key
== WXK_DOWN
) \
371 or (altDown
and key
in (ord('N'), ord('n'))):
372 self
.OnHistoryRetrieve(step
=-1)
373 # Search up the history for the text in front of the cursor.
375 self
.OnHistorySearch()
376 # Home needs to be aware of the prompt.
377 elif key
== WXK_HOME
:
378 home
= self
.promptPosEnd
380 if event
.ShiftDown():
381 # Select text from current position to end of prompt.
382 self
.SetSelection(self
.GetCurrentPos(), home
)
384 self
.SetCurrentPos(home
)
388 # Basic navigation keys should work anywhere.
389 elif key
in (WXK_END
, WXK_LEFT
, WXK_RIGHT
, WXK_UP
, WXK_DOWN
, \
390 WXK_PRIOR
, WXK_NEXT
):
392 # Don't backspace over the latest non-continuation prompt.
393 elif key
== WXK_BACK
:
394 if currpos
> self
.promptPosEnd
:
396 # Only allow these keys after the latest prompt.
397 elif key
in (WXK_TAB
, WXK_DELETE
):
400 # Don't toggle between insert mode and overwrite mode.
401 elif key
== WXK_INSERT
:
403 # Don't allow line deletion.
404 elif controlDown
and key
in (ord('L'), ord('l')):
406 # Don't allow line transposition.
407 elif controlDown
and key
in (ord('T'), ord('t')):
409 # Protect the readonly portion of the shell.
410 elif not self
.CanEdit():
415 def OnHistoryRetrieve(self
, step
):
416 """Retrieve the previous/next command from the history buffer."""
417 if not self
.CanEdit():
419 startpos
= self
.GetCurrentPos()
420 newindex
= self
.historyIndex
+ step
421 if not (-1 <= newindex
< len(self
.history
)):
423 self
.historyIndex
= newindex
425 self
.ReplaceSelection('')
427 self
.ReplaceSelection('')
428 command
= self
.history
[self
.historyIndex
]
429 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
430 self
.ReplaceSelection(command
)
431 endpos
= self
.GetCurrentPos()
432 self
.SetSelection(endpos
, startpos
)
434 def OnHistorySearch(self
):
435 """Search up the history buffer for the text in front of the cursor."""
436 if not self
.CanEdit():
438 startpos
= self
.GetCurrentPos()
439 # The text up to the cursor is what we search for.
440 numCharsAfterCursor
= self
.GetTextLength() - startpos
441 searchText
= self
.getCommand(rstrip
=0)
442 if numCharsAfterCursor
> 0:
443 searchText
= searchText
[:-numCharsAfterCursor
]
446 # Search upwards from the current history position and loop back
447 # to the beginning if we don't find anything.
448 if (self
.historyIndex
<= -1) \
449 or (self
.historyIndex
>= len(self
.history
)-2):
450 searchOrder
= range(len(self
.history
))
452 searchOrder
= range(self
.historyIndex
+1, len(self
.history
)) + \
453 range(self
.historyIndex
)
454 for i
in searchOrder
:
455 command
= self
.history
[i
]
456 if command
[:len(searchText
)] == searchText
:
457 # Replace the current selection with the one we've found.
458 self
.ReplaceSelection(command
[len(searchText
):])
459 endpos
= self
.GetCurrentPos()
460 self
.SetSelection(endpos
, startpos
)
461 # We've now warped into middle of the history.
462 self
.historyIndex
= i
465 def setStatusText(self
, text
):
466 """Display status information."""
468 # This method will most likely be replaced by the enclosing app
469 # to do something more interesting, like write to a status bar.
472 def processLine(self
):
473 """Process the line of text at which the user hit Enter."""
475 # The user hit ENTER and we need to decide what to do. They could be
476 # sitting on any line in the shell.
478 thepos
= self
.GetCurrentPos()
479 endpos
= self
.GetTextLength()
480 # If they hit RETURN at the very bottom, execute the command.
483 if self
.getCommand():
484 command
= self
.GetTextRange(self
.promptPosEnd
, endpos
)
486 # This is a hack, now that we allow editing of previous
487 # lines, which throws off our promptPos values.
488 newend
= endpos
- len(self
.getCommand(rstrip
=0))
489 command
= self
.GetTextRange(self
.promptPosEnd
, newend
)
490 command
= command
.replace(os
.linesep
+ sys
.ps2
, '\n')
492 # Or replace the current command with the other command.
493 elif thepos
< self
.promptPosStart
:
494 theline
= self
.GetCurrentLine()
495 command
= self
.getCommand(rstrip
=0)
496 # If the new line contains a command (even an invalid one).
498 command
= self
.getMultilineCommand()
499 self
.SetCurrentPos(endpos
)
500 startpos
= self
.promptPosEnd
501 self
.SetSelection(startpos
, endpos
)
502 self
.ReplaceSelection('')
505 # Otherwise, put the cursor back where we started.
507 self
.SetCurrentPos(thepos
)
508 self
.SetAnchor(thepos
)
509 # Or add a new line to the current single or multi-line command.
510 elif thepos
> self
.promptPosEnd
:
511 self
.write(os
.linesep
)
515 def getMultilineCommand(self
, rstrip
=1):
516 """Extract a multi-line command from the editor.
518 The command may not necessarily be valid Python syntax."""
519 # XXX Need to extract real prompts here. Need to keep track of the
520 # prompt every time a command is issued.
525 # This is a total hack job, but it works.
526 text
= self
.GetCurLine()[0]
527 line
= self
.GetCurrentLine()
528 while text
[:ps2size
] == ps2
and line
> 0:
531 text
= self
.GetCurLine()[0]
532 if text
[:ps1size
] == ps1
:
533 line
= self
.GetCurrentLine()
535 startpos
= self
.GetCurrentPos() + ps1size
538 while self
.GetCurLine()[0][:ps2size
] == ps2
:
541 stoppos
= self
.GetCurrentPos()
542 command
= self
.GetTextRange(startpos
, stoppos
)
543 command
= command
.replace(os
.linesep
+ sys
.ps2
, '\n')
544 command
= command
.rstrip()
545 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
549 command
= command
.rstrip()
552 def getCommand(self
, text
=None, rstrip
=1):
553 """Extract a command from text which may include a shell prompt.
555 The command may not necessarily be valid Python syntax."""
557 text
= self
.GetCurLine()[0]
558 # Strip the prompt off the front of text leaving just the command.
559 command
= self
.lstripPrompt(text
)
561 command
= '' # Real commands have prompts.
563 command
= command
.rstrip()
566 def lstripPrompt(self
, text
):
567 """Return text without a leading prompt."""
572 # Strip the prompt off the front of text.
573 if text
[:ps1size
] == ps1
:
574 text
= text
[ps1size
:]
575 elif text
[:ps2size
] == ps2
:
576 text
= text
[ps2size
:]
579 def push(self
, command
):
580 """Send command to the interpreter for execution."""
581 self
.write(os
.linesep
)
582 self
.more
= self
.interp
.push(command
)
584 self
.addHistory(command
.rstrip())
587 def addHistory(self
, command
):
588 """Add command to the command history."""
589 # Reset the history position.
590 self
.historyIndex
= -1
591 # Insert this command into the history, unless it's a blank
592 # line or the same as the last command.
594 and (len(self
.history
) == 0 or command
!= self
.history
[0]):
595 self
.history
.insert(0, command
)
597 def write(self
, text
):
598 """Display text in the shell.
600 Replace line endings with OS-specific endings."""
601 text
= self
.fixLineEndings(text
)
603 self
.EnsureCaretVisible()
605 def fixLineEndings(self
, text
):
606 """Return text with line endings replaced by OS-specific endings."""
607 lines
= text
.split('\r\n')
608 for l
in range(len(lines
)):
609 chunks
= lines
[l
].split('\r')
610 for c
in range(len(chunks
)):
611 chunks
[c
] = os
.linesep
.join(chunks
[c
].split('\n'))
612 lines
[l
] = os
.linesep
.join(chunks
)
613 text
= os
.linesep
.join(lines
)
617 """Display appropriate prompt for the context, either ps1 or ps2.
619 If this is a continuation line, autoindent as necessary."""
621 prompt
= str(sys
.ps2
)
623 prompt
= str(sys
.ps1
)
624 pos
= self
.GetCurLine()[1]
625 if pos
> 0: self
.write(os
.linesep
)
627 self
.promptPosStart
= self
.GetCurrentPos()
630 self
.promptPosEnd
= self
.GetCurrentPos()
631 # Keep the undo feature from undoing previous responses.
632 self
.EmptyUndoBuffer()
633 # XXX Add some autoindent magic here if more.
635 self
.write(' '*4) # Temporary hack indentation.
636 self
.EnsureCaretVisible()
637 self
.ScrollToColumn(0)
640 """Replacement for stdin."""
641 prompt
= 'Please enter your response:'
642 dialog
= wxTextEntryDialog(None, prompt
, \
643 'Input Dialog (Standard)', '')
645 if dialog
.ShowModal() == wxID_OK
:
646 text
= dialog
.GetValue()
647 self
.write(text
+ os
.linesep
)
653 def readRaw(self
, prompt
='Please enter your response:'):
654 """Replacement for raw_input."""
655 dialog
= wxTextEntryDialog(None, prompt
, \
656 'Input Dialog (Raw)', '')
658 if dialog
.ShowModal() == wxID_OK
:
659 text
= dialog
.GetValue()
665 def ask(self
, prompt
='Please enter your response:'):
666 """Get response from the user."""
667 return raw_input(prompt
=prompt
)
670 """Halt execution pending a response from the user."""
671 self
.ask('Press enter to continue:')
674 """Delete all text from the shell."""
677 def run(self
, command
, prompt
=1, verbose
=1):
678 """Execute command within the shell as if it was typed in directly.
679 >>> shell.run('print "this"')
684 # Go to the very bottom of the text.
685 endpos
= self
.GetTextLength()
686 self
.SetCurrentPos(endpos
)
687 command
= command
.rstrip()
688 if prompt
: self
.prompt()
689 if verbose
: self
.write(command
)
692 def runfile(self
, filename
):
693 """Execute all commands in file as if they were typed into the shell."""
694 file = open(filename
)
697 for command
in file.readlines():
698 if command
[:6] == 'shell.': # Run shell methods silently.
699 self
.run(command
, prompt
=0, verbose
=0)
701 self
.run(command
, prompt
=0, verbose
=1)
705 def autoCompleteShow(self
, command
):
706 """Display auto-completion popup list."""
707 list = self
.interp
.getAutoCompleteList(command
, \
708 includeMagic
=self
.autoCompleteIncludeMagic
, \
709 includeSingle
=self
.autoCompleteIncludeSingle
, \
710 includeDouble
=self
.autoCompleteIncludeDouble
)
712 options
= ' '.join(list)
714 self
.AutoCompShow(offset
, options
)
716 def autoCallTipShow(self
, command
):
717 """Display argument spec and docstring in a popup bubble thingie."""
718 if self
.CallTipActive
: self
.CallTipCancel()
719 tip
= self
.interp
.getCallTip(command
)
721 offset
= self
.GetCurrentPos()
722 self
.CallTipShow(offset
, tip
)
724 def writeOut(self
, text
):
725 """Replacement for stdout."""
728 def writeErr(self
, text
):
729 """Replacement for stderr."""
732 def redirectStdin(self
, redirect
=1):
733 """If redirect is true then sys.stdin will come from the shell."""
735 sys
.stdin
= PseudoFileIn(self
.readIn
)
737 sys
.stdin
= self
.stdin
739 def redirectStdout(self
, redirect
=1):
740 """If redirect is true then sys.stdout will go to the shell."""
742 sys
.stdout
= PseudoFileOut(self
.writeOut
)
744 sys
.stdout
= self
.stdout
746 def redirectStderr(self
, redirect
=1):
747 """If redirect is true then sys.stderr will go to the shell."""
749 sys
.stderr
= PseudoFileErr(self
.writeErr
)
751 sys
.stderr
= self
.stderr
754 """Return true if text is selected and can be cut."""
755 if self
.GetSelectionStart() != self
.GetSelectionEnd() \
756 and self
.GetSelectionStart() >= self
.promptPosEnd \
757 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
763 """Return true if text is selected and can be copied."""
764 return self
.GetSelectionStart() != self
.GetSelectionEnd()
767 """Return true if a paste should succeed."""
768 if self
.CanEdit() and wxStyledTextCtrl
.CanPaste(self
):
774 """Return true if editing should succeed."""
775 return self
.GetCurrentPos() >= self
.promptPosEnd
778 """Remove selection and place it on the clipboard."""
779 if self
.CanCut() and self
.CanCopy():
780 if self
.AutoCompActive(): self
.AutoCompCancel()
781 if self
.CallTipActive
: self
.CallTipCancel()
783 self
.ReplaceSelection('')
786 """Copy selection and place it on the clipboard."""
788 command
= self
.GetSelectedText()
789 command
= command
.replace(os
.linesep
+ sys
.ps2
, os
.linesep
)
790 command
= command
.replace(os
.linesep
+ sys
.ps1
, os
.linesep
)
791 command
= self
.lstripPrompt(text
=command
)
792 data
= wxTextDataObject(command
)
793 if wxTheClipboard
.Open():
794 wxTheClipboard
.SetData(data
)
795 wxTheClipboard
.Close()
797 def CopyWithPrompts(self
):
798 """Copy selection, including prompts, and place it on the clipboard."""
800 command
= self
.GetSelectedText()
801 data
= wxTextDataObject(command
)
802 if wxTheClipboard
.Open():
803 wxTheClipboard
.SetData(data
)
804 wxTheClipboard
.Close()
807 """Replace selection with clipboard contents."""
809 if wxTheClipboard
.Open():
810 if wxTheClipboard
.IsSupported(wxDataFormat(wxDF_TEXT
)):
811 data
= wxTextDataObject()
812 if wxTheClipboard
.GetData(data
):
813 command
= data
.GetText()
814 command
= command
.rstrip()
815 command
= self
.fixLineEndings(command
)
816 command
= self
.lstripPrompt(text
=command
)
817 command
= command
.replace(os
.linesep
+ sys
.ps2
, '\n')
818 command
= command
.replace(os
.linesep
, '\n')
819 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
820 self
.ReplaceSelection('')
822 wxTheClipboard
.Close()
825 wxID_SELECTALL
= NewId() # This *should* be defined by wxPython.
826 ID_AUTOCOMP
= NewId()
827 ID_AUTOCOMP_SHOW
= NewId()
828 ID_AUTOCOMP_INCLUDE_MAGIC
= NewId()
829 ID_AUTOCOMP_INCLUDE_SINGLE
= NewId()
830 ID_AUTOCOMP_INCLUDE_DOUBLE
= NewId()
831 ID_CALLTIPS
= NewId()
832 ID_CALLTIPS_SHOW
= NewId()
836 """Mixin class to add standard menu items."""
838 def createMenus(self
):
839 m
= self
.fileMenu
= wxMenu()
841 m
.Append(wxID_EXIT
, 'E&xit', 'Exit PyCrust')
843 m
= self
.editMenu
= wxMenu()
844 m
.Append(wxID_UNDO
, '&Undo \tCtrl+Z', 'Undo the last action')
845 m
.Append(wxID_REDO
, '&Redo \tCtrl+Y', 'Redo the last undone action')
847 m
.Append(wxID_CUT
, 'Cu&t \tCtrl+X', 'Cut the selection')
848 m
.Append(wxID_COPY
, '&Copy \tCtrl+C', 'Copy the selection')
849 m
.Append(wxID_PASTE
, '&Paste \tCtrl+V', 'Paste')
851 m
.Append(wxID_CLEAR
, 'Cle&ar', 'Delete the selection')
852 m
.Append(wxID_SELECTALL
, 'Select A&ll', 'Select all text')
854 m
= self
.autocompMenu
= wxMenu()
855 m
.Append(ID_AUTOCOMP_SHOW
, 'Show Auto Completion', \
856 'Show auto completion during dot syntax', \
858 m
.Append(ID_AUTOCOMP_INCLUDE_MAGIC
, 'Include Magic Attributes', \
859 'Include attributes visible to __getattr__ and __setattr__', \
861 m
.Append(ID_AUTOCOMP_INCLUDE_SINGLE
, 'Include Single Underscores', \
862 'Include attibutes prefixed by a single underscore', \
864 m
.Append(ID_AUTOCOMP_INCLUDE_DOUBLE
, 'Include Double Underscores', \
865 'Include attibutes prefixed by a double underscore', \
868 m
= self
.calltipsMenu
= wxMenu()
869 m
.Append(ID_CALLTIPS_SHOW
, 'Show Call Tips', \
870 'Show call tips with argument specifications', checkable
=1)
872 m
= self
.optionsMenu
= wxMenu()
873 m
.AppendMenu(ID_AUTOCOMP
, '&Auto Completion', self
.autocompMenu
, \
874 'Auto Completion Options')
875 m
.AppendMenu(ID_CALLTIPS
, '&Call Tips', self
.calltipsMenu
, \
878 m
= self
.helpMenu
= wxMenu()
880 m
.Append(wxID_ABOUT
, '&About...', 'About PyCrust')
882 b
= self
.menuBar
= wxMenuBar()
883 b
.Append(self
.fileMenu
, '&File')
884 b
.Append(self
.editMenu
, '&Edit')
885 b
.Append(self
.optionsMenu
, '&Options')
886 b
.Append(self
.helpMenu
, '&Help')
889 EVT_MENU(self
, wxID_EXIT
, self
.OnExit
)
890 EVT_MENU(self
, wxID_UNDO
, self
.OnUndo
)
891 EVT_MENU(self
, wxID_REDO
, self
.OnRedo
)
892 EVT_MENU(self
, wxID_CUT
, self
.OnCut
)
893 EVT_MENU(self
, wxID_COPY
, self
.OnCopy
)
894 EVT_MENU(self
, wxID_PASTE
, self
.OnPaste
)
895 EVT_MENU(self
, wxID_CLEAR
, self
.OnClear
)
896 EVT_MENU(self
, wxID_SELECTALL
, self
.OnSelectAll
)
897 EVT_MENU(self
, wxID_ABOUT
, self
.OnAbout
)
898 EVT_MENU(self
, ID_AUTOCOMP_SHOW
, \
899 self
.OnAutoCompleteShow
)
900 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_MAGIC
, \
901 self
.OnAutoCompleteIncludeMagic
)
902 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_SINGLE
, \
903 self
.OnAutoCompleteIncludeSingle
)
904 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_DOUBLE
, \
905 self
.OnAutoCompleteIncludeDouble
)
906 EVT_MENU(self
, ID_CALLTIPS_SHOW
, \
909 EVT_UPDATE_UI(self
, wxID_UNDO
, self
.OnUpdateMenu
)
910 EVT_UPDATE_UI(self
, wxID_REDO
, self
.OnUpdateMenu
)
911 EVT_UPDATE_UI(self
, wxID_CUT
, self
.OnUpdateMenu
)
912 EVT_UPDATE_UI(self
, wxID_COPY
, self
.OnUpdateMenu
)
913 EVT_UPDATE_UI(self
, wxID_PASTE
, self
.OnUpdateMenu
)
914 EVT_UPDATE_UI(self
, wxID_CLEAR
, self
.OnUpdateMenu
)
915 EVT_UPDATE_UI(self
, ID_AUTOCOMP_SHOW
, self
.OnUpdateMenu
)
916 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_MAGIC
, self
.OnUpdateMenu
)
917 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_SINGLE
, self
.OnUpdateMenu
)
918 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_DOUBLE
, self
.OnUpdateMenu
)
919 EVT_UPDATE_UI(self
, ID_CALLTIPS_SHOW
, self
.OnUpdateMenu
)
921 def OnExit(self
, event
):
924 def OnUndo(self
, event
):
927 def OnRedo(self
, event
):
930 def OnCut(self
, event
):
933 def OnCopy(self
, event
):
936 def OnPaste(self
, event
):
939 def OnClear(self
, event
):
942 def OnSelectAll(self
, event
):
943 self
.shell
.SelectAll()
945 def OnAbout(self
, event
):
946 """Display an About PyCrust window."""
948 title
= 'About PyCrust'
949 text
= 'PyCrust %s\n\n' % VERSION
+ \
950 'Yet another Python shell, only flakier.\n\n' + \
951 'Half-baked by Patrick K. O\'Brien,\n' + \
952 'the other half is still in the oven.\n\n' + \
953 'Shell Revision: %s\n' % self
.shell
.revision
+ \
954 'Interpreter Revision: %s\n\n' % self
.shell
.interp
.revision
+ \
955 'Python Version: %s\n' % sys
.version
.split()[0] + \
956 'wxPython Version: %s\n' % wx
.__version
__ + \
957 'Platform: %s\n' % sys
.platform
958 dialog
= wxMessageDialog(self
, text
, title
, wxOK | wxICON_INFORMATION
)
962 def OnAutoCompleteShow(self
, event
):
963 self
.shell
.autoComplete
= event
.IsChecked()
965 def OnAutoCompleteIncludeMagic(self
, event
):
966 self
.shell
.autoCompleteIncludeMagic
= event
.IsChecked()
968 def OnAutoCompleteIncludeSingle(self
, event
):
969 self
.shell
.autoCompleteIncludeSingle
= event
.IsChecked()
971 def OnAutoCompleteIncludeDouble(self
, event
):
972 self
.shell
.autoCompleteIncludeDouble
= event
.IsChecked()
974 def OnCallTipsShow(self
, event
):
975 self
.shell
.autoCallTip
= event
.IsChecked()
977 def OnUpdateMenu(self
, event
):
978 """Update menu items based on current status."""
981 event
.Enable(self
.shell
.CanUndo())
982 elif id == wxID_REDO
:
983 event
.Enable(self
.shell
.CanRedo())
985 event
.Enable(self
.shell
.CanCut())
986 elif id == wxID_COPY
:
987 event
.Enable(self
.shell
.CanCopy())
988 elif id == wxID_PASTE
:
989 event
.Enable(self
.shell
.CanPaste())
990 elif id == wxID_CLEAR
:
991 event
.Enable(self
.shell
.CanCut())
992 elif id == ID_AUTOCOMP_SHOW
:
993 event
.Check(self
.shell
.autoComplete
)
994 elif id == ID_AUTOCOMP_INCLUDE_MAGIC
:
995 event
.Check(self
.shell
.autoCompleteIncludeMagic
)
996 elif id == ID_AUTOCOMP_INCLUDE_SINGLE
:
997 event
.Check(self
.shell
.autoCompleteIncludeSingle
)
998 elif id == ID_AUTOCOMP_INCLUDE_DOUBLE
:
999 event
.Check(self
.shell
.autoCompleteIncludeDouble
)
1000 elif id == ID_CALLTIPS_SHOW
:
1001 event
.Check(self
.shell
.autoCallTip
)
1004 class ShellFrame(wxFrame
, ShellMenu
):
1005 """Frame containing the PyCrust shell component."""
1007 name
= 'PyCrust Shell Frame'
1008 revision
= __version__
1010 def __init__(self
, parent
=None, id=-1, title
='PyShell', \
1011 pos
=wxDefaultPosition
, size
=wxDefaultSize
, \
1012 style
=wxDEFAULT_FRAME_STYLE
, locals=None, \
1013 InterpClass
=None, *args
, **kwds
):
1014 """Create a PyCrust ShellFrame instance."""
1015 wxFrame
.__init
__(self
, parent
, id, title
, pos
, size
, style
)
1016 intro
= 'Welcome To PyCrust %s - The Flakiest Python Shell' % VERSION
1017 intro
+= '\nSponsored by Orbtech.com - Your Source For Python Development Services'
1018 self
.CreateStatusBar()
1019 self
.SetStatusText(intro
.replace('\n', ', '))
1020 if wxPlatform
== '__WXMSW__':
1022 filename
= os
.path
.join(os
.path
.dirname(__file__
), 'PyCrust.ico')
1023 icon
= wxIcon(filename
, wxBITMAP_TYPE_ICO
)
1025 self
.shell
= Shell(parent
=self
, id=-1, introText
=intro
, \
1026 locals=locals, InterpClass
=InterpClass
, \
1028 # Override the shell so that status messages go to the status bar.
1029 self
.shell
.setStatusText
= self
.SetStatusText