1 """The PyCrust Shell is an interactive text control in which a user
2 types in commands to be sent to the interpreter. This particular shell
3 is based on wxPython's wxStyledTextCtrl. The latest files are always
4 available at the SourceForge project page at
5 http://sourceforge.net/projects/pycrust/.
7 Sponsored by Orbtech - Your source for Python programming expertise."""
9 __author__
= "Patrick K. O'Brien <pobrien@orbtech.com>"
11 __revision__
= "$Revision$"[11:-2]
17 from pseudo
import PseudoFileIn
18 from pseudo
import PseudoFileOut
19 from pseudo
import PseudoFileErr
20 from shellmenu
import ShellMenu
21 from version
import VERSION
27 from wxPython
import wx
29 from wxd
.d_wx
import wx
34 from wxPython
import stc
36 from wxd
.d_stc
import stc
44 sys
.ps3
= '<-- ' # Input prompt.
46 NAVKEYS
= (wx
.WXK_END
, wx
.WXK_LEFT
, wx
.WXK_RIGHT
,
47 wx
.WXK_UP
, wx
.WXK_DOWN
, wx
.WXK_PRIOR
, wx
.WXK_NEXT
)
49 if wx
.wxPlatform
== '__WXMSW__':
50 faces
= { 'times' : 'Times New Roman',
51 'mono' : 'Courier New',
52 'helv' : 'Lucida Console',
53 'lucida' : 'Lucida Console',
54 'other' : 'Comic Sans MS',
60 faces
= { 'times' : 'Times',
63 'other' : 'new century schoolbook',
70 class ShellFrame(wx
.wxFrame
, ShellMenu
):
71 """Frame containing the PyCrust shell component."""
73 name
= 'PyCrust Shell Frame'
74 revision
= __revision__
76 def __init__(self
, parent
=None, id=-1, title
='PyShell',
77 pos
=wx
.wxDefaultPosition
, size
=wx
.wxDefaultSize
,
78 style
=wx
.wxDEFAULT_FRAME_STYLE
, locals=None,
79 InterpClass
=None, *args
, **kwds
):
80 """Create a PyCrust ShellFrame instance."""
81 wx
.wxFrame
.__init
__(self
, parent
, id, title
, pos
, size
, style
)
82 intro
= 'PyCrust %s - The Flakiest Python Shell' % VERSION
83 intro
+= '\nSponsored by Orbtech - ' + \
84 'Your source for Python programming expertise.'
85 self
.CreateStatusBar()
86 self
.SetStatusText(intro
.replace('\n', ', '))
88 self
.SetIcon(images
.getPyCrustIcon())
89 self
.shell
= Shell(parent
=self
, id=-1, introText
=intro
,
90 locals=locals, InterpClass
=InterpClass
,
92 # Override the shell so that status messages go to the status bar.
93 self
.shell
.setStatusText
= self
.SetStatusText
95 wx
.EVT_CLOSE(self
, self
.OnCloseWindow
)
97 def OnCloseWindow(self
, event
):
98 """Event handler for closing."""
99 # This isn't working the way I want, but I'll leave it for now.
100 if self
.shell
.waiting
:
108 """Simplified interface to all shell-related functionality.
110 This is a semi-transparent facade, in that all attributes of other
111 are accessible, even though only some are visible to the user."""
113 name
= 'PyCrust Shell Interface'
114 revision
= __revision__
116 def __init__(self
, other
):
117 """Create a ShellFacade instance."""
133 for method
in methods
:
134 self
.__dict
__[method
] = getattr(other
, method
)
140 Home Go to the beginning of the command or line.
141 Shift+Home Select to the beginning of the command or line.
142 Shift+End Select to the end of the line.
143 End Go to the end of the line.
144 Ctrl+C Copy selected text, removing prompts.
145 Ctrl+Shift+C Copy selected text, retaining prompts.
146 Ctrl+X Cut selected text.
147 Ctrl+V Paste from clipboard.
148 Ctrl+Shift+V Paste and run multiple commands from clipboard.
149 Ctrl+Up Arrow Retrieve Previous History item.
150 Alt+P Retrieve Previous History item.
151 Ctrl+Down Arrow Retrieve Next History item.
152 Alt+N Retrieve Next History item.
153 Shift+Up Arrow Insert Previous History item.
154 Shift+Down Arrow Insert Next History item.
155 F8 Command-completion of History item.
156 (Type a few characters of a previous command and press F8.)
157 Ctrl+Enter Insert new line into multiline command.
158 Ctrl+] Increase font size.
159 Ctrl+[ Decrease font size.
160 Ctrl+= Default font size.
164 """Display some useful information about how to use the shell."""
165 self
.write(self
.helpText
)
167 def __getattr__(self
, name
):
168 if hasattr(self
.other
, name
):
169 return getattr(self
.other
, name
)
171 raise AttributeError, name
173 def __setattr__(self
, name
, value
):
174 if self
.__dict
__.has_key(name
):
175 self
.__dict
__[name
] = value
176 elif hasattr(self
.other
, name
):
177 setattr(self
.other
, name
, value
)
179 raise AttributeError, name
181 def _getAttributeNames(self
):
182 """Return list of magic attributes to extend introspection."""
183 list = ['autoCallTip',
185 'autoCompleteCaseInsensitive',
186 'autoCompleteIncludeDouble',
187 'autoCompleteIncludeMagic',
188 'autoCompleteIncludeSingle',
194 class Shell(stc
.wxStyledTextCtrl
):
195 """PyCrust Shell based on wxStyledTextCtrl."""
197 name
= 'PyCrust Shell'
198 revision
= __revision__
200 def __init__(self
, parent
, id=-1, pos
=wx
.wxDefaultPosition
,
201 size
=wx
.wxDefaultSize
, style
=wx
.wxCLIP_CHILDREN
,
202 introText
='', locals=None, InterpClass
=None, *args
, **kwds
):
203 """Create a PyCrust Shell instance."""
204 stc
.wxStyledTextCtrl
.__init
__(self
, parent
, id, pos
, size
, style
)
207 # Grab these so they can be restored by self.redirect* methods.
208 self
.stdin
= sys
.stdin
209 self
.stdout
= sys
.stdout
210 self
.stderr
= sys
.stderr
211 # Add the current working directory "." to the search path.
212 sys
.path
.insert(0, os
.curdir
)
213 # Import a default interpreter class if one isn't provided.
214 if InterpClass
== None:
215 from interpreter
import Interpreter
217 Interpreter
= InterpClass
218 # Create a replacement for stdin.
219 self
.reader
= PseudoFileIn(self
.readline
, self
.readlines
)
220 self
.reader
.input = ''
221 self
.reader
.isreading
= 0
222 # Set up the interpreter.
223 self
.interp
= Interpreter(locals=locals,
224 rawin
=self
.raw_input,
226 stdout
=PseudoFileOut(self
.writeOut
),
227 stderr
=PseudoFileErr(self
.writeErr
),
229 # Find out for which keycodes the interpreter will autocomplete.
230 self
.autoCompleteKeys
= self
.interp
.getAutoCompleteKeys()
231 # Keep track of the last non-continuation prompt positions.
232 self
.promptPosStart
= 0
233 self
.promptPosEnd
= 0
234 # Keep track of multi-line commands.
236 # Create the command history. Commands are added into the
237 # front of the list (ie. at index 0) as they are entered.
238 # self.historyIndex is the current position in the history; it
239 # gets incremented as you retrieve the previous command,
240 # decremented as you retrieve the next, and reset when you hit
241 # Enter. self.historyIndex == -1 means you're on the current
242 # command, not in the history.
244 self
.historyIndex
= -1
245 # Assign handlers for keyboard events.
246 wx
.EVT_KEY_DOWN(self
, self
.OnKeyDown
)
247 wx
.EVT_CHAR(self
, self
.OnChar
)
248 # Assign handlers for wxSTC events.
249 stc
.EVT_STC_UPDATEUI(self
, id, self
.OnUpdateUI
)
250 # Assign handler for idle time.
252 wx
.EVT_IDLE(self
, self
.OnIdle
)
253 dispatcher
.connect(receiver
=self
.fontsizer
, signal
='FontIncrease')
254 dispatcher
.connect(receiver
=self
.fontsizer
, signal
='FontDecrease')
255 dispatcher
.connect(receiver
=self
.fontsizer
, signal
='FontDefault')
256 # Configure various defaults and user preferences.
258 # Display the introductory banner information.
259 self
.showIntro(introText
)
260 # Assign some pseudo keywords to the interpreter's namespace.
261 self
.setBuiltinKeywords()
262 # Add 'shell' to the interpreter's local namespace.
264 # Do this last so the user has complete control over their
265 # environment. They can override anything they want.
266 self
.execStartupScript(self
.interp
.startupScript
)
267 wx
.wxCallAfter(self
.ScrollToLine
, 0)
269 def fontsizer(self
, signal
):
270 """Receiver for Font* signals."""
271 size
= self
.GetZoom()
272 if signal
== 'FontIncrease':
274 elif signal
== 'FontDecrease':
276 elif signal
== 'FontDefault':
285 """Configure shell based on user preferences."""
286 self
.SetMarginType(1, stc
.wxSTC_MARGIN_NUMBER
)
287 self
.SetMarginWidth(1, 40)
289 self
.SetLexer(stc
.wxSTC_LEX_PYTHON
)
290 self
.SetKeyWords(0, ' '.join(keyword
.kwlist
))
292 self
.setStyles(faces
)
293 self
.SetViewWhiteSpace(0)
296 # Do we want to automatically pop up command completion options?
297 self
.autoComplete
= 1
298 self
.autoCompleteIncludeMagic
= 1
299 self
.autoCompleteIncludeSingle
= 1
300 self
.autoCompleteIncludeDouble
= 1
301 self
.autoCompleteCaseInsensitive
= 1
302 self
.AutoCompSetIgnoreCase(self
.autoCompleteCaseInsensitive
)
303 self
.AutoCompSetAutoHide(False)
304 self
.AutoCompStops(' .,;:([)]}\'"\\<>%^&+-=*/|`')
305 # Do we want to automatically pop up command argument help?
307 self
.CallTipSetBackground(wx
.wxColour(255, 255, 232))
310 self
.SetEndAtLastLine(False)
311 except AttributeError:
314 def showIntro(self
, text
=''):
315 """Display introductory text in the shell."""
317 if not text
.endswith(os
.linesep
):
321 self
.write(self
.interp
.introText
)
322 except AttributeError:
325 def setBuiltinKeywords(self
):
326 """Create pseudo keywords as part of builtins.
328 This sets `close`, `exit` and `quit` to a helpful string.
331 __builtin__
.close
= __builtin__
.exit
= __builtin__
.quit
= \
332 'Click on the close button to leave the application.'
335 """Quit the application."""
337 # XXX Good enough for now but later we want to send a close event.
339 # In the close event handler we can make sure they want to
340 # quit. Other applications, like PythonCard, may choose to
341 # hide rather than quit so we should just post the event and
342 # let the surrounding app decide what it wants to do.
343 self
.write('Click on the close button to leave the application.')
345 def setLocalShell(self
):
346 """Add 'shell' to locals as reference to ShellFacade instance."""
347 self
.interp
.locals['shell'] = ShellFacade(other
=self
)
349 def execStartupScript(self
, startupScript
):
350 """Execute the user's PYTHONSTARTUP script if they have one."""
351 if startupScript
and os
.path
.isfile(startupScript
):
352 text
= 'Startup script executed: ' + startupScript
353 self
.push('print %r; execfile(%r)' % (text
, startupScript
))
357 def setStyles(self
, faces
):
358 """Configure font size, typeface and color for lexer."""
361 self
.StyleSetSpec(stc
.wxSTC_STYLE_DEFAULT
,
362 "face:%(mono)s,size:%(size)d,back:%(backcol)s" % \
368 self
.StyleSetSpec(stc
.wxSTC_STYLE_LINENUMBER
,
369 "back:#C0C0C0,face:%(mono)s,size:%(lnsize)d" % faces
)
370 self
.StyleSetSpec(stc
.wxSTC_STYLE_CONTROLCHAR
,
371 "face:%(mono)s" % faces
)
372 self
.StyleSetSpec(stc
.wxSTC_STYLE_BRACELIGHT
,
373 "fore:#0000FF,back:#FFFF88")
374 self
.StyleSetSpec(stc
.wxSTC_STYLE_BRACEBAD
,
375 "fore:#FF0000,back:#FFFF88")
378 self
.StyleSetSpec(stc
.wxSTC_P_DEFAULT
,
379 "face:%(mono)s" % faces
)
380 self
.StyleSetSpec(stc
.wxSTC_P_COMMENTLINE
,
381 "fore:#007F00,face:%(mono)s" % faces
)
382 self
.StyleSetSpec(stc
.wxSTC_P_NUMBER
,
384 self
.StyleSetSpec(stc
.wxSTC_P_STRING
,
385 "fore:#7F007F,face:%(mono)s" % faces
)
386 self
.StyleSetSpec(stc
.wxSTC_P_CHARACTER
,
387 "fore:#7F007F,face:%(mono)s" % faces
)
388 self
.StyleSetSpec(stc
.wxSTC_P_WORD
,
390 self
.StyleSetSpec(stc
.wxSTC_P_TRIPLE
,
392 self
.StyleSetSpec(stc
.wxSTC_P_TRIPLEDOUBLE
,
393 "fore:#000033,back:#FFFFE8")
394 self
.StyleSetSpec(stc
.wxSTC_P_CLASSNAME
,
396 self
.StyleSetSpec(stc
.wxSTC_P_DEFNAME
,
398 self
.StyleSetSpec(stc
.wxSTC_P_OPERATOR
,
400 self
.StyleSetSpec(stc
.wxSTC_P_IDENTIFIER
,
402 self
.StyleSetSpec(stc
.wxSTC_P_COMMENTBLOCK
,
404 self
.StyleSetSpec(stc
.wxSTC_P_STRINGEOL
,
405 "fore:#000000,face:%(mono)s,back:#E0C0E0,eolfilled" % faces
)
408 """Display information about PyCrust."""
413 Interpreter Revision: %s
417 (__author__
, VERSION
, self
.revision
, self
.interp
.revision
,
418 sys
.version
.split()[0], wx
.__version
__, sys
.platform
)
419 self
.write(text
.strip())
421 def OnIdle(self
, event
):
422 """Free the CPU to do other things."""
426 def OnUpdateUI(self
, event
):
427 """Check for matching braces."""
428 # If the auto-complete window is up let it do its thing.
429 if self
.AutoCompActive():
434 caretPos
= self
.GetCurrentPos()
436 charBefore
= self
.GetCharAt(caretPos
- 1)
437 styleBefore
= self
.GetStyleAt(caretPos
- 1)
440 if charBefore
and chr(charBefore
) in '[]{}()' \
441 and styleBefore
== stc
.wxSTC_P_OPERATOR
:
442 braceAtCaret
= caretPos
- 1
446 charAfter
= self
.GetCharAt(caretPos
)
447 styleAfter
= self
.GetStyleAt(caretPos
)
448 if charAfter
and chr(charAfter
) in '[]{}()' \
449 and styleAfter
== stc
.wxSTC_P_OPERATOR
:
450 braceAtCaret
= caretPos
452 if braceAtCaret
>= 0:
453 braceOpposite
= self
.BraceMatch(braceAtCaret
)
455 if braceAtCaret
!= -1 and braceOpposite
== -1:
456 self
.BraceBadLight(braceAtCaret
)
458 self
.BraceHighlight(braceAtCaret
, braceOpposite
)
460 def OnChar(self
, event
):
461 """Keypress event handler.
463 Only receives an event if OnKeyDown calls event.Skip() for the
464 corresponding event."""
466 # Prevent modification of previously submitted
467 # commands/responses.
468 if not self
.CanEdit():
470 key
= event
.KeyCode()
471 currpos
= self
.GetCurrentPos()
472 stoppos
= self
.promptPosEnd
473 # Return (Enter) needs to be ignored in this handler.
474 if key
== wx
.WXK_RETURN
:
476 elif key
in self
.autoCompleteKeys
:
477 # Usually the dot (period) key activates auto completion.
478 # Get the command between the prompt and the cursor. Add
479 # the autocomplete character to the end of the command.
480 if self
.AutoCompActive():
481 self
.AutoCompCancel()
482 command
= self
.GetTextRange(stoppos
, currpos
) + chr(key
)
484 if self
.autoComplete
:
485 self
.autoCompleteShow(command
)
486 elif key
== ord('('):
487 # The left paren activates a call tip and cancels an
488 # active auto completion.
489 if self
.AutoCompActive():
490 self
.AutoCompCancel()
491 # Get the command between the prompt and the cursor. Add
492 # the '(' to the end of the command.
493 self
.ReplaceSelection('')
494 command
= self
.GetTextRange(stoppos
, currpos
) + '('
497 self
.autoCallTipShow(command
)
499 # Allow the normal event handling to take place.
502 def OnKeyDown(self
, event
):
503 """Key down event handler."""
505 key
= event
.KeyCode()
506 # If the auto-complete window is up let it do its thing.
507 if self
.AutoCompActive():
510 # Prevent modification of previously submitted
511 # commands/responses.
512 controlDown
= event
.ControlDown()
513 altDown
= event
.AltDown()
514 shiftDown
= event
.ShiftDown()
515 currpos
= self
.GetCurrentPos()
516 endpos
= self
.GetTextLength()
517 selecting
= self
.GetSelectionStart() != self
.GetSelectionEnd()
518 # Return (Enter) is used to submit a command to the
520 if not controlDown
and key
== wx
.WXK_RETURN
:
521 if self
.CallTipActive():
524 # Ctrl+Return (Cntrl+Enter) is used to insert a line break.
525 elif controlDown
and key
== wx
.WXK_RETURN
:
526 if self
.CallTipActive():
528 if currpos
== endpos
:
531 self
.insertLineBreak()
532 # Let Ctrl-Alt-* get handled normally.
533 elif controlDown
and altDown
:
535 # Clear the current, unexecuted command.
536 elif key
== wx
.WXK_ESCAPE
:
537 if self
.CallTipActive():
541 # Increase font size.
542 elif controlDown
and key
in (ord(']'),):
543 dispatcher
.send(signal
='FontIncrease')
544 # Decrease font size.
545 elif controlDown
and key
in (ord('['),):
546 dispatcher
.send(signal
='FontDecrease')
548 elif controlDown
and key
in (ord('='),):
549 dispatcher
.send(signal
='FontDefault')
550 # Cut to the clipboard.
551 elif (controlDown
and key
in (ord('X'), ord('x'))) \
552 or (shiftDown
and key
== wx
.WXK_DELETE
):
554 # Copy to the clipboard.
555 elif controlDown
and not shiftDown \
556 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
558 # Copy to the clipboard, including prompts.
559 elif controlDown
and shiftDown \
560 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
561 self
.CopyWithPrompts()
562 # Copy to the clipboard, including prefixed prompts.
563 elif altDown
and not controlDown \
564 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
565 self
.CopyWithPromptsPrefixed()
566 # Home needs to be aware of the prompt.
567 elif key
== wx
.WXK_HOME
:
568 home
= self
.promptPosEnd
570 self
.SetCurrentPos(home
)
571 if not selecting
and not shiftDown
:
573 self
.EnsureCaretVisible()
577 # The following handlers modify text, so we need to see if
578 # there is a selection that includes text prior to the prompt.
580 # Don't modify a selection with text prior to the prompt.
581 elif selecting
and key
not in NAVKEYS
and not self
.CanEdit():
583 # Paste from the clipboard.
584 elif (controlDown
and not shiftDown
and key
in (ord('V'), ord('v'))) \
585 or (shiftDown
and not controlDown
and key
== wx
.WXK_INSERT
):
587 # Paste from the clipboard, run commands.
588 elif controlDown
and shiftDown
and key
in (ord('V'), ord('v')):
590 # Replace with the previous command from the history buffer.
591 elif (controlDown
and key
== wx
.WXK_UP
) \
592 or (altDown
and key
in (ord('P'), ord('p'))):
593 self
.OnHistoryReplace(step
=+1)
594 # Replace with the next command from the history buffer.
595 elif (controlDown
and key
== wx
.WXK_DOWN
) \
596 or (altDown
and key
in (ord('N'), ord('n'))):
597 self
.OnHistoryReplace(step
=-1)
598 # Insert the previous command from the history buffer.
599 elif (shiftDown
and key
== wx
.WXK_UP
) and self
.CanEdit():
600 self
.OnHistoryInsert(step
=+1)
601 # Insert the next command from the history buffer.
602 elif (shiftDown
and key
== wx
.WXK_DOWN
) and self
.CanEdit():
603 self
.OnHistoryInsert(step
=-1)
604 # Search up the history for the text in front of the cursor.
605 elif key
== wx
.WXK_F8
:
606 self
.OnHistorySearch()
607 # Don't backspace over the latest non-continuation prompt.
608 elif key
== wx
.WXK_BACK
:
609 if selecting
and self
.CanEdit():
611 elif currpos
> self
.promptPosEnd
:
613 # Only allow these keys after the latest prompt.
614 elif key
in (wx
.WXK_TAB
, wx
.WXK_DELETE
):
617 # Don't toggle between insert mode and overwrite mode.
618 elif key
== wx
.WXK_INSERT
:
620 # Don't allow line deletion.
621 elif controlDown
and key
in (ord('L'), ord('l')):
623 # Don't allow line transposition.
624 elif controlDown
and key
in (ord('T'), ord('t')):
626 # Basic navigation keys should work anywhere.
629 # Protect the readonly portion of the shell.
630 elif not self
.CanEdit():
635 def clearCommand(self
):
636 """Delete the current, unexecuted command."""
637 startpos
= self
.promptPosEnd
638 endpos
= self
.GetTextLength()
639 self
.SetSelection(startpos
, endpos
)
640 self
.ReplaceSelection('')
643 def OnHistoryReplace(self
, step
):
644 """Replace with the previous/next command from the history buffer."""
646 self
.replaceFromHistory(step
)
648 def replaceFromHistory(self
, step
):
649 """Replace selection with command from the history buffer."""
651 self
.ReplaceSelection('')
652 newindex
= self
.historyIndex
+ step
653 if -1 <= newindex
<= len(self
.history
):
654 self
.historyIndex
= newindex
655 if 0 <= newindex
<= len(self
.history
)-1:
656 command
= self
.history
[self
.historyIndex
]
657 command
= command
.replace('\n', os
.linesep
+ ps2
)
658 self
.ReplaceSelection(command
)
660 def OnHistoryInsert(self
, step
):
661 """Insert the previous/next command from the history buffer."""
662 if not self
.CanEdit():
664 startpos
= self
.GetCurrentPos()
665 self
.replaceFromHistory(step
)
666 endpos
= self
.GetCurrentPos()
667 self
.SetSelection(endpos
, startpos
)
669 def OnHistorySearch(self
):
670 """Search up the history buffer for the text in front of the cursor."""
671 if not self
.CanEdit():
673 startpos
= self
.GetCurrentPos()
674 # The text up to the cursor is what we search for.
675 numCharsAfterCursor
= self
.GetTextLength() - startpos
676 searchText
= self
.getCommand(rstrip
=0)
677 if numCharsAfterCursor
> 0:
678 searchText
= searchText
[:-numCharsAfterCursor
]
681 # Search upwards from the current history position and loop
682 # back to the beginning if we don't find anything.
683 if (self
.historyIndex
<= -1) \
684 or (self
.historyIndex
>= len(self
.history
)-2):
685 searchOrder
= range(len(self
.history
))
687 searchOrder
= range(self
.historyIndex
+1, len(self
.history
)) + \
688 range(self
.historyIndex
)
689 for i
in searchOrder
:
690 command
= self
.history
[i
]
691 if command
[:len(searchText
)] == searchText
:
692 # Replace the current selection with the one we found.
693 self
.ReplaceSelection(command
[len(searchText
):])
694 endpos
= self
.GetCurrentPos()
695 self
.SetSelection(endpos
, startpos
)
696 # We've now warped into middle of the history.
697 self
.historyIndex
= i
700 def setStatusText(self
, text
):
701 """Display status information."""
703 # This method will likely be replaced by the enclosing app to
704 # do something more interesting, like write to a status bar.
707 def insertLineBreak(self
):
708 """Insert a new line break."""
710 self
.write(os
.linesep
)
714 def processLine(self
):
715 """Process the line of text at which the user hit Enter."""
717 # The user hit ENTER and we need to decide what to do. They
718 # could be sitting on any line in the shell.
720 thepos
= self
.GetCurrentPos()
721 startpos
= self
.promptPosEnd
722 endpos
= self
.GetTextLength()
724 # If they hit RETURN inside the current command, execute the
727 self
.SetCurrentPos(endpos
)
729 command
= self
.GetTextRange(startpos
, endpos
)
730 lines
= command
.split(os
.linesep
+ ps2
)
731 lines
= [line
.rstrip() for line
in lines
]
732 command
= '\n'.join(lines
)
733 if self
.reader
.isreading
:
735 # Match the behavior of the standard Python shell
736 # when the user hits return without entering a
739 self
.reader
.input = command
740 self
.write(os
.linesep
)
743 # Or replace the current command with the other command.
745 # If the line contains a command (even an invalid one).
746 if self
.getCommand(rstrip
=0):
747 command
= self
.getMultilineCommand()
750 # Otherwise, put the cursor back where we started.
752 self
.SetCurrentPos(thepos
)
753 self
.SetAnchor(thepos
)
755 def getMultilineCommand(self
, rstrip
=1):
756 """Extract a multi-line command from the editor.
758 The command may not necessarily be valid Python syntax."""
759 # XXX Need to extract real prompts here. Need to keep track of
760 # the prompt every time a command is issued.
765 # This is a total hack job, but it works.
766 text
= self
.GetCurLine()[0]
767 line
= self
.GetCurrentLine()
768 while text
[:ps2size
] == ps2
and line
> 0:
771 text
= self
.GetCurLine()[0]
772 if text
[:ps1size
] == ps1
:
773 line
= self
.GetCurrentLine()
775 startpos
= self
.GetCurrentPos() + ps1size
778 while self
.GetCurLine()[0][:ps2size
] == ps2
:
781 stoppos
= self
.GetCurrentPos()
782 command
= self
.GetTextRange(startpos
, stoppos
)
783 command
= command
.replace(os
.linesep
+ ps2
, '\n')
784 command
= command
.rstrip()
785 command
= command
.replace('\n', os
.linesep
+ ps2
)
789 command
= command
.rstrip()
792 def getCommand(self
, text
=None, rstrip
=1):
793 """Extract a command from text which may include a shell prompt.
795 The command may not necessarily be valid Python syntax."""
797 text
= self
.GetCurLine()[0]
798 # Strip the prompt off the front leaving just the command.
799 command
= self
.lstripPrompt(text
)
801 command
= '' # Real commands have prompts.
803 command
= command
.rstrip()
806 def lstripPrompt(self
, text
):
807 """Return text without a leading prompt."""
812 # Strip the prompt off the front of text.
813 if text
[:ps1size
] == ps1
:
814 text
= text
[ps1size
:]
815 elif text
[:ps2size
] == ps2
:
816 text
= text
[ps2size
:]
819 def push(self
, command
):
820 """Send command to the interpreter for execution."""
821 self
.write(os
.linesep
)
822 busy
= wx
.wxBusyCursor()
824 self
.more
= self
.interp
.push(command
)
828 self
.addHistory(command
.rstrip())
831 def addHistory(self
, command
):
832 """Add command to the command history."""
833 # Reset the history position.
834 self
.historyIndex
= -1
835 # Insert this command into the history, unless it's a blank
836 # line or the same as the last command.
838 and (len(self
.history
) == 0 or command
!= self
.history
[0]):
839 self
.history
.insert(0, command
)
841 def write(self
, text
):
842 """Display text in the shell.
844 Replace line endings with OS-specific endings."""
845 text
= self
.fixLineEndings(text
)
847 self
.EnsureCaretVisible()
849 def fixLineEndings(self
, text
):
850 """Return text with line endings replaced by OS-specific endings."""
851 lines
= text
.split('\r\n')
852 for l
in range(len(lines
)):
853 chunks
= lines
[l
].split('\r')
854 for c
in range(len(chunks
)):
855 chunks
[c
] = os
.linesep
.join(chunks
[c
].split('\n'))
856 lines
[l
] = os
.linesep
.join(chunks
)
857 text
= os
.linesep
.join(lines
)
861 """Display proper prompt for the context: ps1, ps2 or ps3.
863 If this is a continuation line, autoindent as necessary."""
864 isreading
= self
.reader
.isreading
867 prompt
= str(sys
.ps3
)
869 prompt
= str(sys
.ps2
)
871 prompt
= str(sys
.ps1
)
872 pos
= self
.GetCurLine()[1]
877 self
.write(os
.linesep
)
879 self
.promptPosStart
= self
.GetCurrentPos()
883 self
.promptPosEnd
= self
.GetCurrentPos()
884 # Keep the undo feature from undoing previous responses.
885 self
.EmptyUndoBuffer()
886 # XXX Add some autoindent magic here if more.
888 self
.write(' '*4) # Temporary hack indentation.
889 self
.EnsureCaretVisible()
890 self
.ScrollToColumn(0)
893 """Replacement for stdin.readline()."""
899 while not reader
.input:
905 input = str(input) # In case of Unicode.
909 """Replacement for stdin.readlines()."""
911 while lines
[-1:] != ['\n']:
912 lines
.append(self
.readline())
915 def raw_input(self
, prompt
=''):
916 """Return string based on user input."""
919 return self
.readline()
921 def ask(self
, prompt
='Please enter your response:'):
922 """Get response from the user using a dialog box."""
923 dialog
= wx
.wxTextEntryDialog(None, prompt
,
924 'Input Dialog (Raw)', '')
926 if dialog
.ShowModal() == wx
.wxID_OK
:
927 text
= dialog
.GetValue()
934 """Halt execution pending a response from the user."""
935 self
.ask('Press enter to continue:')
938 """Delete all text from the shell."""
941 def run(self
, command
, prompt
=1, verbose
=1):
942 """Execute command as if it was typed in directly.
943 >>> shell.run('print "this"')
948 # Go to the very bottom of the text.
949 endpos
= self
.GetTextLength()
950 self
.SetCurrentPos(endpos
)
951 command
= command
.rstrip()
952 if prompt
: self
.prompt()
953 if verbose
: self
.write(command
)
956 def runfile(self
, filename
):
957 """Execute all commands in file as if they were typed into the
959 file = open(filename
)
962 for command
in file.readlines():
963 if command
[:6] == 'shell.':
964 # Run shell methods silently.
965 self
.run(command
, prompt
=0, verbose
=0)
967 self
.run(command
, prompt
=0, verbose
=1)
971 def autoCompleteShow(self
, command
):
972 """Display auto-completion popup list."""
973 list = self
.interp
.getAutoCompleteList(command
,
974 includeMagic
=self
.autoCompleteIncludeMagic
,
975 includeSingle
=self
.autoCompleteIncludeSingle
,
976 includeDouble
=self
.autoCompleteIncludeDouble
)
977 if list and len(list) < 2000:
978 options
= ' '.join(list)
980 self
.AutoCompShow(offset
, options
)
982 def autoCallTipShow(self
, command
):
983 """Display argument spec and docstring in a popup window."""
984 if self
.CallTipActive():
986 (name
, argspec
, tip
) = self
.interp
.getCallTip(command
)
988 startpos
= self
.GetCurrentPos()
989 self
.write(argspec
+ ')')
990 endpos
= self
.GetCurrentPos()
991 self
.SetSelection(endpos
, startpos
)
993 curpos
= self
.GetCurrentPos()
994 tippos
= curpos
- (len(name
) + 1)
995 fallback
= curpos
- self
.GetColumn(curpos
)
996 # In case there isn't enough room, only go back to the
998 tippos
= max(tippos
, fallback
)
999 dispatcher
.send(signal
='Shell.calltip', sender
=self
, calltip
=tip
)
1000 self
.CallTipShow(tippos
, tip
)
1002 def writeOut(self
, text
):
1003 """Replacement for stdout."""
1006 def writeErr(self
, text
):
1007 """Replacement for stderr."""
1010 def redirectStdin(self
, redirect
=1):
1011 """If redirect is true then sys.stdin will come from the shell."""
1013 sys
.stdin
= self
.reader
1015 sys
.stdin
= self
.stdin
1017 def redirectStdout(self
, redirect
=1):
1018 """If redirect is true then sys.stdout will go to the shell."""
1020 sys
.stdout
= PseudoFileOut(self
.writeOut
)
1022 sys
.stdout
= self
.stdout
1024 def redirectStderr(self
, redirect
=1):
1025 """If redirect is true then sys.stderr will go to the shell."""
1027 sys
.stderr
= PseudoFileErr(self
.writeErr
)
1029 sys
.stderr
= self
.stderr
1032 """Return true if text is selected and can be cut."""
1033 if self
.GetSelectionStart() != self
.GetSelectionEnd() \
1034 and self
.GetSelectionStart() >= self
.promptPosEnd \
1035 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
1041 """Return true if text is selected and can be copied."""
1042 return self
.GetSelectionStart() != self
.GetSelectionEnd()
1045 """Return true if a paste should succeed."""
1046 if self
.CanEdit() and stc
.wxStyledTextCtrl
.CanPaste(self
):
1052 """Return true if editing should succeed."""
1053 if self
.GetSelectionStart() != self
.GetSelectionEnd():
1054 if self
.GetSelectionStart() >= self
.promptPosEnd \
1055 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
1060 return self
.GetCurrentPos() >= self
.promptPosEnd
1063 """Remove selection and place it on the clipboard."""
1064 if self
.CanCut() and self
.CanCopy():
1065 if self
.AutoCompActive():
1066 self
.AutoCompCancel()
1067 if self
.CallTipActive():
1068 self
.CallTipCancel()
1070 self
.ReplaceSelection('')
1073 """Copy selection and place it on the clipboard."""
1077 command
= self
.GetSelectedText()
1078 command
= command
.replace(os
.linesep
+ ps2
, os
.linesep
)
1079 command
= command
.replace(os
.linesep
+ ps1
, os
.linesep
)
1080 command
= self
.lstripPrompt(text
=command
)
1081 data
= wx
.wxTextDataObject(command
)
1084 def CopyWithPrompts(self
):
1085 """Copy selection, including prompts, and place it on the clipboard."""
1087 command
= self
.GetSelectedText()
1088 data
= wx
.wxTextDataObject(command
)
1091 def CopyWithPromptsPrefixed(self
):
1092 """Copy selection, including prompts prefixed with four
1093 spaces, and place it on the clipboard."""
1095 command
= self
.GetSelectedText()
1097 command
= spaces
+ command
.replace(os
.linesep
,
1098 os
.linesep
+ spaces
)
1099 data
= wx
.wxTextDataObject(command
)
1102 def _clip(self
, data
):
1103 if wx
.wxTheClipboard
.Open():
1104 wx
.wxTheClipboard
.UsePrimarySelection(False)
1105 wx
.wxTheClipboard
.SetData(data
)
1106 wx
.wxTheClipboard
.Flush()
1107 wx
.wxTheClipboard
.Close()
1110 """Replace selection with clipboard contents."""
1111 if self
.CanPaste() and wx
.wxTheClipboard
.Open():
1113 if wx
.wxTheClipboard
.IsSupported(wx
.wxDataFormat(wx
.wxDF_TEXT
)):
1114 data
= wx
.wxTextDataObject()
1115 if wx
.wxTheClipboard
.GetData(data
):
1116 self
.ReplaceSelection('')
1117 command
= data
.GetText()
1118 command
= command
.rstrip()
1119 command
= self
.fixLineEndings(command
)
1120 command
= self
.lstripPrompt(text
=command
)
1121 command
= command
.replace(os
.linesep
+ ps2
, '\n')
1122 command
= command
.replace(os
.linesep
, '\n')
1123 command
= command
.replace('\n', os
.linesep
+ ps2
)
1125 wx
.wxTheClipboard
.Close()
1127 def PasteAndRun(self
):
1128 """Replace selection with clipboard contents, run commands."""
1129 if wx
.wxTheClipboard
.Open():
1132 if wx
.wxTheClipboard
.IsSupported(wx
.wxDataFormat(wx
.wxDF_TEXT
)):
1133 data
= wx
.wxTextDataObject()
1134 if wx
.wxTheClipboard
.GetData(data
):
1135 endpos
= self
.GetTextLength()
1136 self
.SetCurrentPos(endpos
)
1137 startpos
= self
.promptPosEnd
1138 self
.SetSelection(startpos
, endpos
)
1139 self
.ReplaceSelection('')
1140 text
= data
.GetText()
1141 text
= text
.lstrip()
1142 text
= self
.fixLineEndings(text
)
1143 text
= self
.lstripPrompt(text
)
1144 text
= text
.replace(os
.linesep
+ ps1
, '\n')
1145 text
= text
.replace(os
.linesep
+ ps2
, '\n')
1146 text
= text
.replace(os
.linesep
, '\n')
1147 lines
= text
.split('\n')
1151 if line
.strip() == ps2
.strip():
1152 # If we are pasting from something like a
1153 # web page that drops the trailing space
1154 # from the ps2 prompt of a blank line.
1156 if line
.strip() != '' and line
.lstrip() == line
:
1159 # Add the previous command to the list.
1160 commands
.append(command
)
1161 # Start a new command, which may be multiline.
1164 # Multiline command. Add to the command.
1167 commands
.append(command
)
1168 for command
in commands
:
1169 command
= command
.replace('\n', os
.linesep
+ ps2
)
1172 wx
.wxTheClipboard
.Close()
1174 def wrap(self
, wrap
=1):
1175 """Sets whether text is word wrapped."""
1177 self
.SetWrapMode(wrap
)
1178 except AttributeError:
1179 return 'Wrapping is not available in this version of PyCrust.'
1181 def zoom(self
, points
=0):
1182 """Set the zoom level.
1184 This number of points is added to the size of all fonts. It
1185 may be positive to magnify or negative to reduce."""
1186 self
.SetZoom(points
)