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
)
672 self
.addHistory(command
.rstrip())
675 def addHistory(self
, command
):
676 """Add command to the command history."""
677 # Reset the history position.
678 self
.historyIndex
= -1
679 # Insert this command into the history, unless it's a blank
680 # line or the same as the last command.
682 and (len(self
.history
) == 0 or command
!= self
.history
[0]):
683 self
.history
.insert(0, command
)
685 def write(self
, text
):
686 """Display text in the shell.
688 Replace line endings with OS-specific endings."""
689 text
= self
.fixLineEndings(text
)
691 self
.EnsureCaretVisible()
693 def fixLineEndings(self
, text
):
694 """Return text with line endings replaced by OS-specific endings."""
695 lines
= text
.split('\r\n')
696 for l
in range(len(lines
)):
697 chunks
= lines
[l
].split('\r')
698 for c
in range(len(chunks
)):
699 chunks
[c
] = os
.linesep
.join(chunks
[c
].split('\n'))
700 lines
[l
] = os
.linesep
.join(chunks
)
701 text
= os
.linesep
.join(lines
)
705 """Display appropriate prompt for the context, either ps1 or ps2.
707 If this is a continuation line, autoindent as necessary."""
709 prompt
= str(sys
.ps2
)
711 prompt
= str(sys
.ps1
)
712 pos
= self
.GetCurLine()[1]
713 if pos
> 0: self
.write(os
.linesep
)
715 self
.promptPosStart
= self
.GetCurrentPos()
718 self
.promptPosEnd
= self
.GetCurrentPos()
719 # Keep the undo feature from undoing previous responses.
720 self
.EmptyUndoBuffer()
721 # XXX Add some autoindent magic here if more.
723 self
.write(' '*4) # Temporary hack indentation.
724 self
.EnsureCaretVisible()
725 self
.ScrollToColumn(0)
728 """Replacement for stdin."""
729 prompt
= 'Please enter your response:'
730 dialog
= wxTextEntryDialog(None, prompt
, \
731 'Input Dialog (Standard)', '')
733 if dialog
.ShowModal() == wxID_OK
:
734 text
= dialog
.GetValue()
735 self
.write(text
+ os
.linesep
)
741 def readRaw(self
, prompt
='Please enter your response:'):
742 """Replacement for raw_input."""
743 dialog
= wxTextEntryDialog(None, prompt
, \
744 'Input Dialog (Raw)', '')
746 if dialog
.ShowModal() == wxID_OK
:
747 text
= dialog
.GetValue()
753 def ask(self
, prompt
='Please enter your response:'):
754 """Get response from the user."""
755 return raw_input(prompt
=prompt
)
758 """Halt execution pending a response from the user."""
759 self
.ask('Press enter to continue:')
762 """Delete all text from the shell."""
765 def run(self
, command
, prompt
=1, verbose
=1):
766 """Execute command within the shell as if it was typed in directly.
767 >>> shell.run('print "this"')
772 # Go to the very bottom of the text.
773 endpos
= self
.GetTextLength()
774 self
.SetCurrentPos(endpos
)
775 command
= command
.rstrip()
776 if prompt
: self
.prompt()
777 if verbose
: self
.write(command
)
780 def runfile(self
, filename
):
781 """Execute all commands in file as if they were typed into the shell."""
782 file = open(filename
)
785 for command
in file.readlines():
786 if command
[:6] == 'shell.': # Run shell methods silently.
787 self
.run(command
, prompt
=0, verbose
=0)
789 self
.run(command
, prompt
=0, verbose
=1)
793 def autoCompleteShow(self
, command
):
794 """Display auto-completion popup list."""
795 list = self
.interp
.getAutoCompleteList(command
,
796 includeMagic
=self
.autoCompleteIncludeMagic
,
797 includeSingle
=self
.autoCompleteIncludeSingle
,
798 includeDouble
=self
.autoCompleteIncludeDouble
)
800 options
= ' '.join(list)
802 self
.AutoCompShow(offset
, options
)
804 def autoCallTipShow(self
, command
):
805 """Display argument spec and docstring in a popup bubble thingie."""
806 if self
.CallTipActive
: self
.CallTipCancel()
807 (name
, argspec
, tip
) = self
.interp
.getCallTip(command
)
809 startpos
= self
.GetCurrentPos()
810 self
.write(argspec
+ ')')
811 endpos
= self
.GetCurrentPos()
812 self
.SetSelection(endpos
, startpos
)
814 curpos
= self
.GetCurrentPos()
815 tippos
= curpos
- (len(name
) + 1)
816 fallback
= curpos
- self
.GetColumn(curpos
)
817 # In case there isn't enough room, only go back to the fallback.
818 tippos
= max(tippos
, fallback
)
819 self
.CallTipShow(tippos
, tip
)
821 def writeOut(self
, text
):
822 """Replacement for stdout."""
825 def writeErr(self
, text
):
826 """Replacement for stderr."""
829 def redirectStdin(self
, redirect
=1):
830 """If redirect is true then sys.stdin will come from the shell."""
832 sys
.stdin
= PseudoFileIn(self
.readIn
)
834 sys
.stdin
= self
.stdin
836 def redirectStdout(self
, redirect
=1):
837 """If redirect is true then sys.stdout will go to the shell."""
839 sys
.stdout
= PseudoFileOut(self
.writeOut
)
841 sys
.stdout
= self
.stdout
843 def redirectStderr(self
, redirect
=1):
844 """If redirect is true then sys.stderr will go to the shell."""
846 sys
.stderr
= PseudoFileErr(self
.writeErr
)
848 sys
.stderr
= self
.stderr
851 """Return true if text is selected and can be cut."""
852 if self
.GetSelectionStart() != self
.GetSelectionEnd() \
853 and self
.GetSelectionStart() >= self
.promptPosEnd \
854 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
860 """Return true if text is selected and can be copied."""
861 return self
.GetSelectionStart() != self
.GetSelectionEnd()
864 """Return true if a paste should succeed."""
865 if self
.CanEdit() and wxStyledTextCtrl
.CanPaste(self
):
871 """Return true if editing should succeed."""
872 if self
.GetSelectionStart() != self
.GetSelectionEnd():
873 if self
.GetSelectionStart() >= self
.promptPosEnd \
874 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
879 return self
.GetCurrentPos() >= self
.promptPosEnd
882 """Remove selection and place it on the clipboard."""
883 if self
.CanCut() and self
.CanCopy():
884 if self
.AutoCompActive(): self
.AutoCompCancel()
885 if self
.CallTipActive
: self
.CallTipCancel()
887 self
.ReplaceSelection('')
890 """Copy selection and place it on the clipboard."""
892 command
= self
.GetSelectedText()
893 command
= command
.replace(os
.linesep
+ sys
.ps2
, os
.linesep
)
894 command
= command
.replace(os
.linesep
+ sys
.ps1
, os
.linesep
)
895 command
= self
.lstripPrompt(text
=command
)
896 data
= wxTextDataObject(command
)
897 if wxTheClipboard
.Open():
898 wxTheClipboard
.SetData(data
)
899 wxTheClipboard
.Close()
901 def CopyWithPrompts(self
):
902 """Copy selection, including prompts, and place it on the clipboard."""
904 command
= self
.GetSelectedText()
905 data
= wxTextDataObject(command
)
906 if wxTheClipboard
.Open():
907 wxTheClipboard
.SetData(data
)
908 wxTheClipboard
.Close()
911 """Replace selection with clipboard contents."""
912 if self
.CanPaste() and wxTheClipboard
.Open():
913 if wxTheClipboard
.IsSupported(wxDataFormat(wxDF_TEXT
)):
914 data
= wxTextDataObject()
915 if wxTheClipboard
.GetData(data
):
916 self
.ReplaceSelection('')
917 command
= data
.GetText()
918 command
= command
.rstrip()
919 command
= self
.fixLineEndings(command
)
920 command
= self
.lstripPrompt(text
=command
)
921 command
= command
.replace(os
.linesep
+ sys
.ps2
, '\n')
922 command
= command
.replace(os
.linesep
, '\n')
923 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
925 wxTheClipboard
.Close()
927 def PasteAndRun(self
):
928 """Replace selection with clipboard contents, run commands."""
929 if wxTheClipboard
.Open():
930 if wxTheClipboard
.IsSupported(wxDataFormat(wxDF_TEXT
)):
931 data
= wxTextDataObject()
932 if wxTheClipboard
.GetData(data
):
933 endpos
= self
.GetTextLength()
934 self
.SetCurrentPos(endpos
)
935 startpos
= self
.promptPosEnd
936 self
.SetSelection(startpos
, endpos
)
937 self
.ReplaceSelection('')
938 text
= data
.GetText()
940 text
= self
.fixLineEndings(text
)
941 text
= self
.lstripPrompt(text
=text
)
942 text
= text
.replace(os
.linesep
+ sys
.ps1
, '\n')
943 text
= text
.replace(os
.linesep
+ sys
.ps2
, '\n')
944 text
= text
.replace(os
.linesep
, '\n')
945 lines
= text
.split('\n')
949 if line
.strip() != '' and line
.lstrip() == line
:
952 # Add the previous command to the list.
953 commands
.append(command
)
954 # Start a new command, which may be multiline.
957 # Multiline command. Add to the command.
960 commands
.append(command
)
961 for command
in commands
:
962 command
= command
.replace('\n', os
.linesep
+ sys
.ps2
)
965 wxTheClipboard
.Close()
967 def wrap(self
, wrap
=1):
968 """Sets whether text is word wrapped."""
970 self
.SetWrapMode(wrap
)
971 except AttributeError:
972 return 'Wrapping is not available in this version of PyCrust.'
974 def zoom(self
, points
=0):
975 """Set the zoom level.
977 This number of points is added to the size of all fonts.
978 It may be positive to magnify or negative to reduce."""
982 wxID_SELECTALL
= NewId() # This *should* be defined by wxPython.
983 ID_AUTOCOMP
= NewId()
984 ID_AUTOCOMP_SHOW
= NewId()
985 ID_AUTOCOMP_INCLUDE_MAGIC
= NewId()
986 ID_AUTOCOMP_INCLUDE_SINGLE
= NewId()
987 ID_AUTOCOMP_INCLUDE_DOUBLE
= NewId()
988 ID_CALLTIPS
= NewId()
989 ID_CALLTIPS_SHOW
= NewId()
993 """Mixin class to add standard menu items."""
995 def createMenus(self
):
996 m
= self
.fileMenu
= wxMenu()
998 m
.Append(wxID_EXIT
, 'E&xit', 'Exit PyCrust')
1000 m
= self
.editMenu
= wxMenu()
1001 m
.Append(wxID_UNDO
, '&Undo \tCtrl+Z', 'Undo the last action')
1002 m
.Append(wxID_REDO
, '&Redo \tCtrl+Y', 'Redo the last undone action')
1004 m
.Append(wxID_CUT
, 'Cu&t \tCtrl+X', 'Cut the selection')
1005 m
.Append(wxID_COPY
, '&Copy \tCtrl+C', 'Copy the selection')
1006 m
.Append(wxID_PASTE
, '&Paste \tCtrl+V', 'Paste')
1008 m
.Append(wxID_CLEAR
, 'Cle&ar', 'Delete the selection')
1009 m
.Append(wxID_SELECTALL
, 'Select A&ll', 'Select all text')
1011 m
= self
.autocompMenu
= wxMenu()
1012 m
.Append(ID_AUTOCOMP_SHOW
, 'Show Auto Completion', \
1013 'Show auto completion during dot syntax', 1)
1014 m
.Append(ID_AUTOCOMP_INCLUDE_MAGIC
, 'Include Magic Attributes', \
1015 'Include attributes visible to __getattr__ and __setattr__', 1)
1016 m
.Append(ID_AUTOCOMP_INCLUDE_SINGLE
, 'Include Single Underscores', \
1017 'Include attibutes prefixed by a single underscore', 1)
1018 m
.Append(ID_AUTOCOMP_INCLUDE_DOUBLE
, 'Include Double Underscores', \
1019 'Include attibutes prefixed by a double underscore', 1)
1021 m
= self
.calltipsMenu
= wxMenu()
1022 m
.Append(ID_CALLTIPS_SHOW
, 'Show Call Tips', \
1023 'Show call tips with argument specifications', 1)
1025 m
= self
.optionsMenu
= wxMenu()
1026 m
.AppendMenu(ID_AUTOCOMP
, '&Auto Completion', self
.autocompMenu
, \
1027 'Auto Completion Options')
1028 m
.AppendMenu(ID_CALLTIPS
, '&Call Tips', self
.calltipsMenu
, \
1031 m
= self
.helpMenu
= wxMenu()
1033 m
.Append(wxID_ABOUT
, '&About...', 'About PyCrust')
1035 b
= self
.menuBar
= wxMenuBar()
1036 b
.Append(self
.fileMenu
, '&File')
1037 b
.Append(self
.editMenu
, '&Edit')
1038 b
.Append(self
.optionsMenu
, '&Options')
1039 b
.Append(self
.helpMenu
, '&Help')
1042 EVT_MENU(self
, wxID_EXIT
, self
.OnExit
)
1043 EVT_MENU(self
, wxID_UNDO
, self
.OnUndo
)
1044 EVT_MENU(self
, wxID_REDO
, self
.OnRedo
)
1045 EVT_MENU(self
, wxID_CUT
, self
.OnCut
)
1046 EVT_MENU(self
, wxID_COPY
, self
.OnCopy
)
1047 EVT_MENU(self
, wxID_PASTE
, self
.OnPaste
)
1048 EVT_MENU(self
, wxID_CLEAR
, self
.OnClear
)
1049 EVT_MENU(self
, wxID_SELECTALL
, self
.OnSelectAll
)
1050 EVT_MENU(self
, wxID_ABOUT
, self
.OnAbout
)
1051 EVT_MENU(self
, ID_AUTOCOMP_SHOW
, \
1052 self
.OnAutoCompleteShow
)
1053 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_MAGIC
, \
1054 self
.OnAutoCompleteIncludeMagic
)
1055 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_SINGLE
, \
1056 self
.OnAutoCompleteIncludeSingle
)
1057 EVT_MENU(self
, ID_AUTOCOMP_INCLUDE_DOUBLE
, \
1058 self
.OnAutoCompleteIncludeDouble
)
1059 EVT_MENU(self
, ID_CALLTIPS_SHOW
, \
1060 self
.OnCallTipsShow
)
1062 EVT_UPDATE_UI(self
, wxID_UNDO
, self
.OnUpdateMenu
)
1063 EVT_UPDATE_UI(self
, wxID_REDO
, self
.OnUpdateMenu
)
1064 EVT_UPDATE_UI(self
, wxID_CUT
, self
.OnUpdateMenu
)
1065 EVT_UPDATE_UI(self
, wxID_COPY
, self
.OnUpdateMenu
)
1066 EVT_UPDATE_UI(self
, wxID_PASTE
, self
.OnUpdateMenu
)
1067 EVT_UPDATE_UI(self
, wxID_CLEAR
, self
.OnUpdateMenu
)
1068 EVT_UPDATE_UI(self
, ID_AUTOCOMP_SHOW
, self
.OnUpdateMenu
)
1069 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_MAGIC
, self
.OnUpdateMenu
)
1070 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_SINGLE
, self
.OnUpdateMenu
)
1071 EVT_UPDATE_UI(self
, ID_AUTOCOMP_INCLUDE_DOUBLE
, self
.OnUpdateMenu
)
1072 EVT_UPDATE_UI(self
, ID_CALLTIPS_SHOW
, self
.OnUpdateMenu
)
1074 def OnExit(self
, event
):
1077 def OnUndo(self
, event
):
1080 def OnRedo(self
, event
):
1083 def OnCut(self
, event
):
1086 def OnCopy(self
, event
):
1089 def OnPaste(self
, event
):
1092 def OnClear(self
, event
):
1095 def OnSelectAll(self
, event
):
1096 self
.shell
.SelectAll()
1098 def OnAbout(self
, event
):
1099 """Display an About PyCrust window."""
1101 title
= 'About PyCrust'
1102 text
= 'PyCrust %s\n\n' % VERSION
+ \
1103 'Yet another Python shell, only flakier.\n\n' + \
1104 'Half-baked by Patrick K. O\'Brien,\n' + \
1105 'the other half is still in the oven.\n\n' + \
1106 'Shell Revision: %s\n' % self
.shell
.revision
+ \
1107 'Interpreter Revision: %s\n\n' % self
.shell
.interp
.revision
+ \
1108 'Python Version: %s\n' % sys
.version
.split()[0] + \
1109 'wxPython Version: %s\n' % wx
.__version
__ + \
1110 'Platform: %s\n' % sys
.platform
1111 dialog
= wxMessageDialog(self
, text
, title
, wxOK | wxICON_INFORMATION
)
1115 def OnAutoCompleteShow(self
, event
):
1116 self
.shell
.autoComplete
= event
.IsChecked()
1118 def OnAutoCompleteIncludeMagic(self
, event
):
1119 self
.shell
.autoCompleteIncludeMagic
= event
.IsChecked()
1121 def OnAutoCompleteIncludeSingle(self
, event
):
1122 self
.shell
.autoCompleteIncludeSingle
= event
.IsChecked()
1124 def OnAutoCompleteIncludeDouble(self
, event
):
1125 self
.shell
.autoCompleteIncludeDouble
= event
.IsChecked()
1127 def OnCallTipsShow(self
, event
):
1128 self
.shell
.autoCallTip
= event
.IsChecked()
1130 def OnUpdateMenu(self
, event
):
1131 """Update menu items based on current status."""
1134 event
.Enable(self
.shell
.CanUndo())
1135 elif id == wxID_REDO
:
1136 event
.Enable(self
.shell
.CanRedo())
1137 elif id == wxID_CUT
:
1138 event
.Enable(self
.shell
.CanCut())
1139 elif id == wxID_COPY
:
1140 event
.Enable(self
.shell
.CanCopy())
1141 elif id == wxID_PASTE
:
1142 event
.Enable(self
.shell
.CanPaste())
1143 elif id == wxID_CLEAR
:
1144 event
.Enable(self
.shell
.CanCut())
1145 elif id == ID_AUTOCOMP_SHOW
:
1146 event
.Check(self
.shell
.autoComplete
)
1147 elif id == ID_AUTOCOMP_INCLUDE_MAGIC
:
1148 event
.Check(self
.shell
.autoCompleteIncludeMagic
)
1149 elif id == ID_AUTOCOMP_INCLUDE_SINGLE
:
1150 event
.Check(self
.shell
.autoCompleteIncludeSingle
)
1151 elif id == ID_AUTOCOMP_INCLUDE_DOUBLE
:
1152 event
.Check(self
.shell
.autoCompleteIncludeDouble
)
1153 elif id == ID_CALLTIPS_SHOW
:
1154 event
.Check(self
.shell
.autoCallTip
)
1157 class ShellFrame(wxFrame
, ShellMenu
):
1158 """Frame containing the PyCrust shell component."""
1160 name
= 'PyCrust Shell Frame'
1161 revision
= __revision__
1163 def __init__(self
, parent
=None, id=-1, title
='PyShell', \
1164 pos
=wxDefaultPosition
, size
=wxDefaultSize
, \
1165 style
=wxDEFAULT_FRAME_STYLE
, locals=None, \
1166 InterpClass
=None, *args
, **kwds
):
1167 """Create a PyCrust ShellFrame instance."""
1168 wxFrame
.__init
__(self
, parent
, id, title
, pos
, size
, style
)
1169 intro
= 'Welcome To PyCrust %s - The Flakiest Python Shell' % VERSION
1170 intro
+= '\nSponsored by Orbtech - Your source for Python programming expertise.'
1171 self
.CreateStatusBar()
1172 self
.SetStatusText(intro
.replace('\n', ', '))
1174 self
.SetIcon(images
.getPyCrustIcon())
1175 self
.shell
= Shell(parent
=self
, id=-1, introText
=intro
, \
1176 locals=locals, InterpClass
=InterpClass
, \
1178 # Override the shell so that status messages go to the status bar.
1179 self
.shell
.setStatusText
= self
.SetStatusText
1181 EVT_CLOSE(self
, self
.OnCloseWindow
)
1183 def OnCloseWindow(self
, event
):
1184 self
.shell
.destroy()