1 """Shell is an interactive text control in which a user types in
2 commands to be sent to the interpreter. This particular shell is
3 based on wxPython's wxStyledTextCtrl.
5 Sponsored by Orbtech - Your source for Python programming expertise."""
7 __author__
= "Patrick K. O'Brien <pobrien@orbtech.com>"
9 __revision__
= "$Revision$"[11:-2]
19 from buffer import Buffer
23 from pseudo
import PseudoFileIn
24 from pseudo
import PseudoFileOut
25 from pseudo
import PseudoFileErr
26 from version
import VERSION
28 sys
.ps3
= '<-- ' # Input prompt.
30 NAVKEYS
= (wx
.WXK_END
, wx
.WXK_LEFT
, wx
.WXK_RIGHT
,
31 wx
.WXK_UP
, wx
.WXK_DOWN
, wx
.WXK_PRIOR
, wx
.WXK_NEXT
)
34 class ShellFrame(frame
.Frame
, frame
.ShellFrameMixin
):
35 """Frame containing the shell component."""
38 revision
= __revision__
40 def __init__(self
, parent
=None, id=-1, title
='PyShell',
41 pos
=wx
.DefaultPosition
, size
=wx
.DefaultSize
,
42 style
=wx
.DEFAULT_FRAME_STYLE
, locals=None,
44 config
=None, dataDir
=None,
46 """Create ShellFrame instance."""
47 frame
.Frame
.__init
__(self
, parent
, id, title
, pos
, size
, style
)
48 frame
.ShellFrameMixin
.__init
__(self
, config
, dataDir
)
50 if size
== wx
.DefaultSize
:
51 self
.SetSize((750, 525))
53 intro
= 'PyShell %s - The Flakiest Python Shell' % VERSION
54 self
.SetStatusText(intro
.replace('\n', ', '))
55 self
.shell
= Shell(parent
=self
, id=-1, introText
=intro
,
56 locals=locals, InterpClass
=InterpClass
,
57 startupScript
=self
.startupScript
,
58 execStartupScript
=self
.execStartupScript
,
61 # Override the shell so that status messages go to the status bar.
62 self
.shell
.setStatusText
= self
.SetStatusText
68 def OnClose(self
, event
):
69 """Event handler for closing."""
70 # This isn't working the way I want, but I'll leave it for now.
71 if self
.shell
.waiting
:
79 def OnAbout(self
, event
):
80 """Display an About window."""
81 title
= 'About PyShell'
82 text
= 'PyShell %s\n\n' % VERSION
+ \
83 'Yet another Python shell, only flakier.\n\n' + \
84 'Half-baked by Patrick K. O\'Brien,\n' + \
85 'the other half is still in the oven.\n\n' + \
86 'Shell Revision: %s\n' % self
.shell
.revision
+ \
87 'Interpreter Revision: %s\n\n' % self
.shell
.interp
.revision
+ \
88 'Platform: %s\n' % sys
.platform
+ \
89 'Python Version: %s\n' % sys
.version
.split()[0] + \
90 'wxPython Version: %s\n' % wx
.VERSION_STRING
+ \
91 ('\t(%s)\n' % ", ".join(wx
.PlatformInfo
[1:]))
92 dialog
= wx
.MessageDialog(self
, text
, title
,
93 wx
.OK | wx
.ICON_INFORMATION
)
98 def OnHelp(self
, event
):
99 """Show a help dialog."""
100 frame
.ShellFrameMixin
.OnHelp(self
, event
)
103 def LoadSettings(self
):
104 if self
.config
is not None:
105 frame
.ShellFrameMixin
.LoadSettings(self
)
106 frame
.Frame
.LoadSettings(self
, self
.config
)
107 self
.shell
.LoadSettings(self
.config
)
109 def SaveSettings(self
, force
=False):
110 if self
.config
is not None:
111 frame
.ShellFrameMixin
.SaveSettings(self
)
112 if self
.autoSaveSettings
or force
:
113 frame
.Frame
.SaveSettings(self
, self
.config
)
114 self
.shell
.SaveSettings(self
.config
)
116 def DoSaveSettings(self
):
117 if self
.config
is not None:
118 self
.SaveSettings(force
=True)
126 Home Go to the beginning of the command or line.
127 Shift+Home Select to the beginning of the command or line.
128 Shift+End Select to the end of the line.
129 End Go to the end of the line.
130 Ctrl+C Copy selected text, removing prompts.
131 Ctrl+Shift+C Copy selected text, retaining prompts.
132 Alt+C Copy to the clipboard, including prefixed prompts.
133 Ctrl+X Cut selected text.
134 Ctrl+V Paste from clipboard.
135 Ctrl+Shift+V Paste and run multiple commands from clipboard.
136 Ctrl+Up Arrow Retrieve Previous History item.
137 Alt+P Retrieve Previous History item.
138 Ctrl+Down Arrow Retrieve Next History item.
139 Alt+N Retrieve Next History item.
140 Shift+Up Arrow Insert Previous History item.
141 Shift+Down Arrow Insert Next History item.
142 F8 Command-completion of History item.
143 (Type a few characters of a previous command and press F8.)
144 Ctrl+Enter Insert new line into multiline command.
145 Ctrl+] Increase font size.
146 Ctrl+[ Decrease font size.
147 Ctrl+= Default font size.
148 Ctrl-Space Show Auto Completion.
149 Ctrl-Alt-Space Show Call Tip.
150 Shift+Enter Complete Text from History.
153 Ctrl+H "hide" lines containing selection / "unhide"
154 F12 on/off "free-edit" mode
158 """Simplified interface to all shell-related functionality.
160 This is a semi-transparent facade, in that all attributes of other
161 are accessible, even though only some are visible to the user."""
163 name
= 'Shell Interface'
164 revision
= __revision__
166 def __init__(self
, other
):
167 """Create a ShellFacade instance."""
170 d
['helpText'] = HELP_TEXT
173 """Display some useful information about how to use the shell."""
174 self
.write(self
.helpText
)
176 def __getattr__(self
, name
):
177 if hasattr(self
.other
, name
):
178 return getattr(self
.other
, name
)
180 raise AttributeError, name
182 def __setattr__(self
, name
, value
):
183 if self
.__dict
__.has_key(name
):
184 self
.__dict
__[name
] = value
185 elif hasattr(self
.other
, name
):
186 setattr(self
.other
, name
, value
)
188 raise AttributeError, name
190 def _getAttributeNames(self
):
191 """Return list of magic attributes to extend introspection."""
197 'autoCompleteAutoHide',
198 'autoCompleteCaseInsensitive',
199 'autoCompleteIncludeDouble',
200 'autoCompleteIncludeMagic',
201 'autoCompleteIncludeSingle',
220 class Shell(editwindow
.EditWindow
):
221 """Shell based on StyledTextCtrl."""
224 revision
= __revision__
226 def __init__(self
, parent
, id=-1, pos
=wx
.DefaultPosition
,
227 size
=wx
.DefaultSize
, style
=wx
.CLIP_CHILDREN
,
228 introText
='', locals=None, InterpClass
=None,
229 startupScript
=None, execStartupScript
=True,
231 """Create Shell instance."""
232 editwindow
.EditWindow
.__init
__(self
, parent
, id, pos
, size
, style
)
236 locals = __main__
.__dict
__
238 # Grab these so they can be restored by self.redirect* methods.
239 self
.stdin
= sys
.stdin
240 self
.stdout
= sys
.stdout
241 self
.stderr
= sys
.stderr
243 # Import a default interpreter class if one isn't provided.
244 if InterpClass
== None:
245 from interpreter
import Interpreter
247 Interpreter
= InterpClass
249 # Create a replacement for stdin.
250 self
.reader
= PseudoFileIn(self
.readline
, self
.readlines
)
251 self
.reader
.input = ''
252 self
.reader
.isreading
= False
254 # Set up the interpreter.
255 self
.interp
= Interpreter(locals=locals,
256 rawin
=self
.raw_input,
258 stdout
=PseudoFileOut(self
.writeOut
),
259 stderr
=PseudoFileErr(self
.writeErr
),
263 self
.buffer = Buffer()
265 # Find out for which keycodes the interpreter will autocomplete.
266 self
.autoCompleteKeys
= self
.interp
.getAutoCompleteKeys()
268 # Keep track of the last non-continuation prompt positions.
269 self
.promptPosStart
= 0
270 self
.promptPosEnd
= 0
272 # Keep track of multi-line commands.
275 # Create the command history. Commands are added into the
276 # front of the list (ie. at index 0) as they are entered.
277 # self.historyIndex is the current position in the history; it
278 # gets incremented as you retrieve the previous command,
279 # decremented as you retrieve the next, and reset when you hit
280 # Enter. self.historyIndex == -1 means you're on the current
281 # command, not in the history.
283 self
.historyIndex
= -1
285 #seb add mode for "free edit"
287 self
.MarkerDefine(0,stc
.STC_MARK_ROUNDRECT
) # marker for hidden
290 # Assign handlers for keyboard events.
291 self
.Bind(wx
.EVT_CHAR
, self
.OnChar
)
292 self
.Bind(wx
.EVT_KEY_DOWN
, self
.OnKeyDown
)
294 # Assign handler for idle time.
296 self
.Bind(wx
.EVT_IDLE
, self
.OnIdle
)
298 # Display the introductory banner information.
299 self
.showIntro(introText
)
301 # Assign some pseudo keywords to the interpreter's namespace.
302 self
.setBuiltinKeywords()
304 # Add 'shell' to the interpreter's local namespace.
307 ## NOTE: See note at bottom of this file...
308 ## #seb: File drag and drop
309 ## self.SetDropTarget( FileDropTarget(self) )
311 # Do this last so the user has complete control over their
312 # environment. They can override anything they want.
313 if execStartupScript
:
314 if startupScript
is None:
315 startupScript
= os
.environ
.get('PYTHONSTARTUP')
316 self
.execStartupScript(startupScript
)
320 wx
.CallAfter(self
.ScrollToLine
, 0)
323 def clearHistory(self
):
325 self
.historyIndex
= -1
326 dispatcher
.send(signal
="Shell.clearHistory")
333 """Set focus to the shell."""
336 def OnIdle(self
, event
):
337 """Free the CPU to do other things."""
342 def showIntro(self
, text
=''):
343 """Display introductory text in the shell."""
345 if not text
.endswith(os
.linesep
):
349 self
.write(self
.interp
.introText
)
350 except AttributeError:
353 def setBuiltinKeywords(self
):
354 """Create pseudo keywords as part of builtins.
356 This sets `close`, `exit` and `quit` to a helpful string.
359 __builtin__
.close
= __builtin__
.exit
= __builtin__
.quit
= \
360 'Click on the close button to leave the application.'
364 """Quit the application."""
365 # XXX Good enough for now but later we want to send a close event.
366 # In the close event handler we can make sure they want to
367 # quit. Other applications, like PythonCard, may choose to
368 # hide rather than quit so we should just post the event and
369 # let the surrounding app decide what it wants to do.
370 self
.write('Click on the close button to leave the application.')
373 def setLocalShell(self
):
374 """Add 'shell' to locals as reference to ShellFacade instance."""
375 self
.interp
.locals['shell'] = ShellFacade(other
=self
)
378 def execStartupScript(self
, startupScript
):
379 """Execute the user's PYTHONSTARTUP script if they have one."""
380 if startupScript
and os
.path
.isfile(startupScript
):
381 text
= 'Startup script executed: ' + startupScript
382 self
.push('print %r; execfile(%r)' % (text
, startupScript
))
383 self
.interp
.startupScript
= startupScript
389 """Display information about Py."""
393 Py Shell Revision: %s
394 Py Interpreter Revision: %s
397 wxPython PlatformInfo: %s
399 (__author__
, VERSION
, self
.revision
, self
.interp
.revision
,
400 sys
.version
.split()[0], wx
.VERSION_STRING
, str(wx
.PlatformInfo
),
402 self
.write(text
.strip())
405 def OnChar(self
, event
):
406 """Keypress event handler.
408 Only receives an event if OnKeyDown calls event.Skip() for the
409 corresponding event."""
415 # Prevent modification of previously submitted
416 # commands/responses.
417 if not self
.CanEdit():
419 key
= event
.KeyCode()
420 currpos
= self
.GetCurrentPos()
421 stoppos
= self
.promptPosEnd
422 # Return (Enter) needs to be ignored in this handler.
423 if key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
425 elif key
in self
.autoCompleteKeys
:
426 # Usually the dot (period) key activates auto completion.
427 # Get the command between the prompt and the cursor. Add
428 # the autocomplete character to the end of the command.
429 if self
.AutoCompActive():
430 self
.AutoCompCancel()
431 command
= self
.GetTextRange(stoppos
, currpos
) + chr(key
)
433 if self
.autoComplete
:
434 self
.autoCompleteShow(command
)
435 elif key
== ord('('):
436 # The left paren activates a call tip and cancels an
437 # active auto completion.
438 if self
.AutoCompActive():
439 self
.AutoCompCancel()
440 # Get the command between the prompt and the cursor. Add
441 # the '(' to the end of the command.
442 self
.ReplaceSelection('')
443 command
= self
.GetTextRange(stoppos
, currpos
) + '('
445 self
.autoCallTipShow(command
, self
.GetCurrentPos() == self
.GetTextLength())
447 # Allow the normal event handling to take place.
451 def OnKeyDown(self
, event
):
452 """Key down event handler."""
454 key
= event
.KeyCode()
455 # If the auto-complete window is up let it do its thing.
456 if self
.AutoCompActive():
460 # Prevent modification of previously submitted
461 # commands/responses.
462 controlDown
= event
.ControlDown()
463 altDown
= event
.AltDown()
464 shiftDown
= event
.ShiftDown()
465 currpos
= self
.GetCurrentPos()
466 endpos
= self
.GetTextLength()
467 selecting
= self
.GetSelectionStart() != self
.GetSelectionEnd()
469 if controlDown
and shiftDown
and key
in (ord('F'), ord('f')):
470 li
= self
.GetCurrentLine()
471 m
= self
.MarkerGet(li
)
473 startP
= self
.PositionFromLine(li
)
474 self
.MarkerDelete(li
, 0)
475 maxli
= self
.GetLineCount()
476 li
+= 1 # li stayed visible as header-line
478 while li
<maxli
and self
.GetLineVisible(li
) == 0:
480 endP
= self
.GetLineEndPosition(li
-1)
481 self
.ShowLines(li0
, li
-1)
482 self
.SetSelection( startP
, endP
) # select reappearing text to allow "hide again"
484 startP
,endP
= self
.GetSelection()
486 startL
,endL
= self
.LineFromPosition(startP
), self
.LineFromPosition(endP
)
488 if endL
== self
.LineFromPosition(self
.promptPosEnd
): # never hide last prompt
491 m
= self
.MarkerGet(startL
)
492 self
.MarkerAdd(startL
, 0)
493 self
.HideLines(startL
+1,endL
)
494 self
.SetCurrentPos( startP
) # to ensure caret stays visible !
496 if key
== wx
.WXK_F12
: #seb
498 # self.promptPosStart not used anyway - or ?
499 self
.promptPosEnd
= self
.PositionFromLine( self
.GetLineCount()-1 ) + len(str(sys
.ps1
))
500 self
.GotoLine(self
.GetLineCount())
501 self
.GotoPos(self
.promptPosEnd
)
502 self
.prompt() #make sure we have a prompt
503 self
.SetCaretForeground("black")
504 self
.SetCaretWidth(1) #default
505 self
.SetCaretPeriod(500) #default
507 self
.SetCaretForeground("red")
508 self
.SetCaretWidth(4)
509 self
.SetCaretPeriod(0) #steady
511 self
.noteMode
= not self
.noteMode
517 # Return (Enter) is used to submit a command to the
519 if (not controlDown
and not shiftDown
and not altDown
) and key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
520 if self
.CallTipActive():
524 # Complete Text (from already typed words)
525 elif shiftDown
and key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
526 self
.OnShowCompHistory()
528 # Ctrl+Return (Ctrl+Enter) is used to insert a line break.
529 elif controlDown
and key
in [wx
.WXK_RETURN
, wx
.WXK_NUMPAD_ENTER
]:
530 if self
.CallTipActive():
532 if currpos
== endpos
:
535 self
.insertLineBreak()
537 # Let Ctrl-Alt-* get handled normally.
538 elif controlDown
and altDown
:
541 # Clear the current, unexecuted command.
542 elif key
== wx
.WXK_ESCAPE
:
543 if self
.CallTipActive():
548 # Clear the current command
549 elif key
== wx
.WXK_BACK
and controlDown
and shiftDown
:
552 # Increase font size.
553 elif controlDown
and key
in (ord(']'), wx
.WXK_NUMPAD_ADD
):
554 dispatcher
.send(signal
='FontIncrease')
556 # Decrease font size.
557 elif controlDown
and key
in (ord('['), wx
.WXK_NUMPAD_SUBTRACT
):
558 dispatcher
.send(signal
='FontDecrease')
561 elif controlDown
and key
in (ord('='), wx
.WXK_NUMPAD_DIVIDE
):
562 dispatcher
.send(signal
='FontDefault')
564 # Cut to the clipboard.
565 elif (controlDown
and key
in (ord('X'), ord('x'))) \
566 or (shiftDown
and key
== wx
.WXK_DELETE
):
569 # Copy to the clipboard.
570 elif controlDown
and not shiftDown \
571 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
574 # Copy to the clipboard, including prompts.
575 elif controlDown
and shiftDown \
576 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
577 self
.CopyWithPrompts()
579 # Copy to the clipboard, including prefixed prompts.
580 elif altDown
and not controlDown \
581 and key
in (ord('C'), ord('c'), wx
.WXK_INSERT
):
582 self
.CopyWithPromptsPrefixed()
584 # Home needs to be aware of the prompt.
585 elif key
== wx
.WXK_HOME
:
586 home
= self
.promptPosEnd
588 self
.SetCurrentPos(home
)
589 if not selecting
and not shiftDown
:
591 self
.EnsureCaretVisible()
596 # The following handlers modify text, so we need to see if
597 # there is a selection that includes text prior to the prompt.
599 # Don't modify a selection with text prior to the prompt.
600 elif selecting
and key
not in NAVKEYS
and not self
.CanEdit():
603 # Paste from the clipboard.
604 elif (controlDown
and not shiftDown
and key
in (ord('V'), ord('v'))) \
605 or (shiftDown
and not controlDown
and key
== wx
.WXK_INSERT
):
608 # manually invoke AutoComplete and Calltips
609 elif controlDown
and key
== wx
.WXK_SPACE
:
610 self
.OnCallTipAutoCompleteManually(shiftDown
)
612 # Paste from the clipboard, run commands.
613 elif controlDown
and shiftDown
and key
in (ord('V'), ord('v')):
616 # Replace with the previous command from the history buffer.
617 elif (controlDown
and key
== wx
.WXK_UP
) \
618 or (altDown
and key
in (ord('P'), ord('p'))):
619 self
.OnHistoryReplace(step
=+1)
621 # Replace with the next command from the history buffer.
622 elif (controlDown
and key
== wx
.WXK_DOWN
) \
623 or (altDown
and key
in (ord('N'), ord('n'))):
624 self
.OnHistoryReplace(step
=-1)
626 # Insert the previous command from the history buffer.
627 elif (shiftDown
and key
== wx
.WXK_UP
) and self
.CanEdit():
628 self
.OnHistoryInsert(step
=+1)
630 # Insert the next command from the history buffer.
631 elif (shiftDown
and key
== wx
.WXK_DOWN
) and self
.CanEdit():
632 self
.OnHistoryInsert(step
=-1)
634 # Search up the history for the text in front of the cursor.
635 elif key
== wx
.WXK_F8
:
636 self
.OnHistorySearch()
638 # Don't backspace over the latest non-continuation prompt.
639 elif key
== wx
.WXK_BACK
:
640 if selecting
and self
.CanEdit():
642 elif currpos
> self
.promptPosEnd
:
645 # Only allow these keys after the latest prompt.
646 elif key
in (wx
.WXK_TAB
, wx
.WXK_DELETE
):
650 # Don't toggle between insert mode and overwrite mode.
651 elif key
== wx
.WXK_INSERT
:
654 # Don't allow line deletion.
655 elif controlDown
and key
in (ord('L'), ord('l')):
658 # Don't allow line transposition.
659 elif controlDown
and key
in (ord('T'), ord('t')):
662 # Basic navigation keys should work anywhere.
666 # Protect the readonly portion of the shell.
667 elif not self
.CanEdit():
674 def OnShowCompHistory(self
):
675 """Show possible autocompletion Words from already typed words."""
678 his
= self
.history
[:]
680 #put together in one string
681 joined
= " ".join (his
)
684 #sort out only "good" words
685 newlist
= re
.split("[ \.\[\]=}(\)\,0-9\"]", joined
)
687 #length > 1 (mix out "trash")
693 #unique (no duplicate words
694 #oneliner from german python forum => unique list
695 unlist
= [thlist
[i
] for i
in xrange(len(thlist
)) if thlist
[i
] not in thlist
[:i
]]
698 unlist
.sort(lambda a
, b
: cmp(a
.lower(), b
.lower()))
700 #this is more convenient, isn't it?
701 self
.AutoCompSetIgnoreCase(True)
703 #join again together in a string
704 stringlist
= " ".join(unlist
)
706 #pos von 0 noch ausrechnen
708 #how big is the offset?
709 cpos
= self
.GetCurrentPos() - 1
710 while chr (self
.GetCharAt (cpos
)).isalnum():
713 #the most important part
714 self
.AutoCompShow(self
.GetCurrentPos() - cpos
-1, stringlist
)
717 def clearCommand(self
):
718 """Delete the current, unexecuted command."""
719 startpos
= self
.promptPosEnd
720 endpos
= self
.GetTextLength()
721 self
.SetSelection(startpos
, endpos
)
722 self
.ReplaceSelection('')
725 def OnHistoryReplace(self
, step
):
726 """Replace with the previous/next command from the history buffer."""
728 self
.replaceFromHistory(step
)
730 def replaceFromHistory(self
, step
):
731 """Replace selection with command from the history buffer."""
733 self
.ReplaceSelection('')
734 newindex
= self
.historyIndex
+ step
735 if -1 <= newindex
<= len(self
.history
):
736 self
.historyIndex
= newindex
737 if 0 <= newindex
<= len(self
.history
)-1:
738 command
= self
.history
[self
.historyIndex
]
739 command
= command
.replace('\n', os
.linesep
+ ps2
)
740 self
.ReplaceSelection(command
)
742 def OnHistoryInsert(self
, step
):
743 """Insert the previous/next command from the history buffer."""
744 if not self
.CanEdit():
746 startpos
= self
.GetCurrentPos()
747 self
.replaceFromHistory(step
)
748 endpos
= self
.GetCurrentPos()
749 self
.SetSelection(endpos
, startpos
)
751 def OnHistorySearch(self
):
752 """Search up the history buffer for the text in front of the cursor."""
753 if not self
.CanEdit():
755 startpos
= self
.GetCurrentPos()
756 # The text up to the cursor is what we search for.
757 numCharsAfterCursor
= self
.GetTextLength() - startpos
758 searchText
= self
.getCommand(rstrip
=False)
759 if numCharsAfterCursor
> 0:
760 searchText
= searchText
[:-numCharsAfterCursor
]
763 # Search upwards from the current history position and loop
764 # back to the beginning if we don't find anything.
765 if (self
.historyIndex
<= -1) \
766 or (self
.historyIndex
>= len(self
.history
)-2):
767 searchOrder
= range(len(self
.history
))
769 searchOrder
= range(self
.historyIndex
+1, len(self
.history
)) + \
770 range(self
.historyIndex
)
771 for i
in searchOrder
:
772 command
= self
.history
[i
]
773 if command
[:len(searchText
)] == searchText
:
774 # Replace the current selection with the one we found.
775 self
.ReplaceSelection(command
[len(searchText
):])
776 endpos
= self
.GetCurrentPos()
777 self
.SetSelection(endpos
, startpos
)
778 # We've now warped into middle of the history.
779 self
.historyIndex
= i
782 def setStatusText(self
, text
):
783 """Display status information."""
785 # This method will likely be replaced by the enclosing app to
786 # do something more interesting, like write to a status bar.
789 def insertLineBreak(self
):
790 """Insert a new line break."""
792 self
.write(os
.linesep
)
796 def processLine(self
):
797 """Process the line of text at which the user hit Enter."""
799 # The user hit ENTER and we need to decide what to do. They
800 # could be sitting on any line in the shell.
802 thepos
= self
.GetCurrentPos()
803 startpos
= self
.promptPosEnd
804 endpos
= self
.GetTextLength()
806 # If they hit RETURN inside the current command, execute the
809 self
.SetCurrentPos(endpos
)
810 self
.interp
.more
= False
811 command
= self
.GetTextRange(startpos
, endpos
)
812 lines
= command
.split(os
.linesep
+ ps2
)
813 lines
= [line
.rstrip() for line
in lines
]
814 command
= '\n'.join(lines
)
815 if self
.reader
.isreading
:
817 # Match the behavior of the standard Python shell
818 # when the user hits return without entering a
821 self
.reader
.input = command
822 self
.write(os
.linesep
)
825 wx
.FutureCall(1, self
.EnsureCaretVisible
)
826 # Or replace the current command with the other command.
828 # If the line contains a command (even an invalid one).
829 if self
.getCommand(rstrip
=False):
830 command
= self
.getMultilineCommand()
833 # Otherwise, put the cursor back where we started.
835 self
.SetCurrentPos(thepos
)
836 self
.SetAnchor(thepos
)
838 def getMultilineCommand(self
, rstrip
=True):
839 """Extract a multi-line command from the editor.
841 The command may not necessarily be valid Python syntax."""
842 # XXX Need to extract real prompts here. Need to keep track of
843 # the prompt every time a command is issued.
848 # This is a total hack job, but it works.
849 text
= self
.GetCurLine()[0]
850 line
= self
.GetCurrentLine()
851 while text
[:ps2size
] == ps2
and line
> 0:
854 text
= self
.GetCurLine()[0]
855 if text
[:ps1size
] == ps1
:
856 line
= self
.GetCurrentLine()
858 startpos
= self
.GetCurrentPos() + ps1size
861 while self
.GetCurLine()[0][:ps2size
] == ps2
:
864 stoppos
= self
.GetCurrentPos()
865 command
= self
.GetTextRange(startpos
, stoppos
)
866 command
= command
.replace(os
.linesep
+ ps2
, '\n')
867 command
= command
.rstrip()
868 command
= command
.replace('\n', os
.linesep
+ ps2
)
872 command
= command
.rstrip()
875 def getCommand(self
, text
=None, rstrip
=True):
876 """Extract a command from text which may include a shell prompt.
878 The command may not necessarily be valid Python syntax."""
880 text
= self
.GetCurLine()[0]
881 # Strip the prompt off the front leaving just the command.
882 command
= self
.lstripPrompt(text
)
884 command
= '' # Real commands have prompts.
886 command
= command
.rstrip()
889 def lstripPrompt(self
, text
):
890 """Return text without a leading prompt."""
895 # Strip the prompt off the front of text.
896 if text
[:ps1size
] == ps1
:
897 text
= text
[ps1size
:]
898 elif text
[:ps2size
] == ps2
:
899 text
= text
[ps2size
:]
902 def push(self
, command
, silent
= False):
903 """Send command to the interpreter for execution."""
905 self
.write(os
.linesep
)
906 busy
= wx
.BusyCursor()
908 self
.more
= self
.interp
.push(command
)
912 self
.addHistory(command
.rstrip())
916 def addHistory(self
, command
):
917 """Add command to the command history."""
918 # Reset the history position.
919 self
.historyIndex
= -1
920 # Insert this command into the history, unless it's a blank
921 # line or the same as the last command.
923 and (len(self
.history
) == 0 or command
!= self
.history
[0]):
924 self
.history
.insert(0, command
)
925 dispatcher
.send(signal
="Shell.addHistory", command
=command
)
927 def write(self
, text
):
928 """Display text in the shell.
930 Replace line endings with OS-specific endings."""
931 text
= self
.fixLineEndings(text
)
933 self
.EnsureCaretVisible()
935 def fixLineEndings(self
, text
):
936 """Return text with line endings replaced by OS-specific endings."""
937 lines
= text
.split('\r\n')
938 for l
in range(len(lines
)):
939 chunks
= lines
[l
].split('\r')
940 for c
in range(len(chunks
)):
941 chunks
[c
] = os
.linesep
.join(chunks
[c
].split('\n'))
942 lines
[l
] = os
.linesep
.join(chunks
)
943 text
= os
.linesep
.join(lines
)
947 """Display proper prompt for the context: ps1, ps2 or ps3.
949 If this is a continuation line, autoindent as necessary."""
950 isreading
= self
.reader
.isreading
953 prompt
= str(sys
.ps3
)
955 prompt
= str(sys
.ps2
)
957 prompt
= str(sys
.ps1
)
958 pos
= self
.GetCurLine()[1]
963 self
.write(os
.linesep
)
965 self
.promptPosStart
= self
.GetCurrentPos()
969 self
.promptPosEnd
= self
.GetCurrentPos()
970 # Keep the undo feature from undoing previous responses.
971 self
.EmptyUndoBuffer()
972 # XXX Add some autoindent magic here if more.
974 self
.write(' '*4) # Temporary hack indentation.
975 self
.EnsureCaretVisible()
976 self
.ScrollToColumn(0)
979 """Replacement for stdin.readline()."""
982 reader
.isreading
= True
985 while not reader
.input:
990 reader
.isreading
= False
991 input = str(input) # In case of Unicode.
995 """Replacement for stdin.readlines()."""
997 while lines
[-1:] != ['\n']:
998 lines
.append(self
.readline())
1001 def raw_input(self
, prompt
=''):
1002 """Return string based on user input."""
1005 return self
.readline()
1007 def ask(self
, prompt
='Please enter your response:'):
1008 """Get response from the user using a dialog box."""
1009 dialog
= wx
.TextEntryDialog(None, prompt
,
1010 'Input Dialog (Raw)', '')
1012 if dialog
.ShowModal() == wx
.ID_OK
:
1013 text
= dialog
.GetValue()
1020 """Halt execution pending a response from the user."""
1021 self
.ask('Press enter to continue:')
1024 """Delete all text from the shell."""
1027 def run(self
, command
, prompt
=True, verbose
=True):
1028 """Execute command as if it was typed in directly.
1029 >>> shell.run('print "this"')
1034 # Go to the very bottom of the text.
1035 endpos
= self
.GetTextLength()
1036 self
.SetCurrentPos(endpos
)
1037 command
= command
.rstrip()
1038 if prompt
: self
.prompt()
1039 if verbose
: self
.write(command
)
1042 def runfile(self
, filename
):
1043 """Execute all commands in file as if they were typed into the
1045 file = open(filename
)
1048 for command
in file.readlines():
1049 if command
[:6] == 'shell.':
1050 # Run shell methods silently.
1051 self
.run(command
, prompt
=False, verbose
=False)
1053 self
.run(command
, prompt
=False, verbose
=True)
1057 def autoCompleteShow(self
, command
, offset
= 0):
1058 """Display auto-completion popup list."""
1059 self
.AutoCompSetAutoHide(self
.autoCompleteAutoHide
)
1060 self
.AutoCompSetIgnoreCase(self
.autoCompleteCaseInsensitive
)
1061 list = self
.interp
.getAutoCompleteList(command
,
1062 includeMagic
=self
.autoCompleteIncludeMagic
,
1063 includeSingle
=self
.autoCompleteIncludeSingle
,
1064 includeDouble
=self
.autoCompleteIncludeDouble
)
1066 options
= ' '.join(list)
1068 self
.AutoCompShow(offset
, options
)
1070 def autoCallTipShow(self
, command
, insertcalltip
= True, forceCallTip
= False):
1071 """Display argument spec and docstring in a popup window."""
1072 if self
.CallTipActive():
1073 self
.CallTipCancel()
1074 (name
, argspec
, tip
) = self
.interp
.getCallTip(command
)
1076 dispatcher
.send(signal
='Shell.calltip', sender
=self
, calltip
=tip
)
1077 if not self
.autoCallTip
and not forceCallTip
:
1079 if argspec
and insertcalltip
and self
.callTipInsert
:
1080 startpos
= self
.GetCurrentPos()
1081 self
.write(argspec
+ ')')
1082 endpos
= self
.GetCurrentPos()
1083 self
.SetSelection(endpos
, startpos
)
1085 curpos
= self
.GetCurrentPos()
1086 tippos
= curpos
- (len(name
) + 1)
1087 fallback
= curpos
- self
.GetColumn(curpos
)
1088 # In case there isn't enough room, only go back to the
1090 tippos
= max(tippos
, fallback
)
1091 self
.CallTipShow(tippos
, tip
)
1093 def OnCallTipAutoCompleteManually (self
, shiftDown
):
1094 """AutoComplete and Calltips manually."""
1095 if self
.AutoCompActive():
1096 self
.AutoCompCancel()
1097 currpos
= self
.GetCurrentPos()
1098 stoppos
= self
.promptPosEnd
1101 #go back until '.' is found
1103 while cpos
>= stoppos
:
1104 if self
.GetCharAt(cpos
) == ord ('.'):
1105 pointavailpos
= cpos
1109 #word from non whitespace until '.'
1110 if pointavailpos
!= -1:
1111 #look backward for first whitespace char
1112 textbehind
= self
.GetTextRange (pointavailpos
+ 1, currpos
)
1117 stoppos
= self
.promptPosEnd
1118 textbefore
= self
.GetTextRange(stoppos
, pointavailpos
)
1119 self
.autoCompleteShow(textbefore
, len (textbehind
))
1122 cpos
= pointavailpos
1124 while cpos
> stoppos
:
1125 if chr(self
.GetCharAt(cpos
)).isspace():
1131 ctips
= self
.GetTextRange (begpos
, currpos
)
1132 ctindex
= ctips
.find ('(')
1133 if ctindex
!= -1 and not self
.CallTipActive():
1134 #insert calltip, if current pos is '(', otherwise show it only
1135 self
.autoCallTipShow(ctips
[:ctindex
+ 1],
1136 self
.GetCharAt(currpos
- 1) == ord('(') and self
.GetCurrentPos() == self
.GetTextLength(),
1140 def writeOut(self
, text
):
1141 """Replacement for stdout."""
1144 def writeErr(self
, text
):
1145 """Replacement for stderr."""
1148 def redirectStdin(self
, redirect
=True):
1149 """If redirect is true then sys.stdin will come from the shell."""
1151 sys
.stdin
= self
.reader
1153 sys
.stdin
= self
.stdin
1155 def redirectStdout(self
, redirect
=True):
1156 """If redirect is true then sys.stdout will go to the shell."""
1158 sys
.stdout
= PseudoFileOut(self
.writeOut
)
1160 sys
.stdout
= self
.stdout
1162 def redirectStderr(self
, redirect
=True):
1163 """If redirect is true then sys.stderr will go to the shell."""
1165 sys
.stderr
= PseudoFileErr(self
.writeErr
)
1167 sys
.stderr
= self
.stderr
1170 """Return true if text is selected and can be cut."""
1171 if self
.GetSelectionStart() != self
.GetSelectionEnd() \
1172 and self
.GetSelectionStart() >= self
.promptPosEnd \
1173 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
1179 """Return true if a paste should succeed."""
1180 if self
.CanEdit() and editwindow
.EditWindow
.CanPaste(self
):
1186 """Return true if editing should succeed."""
1187 if self
.GetSelectionStart() != self
.GetSelectionEnd():
1188 if self
.GetSelectionStart() >= self
.promptPosEnd \
1189 and self
.GetSelectionEnd() >= self
.promptPosEnd
:
1194 return self
.GetCurrentPos() >= self
.promptPosEnd
1197 """Remove selection and place it on the clipboard."""
1198 if self
.CanCut() and self
.CanCopy():
1199 if self
.AutoCompActive():
1200 self
.AutoCompCancel()
1201 if self
.CallTipActive():
1202 self
.CallTipCancel()
1204 self
.ReplaceSelection('')
1207 """Copy selection and place it on the clipboard."""
1211 command
= self
.GetSelectedText()
1212 command
= command
.replace(os
.linesep
+ ps2
, os
.linesep
)
1213 command
= command
.replace(os
.linesep
+ ps1
, os
.linesep
)
1214 command
= self
.lstripPrompt(text
=command
)
1215 data
= wx
.TextDataObject(command
)
1218 def CopyWithPrompts(self
):
1219 """Copy selection, including prompts, and place it on the clipboard."""
1221 command
= self
.GetSelectedText()
1222 data
= wx
.TextDataObject(command
)
1225 def CopyWithPromptsPrefixed(self
):
1226 """Copy selection, including prompts prefixed with four
1227 spaces, and place it on the clipboard."""
1229 command
= self
.GetSelectedText()
1231 command
= spaces
+ command
.replace(os
.linesep
,
1232 os
.linesep
+ spaces
)
1233 data
= wx
.TextDataObject(command
)
1236 def _clip(self
, data
):
1237 if wx
.TheClipboard
.Open():
1238 wx
.TheClipboard
.UsePrimarySelection(False)
1239 wx
.TheClipboard
.SetData(data
)
1240 wx
.TheClipboard
.Flush()
1241 wx
.TheClipboard
.Close()
1244 """Replace selection with clipboard contents."""
1245 if self
.CanPaste() and wx
.TheClipboard
.Open():
1247 if wx
.TheClipboard
.IsSupported(wx
.DataFormat(wx
.DF_TEXT
)):
1248 data
= wx
.TextDataObject()
1249 if wx
.TheClipboard
.GetData(data
):
1250 self
.ReplaceSelection('')
1251 command
= data
.GetText()
1252 command
= command
.rstrip()
1253 command
= self
.fixLineEndings(command
)
1254 command
= self
.lstripPrompt(text
=command
)
1255 command
= command
.replace(os
.linesep
+ ps2
, '\n')
1256 command
= command
.replace(os
.linesep
, '\n')
1257 command
= command
.replace('\n', os
.linesep
+ ps2
)
1259 wx
.TheClipboard
.Close()
1262 def PasteAndRun(self
):
1263 """Replace selection with clipboard contents, run commands."""
1265 if wx
.TheClipboard
.Open():
1266 if wx
.TheClipboard
.IsSupported(wx
.DataFormat(wx
.DF_TEXT
)):
1267 data
= wx
.TextDataObject()
1268 if wx
.TheClipboard
.GetData(data
):
1269 text
= data
.GetText()
1270 wx
.TheClipboard
.Close()
1275 def Execute(self
, text
):
1276 """Replace selection with text and run commands."""
1279 endpos
= self
.GetTextLength()
1280 self
.SetCurrentPos(endpos
)
1281 startpos
= self
.promptPosEnd
1282 self
.SetSelection(startpos
, endpos
)
1283 self
.ReplaceSelection('')
1284 text
= text
.lstrip()
1285 text
= self
.fixLineEndings(text
)
1286 text
= self
.lstripPrompt(text
)
1287 text
= text
.replace(os
.linesep
+ ps1
, '\n')
1288 text
= text
.replace(os
.linesep
+ ps2
, '\n')
1289 text
= text
.replace(os
.linesep
, '\n')
1290 lines
= text
.split('\n')
1294 if line
.strip() == ps2
.strip():
1295 # If we are pasting from something like a
1296 # web page that drops the trailing space
1297 # from the ps2 prompt of a blank line.
1299 lstrip
= line
.lstrip()
1300 if line
.strip() != '' and lstrip
== line
and \
1301 lstrip
[:4] not in ['else','elif'] and \
1302 lstrip
[:6] != 'except':
1305 # Add the previous command to the list.
1306 commands
.append(command
)
1307 # Start a new command, which may be multiline.
1310 # Multiline command. Add to the command.
1313 commands
.append(command
)
1314 for command
in commands
:
1315 command
= command
.replace('\n', os
.linesep
+ ps2
)
1320 def wrap(self
, wrap
=True):
1321 """Sets whether text is word wrapped."""
1323 self
.SetWrapMode(wrap
)
1324 except AttributeError:
1325 return 'Wrapping is not available in this version.'
1327 def zoom(self
, points
=0):
1328 """Set the zoom level.
1330 This number of points is added to the size of all fonts. It
1331 may be positive to magnify or negative to reduce."""
1332 self
.SetZoom(points
)
1336 def LoadSettings(self
, config
):
1337 self
.autoComplete
= config
.ReadBool('Options/AutoComplete', True)
1338 self
.autoCompleteIncludeMagic
= config
.ReadBool('Options/AutoCompleteIncludeMagic', True)
1339 self
.autoCompleteIncludeSingle
= config
.ReadBool('Options/AutoCompleteIncludeSingle', True)
1340 self
.autoCompleteIncludeDouble
= config
.ReadBool('Options/AutoCompleteIncludeDouble', True)
1342 self
.autoCallTip
= config
.ReadBool('Options/AutoCallTip', True)
1343 self
.callTipInsert
= config
.ReadBool('Options/CallTipInsert', True)
1344 self
.SetWrapMode(config
.ReadBool('View/WrapMode', True))
1346 useAA
= config
.ReadBool('Options/UseAntiAliasing', self
.GetUseAntiAliasing())
1347 self
.SetUseAntiAliasing(useAA
)
1348 self
.lineNumbers
= config
.ReadBool('View/ShowLineNumbers', True)
1349 self
.setDisplayLineNumbers (self
.lineNumbers
)
1350 zoom
= config
.ReadInt('View/Zoom/Shell', -99)
1356 def SaveSettings(self
, config
):
1357 config
.WriteBool('Options/AutoComplete', self
.autoComplete
)
1358 config
.WriteBool('Options/AutoCompleteIncludeMagic', self
.autoCompleteIncludeMagic
)
1359 config
.WriteBool('Options/AutoCompleteIncludeSingle', self
.autoCompleteIncludeSingle
)
1360 config
.WriteBool('Options/AutoCompleteIncludeDouble', self
.autoCompleteIncludeDouble
)
1361 config
.WriteBool('Options/AutoCallTip', self
.autoCallTip
)
1362 config
.WriteBool('Options/CallTipInsert', self
.callTipInsert
)
1363 config
.WriteBool('Options/UseAntiAliasing', self
.GetUseAntiAliasing())
1364 config
.WriteBool('View/WrapMode', self
.GetWrapMode())
1365 config
.WriteBool('View/ShowLineNumbers', self
.lineNumbers
)
1366 config
.WriteInt('View/Zoom/Shell', self
.GetZoom())
1370 ## NOTE: The DnD of file names is disabled until I can figure out how
1371 ## best to still allow DnD of text.
1374 ## #seb : File drag and drop
1375 ## class FileDropTarget(wx.FileDropTarget):
1376 ## def __init__(self, obj):
1377 ## wx.FileDropTarget.__init__(self)
1379 ## def OnDropFiles(self, x, y, filenames):
1380 ## if len(filenames) == 1:
1381 ## txt = 'r\"%s\"' % filenames[0]
1384 ## for f in filenames:
1385 ## txt += 'r\"%s\" , ' % f
1387 ## self.obj.AppendText(txt)
1388 ## pos = self.obj.GetCurrentPos()
1389 ## self.obj.SetCurrentPos( pos )
1390 ## self.obj.SetSelection( pos, pos )
1394 ## class TextAndFileDropTarget(wx.DropTarget):
1395 ## def __init__(self, shell):
1396 ## wx.DropTarget.__init__(self)
1397 ## self.shell = shell
1398 ## self.compdo = wx.DataObjectComposite()
1399 ## self.textdo = wx.TextDataObject()
1400 ## self.filedo = wx.FileDataObject()
1401 ## self.compdo.Add(self.textdo)
1402 ## self.compdo.Add(self.filedo, True)
1404 ## self.SetDataObject(self.compdo)
1406 ## def OnDrop(self, x, y):
1409 ## def OnData(self, x, y, result):
1411 ## if self.textdo.GetTextLength() > 1:
1412 ## text = self.textdo.GetText()
1413 ## # *** Do somethign with the dragged text here...
1414 ## self.textdo.SetText('')
1416 ## filenames = str(self.filename.GetFilenames())
1417 ## if len(filenames) == 1:
1418 ## txt = 'r\"%s\"' % filenames[0]
1421 ## for f in filenames:
1422 ## txt += 'r\"%s\" , ' % f
1424 ## self.shell.AppendText(txt)
1425 ## pos = self.shell.GetCurrentPos()
1426 ## self.shell.SetCurrentPos( pos )
1427 ## self.shell.SetSelection( pos, pos )