1 """The PyCrust Shell is an interactive text control in which a user types in
2 commands to be sent to the interpreter. This particular shell is based on
3 wxPython's wxStyledTextCtrl. The latest files are always available at the
4 SourceForge project page at http://sourceforge.net/projects/pycrust/.
5 Sponsored by Orbtech - Your Source For Python Development Services"""
7 __author__
= "Patrick K. O'Brien <pobrien@orbtech.com>"
9 __revision__
= "$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
= __revision__
57 def __init__(self
, other
):
58 """Create a ShellFacade instance."""
70 for method
in methods
:
71 self
.__dict
__[method
] = getattr(other
, method
)
77 Home Go to the beginning of the command or line.
78 Shift+Home Select to the beginning of the command or line.
79 Shift+End Select to the end of the line.
80 End Go to the end of the line.
81 Ctrl+C Copy selected text, removing prompts.
82 Ctrl+Shift+C Copy selected text, retaining prompts.
83 Ctrl+X Cut selected text.
84 Ctrl+V Paste from clipboard.
85 Ctrl+Shift+V Paste and run multiple commands from clipboard.
86 Ctrl+Up Arrow Retrieve Previous History item.
87 Alt+P Retrieve Previous History item.
88 Ctrl+Down Arrow Retrieve Next History item.
89 Alt+N Retrieve Next History item.
90 Shift+Up Arrow Insert Previous History item.
91 Shift+Down Arrow Insert Next History item.
92 F8 Command-completion of History item.
93 (Type a few characters of a previous command and then press F8.)
97 """Display some useful information about how to use the shell."""
98 self
.write(self
.helpText
)
100 def __getattr__(self
, name
):
101 if hasattr(self
.other
, name
):
102 return getattr(self
.other
, name
)
104 raise AttributeError, name
106 def __setattr__(self
, name
, value
):
107 if self
.__dict
__.has_key(name
):
108 self
.__dict
__[name
] = value
109 elif hasattr(self
.other
, name
):
110 return setattr(self
.other
, name
, value
)
112 raise AttributeError, name
114 def _getAttributeNames(self
):
115 """Return list of magic attributes to extend introspection."""
116 list = ['autoCallTip',
118 'autoCompleteCaseInsensitive',
119 'autoCompleteIncludeDouble',
120 'autoCompleteIncludeMagic',
121 'autoCompleteIncludeSingle',
127 class Shell(wxStyledTextCtrl
):
128 """PyCrust Shell based on wxStyledTextCtrl."""
130 name
= 'PyCrust Shell'
131 revision
= __revision__
133 def __init__(self
, parent
, id=-1, pos
=wxDefaultPosition
, \
134 size
=wxDefaultSize
, style
=wxCLIP_CHILDREN
, introText
='', \
135 locals=None, InterpClass
=None, *args
, **kwds
):
136 """Create a PyCrust Shell instance."""
137 wxStyledTextCtrl
.__init
__(self
, parent
, id, pos
, size
, style
)
138 # Grab these so they can be restored by self.redirect* methods.
139 self
.stdin
= sys
.stdin
140 self
.stdout
= sys
.stdout
141 self
.stderr
= sys
.stderr
142 # Add the current working directory "." to the search path.
143 sys
.path
.insert(0, os
.curdir
)
144 # Import a default interpreter class if one isn't provided.
145 if InterpClass
== None:
146 from interpreter
import Interpreter
148 Interpreter
= InterpClass
149 # Create default locals so we have something interesting.
150 shellLocals
= {'__name__': 'PyCrust-Shell',
151 '__doc__': 'PyCrust-Shell, The PyCrust Python Shell.',
152 '__version__': VERSION
,
154 # Add the dictionary that was passed in.
156 shellLocals
.update(locals)
157 self
.interp
= Interpreter(locals=shellLocals
, \
158 rawin
=self
.readRaw
, \
159 stdin
=PseudoFileIn(self
.readIn
), \
160 stdout
=PseudoFileOut(self
.writeOut
), \
161 stderr
=PseudoFileErr(self
.writeErr
), \
163 # Keep track of the last non-continuation prompt positions.
164 self
.promptPosStart
= 0
165 self
.promptPosEnd
= 0
166 # Keep track of multi-line commands.
168 # Create the command history. Commands are added into the front of
169 # the list (ie. at index 0) as they are entered. self.historyIndex
170 # is the current position in the history; it gets incremented as you
171 # retrieve the previous command, decremented as you retrieve the
172 # next, and reset when you hit Enter. self.historyIndex == -1 means
173 # you're on the current command, not in the history.
175 self
.historyIndex
= -1
176 # Assign handlers for keyboard events.
177 EVT_KEY_DOWN(self
, self
.OnKeyDown
)
178 EVT_CHAR(self
, self
.OnChar
)
179 # Configure various defaults and user preferences.
181 # Display the introductory banner information.
182 try: self
.showIntro(introText
)
184 # Assign some pseudo keywords to the interpreter's namespace.
185 try: self
.setBuiltinKeywords()
187 # Add 'shell' to the interpreter's local namespace.
188 try: self
.setLocalShell()
190 # Do this last so the user has complete control over their
191 # environment. They can override anything they want.
192 try: self
.execStartupScript(self
.interp
.startupScript
)
199 """Configure shell based on user preferences."""
200 self
.SetMarginType(1, wxSTC_MARGIN_NUMBER
)
201 self
.SetMarginWidth(1, 40)
203 self
.SetLexer(wxSTC_LEX_PYTHON
)
204 self
.SetKeyWords(0, ' '.join(keyword
.kwlist
))
206 self
.setStyles(faces
)
207 self
.SetViewWhiteSpace(0)
210 # Do we want to automatically pop up command completion options?
211 self
.autoComplete
= 1
212 self
.autoCompleteIncludeMagic
= 1
213 self
.autoCompleteIncludeSingle
= 1
214 self
.autoCompleteIncludeDouble
= 1
215 self
.autoCompleteCaseInsensitive
= 1
216 self
.AutoCompSetIgnoreCase(self
.autoCompleteCaseInsensitive
)
217 # Do we want to automatically pop up command argument help?
219 self
.CallTipSetBackground(wxColour(255, 255, 232))
221 def showIntro(self
, text
=''):
222 """Display introductory text in the shell."""
224 if not text
.endswith(os
.linesep
): text
+= os
.linesep
227 self
.write(self
.interp
.introText
)
228 except AttributeError:
231 def setBuiltinKeywords(self
):
232 """Create pseudo keywords as part of builtins.
234 This simply sets "close", "exit" and "quit" to a helpful string.
237 __builtin__
.close
= __builtin__
.exit
= __builtin__
.quit
= \
238 'Click on the close button to leave the application.'
241 """Quit the application."""
243 # XXX Good enough for now but later we want to send a close event.
245 # In the close event handler we can make sure they want to quit.
246 # Other applications, like PythonCard, may choose to hide rather than
247 # quit so we should just post the event and let the surrounding app
248 # decide what it wants to do.
249 self
.write('Click on the close button to leave the application.')
251 def setLocalShell(self
):
252 """Add 'shell' to locals as reference to ShellFacade instance."""
253 self
.interp
.locals['shell'] = ShellFacade(other
=self
)
255 def execStartupScript(self
, startupScript
):
256 """Execute the user's PYTHONSTARTUP script if they have one."""
257 if startupScript
and os
.path
.isfile(startupScript
):
258 startupText
= 'Startup script executed: ' + startupScript
259 self
.push('print %s;execfile(%s)' % \
260 (`startupText`
, `startupScript`
))
264 def setStyles(self
, faces
):
265 """Configure font size, typeface and color for lexer."""
268 self
.StyleSetSpec(wxSTC_STYLE_DEFAULT
, "face:%(mono)s,size:%(size)d,back:%(backcol)s" % faces
)
273 self
.StyleSetSpec(wxSTC_STYLE_LINENUMBER
, "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % faces
)
274 self
.StyleSetSpec(wxSTC_STYLE_CONTROLCHAR
, "face:%(mono)s" % faces
)
275 self
.StyleSetSpec(wxSTC_STYLE_BRACELIGHT
, "fore:#0000FF,back:#FFFF88")
276 self
.StyleSetSpec(wxSTC_STYLE_BRACEBAD
, "fore:#FF0000,back:#FFFF88")
279 self
.StyleSetSpec(wxSTC_P_DEFAULT
, "face:%(mono)s" % faces
)
280 self
.StyleSetSpec(wxSTC_P_COMMENTLINE
, "fore:#007F00,face:%(mono)s" % faces
)
281 self
.StyleSetSpec(wxSTC_P_NUMBER
, "")
282 self
.StyleSetSpec(wxSTC_P_STRING
, "fore:#7F007F,face:%(mono)s" % faces
)
283 self
.StyleSetSpec(wxSTC_P_CHARACTER
, "fore:#7F007F,face:%(mono)s" % faces
)
284 self
.StyleSetSpec(wxSTC_P_WORD
, "fore:#00007F,bold")
285 self
.StyleSetSpec(wxSTC_P_TRIPLE
, "fore:#7F0000")
286 self
.StyleSetSpec(wxSTC_P_TRIPLEDOUBLE
, "fore:#000033,back:#FFFFE8")
287 self
.StyleSetSpec(wxSTC_P_CLASSNAME
, "fore:#0000FF,bold")
288 self
.StyleSetSpec(wxSTC_P_DEFNAME
, "fore:#007F7F,bold")
289 self
.StyleSetSpec(wxSTC_P_OPERATOR
, "")
290 self
.StyleSetSpec(wxSTC_P_IDENTIFIER
, "")
291 self
.StyleSetSpec(wxSTC_P_COMMENTBLOCK
, "fore:#7F7F7F")
292 self
.StyleSetSpec(wxSTC_P_STRINGEOL
, "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces
)
294 def OnChar(self
, event
):
295 """Keypress event handler.
297 Prevents modification of previously submitted commands/responses."""
298 if not self
.CanEdit():
300 key
= event
.KeyCode()
301 currpos
= self
.GetCurrentPos()
302 stoppos
= self
.promptPosEnd
304 # The dot or period key activates auto completion.
305 # Get the command between the prompt and the cursor.
306 # Add a dot to the end of the command.
307 command
= self
.GetTextRange(stoppos
, currpos
) + '.'
309 if self
.autoComplete
: self
.autoCompleteShow(command
)
310 elif key
== ord('('):
311 # The left paren activates a call tip and cancels
312 # an active auto completion.
313 if self
.AutoCompActive(): self
.AutoCompCancel()
314 # Get the command between the prompt and the cursor.
315 # Add the '(' to the end of the command.
316 self
.ReplaceSelection('')
317 command
= self
.GetTextRange(stoppos
, currpos
) + '('
319 if self
.autoCallTip
: self
.autoCallTipShow(command
)
321 # Allow the normal event handling to take place.
324 def OnKeyDown(self
, event
):
325 """Key down event handler.
327 Prevents modification of previously submitted commands/responses."""
328 key
= event
.KeyCode()
329 controlDown
= event
.ControlDown()
330 altDown
= event
.AltDown()
331 shiftDown
= event
.ShiftDown()
332 currpos
= self
.GetCurrentPos()
333 endpos
= self
.GetTextLength()
334 # Return (Enter) is used to submit a command to the interpreter.
335 if not controlDown
and key
== WXK_RETURN
:
336 if self
.AutoCompActive(): self
.AutoCompCancel()
337 if self
.CallTipActive(): self
.CallTipCancel()
339 # Ctrl+Return (Cntrl+Enter) is used to insert a line break.
340 elif controlDown
and key
== WXK_RETURN
:
341 if self
.AutoCompActive(): self
.AutoCompCancel()
342 if self
.CallTipActive(): self
.CallTipCancel()
343 if currpos
== endpos
:
346 self
.insertLineBreak()
347 # If the auto-complete window is up let it do its thing.
348 elif self
.AutoCompActive():
350 # Let Ctrl-Alt-* get handled normally.
351 elif controlDown
and altDown
:
353 # Clear the current, unexecuted command.
354 elif key
== WXK_ESCAPE
:
355 if self
.CallTipActive():
359 # Cut to the clipboard.
360 elif (controlDown
and key
in (ord('X'), ord('x'))) \
361 or (shiftDown
and key
== WXK_DELETE
):
363 # Copy to the clipboard.
364 elif controlDown
and not shiftDown \
365 and key
in (ord('C'), ord('c'), WXK_INSERT
):
367 # Copy to the clipboard, including prompts.
368 elif controlDown
and shiftDown \
369 and key
in (ord('C'), ord('c'), WXK_INSERT
):
370 self
.CopyWithPrompts()
371 # Paste from the clipboard.
372 elif (controlDown
and not shiftDown \
373 and key
in (ord('V'), ord('v'))) \
374 or (shiftDown
and not controlDown
and key
== WXK_INSERT
):
376 # Paste from the clipboard, run commands.
377 elif controlDown
and shiftDown \
378 and key
in (ord('V'), ord('v')):
380 # Replace with the previous command from the history buffer.
381 elif (controlDown
and key
== WXK_UP
) \
382 or (altDown
and key
in (ord('P'), ord('p'))):
383 self
.OnHistoryReplace(step
=+1)
384 # Replace with the next command from the history buffer.
385 elif (controlDown
and key
== WXK_DOWN
) \
386 or (altDown
and key
in (ord('N'), ord('n'))):
387 self
.OnHistoryReplace(step
=-1)
388 # Insert the previous command from the history buffer.
389 elif (shiftDown
and key
== WXK_UP
):
390 self
.OnHistoryInsert(step
=+1)
391 # Insert the next command from the history buffer.
392 elif (shiftDown
and key
== WXK_DOWN
):
393 self
.OnHistoryInsert(step
=-1)
394 # Search up the history for the text in front of the cursor.
396 self
.OnHistorySearch()
397 # Home needs to be aware of the prompt.
398 elif key
== WXK_HOME
:
399 home
= self
.promptPosEnd
401 if event
.ShiftDown():
402 # Select text from current position to end of prompt.
403 self
.SetSelection(self
.GetCurrentPos(), home
)
405 self
.SetCurrentPos(home
)
407 self
.EnsureCaretVisible()
410 # Basic navigation keys should work anywhere.
411 elif key
in (WXK_END
, WXK_LEFT
, WXK_RIGHT
, WXK_UP
, WXK_DOWN
, \
412 WXK_PRIOR
, WXK_NEXT
):
414 # Don't backspace over the latest non-continuation prompt.
415 elif key
== WXK_BACK
:
416 if currpos
> self
.promptPosEnd
:
418 # Only allow these keys after the latest prompt.
419 elif key
in (WXK_TAB
, WXK_DELETE
):
422 # Don't toggle between insert mode and overwrite mode.
423 elif key
== WXK_INSERT
:
425 # Don't allow line deletion.
426 elif controlDown
and key
in (ord('L'), ord('l')):
428 # Don't allow line transposition.
429 elif controlDown
and key
in (ord('T'), ord('t')):
431 # Protect the readonly portion of the shell.
432 elif not self
.CanEdit():
437 def clearCommand(self
):
438 """Delete the current, unexecuted command."""
439 startpos
= self
.promptPosEnd
440 endpos
= self
.GetTextLength()
441 self
.SetSelection(startpos
, endpos
)
442 self
.ReplaceSelection('')
445 def OnHistoryReplace(self
, step
):
446 """Replace with the previous/next command from the history buffer."""
448 self
.replaceFromHistory(step
)
450 def replaceFromHistory(self
, step
):
451 """Replace selection with command from the history buffer."""
452 self
.ReplaceSelection('')
453 newindex
= self
.historyIndex
+ step
454 if -1 <= newindex
<= len(self
.history
):
455 self
.historyIndex
= newindex
456 if 0 <= newindex
<= len(self
.history
)-1:
457 command
= self
.history
[self
.historyIndex
]
458 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
459 self
.ReplaceSelection(command
)
461 def OnHistoryInsert(self
, step
):
462 """Insert the previous/next command from the history buffer."""
463 if not self
.CanEdit():
465 startpos
= self
.GetCurrentPos()
466 self
.replaceFromHistory(step
)
467 endpos
= self
.GetCurrentPos()
468 self
.SetSelection(endpos
, startpos
)
470 def OnHistorySearch(self
):
471 """Search up the history buffer for the text in front of the cursor."""
472 if not self
.CanEdit():
474 startpos
= self
.GetCurrentPos()
475 # The text up to the cursor is what we search for.
476 numCharsAfterCursor
= self
.GetTextLength() - startpos
477 searchText
= self
.getCommand(rstrip
=0)
478 if numCharsAfterCursor
> 0:
479 searchText
= searchText
[:-numCharsAfterCursor
]
482 # Search upwards from the current history position and loop back
483 # to the beginning if we don't find anything.
484 if (self
.historyIndex
<= -1) \
485 or (self
.historyIndex
>= len(self
.history
)-2):
486 searchOrder
= range(len(self
.history
))
488 searchOrder
= range(self
.historyIndex
+1, len(self
.history
)) + \
489 range(self
.historyIndex
)
490 for i
in searchOrder
:
491 command
= self
.history
[i
]
492 if command
[:len(searchText
)] == searchText
:
493 # Replace the current selection with the one we've found.
494 self
.ReplaceSelection(command
[len(searchText
):])
495 endpos
= self
.GetCurrentPos()
496 self
.SetSelection(endpos
, startpos
)
497 # We've now warped into middle of the history.
498 self
.historyIndex
= i
501 def setStatusText(self
, text
):
502 """Display status information."""
504 # This method will most likely be replaced by the enclosing app
505 # to do something more interesting, like write to a status bar.
508 def insertLineBreak(self
):
509 """Insert a new line break."""
511 self
.write(os
.linesep
)
515 def processLine(self
):
516 """Process the line of text at which the user hit Enter."""
518 # The user hit ENTER and we need to decide what to do. They could be
519 # sitting on any line in the shell.
521 thepos
= self
.GetCurrentPos()
522 startpos
= self
.promptPosEnd
523 endpos
= self
.GetTextLength()
524 # If they hit RETURN inside the current command, execute the command.
526 self
.SetCurrentPos(endpos
)
528 command
= self
.GetTextRange(startpos
, endpos
)
529 lines
= command
.split(os
.linesep
+ sys
.ps2
)
530 lines
= [line
.rstrip() for line
in lines
]
531 command
= '\n'.join(lines
)
533 # Or replace the current command with the other command.
535 # If the line contains a command (even an invalid one).
536 if self
.getCommand(rstrip
=0):
537 command
= self
.getMultilineCommand()
540 # Otherwise, put the cursor back where we started.
542 self
.SetCurrentPos(thepos
)
543 self
.SetAnchor(thepos
)
545 def getMultilineCommand(self
, rstrip
=1):
546 """Extract a multi-line command from the editor.
548 The command may not necessarily be valid Python syntax."""
549 # XXX Need to extract real prompts here. Need to keep track of the
550 # prompt every time a command is issued.
555 # This is a total hack job, but it works.
556 text
= self
.GetCurLine()[0]
557 line
= self
.GetCurrentLine()
558 while text
[:ps2size
] == ps2
and line
> 0:
561 text
= self
.GetCurLine()[0]
562 if text
[:ps1size
] == ps1
:
563 line
= self
.GetCurrentLine()
565 startpos
= self
.GetCurrentPos() + ps1size
568 while self
.GetCurLine()[0][:ps2size
] == ps2
:
571 stoppos
= self
.GetCurrentPos()
572 command
= self
.GetTextRange(startpos
, stoppos
)
573 command
= command
.replace(os
.linesep
+ sys
.ps2
, '\n')
574 command
= command
.rstrip()
575 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
579 command
= command
.rstrip()
582 def getCommand(self
, text
=None, rstrip
=1):
583 """Extract a command from text which may include a shell prompt.
585 The command may not necessarily be valid Python syntax."""
587 text
= self
.GetCurLine()[0]
588 # Strip the prompt off the front of text leaving just the command.
589 command
= self
.lstripPrompt(text
)
591 command
= '' # Real commands have prompts.
593 command
= command
.rstrip()
596 def lstripPrompt(self
, text
):
597 """Return text without a leading prompt."""
602 # Strip the prompt off the front of text.
603 if text
[:ps1size
] == ps1
:
604 text
= text
[ps1size
:]
605 elif text
[:ps2size
] == ps2
:
606 text
= text
[ps2size
:]
609 def push(self
, command
):
610 """Send command to the interpreter for execution."""
611 self
.write(os
.linesep
)
612 self
.more
= self
.interp
.push(command
)
614 self
.addHistory(command
.rstrip())
617 def addHistory(self
, command
):
618 """Add command to the command history."""
619 # Reset the history position.
620 self
.historyIndex
= -1
621 # Insert this command into the history, unless it's a blank
622 # line or the same as the last command.
624 and (len(self
.history
) == 0 or command
!= self
.history
[0]):
625 self
.history
.insert(0, command
)
627 def write(self
, text
):
628 """Display text in the shell.
630 Replace line endings with OS-specific endings."""
631 text
= self
.fixLineEndings(text
)
633 self
.EnsureCaretVisible()
635 def fixLineEndings(self
, text
):
636 """Return text with line endings replaced by OS-specific endings."""
637 lines
= text
.split('\r\n')
638 for l
in range(len(lines
)):
639 chunks
= lines
[l
].split('\r')
640 for c
in range(len(chunks
)):
641 chunks
[c
] = os
.linesep
.join(chunks
[c
].split('\n'))
642 lines
[l
] = os
.linesep
.join(chunks
)
643 text
= os
.linesep
.join(lines
)
647 """Display appropriate prompt for the context, either ps1 or ps2.
649 If this is a continuation line, autoindent as necessary."""
651 prompt
= str(sys
.ps2
)
653 prompt
= str(sys
.ps1
)
654 pos
= self
.GetCurLine()[1]
655 if pos
> 0: self
.write(os
.linesep
)
657 self
.promptPosStart
= self
.GetCurrentPos()
660 self
.promptPosEnd
= self
.GetCurrentPos()
661 # Keep the undo feature from undoing previous responses.
662 self
.EmptyUndoBuffer()
663 # XXX Add some autoindent magic here if more.
665 self
.write(' '*4) # Temporary hack indentation.
666 self
.EnsureCaretVisible()
667 self
.ScrollToColumn(0)
670 """Replacement for stdin."""
671 prompt
= 'Please enter your response:'
672 dialog
= wxTextEntryDialog(None, prompt
, \
673 'Input Dialog (Standard)', '')
675 if dialog
.ShowModal() == wxID_OK
:
676 text
= dialog
.GetValue()
677 self
.write(text
+ os
.linesep
)
683 def readRaw(self
, prompt
='Please enter your response:'):
684 """Replacement for raw_input."""
685 dialog
= wxTextEntryDialog(None, prompt
, \
686 'Input Dialog (Raw)', '')
688 if dialog
.ShowModal() == wxID_OK
:
689 text
= dialog
.GetValue()
695 def ask(self
, prompt
='Please enter your response:'):
696 """Get response from the user."""
697 return raw_input(prompt
=prompt
)
700 """Halt execution pending a response from the user."""
701 self
.ask('Press enter to continue:')
704 """Delete all text from the shell."""
707 def run(self
, command
, prompt
=1, verbose
=1):
708 """Execute command within the shell as if it was typed in directly.
709 >>> shell.run('print "this"')
714 # Go to the very bottom of the text.
715 endpos
= self
.GetTextLength()
716 self
.SetCurrentPos(endpos
)
717 command
= command
.rstrip()
718 if prompt
: self
.prompt()
719 if verbose
: self
.write(command
)
722 def runfile(self
, filename
):
723 """Execute all commands in file as if they were typed into the shell."""
724 file = open(filename
)
727 for command
in file.readlines():
728 if command
[:6] == 'shell.': # Run shell methods silently.
729 self
.run(command
, prompt
=0, verbose
=0)
731 self
.run(command
, prompt
=0, verbose
=1)
735 def autoCompleteShow(self
, command
):
736 """Display auto-completion popup list."""
737 list = self
.interp
.getAutoCompleteList(command
, \
738 includeMagic
=self
.autoCompleteIncludeMagic
, \
739 includeSingle
=self
.autoCompleteIncludeSingle
, \
740 includeDouble
=self
.autoCompleteIncludeDouble
)
742 options
= ' '.join(list)
744 self
.AutoCompShow(offset
, options
)
746 def autoCallTipShow(self
, command
):
747 """Display argument spec and docstring in a popup bubble thingie."""
748 if self
.CallTipActive
: self
.CallTipCancel()
749 (name
, argspec
, tip
) = self
.interp
.getCallTip(command
)
751 startpos
= self
.GetCurrentPos()
752 self
.write(argspec
+ ')')
753 endpos
= self
.GetCurrentPos()
754 self
.SetSelection(endpos
, startpos
)
756 curpos
= self
.GetCurrentPos()
757 tippos
= curpos
- (len(name
) + 1)
758 fallback
= curpos
- self
.GetColumn(curpos
)
759 # In case there isn't enough room, only go back to the fallback.
760 tippos
= max(tippos
, fallback
)
761 self
.CallTipShow(tippos
, tip
)
763 def writeOut(self
, text
):
764 """Replacement for stdout."""
767 def writeErr(self
, text
):
768 """Replacement for stderr."""
771 def redirectStdin(self
, redirect
=1):
772 """If redirect is true then sys.stdin will come from the shell."""
774 sys
.stdin
= PseudoFileIn(self
.readIn
)
776 sys
.stdin
= self
.stdin
778 def redirectStdout(self
, redirect
=1):
779 """If redirect is true then sys.stdout will go to the shell."""
781 sys
.stdout
= PseudoFileOut(self
.writeOut
)
783 sys
.stdout
= self
.stdout
785 def redirectStderr(self
, redirect
=1):
786 """If redirect is true then sys.stderr will go to the shell."""
788 sys
.stderr
= PseudoFileErr(self
.writeErr
)
790 sys
.stderr
= self
.stderr
793 """Return true if text is selected and can be cut."""
794 if self
.GetSelectionStart() != self
.GetSelectionEnd() \
795 and self
.GetSelectionStart() >= self
.promptPosEnd \
796 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
802 """Return true if text is selected and can be copied."""
803 return self
.GetSelectionStart() != self
.GetSelectionEnd()
806 """Return true if a paste should succeed."""
807 if self
.CanEdit() and wxStyledTextCtrl
.CanPaste(self
):
813 """Return true if editing should succeed."""
814 return self
.GetCurrentPos() >= self
.promptPosEnd
817 """Remove selection and place it on the clipboard."""
818 if self
.CanCut() and self
.CanCopy():
819 if self
.AutoCompActive(): self
.AutoCompCancel()
820 if self
.CallTipActive
: self
.CallTipCancel()
822 self
.ReplaceSelection('')
825 """Copy selection and place it on the clipboard."""
827 command
= self
.GetSelectedText()
828 command
= command
.replace(os
.linesep
+ sys
.ps2
, os
.linesep
)
829 command
= command
.replace(os
.linesep
+ sys
.ps1
, os
.linesep
)
830 command
= self
.lstripPrompt(text
=command
)
831 data
= wxTextDataObject(command
)
832 if wxTheClipboard
.Open():
833 wxTheClipboard
.SetData(data
)
834 wxTheClipboard
.Close()
836 def CopyWithPrompts(self
):
837 """Copy selection, including prompts, and place it on the clipboard."""
839 command
= self
.GetSelectedText()
840 data
= wxTextDataObject(command
)
841 if wxTheClipboard
.Open():
842 wxTheClipboard
.SetData(data
)
843 wxTheClipboard
.Close()
846 """Replace selection with clipboard contents."""
847 if self
.CanPaste() and wxTheClipboard
.Open():
848 if wxTheClipboard
.IsSupported(wxDataFormat(wxDF_TEXT
)):
849 data
= wxTextDataObject()
850 if wxTheClipboard
.GetData(data
):
851 self
.ReplaceSelection('')
852 command
= data
.GetText()
853 command
= command
.rstrip()
854 command
= self
.fixLineEndings(command
)
855 command
= self
.lstripPrompt(text
=command
)
856 command
= command
.replace(os
.linesep
+ sys
.ps2
, '\n')
857 command
= command
.replace(os
.linesep
, '\n')
858 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
860 wxTheClipboard
.Close()
862 def PasteAndRun(self
):
863 """Replace selection with clipboard contents, run commands."""
864 if wxTheClipboard
.Open():
865 if wxTheClipboard
.IsSupported(wxDataFormat(wxDF_TEXT
)):
866 data
= wxTextDataObject()
867 if wxTheClipboard
.GetData(data
):
868 endpos
= self
.GetTextLength()
869 self
.SetCurrentPos(endpos
)
870 startpos
= self
.promptPosEnd
871 self
.SetSelection(startpos
, endpos
)
872 self
.ReplaceSelection('')
873 text
= data
.GetText()
875 text
= self
.fixLineEndings(text
)
876 text
= self
.lstripPrompt(text
=text
)
877 text
= text
.replace(os
.linesep
+ sys
.ps1
, '\n')
878 text
= text
.replace(os
.linesep
+ sys
.ps2
, '\n')
879 text
= text
.replace(os
.linesep
, '\n')
880 lines
= text
.split('\n')
884 if line
.strip() != '' and line
.lstrip() == line
:
887 # Add the previous command to the list.
888 commands
.append(command
)
889 # Start a new command, which may be multiline.
892 # Multiline command. Add to the command.
895 commands
.append(command
)
896 for command
in commands
:
897 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
900 wxTheClipboard
.Close()
903 wxID_SELECTALL
= NewId() # This *should* be defined by wxPython.
904 ID_AUTOCOMP
= NewId()
905 ID_AUTOCOMP_SHOW
= NewId()
906 ID_AUTOCOMP_INCLUDE_MAGIC
= NewId()
907 ID_AUTOCOMP_INCLUDE_SINGLE
= NewId()
908 ID_AUTOCOMP_INCLUDE_DOUBLE
= NewId()
909 ID_CALLTIPS
= NewId()
910 ID_CALLTIPS_SHOW
= NewId()
914 """Mixin class to add standard menu items."""
916 def createMenus(self
):
917 m
= self
.fileMenu
= wxMenu()
919 m
.Append(wxID_EXIT
, 'E&xit', 'Exit PyCrust')
921 m
= self
.editMenu
= wxMenu()
922 m
.Append(wxID_UNDO
, '&Undo \tCtrl+Z', 'Undo the last action')
923 m
.Append(wxID_REDO
, '&Redo \tCtrl+Y', 'Redo the last undone action')
925 m
.Append(wxID_CUT
, 'Cu&t \tCtrl+X', 'Cut the selection')
926 m
.Append(wxID_COPY
, '&Copy \tCtrl+C', 'Copy the selection')
927 m
.Append(wxID_PASTE
, '&Paste \tCtrl+V', 'Paste')
929 m
.Append(wxID_CLEAR
, 'Cle&ar', 'Delete the selection')
930 m
.Append(wxID_SELECTALL
, 'Select A&ll', 'Select all text')
932 m
= self
.autocompMenu
= wxMenu()
933 m
.Append(ID_AUTOCOMP_SHOW
, 'Show Auto Completion', \
934 'Show auto completion during dot syntax', 1)
935 m
.Append(ID_AUTOCOMP_INCLUDE_MAGIC
, 'Include Magic Attributes', \
936 'Include attributes visible to __getattr__ and __setattr__', 1)
937 m
.Append(ID_AUTOCOMP_INCLUDE_SINGLE
, 'Include Single Underscores', \
938 'Include attibutes prefixed by a single underscore', 1)
939 m
.Append(ID_AUTOCOMP_INCLUDE_DOUBLE
, 'Include Double Underscores', \
940 'Include attibutes prefixed by a double underscore', 1)
942 m
= self
.calltipsMenu
= wxMenu()
943 m
.Append(ID_CALLTIPS_SHOW
, 'Show Call Tips', \
944 'Show call tips with argument specifications', 1)
946 m
= self
.optionsMenu
= wxMenu()
947 m
.AppendMenu(ID_AUTOCOMP
, '&Auto Completion', self
.autocompMenu
, \
948 'Auto Completion Options')
949 m
.AppendMenu(ID_CALLTIPS
, '&Call Tips', self
.calltipsMenu
, \
952 m
= self
.helpMenu
= wxMenu()
954 m
.Append(wxID_ABOUT
, '&About...', 'About PyCrust')
956 b
= self
.menuBar
= wxMenuBar()
957 b
.Append(self
.fileMenu
, '&File')
958 b
.Append(self
.editMenu
, '&Edit')
959 b
.Append(self
.optionsMenu
, '&Options')
960 b
.Append(self
.helpMenu
, '&Help')
963 EVT_MENU(self
, wxID_EXIT
, self
.OnExit
)
964 EVT_MENU(self
, wxID_UNDO
, self
.OnUndo
)
965 EVT_MENU(self
, wxID_REDO
, self
.OnRedo
)
966 EVT_MENU(self
, wxID_CUT
, self
.OnCut
)
967 EVT_MENU(self
, wxID_COPY
, self
.OnCopy
)
968 EVT_MENU(self
, wxID_PASTE
, self
.OnPaste
)
969 EVT_MENU(self
, wxID_CLEAR
, self
.OnClear
)
970 EVT_MENU(self
, wxID_SELECTALL
, self
.OnSelectAll
)
971 EVT_MENU(self
, wxID_ABOUT
, self
.OnAbout
)
972 EVT_MENU(self
, ID_AUTOCOMP_SHOW
, \
973 self
.OnAutoCompleteShow
)
974 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_MAGIC
, \
975 self
.OnAutoCompleteIncludeMagic
)
976 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_SINGLE
, \
977 self
.OnAutoCompleteIncludeSingle
)
978 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_DOUBLE
, \
979 self
.OnAutoCompleteIncludeDouble
)
980 EVT_MENU(self
, ID_CALLTIPS_SHOW
, \
983 EVT_UPDATE_UI(self
, wxID_UNDO
, self
.OnUpdateMenu
)
984 EVT_UPDATE_UI(self
, wxID_REDO
, self
.OnUpdateMenu
)
985 EVT_UPDATE_UI(self
, wxID_CUT
, self
.OnUpdateMenu
)
986 EVT_UPDATE_UI(self
, wxID_COPY
, self
.OnUpdateMenu
)
987 EVT_UPDATE_UI(self
, wxID_PASTE
, self
.OnUpdateMenu
)
988 EVT_UPDATE_UI(self
, wxID_CLEAR
, self
.OnUpdateMenu
)
989 EVT_UPDATE_UI(self
, ID_AUTOCOMP_SHOW
, self
.OnUpdateMenu
)
990 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_MAGIC
, self
.OnUpdateMenu
)
991 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_SINGLE
, self
.OnUpdateMenu
)
992 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_DOUBLE
, self
.OnUpdateMenu
)
993 EVT_UPDATE_UI(self
, ID_CALLTIPS_SHOW
, self
.OnUpdateMenu
)
995 def OnExit(self
, event
):
998 def OnUndo(self
, event
):
1001 def OnRedo(self
, event
):
1004 def OnCut(self
, event
):
1007 def OnCopy(self
, event
):
1010 def OnPaste(self
, event
):
1013 def OnClear(self
, event
):
1016 def OnSelectAll(self
, event
):
1017 self
.shell
.SelectAll()
1019 def OnAbout(self
, event
):
1020 """Display an About PyCrust window."""
1022 title
= 'About PyCrust'
1023 text
= 'PyCrust %s\n\n' % VERSION
+ \
1024 'Yet another Python shell, only flakier.\n\n' + \
1025 'Half-baked by Patrick K. O\'Brien,\n' + \
1026 'the other half is still in the oven.\n\n' + \
1027 'Shell Revision: %s\n' % self
.shell
.revision
+ \
1028 'Interpreter Revision: %s\n\n' % self
.shell
.interp
.revision
+ \
1029 'Python Version: %s\n' % sys
.version
.split()[0] + \
1030 'wxPython Version: %s\n' % wx
.__version
__ + \
1031 'Platform: %s\n' % sys
.platform
1032 dialog
= wxMessageDialog(self
, text
, title
, wxOK | wxICON_INFORMATION
)
1036 def OnAutoCompleteShow(self
, event
):
1037 self
.shell
.autoComplete
= event
.IsChecked()
1039 def OnAutoCompleteIncludeMagic(self
, event
):
1040 self
.shell
.autoCompleteIncludeMagic
= event
.IsChecked()
1042 def OnAutoCompleteIncludeSingle(self
, event
):
1043 self
.shell
.autoCompleteIncludeSingle
= event
.IsChecked()
1045 def OnAutoCompleteIncludeDouble(self
, event
):
1046 self
.shell
.autoCompleteIncludeDouble
= event
.IsChecked()
1048 def OnCallTipsShow(self
, event
):
1049 self
.shell
.autoCallTip
= event
.IsChecked()
1051 def OnUpdateMenu(self
, event
):
1052 """Update menu items based on current status."""
1055 event
.Enable(self
.shell
.CanUndo())
1056 elif id == wxID_REDO
:
1057 event
.Enable(self
.shell
.CanRedo())
1058 elif id == wxID_CUT
:
1059 event
.Enable(self
.shell
.CanCut())
1060 elif id == wxID_COPY
:
1061 event
.Enable(self
.shell
.CanCopy())
1062 elif id == wxID_PASTE
:
1063 event
.Enable(self
.shell
.CanPaste())
1064 elif id == wxID_CLEAR
:
1065 event
.Enable(self
.shell
.CanCut())
1066 elif id == ID_AUTOCOMP_SHOW
:
1067 event
.Check(self
.shell
.autoComplete
)
1068 elif id == ID_AUTOCOMP_INCLUDE_MAGIC
:
1069 event
.Check(self
.shell
.autoCompleteIncludeMagic
)
1070 elif id == ID_AUTOCOMP_INCLUDE_SINGLE
:
1071 event
.Check(self
.shell
.autoCompleteIncludeSingle
)
1072 elif id == ID_AUTOCOMP_INCLUDE_DOUBLE
:
1073 event
.Check(self
.shell
.autoCompleteIncludeDouble
)
1074 elif id == ID_CALLTIPS_SHOW
:
1075 event
.Check(self
.shell
.autoCallTip
)
1078 class ShellFrame(wxFrame
, ShellMenu
):
1079 """Frame containing the PyCrust shell component."""
1081 name
= 'PyCrust Shell Frame'
1082 revision
= __revision__
1084 def __init__(self
, parent
=None, id=-1, title
='PyShell', \
1085 pos
=wxDefaultPosition
, size
=wxDefaultSize
, \
1086 style
=wxDEFAULT_FRAME_STYLE
, locals=None, \
1087 InterpClass
=None, *args
, **kwds
):
1088 """Create a PyCrust ShellFrame instance."""
1089 wxFrame
.__init
__(self
, parent
, id, title
, pos
, size
, style
)
1090 intro
= 'Welcome To PyCrust %s - The Flakiest Python Shell' % VERSION
1091 intro
+= '\nSponsored by Orbtech - Specializing in Python Application Development'
1092 self
.CreateStatusBar()
1093 self
.SetStatusText(intro
.replace('\n', ', '))
1094 if wxPlatform
== '__WXMSW__':
1096 filename
= os
.path
.join(os
.path
.dirname(__file__
), 'PyCrust.ico')
1097 icon
= wxIcon(filename
, wxBITMAP_TYPE_ICO
)
1099 self
.shell
= Shell(parent
=self
, id=-1, introText
=intro
, \
1100 locals=locals, InterpClass
=InterpClass
, \
1102 # Override the shell so that status messages go to the status bar.
1103 self
.shell
.setStatusText
= self
.SetStatusText