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 programming expertise."""
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 NAVKEYS
= (WXK_END
, WXK_LEFT
, WXK_RIGHT
, WXK_UP
, WXK_DOWN
, WXK_PRIOR
, WXK_NEXT
)
24 if wxPlatform
== '__WXMSW__':
25 faces
= { 'times' : 'Times New Roman',
26 'mono' : 'Courier New',
27 'helv' : 'Lucida Console',
28 'lucida' : 'Lucida Console',
29 'other' : 'Comic Sans MS',
34 # Versions of wxPython prior to 2.3.2 had a sizing bug on Win platform.
35 # The font was 2 points too large. So we need to reduce the font size.
36 if (wxMAJOR_VERSION
, wxMINOR_VERSION
, wxRELEASE_NUMBER
) < (2, 3, 2):
40 faces
= { 'times' : 'Times',
43 'other' : 'new century schoolbook',
51 """Simplified interface to all shell-related functionality.
53 This is a semi-transparent facade, in that all attributes of other are
54 still accessible, even though only some are visible to the user."""
56 name
= 'PyCrust Shell Interface'
57 revision
= __revision__
59 def __init__(self
, other
):
60 """Create a ShellFacade instance."""
74 for method
in methods
:
75 self
.__dict
__[method
] = getattr(other
, method
)
81 Home Go to the beginning of the command or line.
82 Shift+Home Select to the beginning of the command or line.
83 Shift+End Select to the end of the line.
84 End Go to the end of the line.
85 Ctrl+C Copy selected text, removing prompts.
86 Ctrl+Shift+C Copy selected text, retaining prompts.
87 Ctrl+X Cut selected text.
88 Ctrl+V Paste from clipboard.
89 Ctrl+Shift+V Paste and run multiple commands from clipboard.
90 Ctrl+Up Arrow Retrieve Previous History item.
91 Alt+P Retrieve Previous History item.
92 Ctrl+Down Arrow Retrieve Next History item.
93 Alt+N Retrieve Next History item.
94 Shift+Up Arrow Insert Previous History item.
95 Shift+Down Arrow Insert Next History item.
96 F8 Command-completion of History item.
97 (Type a few characters of a previous command and then press F8.)
101 """Display some useful information about how to use the shell."""
102 self
.write(self
.helpText
)
104 def __getattr__(self
, name
):
105 if hasattr(self
.other
, name
):
106 return getattr(self
.other
, name
)
108 raise AttributeError, name
110 def __setattr__(self
, name
, value
):
111 if self
.__dict
__.has_key(name
):
112 self
.__dict
__[name
] = value
113 elif hasattr(self
.other
, name
):
114 return setattr(self
.other
, name
, value
)
116 raise AttributeError, name
118 def _getAttributeNames(self
):
119 """Return list of magic attributes to extend introspection."""
120 list = ['autoCallTip',
122 'autoCompleteCaseInsensitive',
123 'autoCompleteIncludeDouble',
124 'autoCompleteIncludeMagic',
125 'autoCompleteIncludeSingle',
131 class Shell(wxStyledTextCtrl
):
132 """PyCrust Shell based on wxStyledTextCtrl."""
134 name
= 'PyCrust Shell'
135 revision
= __revision__
137 def __init__(self
, parent
, id=-1, pos
=wxDefaultPosition
, \
138 size
=wxDefaultSize
, style
=wxCLIP_CHILDREN
, introText
='', \
139 locals=None, InterpClass
=None, *args
, **kwds
):
140 """Create a PyCrust Shell instance."""
141 wxStyledTextCtrl
.__init
__(self
, parent
, id, pos
, size
, style
)
142 # Grab these so they can be restored by self.redirect* methods.
143 self
.stdin
= sys
.stdin
144 self
.stdout
= sys
.stdout
145 self
.stderr
= sys
.stderr
146 # Add the current working directory "." to the search path.
147 sys
.path
.insert(0, os
.curdir
)
148 # Import a default interpreter class if one isn't provided.
149 if InterpClass
== None:
150 from interpreter
import Interpreter
152 Interpreter
= InterpClass
153 # Create default locals so we have something interesting.
154 shellLocals
= {'__name__': 'PyCrust-Shell',
155 '__doc__': 'PyCrust-Shell, The PyCrust Python Shell.',
156 '__version__': VERSION
,
158 # Add the dictionary that was passed in.
160 shellLocals
.update(locals)
161 self
.interp
= Interpreter(locals=shellLocals
, \
162 rawin
=self
.readRaw
, \
163 stdin
=PseudoFileIn(self
.readIn
), \
164 stdout
=PseudoFileOut(self
.writeOut
), \
165 stderr
=PseudoFileErr(self
.writeErr
), \
167 # Find out for which keycodes the interpreter will autocomplete.
168 self
.autoCompleteKeys
= self
.interp
.getAutoCompleteKeys()
169 # Keep track of the last non-continuation prompt positions.
170 self
.promptPosStart
= 0
171 self
.promptPosEnd
= 0
172 # Keep track of multi-line commands.
174 # Create the command history. Commands are added into the front of
175 # the list (ie. at index 0) as they are entered. self.historyIndex
176 # is the current position in the history; it gets incremented as you
177 # retrieve the previous command, decremented as you retrieve the
178 # next, and reset when you hit Enter. self.historyIndex == -1 means
179 # you're on the current command, not in the history.
181 self
.historyIndex
= -1
182 # Assign handlers for keyboard events.
183 EVT_KEY_DOWN(self
, self
.OnKeyDown
)
184 EVT_CHAR(self
, self
.OnChar
)
185 # Assign handlers for wxSTC events.
186 EVT_STC_UPDATEUI(self
, id, self
.OnUpdateUI
)
187 # Configure various defaults and user preferences.
189 # Display the introductory banner information.
190 try: self
.showIntro(introText
)
192 # Assign some pseudo keywords to the interpreter's namespace.
193 try: self
.setBuiltinKeywords()
195 # Add 'shell' to the interpreter's local namespace.
196 try: self
.setLocalShell()
198 # Do this last so the user has complete control over their
199 # environment. They can override anything they want.
200 try: self
.execStartupScript(self
.interp
.startupScript
)
207 """Configure shell based on user preferences."""
208 self
.SetMarginType(1, wxSTC_MARGIN_NUMBER
)
209 self
.SetMarginWidth(1, 40)
211 self
.SetLexer(wxSTC_LEX_PYTHON
)
212 self
.SetKeyWords(0, ' '.join(keyword
.kwlist
))
214 self
.setStyles(faces
)
215 self
.SetViewWhiteSpace(0)
218 # Do we want to automatically pop up command completion options?
219 self
.autoComplete
= 1
220 self
.autoCompleteIncludeMagic
= 1
221 self
.autoCompleteIncludeSingle
= 1
222 self
.autoCompleteIncludeDouble
= 1
223 self
.autoCompleteCaseInsensitive
= 1
224 self
.AutoCompSetIgnoreCase(self
.autoCompleteCaseInsensitive
)
225 # Do we want to automatically pop up command argument help?
227 self
.CallTipSetBackground(wxColour(255, 255, 232))
230 def showIntro(self
, text
=''):
231 """Display introductory text in the shell."""
233 if not text
.endswith(os
.linesep
): text
+= os
.linesep
236 self
.write(self
.interp
.introText
)
237 except AttributeError:
240 def setBuiltinKeywords(self
):
241 """Create pseudo keywords as part of builtins.
243 This simply sets "close", "exit" and "quit" to a helpful string.
246 __builtin__
.close
= __builtin__
.exit
= __builtin__
.quit
= \
247 'Click on the close button to leave the application.'
250 """Quit the application."""
252 # XXX Good enough for now but later we want to send a close event.
254 # In the close event handler we can make sure they want to quit.
255 # Other applications, like PythonCard, may choose to hide rather than
256 # quit so we should just post the event and let the surrounding app
257 # decide what it wants to do.
258 self
.write('Click on the close button to leave the application.')
260 def setLocalShell(self
):
261 """Add 'shell' to locals as reference to ShellFacade instance."""
262 self
.interp
.locals['shell'] = ShellFacade(other
=self
)
264 def execStartupScript(self
, startupScript
):
265 """Execute the user's PYTHONSTARTUP script if they have one."""
266 if startupScript
and os
.path
.isfile(startupScript
):
267 startupText
= 'Startup script executed: ' + startupScript
268 self
.push('print %s;execfile(%s)' % \
269 (`startupText`
, `startupScript`
))
273 def setStyles(self
, faces
):
274 """Configure font size, typeface and color for lexer."""
277 self
.StyleSetSpec(wxSTC_STYLE_DEFAULT
, "face:%(mono)s,size:%(size)d,back:%(backcol)s" % faces
)
282 self
.StyleSetSpec(wxSTC_STYLE_LINENUMBER
, "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % faces
)
283 self
.StyleSetSpec(wxSTC_STYLE_CONTROLCHAR
, "face:%(mono)s" % faces
)
284 self
.StyleSetSpec(wxSTC_STYLE_BRACELIGHT
, "fore:#0000FF,back:#FFFF88")
285 self
.StyleSetSpec(wxSTC_STYLE_BRACEBAD
, "fore:#FF0000,back:#FFFF88")
288 self
.StyleSetSpec(wxSTC_P_DEFAULT
, "face:%(mono)s" % faces
)
289 self
.StyleSetSpec(wxSTC_P_COMMENTLINE
, "fore:#007F00,face:%(mono)s" % faces
)
290 self
.StyleSetSpec(wxSTC_P_NUMBER
, "")
291 self
.StyleSetSpec(wxSTC_P_STRING
, "fore:#7F007F,face:%(mono)s" % faces
)
292 self
.StyleSetSpec(wxSTC_P_CHARACTER
, "fore:#7F007F,face:%(mono)s" % faces
)
293 self
.StyleSetSpec(wxSTC_P_WORD
, "fore:#00007F,bold")
294 self
.StyleSetSpec(wxSTC_P_TRIPLE
, "fore:#7F0000")
295 self
.StyleSetSpec(wxSTC_P_TRIPLEDOUBLE
, "fore:#000033,back:#FFFFE8")
296 self
.StyleSetSpec(wxSTC_P_CLASSNAME
, "fore:#0000FF,bold")
297 self
.StyleSetSpec(wxSTC_P_DEFNAME
, "fore:#007F7F,bold")
298 self
.StyleSetSpec(wxSTC_P_OPERATOR
, "")
299 self
.StyleSetSpec(wxSTC_P_IDENTIFIER
, "")
300 self
.StyleSetSpec(wxSTC_P_COMMENTBLOCK
, "fore:#7F7F7F")
301 self
.StyleSetSpec(wxSTC_P_STRINGEOL
, "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces
)
303 def OnUpdateUI(self
, evt
):
304 """Check for matching braces."""
308 caretPos
= self
.GetCurrentPos()
310 charBefore
= self
.GetCharAt(caretPos
- 1)
311 #*** Patch to fix bug in wxSTC for wxPython < 2.3.3.
313 charBefore
= 32 # Mimic a space.
315 styleBefore
= self
.GetStyleAt(caretPos
- 1)
318 if charBefore
and chr(charBefore
) in '[]{}()' \
319 and styleBefore
== wxSTC_P_OPERATOR
:
320 braceAtCaret
= caretPos
- 1
324 charAfter
= self
.GetCharAt(caretPos
)
325 #*** Patch to fix bug in wxSTC for wxPython < 2.3.3.
327 charAfter
= 32 # Mimic a space.
329 styleAfter
= self
.GetStyleAt(caretPos
)
330 if charAfter
and chr(charAfter
) in '[]{}()' \
331 and styleAfter
== wxSTC_P_OPERATOR
:
332 braceAtCaret
= caretPos
334 if braceAtCaret
>= 0:
335 braceOpposite
= self
.BraceMatch(braceAtCaret
)
337 if braceAtCaret
!= -1 and braceOpposite
== -1:
338 self
.BraceBadLight(braceAtCaret
)
340 self
.BraceHighlight(braceAtCaret
, braceOpposite
)
342 def OnChar(self
, event
):
343 """Keypress event handler."""
345 # Prevent modification of previously submitted commands/responses.
346 if not self
.CanEdit():
348 key
= event
.KeyCode()
349 currpos
= self
.GetCurrentPos()
350 stoppos
= self
.promptPosEnd
351 if key
in self
.autoCompleteKeys
:
352 # Usually the dot (period) key activates auto completion.
353 # Get the command between the prompt and the cursor.
354 # Add the autocomplete character to the end of the command.
355 command
= self
.GetTextRange(stoppos
, currpos
) + chr(key
)
357 if self
.autoComplete
: self
.autoCompleteShow(command
)
358 elif key
== ord('('):
359 # The left paren activates a call tip and cancels
360 # an active auto completion.
361 if self
.AutoCompActive(): self
.AutoCompCancel()
362 # Get the command between the prompt and the cursor.
363 # Add the '(' to the end of the command.
364 self
.ReplaceSelection('')
365 command
= self
.GetTextRange(stoppos
, currpos
) + '('
367 if self
.autoCallTip
: self
.autoCallTipShow(command
)
369 # Allow the normal event handling to take place.
372 def OnKeyDown(self
, event
):
373 """Key down event handler."""
375 # Prevent modification of previously submitted commands/responses.
376 key
= event
.KeyCode()
377 controlDown
= event
.ControlDown()
378 altDown
= event
.AltDown()
379 shiftDown
= event
.ShiftDown()
380 currpos
= self
.GetCurrentPos()
381 endpos
= self
.GetTextLength()
382 # Return (Enter) is used to submit a command to the interpreter.
383 if not controlDown
and key
== WXK_RETURN
:
384 if self
.AutoCompActive(): self
.AutoCompCancel()
385 if self
.CallTipActive(): self
.CallTipCancel()
387 # Ctrl+Return (Cntrl+Enter) is used to insert a line break.
388 elif controlDown
and key
== WXK_RETURN
:
389 if self
.AutoCompActive(): self
.AutoCompCancel()
390 if self
.CallTipActive(): self
.CallTipCancel()
391 if currpos
== endpos
:
394 self
.insertLineBreak()
395 # If the auto-complete window is up let it do its thing.
396 elif self
.AutoCompActive():
398 # Let Ctrl-Alt-* get handled normally.
399 elif controlDown
and altDown
:
401 # Clear the current, unexecuted command.
402 elif key
== WXK_ESCAPE
:
403 if self
.CallTipActive():
407 # Cut to the clipboard.
408 elif (controlDown
and key
in (ord('X'), ord('x'))) \
409 or (shiftDown
and key
== WXK_DELETE
):
411 # Copy to the clipboard.
412 elif controlDown
and not shiftDown \
413 and key
in (ord('C'), ord('c'), WXK_INSERT
):
415 # Copy to the clipboard, including prompts.
416 elif controlDown
and shiftDown \
417 and key
in (ord('C'), ord('c'), WXK_INSERT
):
418 self
.CopyWithPrompts()
419 # Home needs to be aware of the prompt.
420 elif key
== WXK_HOME
:
421 home
= self
.promptPosEnd
423 selecting
= self
.GetSelectionStart() != self
.GetSelectionEnd()
424 self
.SetCurrentPos(home
)
425 if not selecting
and not shiftDown
:
427 self
.EnsureCaretVisible()
431 # The following handlers modify text, so we need to see if there
432 # is a selection that includes text prior to the prompt.
434 # Don't modify a selection with text prior to the prompt.
435 elif self
.GetSelectionStart() != self
.GetSelectionEnd()\
436 and key
not in NAVKEYS
and not self
.CanEdit():
438 # Paste from the clipboard.
439 elif (controlDown
and not shiftDown \
440 and key
in (ord('V'), ord('v'))) \
441 or (shiftDown
and not controlDown
and key
== WXK_INSERT
):
443 # Paste from the clipboard, run commands.
444 elif controlDown
and shiftDown \
445 and key
in (ord('V'), ord('v')):
447 # Replace with the previous command from the history buffer.
448 elif (controlDown
and key
== WXK_UP
) \
449 or (altDown
and key
in (ord('P'), ord('p'))):
450 self
.OnHistoryReplace(step
=+1)
451 # Replace with the next command from the history buffer.
452 elif (controlDown
and key
== WXK_DOWN
) \
453 or (altDown
and key
in (ord('N'), ord('n'))):
454 self
.OnHistoryReplace(step
=-1)
455 # Insert the previous command from the history buffer.
456 elif (shiftDown
and key
== WXK_UP
) and self
.CanEdit():
457 self
.OnHistoryInsert(step
=+1)
458 # Insert the next command from the history buffer.
459 elif (shiftDown
and key
== WXK_DOWN
) and self
.CanEdit():
460 self
.OnHistoryInsert(step
=-1)
461 # Search up the history for the text in front of the cursor.
463 self
.OnHistorySearch()
464 # Don't backspace over the latest non-continuation prompt.
465 elif key
== WXK_BACK
:
466 if self
.GetSelectionStart() != self
.GetSelectionEnd()\
469 elif currpos
> self
.promptPosEnd
:
471 # Only allow these keys after the latest prompt.
472 elif key
in (WXK_TAB
, WXK_DELETE
):
475 # Don't toggle between insert mode and overwrite mode.
476 elif key
== WXK_INSERT
:
478 # Don't allow line deletion.
479 elif controlDown
and key
in (ord('L'), ord('l')):
481 # Don't allow line transposition.
482 elif controlDown
and key
in (ord('T'), ord('t')):
484 # Basic navigation keys should work anywhere.
487 # Protect the readonly portion of the shell.
488 elif not self
.CanEdit():
493 def clearCommand(self
):
494 """Delete the current, unexecuted command."""
495 startpos
= self
.promptPosEnd
496 endpos
= self
.GetTextLength()
497 self
.SetSelection(startpos
, endpos
)
498 self
.ReplaceSelection('')
501 def OnHistoryReplace(self
, step
):
502 """Replace with the previous/next command from the history buffer."""
504 self
.replaceFromHistory(step
)
506 def replaceFromHistory(self
, step
):
507 """Replace selection with command from the history buffer."""
508 self
.ReplaceSelection('')
509 newindex
= self
.historyIndex
+ step
510 if -1 <= newindex
<= len(self
.history
):
511 self
.historyIndex
= newindex
512 if 0 <= newindex
<= len(self
.history
)-1:
513 command
= self
.history
[self
.historyIndex
]
514 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
515 self
.ReplaceSelection(command
)
517 def OnHistoryInsert(self
, step
):
518 """Insert the previous/next command from the history buffer."""
519 if not self
.CanEdit():
521 startpos
= self
.GetCurrentPos()
522 self
.replaceFromHistory(step
)
523 endpos
= self
.GetCurrentPos()
524 self
.SetSelection(endpos
, startpos
)
526 def OnHistorySearch(self
):
527 """Search up the history buffer for the text in front of the cursor."""
528 if not self
.CanEdit():
530 startpos
= self
.GetCurrentPos()
531 # The text up to the cursor is what we search for.
532 numCharsAfterCursor
= self
.GetTextLength() - startpos
533 searchText
= self
.getCommand(rstrip
=0)
534 if numCharsAfterCursor
> 0:
535 searchText
= searchText
[:-numCharsAfterCursor
]
538 # Search upwards from the current history position and loop back
539 # to the beginning if we don't find anything.
540 if (self
.historyIndex
<= -1) \
541 or (self
.historyIndex
>= len(self
.history
)-2):
542 searchOrder
= range(len(self
.history
))
544 searchOrder
= range(self
.historyIndex
+1, len(self
.history
)) + \
545 range(self
.historyIndex
)
546 for i
in searchOrder
:
547 command
= self
.history
[i
]
548 if command
[:len(searchText
)] == searchText
:
549 # Replace the current selection with the one we've found.
550 self
.ReplaceSelection(command
[len(searchText
):])
551 endpos
= self
.GetCurrentPos()
552 self
.SetSelection(endpos
, startpos
)
553 # We've now warped into middle of the history.
554 self
.historyIndex
= i
557 def setStatusText(self
, text
):
558 """Display status information."""
560 # This method will most likely be replaced by the enclosing app
561 # to do something more interesting, like write to a status bar.
564 def insertLineBreak(self
):
565 """Insert a new line break."""
567 self
.write(os
.linesep
)
571 def processLine(self
):
572 """Process the line of text at which the user hit Enter."""
574 # The user hit ENTER and we need to decide what to do. They could be
575 # sitting on any line in the shell.
577 thepos
= self
.GetCurrentPos()
578 startpos
= self
.promptPosEnd
579 endpos
= self
.GetTextLength()
580 # If they hit RETURN inside the current command, execute the command.
582 self
.SetCurrentPos(endpos
)
584 command
= self
.GetTextRange(startpos
, endpos
)
585 lines
= command
.split(os
.linesep
+ sys
.ps2
)
586 lines
= [line
.rstrip() for line
in lines
]
587 command
= '\n'.join(lines
)
589 # Or replace the current command with the other command.
591 # If the line contains a command (even an invalid one).
592 if self
.getCommand(rstrip
=0):
593 command
= self
.getMultilineCommand()
596 # Otherwise, put the cursor back where we started.
598 self
.SetCurrentPos(thepos
)
599 self
.SetAnchor(thepos
)
601 def getMultilineCommand(self
, rstrip
=1):
602 """Extract a multi-line command from the editor.
604 The command may not necessarily be valid Python syntax."""
605 # XXX Need to extract real prompts here. Need to keep track of the
606 # prompt every time a command is issued.
611 # This is a total hack job, but it works.
612 text
= self
.GetCurLine()[0]
613 line
= self
.GetCurrentLine()
614 while text
[:ps2size
] == ps2
and line
> 0:
617 text
= self
.GetCurLine()[0]
618 if text
[:ps1size
] == ps1
:
619 line
= self
.GetCurrentLine()
621 startpos
= self
.GetCurrentPos() + ps1size
624 while self
.GetCurLine()[0][:ps2size
] == ps2
:
627 stoppos
= self
.GetCurrentPos()
628 command
= self
.GetTextRange(startpos
, stoppos
)
629 command
= command
.replace(os
.linesep
+ sys
.ps2
, '\n')
630 command
= command
.rstrip()
631 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
635 command
= command
.rstrip()
638 def getCommand(self
, text
=None, rstrip
=1):
639 """Extract a command from text which may include a shell prompt.
641 The command may not necessarily be valid Python syntax."""
643 text
= self
.GetCurLine()[0]
644 # Strip the prompt off the front of text leaving just the command.
645 command
= self
.lstripPrompt(text
)
647 command
= '' # Real commands have prompts.
649 command
= command
.rstrip()
652 def lstripPrompt(self
, text
):
653 """Return text without a leading prompt."""
658 # Strip the prompt off the front of text.
659 if text
[:ps1size
] == ps1
:
660 text
= text
[ps1size
:]
661 elif text
[:ps2size
] == ps2
:
662 text
= text
[ps2size
:]
665 def push(self
, command
):
666 """Send command to the interpreter for execution."""
667 busy
= wxBusyCursor()
668 self
.write(os
.linesep
)
669 self
.more
= self
.interp
.push(command
)
671 self
.addHistory(command
.rstrip())
674 def addHistory(self
, command
):
675 """Add command to the command history."""
676 # Reset the history position.
677 self
.historyIndex
= -1
678 # Insert this command into the history, unless it's a blank
679 # line or the same as the last command.
681 and (len(self
.history
) == 0 or command
!= self
.history
[0]):
682 self
.history
.insert(0, command
)
684 def write(self
, text
):
685 """Display text in the shell.
687 Replace line endings with OS-specific endings."""
688 text
= self
.fixLineEndings(text
)
690 self
.EnsureCaretVisible()
692 def fixLineEndings(self
, text
):
693 """Return text with line endings replaced by OS-specific endings."""
694 lines
= text
.split('\r\n')
695 for l
in range(len(lines
)):
696 chunks
= lines
[l
].split('\r')
697 for c
in range(len(chunks
)):
698 chunks
[c
] = os
.linesep
.join(chunks
[c
].split('\n'))
699 lines
[l
] = os
.linesep
.join(chunks
)
700 text
= os
.linesep
.join(lines
)
704 """Display appropriate prompt for the context, either ps1 or ps2.
706 If this is a continuation line, autoindent as necessary."""
708 prompt
= str(sys
.ps2
)
710 prompt
= str(sys
.ps1
)
711 pos
= self
.GetCurLine()[1]
712 if pos
> 0: self
.write(os
.linesep
)
714 self
.promptPosStart
= self
.GetCurrentPos()
717 self
.promptPosEnd
= self
.GetCurrentPos()
718 # Keep the undo feature from undoing previous responses.
719 self
.EmptyUndoBuffer()
720 # XXX Add some autoindent magic here if more.
722 self
.write(' '*4) # Temporary hack indentation.
723 self
.EnsureCaretVisible()
724 self
.ScrollToColumn(0)
727 """Replacement for stdin."""
728 prompt
= 'Please enter your response:'
729 dialog
= wxTextEntryDialog(None, prompt
, \
730 'Input Dialog (Standard)', '')
732 if dialog
.ShowModal() == wxID_OK
:
733 text
= dialog
.GetValue()
734 self
.write(text
+ os
.linesep
)
740 def readRaw(self
, prompt
='Please enter your response:'):
741 """Replacement for raw_input."""
742 dialog
= wxTextEntryDialog(None, prompt
, \
743 'Input Dialog (Raw)', '')
745 if dialog
.ShowModal() == wxID_OK
:
746 text
= dialog
.GetValue()
752 def ask(self
, prompt
='Please enter your response:'):
753 """Get response from the user."""
754 return raw_input(prompt
=prompt
)
757 """Halt execution pending a response from the user."""
758 self
.ask('Press enter to continue:')
761 """Delete all text from the shell."""
764 def run(self
, command
, prompt
=1, verbose
=1):
765 """Execute command within the shell as if it was typed in directly.
766 >>> shell.run('print "this"')
771 # Go to the very bottom of the text.
772 endpos
= self
.GetTextLength()
773 self
.SetCurrentPos(endpos
)
774 command
= command
.rstrip()
775 if prompt
: self
.prompt()
776 if verbose
: self
.write(command
)
779 def runfile(self
, filename
):
780 """Execute all commands in file as if they were typed into the shell."""
781 file = open(filename
)
784 for command
in file.readlines():
785 if command
[:6] == 'shell.': # Run shell methods silently.
786 self
.run(command
, prompt
=0, verbose
=0)
788 self
.run(command
, prompt
=0, verbose
=1)
792 def autoCompleteShow(self
, command
):
793 """Display auto-completion popup list."""
794 list = self
.interp
.getAutoCompleteList(command
,
795 includeMagic
=self
.autoCompleteIncludeMagic
,
796 includeSingle
=self
.autoCompleteIncludeSingle
,
797 includeDouble
=self
.autoCompleteIncludeDouble
)
799 options
= ' '.join(list)
801 self
.AutoCompShow(offset
, options
)
803 def autoCallTipShow(self
, command
):
804 """Display argument spec and docstring in a popup bubble thingie."""
805 if self
.CallTipActive
: self
.CallTipCancel()
806 (name
, argspec
, tip
) = self
.interp
.getCallTip(command
)
808 startpos
= self
.GetCurrentPos()
809 self
.write(argspec
+ ')')
810 endpos
= self
.GetCurrentPos()
811 self
.SetSelection(endpos
, startpos
)
813 curpos
= self
.GetCurrentPos()
814 tippos
= curpos
- (len(name
) + 1)
815 fallback
= curpos
- self
.GetColumn(curpos
)
816 # In case there isn't enough room, only go back to the fallback.
817 tippos
= max(tippos
, fallback
)
818 self
.CallTipShow(tippos
, tip
)
820 def writeOut(self
, text
):
821 """Replacement for stdout."""
824 def writeErr(self
, text
):
825 """Replacement for stderr."""
828 def redirectStdin(self
, redirect
=1):
829 """If redirect is true then sys.stdin will come from the shell."""
831 sys
.stdin
= PseudoFileIn(self
.readIn
)
833 sys
.stdin
= self
.stdin
835 def redirectStdout(self
, redirect
=1):
836 """If redirect is true then sys.stdout will go to the shell."""
838 sys
.stdout
= PseudoFileOut(self
.writeOut
)
840 sys
.stdout
= self
.stdout
842 def redirectStderr(self
, redirect
=1):
843 """If redirect is true then sys.stderr will go to the shell."""
845 sys
.stderr
= PseudoFileErr(self
.writeErr
)
847 sys
.stderr
= self
.stderr
850 """Return true if text is selected and can be cut."""
851 if self
.GetSelectionStart() != self
.GetSelectionEnd() \
852 and self
.GetSelectionStart() >= self
.promptPosEnd \
853 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
859 """Return true if text is selected and can be copied."""
860 return self
.GetSelectionStart() != self
.GetSelectionEnd()
863 """Return true if a paste should succeed."""
864 if self
.CanEdit() and wxStyledTextCtrl
.CanPaste(self
):
870 """Return true if editing should succeed."""
871 if self
.GetSelectionStart() != self
.GetSelectionEnd():
872 if self
.GetSelectionStart() >= self
.promptPosEnd \
873 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
878 return self
.GetCurrentPos() >= self
.promptPosEnd
881 """Remove selection and place it on the clipboard."""
882 if self
.CanCut() and self
.CanCopy():
883 if self
.AutoCompActive(): self
.AutoCompCancel()
884 if self
.CallTipActive
: self
.CallTipCancel()
886 self
.ReplaceSelection('')
889 """Copy selection and place it on the clipboard."""
891 command
= self
.GetSelectedText()
892 command
= command
.replace(os
.linesep
+ sys
.ps2
, os
.linesep
)
893 command
= command
.replace(os
.linesep
+ sys
.ps1
, os
.linesep
)
894 command
= self
.lstripPrompt(text
=command
)
895 data
= wxTextDataObject(command
)
896 if wxTheClipboard
.Open():
897 wxTheClipboard
.SetData(data
)
898 wxTheClipboard
.Close()
900 def CopyWithPrompts(self
):
901 """Copy selection, including prompts, and place it on the clipboard."""
903 command
= self
.GetSelectedText()
904 data
= wxTextDataObject(command
)
905 if wxTheClipboard
.Open():
906 wxTheClipboard
.SetData(data
)
907 wxTheClipboard
.Close()
910 """Replace selection with clipboard contents."""
911 if self
.CanPaste() and wxTheClipboard
.Open():
912 if wxTheClipboard
.IsSupported(wxDataFormat(wxDF_TEXT
)):
913 data
= wxTextDataObject()
914 if wxTheClipboard
.GetData(data
):
915 self
.ReplaceSelection('')
916 command
= data
.GetText()
917 command
= command
.rstrip()
918 command
= self
.fixLineEndings(command
)
919 command
= self
.lstripPrompt(text
=command
)
920 command
= command
.replace(os
.linesep
+ sys
.ps2
, '\n')
921 command
= command
.replace(os
.linesep
, '\n')
922 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
924 wxTheClipboard
.Close()
926 def PasteAndRun(self
):
927 """Replace selection with clipboard contents, run commands."""
928 if wxTheClipboard
.Open():
929 if wxTheClipboard
.IsSupported(wxDataFormat(wxDF_TEXT
)):
930 data
= wxTextDataObject()
931 if wxTheClipboard
.GetData(data
):
932 endpos
= self
.GetTextLength()
933 self
.SetCurrentPos(endpos
)
934 startpos
= self
.promptPosEnd
935 self
.SetSelection(startpos
, endpos
)
936 self
.ReplaceSelection('')
937 text
= data
.GetText()
939 text
= self
.fixLineEndings(text
)
940 text
= self
.lstripPrompt(text
=text
)
941 text
= text
.replace(os
.linesep
+ sys
.ps1
, '\n')
942 text
= text
.replace(os
.linesep
+ sys
.ps2
, '\n')
943 text
= text
.replace(os
.linesep
, '\n')
944 lines
= text
.split('\n')
948 if line
.strip() != '' and line
.lstrip() == line
:
951 # Add the previous command to the list.
952 commands
.append(command
)
953 # Start a new command, which may be multiline.
956 # Multiline command. Add to the command.
959 commands
.append(command
)
960 for command
in commands
:
961 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
964 wxTheClipboard
.Close()
966 def wrap(self
, wrap
=1):
967 """Sets whether text is word wrapped."""
969 self
.SetWrapMode(wrap
)
970 except AttributeError:
971 return 'Wrapping is not available in this version of PyCrust.'
973 def zoom(self
, points
=0):
974 """Set the zoom level.
976 This number of points is added to the size of all fonts.
977 It may be positive to magnify or negative to reduce."""
981 wxID_SELECTALL
= NewId() # This *should* be defined by wxPython.
982 ID_AUTOCOMP
= NewId()
983 ID_AUTOCOMP_SHOW
= NewId()
984 ID_AUTOCOMP_INCLUDE_MAGIC
= NewId()
985 ID_AUTOCOMP_INCLUDE_SINGLE
= NewId()
986 ID_AUTOCOMP_INCLUDE_DOUBLE
= NewId()
987 ID_CALLTIPS
= NewId()
988 ID_CALLTIPS_SHOW
= NewId()
992 """Mixin class to add standard menu items."""
994 def createMenus(self
):
995 m
= self
.fileMenu
= wxMenu()
997 m
.Append(wxID_EXIT
, 'E&xit', 'Exit PyCrust')
999 m
= self
.editMenu
= wxMenu()
1000 m
.Append(wxID_UNDO
, '&Undo \tCtrl+Z', 'Undo the last action')
1001 m
.Append(wxID_REDO
, '&Redo \tCtrl+Y', 'Redo the last undone action')
1003 m
.Append(wxID_CUT
, 'Cu&t \tCtrl+X', 'Cut the selection')
1004 m
.Append(wxID_COPY
, '&Copy \tCtrl+C', 'Copy the selection')
1005 m
.Append(wxID_PASTE
, '&Paste \tCtrl+V', 'Paste')
1007 m
.Append(wxID_CLEAR
, 'Cle&ar', 'Delete the selection')
1008 m
.Append(wxID_SELECTALL
, 'Select A&ll', 'Select all text')
1010 m
= self
.autocompMenu
= wxMenu()
1011 m
.Append(ID_AUTOCOMP_SHOW
, 'Show Auto Completion', \
1012 'Show auto completion during dot syntax', 1)
1013 m
.Append(ID_AUTOCOMP_INCLUDE_MAGIC
, 'Include Magic Attributes', \
1014 'Include attributes visible to __getattr__ and __setattr__', 1)
1015 m
.Append(ID_AUTOCOMP_INCLUDE_SINGLE
, 'Include Single Underscores', \
1016 'Include attibutes prefixed by a single underscore', 1)
1017 m
.Append(ID_AUTOCOMP_INCLUDE_DOUBLE
, 'Include Double Underscores', \
1018 'Include attibutes prefixed by a double underscore', 1)
1020 m
= self
.calltipsMenu
= wxMenu()
1021 m
.Append(ID_CALLTIPS_SHOW
, 'Show Call Tips', \
1022 'Show call tips with argument specifications', 1)
1024 m
= self
.optionsMenu
= wxMenu()
1025 m
.AppendMenu(ID_AUTOCOMP
, '&Auto Completion', self
.autocompMenu
, \
1026 'Auto Completion Options')
1027 m
.AppendMenu(ID_CALLTIPS
, '&Call Tips', self
.calltipsMenu
, \
1030 m
= self
.helpMenu
= wxMenu()
1032 m
.Append(wxID_ABOUT
, '&About...', 'About PyCrust')
1034 b
= self
.menuBar
= wxMenuBar()
1035 b
.Append(self
.fileMenu
, '&File')
1036 b
.Append(self
.editMenu
, '&Edit')
1037 b
.Append(self
.optionsMenu
, '&Options')
1038 b
.Append(self
.helpMenu
, '&Help')
1041 EVT_MENU(self
, wxID_EXIT
, self
.OnExit
)
1042 EVT_MENU(self
, wxID_UNDO
, self
.OnUndo
)
1043 EVT_MENU(self
, wxID_REDO
, self
.OnRedo
)
1044 EVT_MENU(self
, wxID_CUT
, self
.OnCut
)
1045 EVT_MENU(self
, wxID_COPY
, self
.OnCopy
)
1046 EVT_MENU(self
, wxID_PASTE
, self
.OnPaste
)
1047 EVT_MENU(self
, wxID_CLEAR
, self
.OnClear
)
1048 EVT_MENU(self
, wxID_SELECTALL
, self
.OnSelectAll
)
1049 EVT_MENU(self
, wxID_ABOUT
, self
.OnAbout
)
1050 EVT_MENU(self
, ID_AUTOCOMP_SHOW
, \
1051 self
.OnAutoCompleteShow
)
1052 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_MAGIC
, \
1053 self
.OnAutoCompleteIncludeMagic
)
1054 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_SINGLE
, \
1055 self
.OnAutoCompleteIncludeSingle
)
1056 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_DOUBLE
, \
1057 self
.OnAutoCompleteIncludeDouble
)
1058 EVT_MENU(self
, ID_CALLTIPS_SHOW
, \
1059 self
.OnCallTipsShow
)
1061 EVT_UPDATE_UI(self
, wxID_UNDO
, self
.OnUpdateMenu
)
1062 EVT_UPDATE_UI(self
, wxID_REDO
, self
.OnUpdateMenu
)
1063 EVT_UPDATE_UI(self
, wxID_CUT
, self
.OnUpdateMenu
)
1064 EVT_UPDATE_UI(self
, wxID_COPY
, self
.OnUpdateMenu
)
1065 EVT_UPDATE_UI(self
, wxID_PASTE
, self
.OnUpdateMenu
)
1066 EVT_UPDATE_UI(self
, wxID_CLEAR
, self
.OnUpdateMenu
)
1067 EVT_UPDATE_UI(self
, ID_AUTOCOMP_SHOW
, self
.OnUpdateMenu
)
1068 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_MAGIC
, self
.OnUpdateMenu
)
1069 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_SINGLE
, self
.OnUpdateMenu
)
1070 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_DOUBLE
, self
.OnUpdateMenu
)
1071 EVT_UPDATE_UI(self
, ID_CALLTIPS_SHOW
, self
.OnUpdateMenu
)
1073 def OnExit(self
, event
):
1076 def OnUndo(self
, event
):
1079 def OnRedo(self
, event
):
1082 def OnCut(self
, event
):
1085 def OnCopy(self
, event
):
1088 def OnPaste(self
, event
):
1091 def OnClear(self
, event
):
1094 def OnSelectAll(self
, event
):
1095 self
.shell
.SelectAll()
1097 def OnAbout(self
, event
):
1098 """Display an About PyCrust window."""
1100 title
= 'About PyCrust'
1101 text
= 'PyCrust %s\n\n' % VERSION
+ \
1102 'Yet another Python shell, only flakier.\n\n' + \
1103 'Half-baked by Patrick K. O\'Brien,\n' + \
1104 'the other half is still in the oven.\n\n' + \
1105 'Shell Revision: %s\n' % self
.shell
.revision
+ \
1106 'Interpreter Revision: %s\n\n' % self
.shell
.interp
.revision
+ \
1107 'Python Version: %s\n' % sys
.version
.split()[0] + \
1108 'wxPython Version: %s\n' % wx
.__version
__ + \
1109 'Platform: %s\n' % sys
.platform
1110 dialog
= wxMessageDialog(self
, text
, title
, wxOK | wxICON_INFORMATION
)
1114 def OnAutoCompleteShow(self
, event
):
1115 self
.shell
.autoComplete
= event
.IsChecked()
1117 def OnAutoCompleteIncludeMagic(self
, event
):
1118 self
.shell
.autoCompleteIncludeMagic
= event
.IsChecked()
1120 def OnAutoCompleteIncludeSingle(self
, event
):
1121 self
.shell
.autoCompleteIncludeSingle
= event
.IsChecked()
1123 def OnAutoCompleteIncludeDouble(self
, event
):
1124 self
.shell
.autoCompleteIncludeDouble
= event
.IsChecked()
1126 def OnCallTipsShow(self
, event
):
1127 self
.shell
.autoCallTip
= event
.IsChecked()
1129 def OnUpdateMenu(self
, event
):
1130 """Update menu items based on current status."""
1133 event
.Enable(self
.shell
.CanUndo())
1134 elif id == wxID_REDO
:
1135 event
.Enable(self
.shell
.CanRedo())
1136 elif id == wxID_CUT
:
1137 event
.Enable(self
.shell
.CanCut())
1138 elif id == wxID_COPY
:
1139 event
.Enable(self
.shell
.CanCopy())
1140 elif id == wxID_PASTE
:
1141 event
.Enable(self
.shell
.CanPaste())
1142 elif id == wxID_CLEAR
:
1143 event
.Enable(self
.shell
.CanCut())
1144 elif id == ID_AUTOCOMP_SHOW
:
1145 event
.Check(self
.shell
.autoComplete
)
1146 elif id == ID_AUTOCOMP_INCLUDE_MAGIC
:
1147 event
.Check(self
.shell
.autoCompleteIncludeMagic
)
1148 elif id == ID_AUTOCOMP_INCLUDE_SINGLE
:
1149 event
.Check(self
.shell
.autoCompleteIncludeSingle
)
1150 elif id == ID_AUTOCOMP_INCLUDE_DOUBLE
:
1151 event
.Check(self
.shell
.autoCompleteIncludeDouble
)
1152 elif id == ID_CALLTIPS_SHOW
:
1153 event
.Check(self
.shell
.autoCallTip
)
1156 class ShellFrame(wxFrame
, ShellMenu
):
1157 """Frame containing the PyCrust shell component."""
1159 name
= 'PyCrust Shell Frame'
1160 revision
= __revision__
1162 def __init__(self
, parent
=None, id=-1, title
='PyShell', \
1163 pos
=wxDefaultPosition
, size
=wxDefaultSize
, \
1164 style
=wxDEFAULT_FRAME_STYLE
, locals=None, \
1165 InterpClass
=None, *args
, **kwds
):
1166 """Create a PyCrust ShellFrame instance."""
1167 wxFrame
.__init
__(self
, parent
, id, title
, pos
, size
, style
)
1168 intro
= 'Welcome To PyCrust %s - The Flakiest Python Shell' % VERSION
1169 intro
+= '\nSponsored by Orbtech - Your source for Python programming expertise.'
1170 self
.CreateStatusBar()
1171 self
.SetStatusText(intro
.replace('\n', ', '))
1173 self
.SetIcon(images
.getPyCrustIcon())
1174 self
.shell
= Shell(parent
=self
, id=-1, introText
=intro
, \
1175 locals=locals, InterpClass
=InterpClass
, \
1177 # Override the shell so that status messages go to the status bar.
1178 self
.shell
.setStatusText
= self
.SetStatusText
1180 EVT_CLOSE(self
, self
.OnCloseWindow
)
1182 def OnCloseWindow(self
, event
):
1183 self
.shell
.destroy()