]> git.saurik.com Git - wxWidgets.git/blobdiff - wxPython/wx/py/shell.py
wxTaskBarIcon works on wxGTK now too.
[wxWidgets.git] / wxPython / wx / py / shell.py
index e6422e31fcd4785b98477f8e1ccfb0e46e52f27b..a3ad8b7a72396197096c47078668f519bd043c20 100644 (file)
+"""Shell is an interactive text control in which a user types in
+commands to be sent to the interpreter.  This particular shell is
+based on wxPython's wxStyledTextCtrl.
 
 
-"""Renamer stub: provides a way to drop the wx prefix from wxPython objects."""
+Sponsored by Orbtech - Your source for Python programming expertise."""
 
 
+__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
 __cvsid__ = "$Id$"
 __revision__ = "$Revision$"[11:-2]
 
 __cvsid__ = "$Id$"
 __revision__ = "$Revision$"[11:-2]
 
-from wx import _rename
-from wxPython.py import shell
-_rename(globals(), shell.__dict__, modulename='py.shell')
-del shell
-del _rename
+#from wxd.d_wx import wx
+#from wxd.d_stc import stc
+
+import wx
+from wx import stc
+
+import keyword
+import os
+import sys
+import time
+
+from buffer import Buffer
+import dispatcher
+import editwindow
+import frame
+from pseudo import PseudoFileIn
+from pseudo import PseudoFileOut
+from pseudo import PseudoFileErr
+from version import VERSION
+
+try:
+    True
+except NameError:
+    True = 1==1
+    False = 1==0
+
+sys.ps3 = '<-- '  # Input prompt.
+
+NAVKEYS = (wx.WXK_END, wx.WXK_LEFT, wx.WXK_RIGHT,
+           wx.WXK_UP, wx.WXK_DOWN, wx.WXK_PRIOR, wx.WXK_NEXT)
+
+
+class ShellFrame(frame.Frame):
+    """Frame containing the shell component."""
+
+    name = 'Shell Frame'
+    revision = __revision__
+
+    def __init__(self, parent=None, id=-1, title='PyShell',
+                 pos=wx.DefaultPosition, size=wx.DefaultSize,
+                 style=wx.DEFAULT_FRAME_STYLE, locals=None,
+                 InterpClass=None, *args, **kwds):
+        """Create ShellFrame instance."""
+        frame.Frame.__init__(self, parent, id, title, pos, size, style)
+        intro = 'PyShell %s - The Flakiest Python Shell' % VERSION
+        intro += '\nSponsored by Orbtech - ' + \
+                 'Your source for Python programming expertise.'
+        self.SetStatusText(intro.replace('\n', ', '))
+        self.shell = Shell(parent=self, id=-1, introText=intro,
+                           locals=locals, InterpClass=InterpClass,
+                           *args, **kwds)
+        # Override the shell so that status messages go to the status bar.
+        self.shell.setStatusText = self.SetStatusText
+
+    def OnClose(self, event):
+        """Event handler for closing."""
+        # This isn't working the way I want, but I'll leave it for now.
+        if self.shell.waiting:
+            if event.CanVeto():
+                event.Veto(True)
+        else:
+            self.shell.destroy()
+            self.Destroy()
+
+    def OnAbout(self, event):
+        """Display an About window."""
+        title = 'About PyShell'
+        text = 'PyShell %s\n\n' % VERSION + \
+               'Yet another Python shell, only flakier.\n\n' + \
+               'Half-baked by Patrick K. O\'Brien,\n' + \
+               'the other half is still in the oven.\n\n' + \
+               'Shell Revision: %s\n' % self.shell.revision + \
+               'Interpreter Revision: %s\n\n' % self.shell.interp.revision + \
+               'Python Version: %s\n' % sys.version.split()[0] + \
+               'wxPython Version: %s\n' % wx.VERSION_STRING + \
+               'Platform: %s\n' % sys.platform
+        dialog = wx.MessageDialog(self, text, title,
+                                  wx.OK | wx.ICON_INFORMATION)
+        dialog.ShowModal()
+        dialog.Destroy()
+
+
+class ShellFacade:
+    """Simplified interface to all shell-related functionality.
+
+    This is a semi-transparent facade, in that all attributes of other
+    are accessible, even though only some are visible to the user."""
+
+    name = 'Shell Interface'
+    revision = __revision__
+
+    def __init__(self, other):
+        """Create a ShellFacade instance."""
+        d = self.__dict__
+        d['other'] = other
+        d['helpText'] = \
+"""
+* Key bindings:
+Home              Go to the beginning of the command or line.
+Shift+Home        Select to the beginning of the command or line.
+Shift+End         Select to the end of the line.
+End               Go to the end of the line.
+Ctrl+C            Copy selected text, removing prompts.
+Ctrl+Shift+C      Copy selected text, retaining prompts.
+Ctrl+X            Cut selected text.
+Ctrl+V            Paste from clipboard.
+Ctrl+Shift+V      Paste and run multiple commands from clipboard.
+Ctrl+Up Arrow     Retrieve Previous History item.
+Alt+P             Retrieve Previous History item.
+Ctrl+Down Arrow   Retrieve Next History item.
+Alt+N             Retrieve Next History item.
+Shift+Up Arrow    Insert Previous History item.
+Shift+Down Arrow  Insert Next History item.
+F8                Command-completion of History item.
+                  (Type a few characters of a previous command and press F8.)
+Ctrl+Enter        Insert new line into multiline command.
+Ctrl+]            Increase font size.
+Ctrl+[            Decrease font size.
+Ctrl+=            Default font size.
+"""
+
+    def help(self):
+        """Display some useful information about how to use the shell."""
+        self.write(self.helpText)
+
+    def __getattr__(self, name):
+        if hasattr(self.other, name):
+            return getattr(self.other, name)
+        else:
+            raise AttributeError, name
+
+    def __setattr__(self, name, value):
+        if self.__dict__.has_key(name):
+            self.__dict__[name] = value
+        elif hasattr(self.other, name):
+            setattr(self.other, name, value)
+        else:
+            raise AttributeError, name
+
+    def _getAttributeNames(self):
+        """Return list of magic attributes to extend introspection."""
+        list = [
+            'about',
+            'ask',
+            'autoCallTip',
+            'autoComplete',
+            'autoCompleteCaseInsensitive',
+            'autoCompleteIncludeDouble',
+            'autoCompleteIncludeMagic',
+            'autoCompleteIncludeSingle',
+            'clear',
+            'pause',
+            'prompt',
+            'quit',
+            'redirectStderr',
+            'redirectStdin',
+            'redirectStdout',
+            'run',
+            'runfile',
+            'wrap',
+            'zoom',
+            ]
+        list.sort()
+        return list
+
+
+class Shell(editwindow.EditWindow):
+    """Shell based on StyledTextCtrl."""
+
+    name = 'Shell'
+    revision = __revision__
+
+    def __init__(self, parent, id=-1, pos=wx.DefaultPosition,
+                 size=wx.DefaultSize, style=wx.CLIP_CHILDREN,
+                 introText='', locals=None, InterpClass=None, *args, **kwds):
+        """Create Shell instance."""
+        editwindow.EditWindow.__init__(self, parent, id, pos, size, style)
+        self.wrap()
+        if locals is None:
+            locals = {}
+        # Grab these so they can be restored by self.redirect* methods.
+        self.stdin = sys.stdin
+        self.stdout = sys.stdout
+        self.stderr = sys.stderr
+        # Import a default interpreter class if one isn't provided.
+        if InterpClass == None:
+            from interpreter import Interpreter
+        else:
+            Interpreter = InterpClass
+        # Create a replacement for stdin.
+        self.reader = PseudoFileIn(self.readline, self.readlines)
+        self.reader.input = ''
+        self.reader.isreading = False
+        # Set up the interpreter.
+        self.interp = Interpreter(locals=locals,
+                                  rawin=self.raw_input,
+                                  stdin=self.reader,
+                                  stdout=PseudoFileOut(self.writeOut),
+                                  stderr=PseudoFileErr(self.writeErr),
+                                  *args, **kwds)
+        # Set up the buffer.
+        self.buffer = Buffer()
+        # Find out for which keycodes the interpreter will autocomplete.
+        self.autoCompleteKeys = self.interp.getAutoCompleteKeys()
+        # Keep track of the last non-continuation prompt positions.
+        self.promptPosStart = 0
+        self.promptPosEnd = 0
+        # Keep track of multi-line commands.
+        self.more = False
+        # Create the command history.  Commands are added into the
+        # front of the list (ie. at index 0) as they are entered.
+        # self.historyIndex is the current position in the history; it
+        # gets incremented as you retrieve the previous command,
+        # decremented as you retrieve the next, and reset when you hit
+        # Enter.  self.historyIndex == -1 means you're on the current
+        # command, not in the history.
+        self.history = []
+        self.historyIndex = -1
+        # Assign handlers for keyboard events.
+        wx.EVT_CHAR(self, self.OnChar)
+        wx.EVT_KEY_DOWN(self, self.OnKeyDown)
+        # Assign handler for idle time.
+        self.waiting = False
+        wx.EVT_IDLE(self, self.OnIdle)
+        # Display the introductory banner information.
+        self.showIntro(introText)
+        # Assign some pseudo keywords to the interpreter's namespace.
+        self.setBuiltinKeywords()
+        # Add 'shell' to the interpreter's local namespace.
+        self.setLocalShell()
+        # Do this last so the user has complete control over their
+        # environment.  They can override anything they want.
+        self.execStartupScript(self.interp.startupScript)
+        wx.CallAfter(self.ScrollToLine, 0)
+
+    def destroy(self):
+        del self.interp
+
+    def setFocus(self):
+        """Set focus to the shell."""
+        self.SetFocus()
+
+    def OnIdle(self, event):
+        """Free the CPU to do other things."""
+        if self.waiting:
+            time.sleep(0.05)
+        event.Skip()
+
+    def showIntro(self, text=''):
+        """Display introductory text in the shell."""
+        if text:
+            if not text.endswith(os.linesep):
+                text += os.linesep
+            self.write(text)
+        try:
+            self.write(self.interp.introText)
+        except AttributeError:
+            pass
+
+    def setBuiltinKeywords(self):
+        """Create pseudo keywords as part of builtins.
+
+        This sets `close`, `exit` and `quit` to a helpful string.
+        """
+        import __builtin__
+        __builtin__.close = __builtin__.exit = __builtin__.quit = \
+            'Click on the close button to leave the application.'
+
+    def quit(self):
+        """Quit the application."""
+
+        # XXX Good enough for now but later we want to send a close event.
+
+        # In the close event handler we can make sure they want to
+        # quit.  Other applications, like PythonCard, may choose to
+        # hide rather than quit so we should just post the event and
+        # let the surrounding app decide what it wants to do.
+        self.write('Click on the close button to leave the application.')
+
+    def setLocalShell(self):
+        """Add 'shell' to locals as reference to ShellFacade instance."""
+        self.interp.locals['shell'] = ShellFacade(other=self)
+
+    def execStartupScript(self, startupScript):
+        """Execute the user's PYTHONSTARTUP script if they have one."""
+        if startupScript and os.path.isfile(startupScript):
+            text = 'Startup script executed: ' + startupScript
+            self.push('print %r; execfile(%r)' % (text, startupScript))
+        else:
+            self.push('')
+
+    def about(self):
+        """Display information about Py."""
+        text = """
+Author: %r
+Py Version: %s
+Py Shell Revision: %s
+Py Interpreter Revision: %s
+Python Version: %s
+wxPython Version: %s
+Platform: %s""" % \
+        (__author__, VERSION, self.revision, self.interp.revision,
+         sys.version.split()[0], wx.VERSION_STRING, sys.platform)
+        self.write(text.strip())
+
+    def OnChar(self, event):
+        """Keypress event handler.
+
+        Only receives an event if OnKeyDown calls event.Skip() for the
+        corresponding event."""
+
+        # Prevent modification of previously submitted
+        # commands/responses.
+        if not self.CanEdit():
+            return
+        key = event.KeyCode()
+        currpos = self.GetCurrentPos()
+        stoppos = self.promptPosEnd
+        # Return (Enter) needs to be ignored in this handler.
+        if key == wx.WXK_RETURN:
+            pass
+        elif key in self.autoCompleteKeys:
+            # Usually the dot (period) key activates auto completion.
+            # Get the command between the prompt and the cursor.  Add
+            # the autocomplete character to the end of the command.
+            if self.AutoCompActive():
+                self.AutoCompCancel()
+            command = self.GetTextRange(stoppos, currpos) + chr(key)
+            self.write(chr(key))
+            if self.autoComplete:
+                self.autoCompleteShow(command)
+        elif key == ord('('):
+            # The left paren activates a call tip and cancels an
+            # active auto completion.
+            if self.AutoCompActive():
+                self.AutoCompCancel()
+            # Get the command between the prompt and the cursor.  Add
+            # the '(' to the end of the command.
+            self.ReplaceSelection('')
+            command = self.GetTextRange(stoppos, currpos) + '('
+            self.write('(')
+            self.autoCallTipShow(command)
+        else:
+            # Allow the normal event handling to take place.
+            event.Skip()
+
+    def OnKeyDown(self, event):
+        """Key down event handler."""
+
+        key = event.KeyCode()
+        # If the auto-complete window is up let it do its thing.
+        if self.AutoCompActive():
+            event.Skip()
+            return
+        # Prevent modification of previously submitted
+        # commands/responses.
+        controlDown = event.ControlDown()
+        altDown = event.AltDown()
+        shiftDown = event.ShiftDown()
+        currpos = self.GetCurrentPos()
+        endpos = self.GetTextLength()
+        selecting = self.GetSelectionStart() != self.GetSelectionEnd()
+        # Return (Enter) is used to submit a command to the
+        # interpreter.
+        if not controlDown and key == wx.WXK_RETURN:
+            if self.CallTipActive():
+                self.CallTipCancel()
+            self.processLine()
+        # Ctrl+Return (Cntrl+Enter) is used to insert a line break.
+        elif controlDown and key == wx.WXK_RETURN:
+            if self.CallTipActive():
+                self.CallTipCancel()
+            if currpos == endpos:
+                self.processLine()
+            else:
+                self.insertLineBreak()
+        # Let Ctrl-Alt-* get handled normally.
+        elif controlDown and altDown:
+            event.Skip()
+        # Clear the current, unexecuted command.
+        elif key == wx.WXK_ESCAPE:
+            if self.CallTipActive():
+                event.Skip()
+            else:
+                self.clearCommand()
+        # Increase font size.
+        elif controlDown and key in (ord(']'),):
+            dispatcher.send(signal='FontIncrease')
+        # Decrease font size.
+        elif controlDown and key in (ord('['),):
+            dispatcher.send(signal='FontDecrease')
+        # Default font size.
+        elif controlDown and key in (ord('='),):
+            dispatcher.send(signal='FontDefault')
+        # Cut to the clipboard.
+        elif (controlDown and key in (ord('X'), ord('x'))) \
+        or (shiftDown and key == wx.WXK_DELETE):
+            self.Cut()
+        # Copy to the clipboard.
+        elif controlDown and not shiftDown \
+            and key in (ord('C'), ord('c'), wx.WXK_INSERT):
+            self.Copy()
+        # Copy to the clipboard, including prompts.
+        elif controlDown and shiftDown \
+            and key in (ord('C'), ord('c'), wx.WXK_INSERT):
+            self.CopyWithPrompts()
+        # Copy to the clipboard, including prefixed prompts.
+        elif altDown and not controlDown \
+            and key in (ord('C'), ord('c'), wx.WXK_INSERT):
+            self.CopyWithPromptsPrefixed()
+        # Home needs to be aware of the prompt.
+        elif key == wx.WXK_HOME:
+            home = self.promptPosEnd
+            if currpos > home:
+                self.SetCurrentPos(home)
+                if not selecting and not shiftDown:
+                    self.SetAnchor(home)
+                    self.EnsureCaretVisible()
+            else:
+                event.Skip()
+        #
+        # The following handlers modify text, so we need to see if
+        # there is a selection that includes text prior to the prompt.
+        #
+        # Don't modify a selection with text prior to the prompt.
+        elif selecting and key not in NAVKEYS and not self.CanEdit():
+            pass
+        # Paste from the clipboard.
+        elif (controlDown and not shiftDown and key in (ord('V'), ord('v'))) \
+                 or (shiftDown and not controlDown and key == wx.WXK_INSERT):
+            self.Paste()
+        # Paste from the clipboard, run commands.
+        elif controlDown and shiftDown and key in (ord('V'), ord('v')):
+            self.PasteAndRun()
+        # Replace with the previous command from the history buffer.
+        elif (controlDown and key == wx.WXK_UP) \
+                 or (altDown and key in (ord('P'), ord('p'))):
+            self.OnHistoryReplace(step=+1)
+        # Replace with the next command from the history buffer.
+        elif (controlDown and key == wx.WXK_DOWN) \
+                 or (altDown and key in (ord('N'), ord('n'))):
+            self.OnHistoryReplace(step=-1)
+        # Insert the previous command from the history buffer.
+        elif (shiftDown and key == wx.WXK_UP) and self.CanEdit():
+            self.OnHistoryInsert(step=+1)
+        # Insert the next command from the history buffer.
+        elif (shiftDown and key == wx.WXK_DOWN) and self.CanEdit():
+            self.OnHistoryInsert(step=-1)
+        # Search up the history for the text in front of the cursor.
+        elif key == wx.WXK_F8:
+            self.OnHistorySearch()
+        # Don't backspace over the latest non-continuation prompt.
+        elif key == wx.WXK_BACK:
+            if selecting and self.CanEdit():
+                event.Skip()
+            elif currpos > self.promptPosEnd:
+                event.Skip()
+        # Only allow these keys after the latest prompt.
+        elif key in (wx.WXK_TAB, wx.WXK_DELETE):
+            if self.CanEdit():
+                event.Skip()
+        # Don't toggle between insert mode and overwrite mode.
+        elif key == wx.WXK_INSERT:
+            pass
+        # Don't allow line deletion.
+        elif controlDown and key in (ord('L'), ord('l')):
+            pass
+        # Don't allow line transposition.
+        elif controlDown and key in (ord('T'), ord('t')):
+            pass
+        # Basic navigation keys should work anywhere.
+        elif key in NAVKEYS:
+            event.Skip()
+        # Protect the readonly portion of the shell.
+        elif not self.CanEdit():
+            pass
+        else:
+            event.Skip()
+
+    def clearCommand(self):
+        """Delete the current, unexecuted command."""
+        startpos = self.promptPosEnd
+        endpos = self.GetTextLength()
+        self.SetSelection(startpos, endpos)
+        self.ReplaceSelection('')
+        self.more = False
+
+    def OnHistoryReplace(self, step):
+        """Replace with the previous/next command from the history buffer."""
+        self.clearCommand()
+        self.replaceFromHistory(step)
+
+    def replaceFromHistory(self, step):
+        """Replace selection with command from the history buffer."""
+        ps2 = str(sys.ps2)
+        self.ReplaceSelection('')
+        newindex = self.historyIndex + step
+        if -1 <= newindex <= len(self.history):
+            self.historyIndex = newindex
+        if 0 <= newindex <= len(self.history)-1:
+            command = self.history[self.historyIndex]
+            command = command.replace('\n', os.linesep + ps2)
+            self.ReplaceSelection(command)
+
+    def OnHistoryInsert(self, step):
+        """Insert the previous/next command from the history buffer."""
+        if not self.CanEdit():
+            return
+        startpos = self.GetCurrentPos()
+        self.replaceFromHistory(step)
+        endpos = self.GetCurrentPos()
+        self.SetSelection(endpos, startpos)
+
+    def OnHistorySearch(self):
+        """Search up the history buffer for the text in front of the cursor."""
+        if not self.CanEdit():
+            return
+        startpos = self.GetCurrentPos()
+        # The text up to the cursor is what we search for.
+        numCharsAfterCursor = self.GetTextLength() - startpos
+        searchText = self.getCommand(rstrip=False)
+        if numCharsAfterCursor > 0:
+            searchText = searchText[:-numCharsAfterCursor]
+        if not searchText:
+            return
+        # Search upwards from the current history position and loop
+        # back to the beginning if we don't find anything.
+        if (self.historyIndex <= -1) \
+        or (self.historyIndex >= len(self.history)-2):
+            searchOrder = range(len(self.history))
+        else:
+            searchOrder = range(self.historyIndex+1, len(self.history)) + \
+                          range(self.historyIndex)
+        for i in searchOrder:
+            command = self.history[i]
+            if command[:len(searchText)] == searchText:
+                # Replace the current selection with the one we found.
+                self.ReplaceSelection(command[len(searchText):])
+                endpos = self.GetCurrentPos()
+                self.SetSelection(endpos, startpos)
+                # We've now warped into middle of the history.
+                self.historyIndex = i
+                break
+
+    def setStatusText(self, text):
+        """Display status information."""
+
+        # This method will likely be replaced by the enclosing app to
+        # do something more interesting, like write to a status bar.
+        print text
+
+    def insertLineBreak(self):
+        """Insert a new line break."""
+        if self.CanEdit():
+            self.write(os.linesep)
+            self.more = True
+            self.prompt()
+
+    def processLine(self):
+        """Process the line of text at which the user hit Enter."""
+
+        # The user hit ENTER and we need to decide what to do. They
+        # could be sitting on any line in the shell.
+
+        thepos = self.GetCurrentPos()
+        startpos = self.promptPosEnd
+        endpos = self.GetTextLength()
+        ps2 = str(sys.ps2)
+        # If they hit RETURN inside the current command, execute the
+        # command.
+        if self.CanEdit():
+            self.SetCurrentPos(endpos)
+            self.interp.more = False
+            command = self.GetTextRange(startpos, endpos)
+            lines = command.split(os.linesep + ps2)
+            lines = [line.rstrip() for line in lines]
+            command = '\n'.join(lines)
+            if self.reader.isreading:
+                if not command:
+                    # Match the behavior of the standard Python shell
+                    # when the user hits return without entering a
+                    # value.
+                    command = '\n'
+                self.reader.input = command
+                self.write(os.linesep)
+            else:
+                self.push(command)
+        # Or replace the current command with the other command.
+        else:
+            # If the line contains a command (even an invalid one).
+            if self.getCommand(rstrip=False):
+                command = self.getMultilineCommand()
+                self.clearCommand()
+                self.write(command)
+            # Otherwise, put the cursor back where we started.
+            else:
+                self.SetCurrentPos(thepos)
+                self.SetAnchor(thepos)
+
+    def getMultilineCommand(self, rstrip=True):
+        """Extract a multi-line command from the editor.
+
+        The command may not necessarily be valid Python syntax."""
+        # XXX Need to extract real prompts here. Need to keep track of
+        # the prompt every time a command is issued.
+        ps1 = str(sys.ps1)
+        ps1size = len(ps1)
+        ps2 = str(sys.ps2)
+        ps2size = len(ps2)
+        # This is a total hack job, but it works.
+        text = self.GetCurLine()[0]
+        line = self.GetCurrentLine()
+        while text[:ps2size] == ps2 and line > 0:
+            line -= 1
+            self.GotoLine(line)
+            text = self.GetCurLine()[0]
+        if text[:ps1size] == ps1:
+            line = self.GetCurrentLine()
+            self.GotoLine(line)
+            startpos = self.GetCurrentPos() + ps1size
+            line += 1
+            self.GotoLine(line)
+            while self.GetCurLine()[0][:ps2size] == ps2:
+                line += 1
+                self.GotoLine(line)
+            stoppos = self.GetCurrentPos()
+            command = self.GetTextRange(startpos, stoppos)
+            command = command.replace(os.linesep + ps2, '\n')
+            command = command.rstrip()
+            command = command.replace('\n', os.linesep + ps2)
+        else:
+            command = ''
+        if rstrip:
+            command = command.rstrip()
+        return command
+
+    def getCommand(self, text=None, rstrip=True):
+        """Extract a command from text which may include a shell prompt.
+
+        The command may not necessarily be valid Python syntax."""
+        if not text:
+            text = self.GetCurLine()[0]
+        # Strip the prompt off the front leaving just the command.
+        command = self.lstripPrompt(text)
+        if command == text:
+            command = ''  # Real commands have prompts.
+        if rstrip:
+            command = command.rstrip()
+        return command
+
+    def lstripPrompt(self, text):
+        """Return text without a leading prompt."""
+        ps1 = str(sys.ps1)
+        ps1size = len(ps1)
+        ps2 = str(sys.ps2)
+        ps2size = len(ps2)
+        # Strip the prompt off the front of text.
+        if text[:ps1size] == ps1:
+            text = text[ps1size:]
+        elif text[:ps2size] == ps2:
+            text = text[ps2size:]
+        return text
+
+    def push(self, command):
+        """Send command to the interpreter for execution."""
+        self.write(os.linesep)
+        busy = wx.BusyCursor()
+        self.waiting = True
+        self.more = self.interp.push(command)
+        self.waiting = False
+        del busy
+        if not self.more:
+            self.addHistory(command.rstrip())
+        self.prompt()
+
+    def addHistory(self, command):
+        """Add command to the command history."""
+        # Reset the history position.
+        self.historyIndex = -1
+        # Insert this command into the history, unless it's a blank
+        # line or the same as the last command.
+        if command != '' \
+        and (len(self.history) == 0 or command != self.history[0]):
+            self.history.insert(0, command)
+
+    def write(self, text):
+        """Display text in the shell.
+
+        Replace line endings with OS-specific endings."""
+        text = self.fixLineEndings(text)
+        self.AddText(text)
+        self.EnsureCaretVisible()
+
+    def fixLineEndings(self, text):
+        """Return text with line endings replaced by OS-specific endings."""
+        lines = text.split('\r\n')
+        for l in range(len(lines)):
+            chunks = lines[l].split('\r')
+            for c in range(len(chunks)):
+                chunks[c] = os.linesep.join(chunks[c].split('\n'))
+            lines[l] = os.linesep.join(chunks)
+        text = os.linesep.join(lines)
+        return text
+
+    def prompt(self):
+        """Display proper prompt for the context: ps1, ps2 or ps3.
+
+        If this is a continuation line, autoindent as necessary."""
+        isreading = self.reader.isreading
+        skip = False
+        if isreading:
+            prompt = str(sys.ps3)
+        elif self.more:
+            prompt = str(sys.ps2)
+        else:
+            prompt = str(sys.ps1)
+        pos = self.GetCurLine()[1]
+        if pos > 0:
+            if isreading:
+                skip = True
+            else:
+                self.write(os.linesep)
+        if not self.more:
+            self.promptPosStart = self.GetCurrentPos()
+        if not skip:
+            self.write(prompt)
+        if not self.more:
+            self.promptPosEnd = self.GetCurrentPos()
+            # Keep the undo feature from undoing previous responses.
+            self.EmptyUndoBuffer()
+        # XXX Add some autoindent magic here if more.
+        if self.more:
+            self.write(' '*4)  # Temporary hack indentation.
+        self.EnsureCaretVisible()
+        self.ScrollToColumn(0)
+
+    def readline(self):
+        """Replacement for stdin.readline()."""
+        input = ''
+        reader = self.reader
+        reader.isreading = True
+        self.prompt()
+        try:
+            while not reader.input:
+                wx.YieldIfNeeded()
+            input = reader.input
+        finally:
+            reader.input = ''
+            reader.isreading = False
+        input = str(input)  # In case of Unicode.
+        return input
+
+    def readlines(self):
+        """Replacement for stdin.readlines()."""
+        lines = []
+        while lines[-1:] != ['\n']:
+            lines.append(self.readline())
+        return lines
+
+    def raw_input(self, prompt=''):
+        """Return string based on user input."""
+        if prompt:
+            self.write(prompt)
+        return self.readline()
+
+    def ask(self, prompt='Please enter your response:'):
+        """Get response from the user using a dialog box."""
+        dialog = wx.TextEntryDialog(None, prompt,
+                                    'Input Dialog (Raw)', '')
+        try:
+            if dialog.ShowModal() == wx.ID_OK:
+                text = dialog.GetValue()
+                return text
+        finally:
+            dialog.Destroy()
+        return ''
+
+    def pause(self):
+        """Halt execution pending a response from the user."""
+        self.ask('Press enter to continue:')
+
+    def clear(self):
+        """Delete all text from the shell."""
+        self.ClearAll()
+
+    def run(self, command, prompt=True, verbose=True):
+        """Execute command as if it was typed in directly.
+        >>> shell.run('print "this"')
+        >>> print "this"
+        this
+        >>>
+        """
+        # Go to the very bottom of the text.
+        endpos = self.GetTextLength()
+        self.SetCurrentPos(endpos)
+        command = command.rstrip()
+        if prompt: self.prompt()
+        if verbose: self.write(command)
+        self.push(command)
+
+    def runfile(self, filename):
+        """Execute all commands in file as if they were typed into the
+        shell."""
+        file = open(filename)
+        try:
+            self.prompt()
+            for command in file.readlines():
+                if command[:6] == 'shell.':
+                    # Run shell methods silently.
+                    self.run(command, prompt=False, verbose=False)
+                else:
+                    self.run(command, prompt=False, verbose=True)
+        finally:
+            file.close()
+
+    def autoCompleteShow(self, command):
+        """Display auto-completion popup list."""
+        list = self.interp.getAutoCompleteList(command,
+                    includeMagic=self.autoCompleteIncludeMagic,
+                    includeSingle=self.autoCompleteIncludeSingle,
+                    includeDouble=self.autoCompleteIncludeDouble)
+        if list:
+            options = ' '.join(list)
+            offset = 0
+            self.AutoCompShow(offset, options)
+
+    def autoCallTipShow(self, command):
+        """Display argument spec and docstring in a popup window."""
+        if self.CallTipActive():
+            self.CallTipCancel()
+        (name, argspec, tip) = self.interp.getCallTip(command)
+        if tip:
+            dispatcher.send(signal='Shell.calltip', sender=self, calltip=tip)
+        if not self.autoCallTip:
+            return
+        if argspec:
+            startpos = self.GetCurrentPos()
+            self.write(argspec + ')')
+            endpos = self.GetCurrentPos()
+            self.SetSelection(endpos, startpos)
+        if tip:
+            curpos = self.GetCurrentPos()
+            tippos = curpos - (len(name) + 1)
+            fallback = curpos - self.GetColumn(curpos)
+            # In case there isn't enough room, only go back to the
+            # fallback.
+            tippos = max(tippos, fallback)
+            self.CallTipShow(tippos, tip)
+
+    def writeOut(self, text):
+        """Replacement for stdout."""
+        self.write(text)
+
+    def writeErr(self, text):
+        """Replacement for stderr."""
+        self.write(text)
+
+    def redirectStdin(self, redirect=True):
+        """If redirect is true then sys.stdin will come from the shell."""
+        if redirect:
+            sys.stdin = self.reader
+        else:
+            sys.stdin = self.stdin
+
+    def redirectStdout(self, redirect=True):
+        """If redirect is true then sys.stdout will go to the shell."""
+        if redirect:
+            sys.stdout = PseudoFileOut(self.writeOut)
+        else:
+            sys.stdout = self.stdout
+
+    def redirectStderr(self, redirect=True):
+        """If redirect is true then sys.stderr will go to the shell."""
+        if redirect:
+            sys.stderr = PseudoFileErr(self.writeErr)
+        else:
+            sys.stderr = self.stderr
+
+    def CanCut(self):
+        """Return true if text is selected and can be cut."""
+        if self.GetSelectionStart() != self.GetSelectionEnd() \
+               and self.GetSelectionStart() >= self.promptPosEnd \
+               and self.GetSelectionEnd() >= self.promptPosEnd:
+            return True
+        else:
+            return False
+
+    def CanPaste(self):
+        """Return true if a paste should succeed."""
+        if self.CanEdit() and editwindow.EditWindow.CanPaste(self):
+            return True
+        else:
+            return False
+
+    def CanEdit(self):
+        """Return true if editing should succeed."""
+        if self.GetSelectionStart() != self.GetSelectionEnd():
+            if self.GetSelectionStart() >= self.promptPosEnd \
+                   and self.GetSelectionEnd() >= self.promptPosEnd:
+                return True
+            else:
+                return False
+        else:
+            return self.GetCurrentPos() >= self.promptPosEnd
+
+    def Cut(self):
+        """Remove selection and place it on the clipboard."""
+        if self.CanCut() and self.CanCopy():
+            if self.AutoCompActive():
+                self.AutoCompCancel()
+            if self.CallTipActive():
+                self.CallTipCancel()
+            self.Copy()
+            self.ReplaceSelection('')
+
+    def Copy(self):
+        """Copy selection and place it on the clipboard."""
+        if self.CanCopy():
+            ps1 = str(sys.ps1)
+            ps2 = str(sys.ps2)
+            command = self.GetSelectedText()
+            command = command.replace(os.linesep + ps2, os.linesep)
+            command = command.replace(os.linesep + ps1, os.linesep)
+            command = self.lstripPrompt(text=command)
+            data = wx.TextDataObject(command)
+            self._clip(data)
+
+    def CopyWithPrompts(self):
+        """Copy selection, including prompts, and place it on the clipboard."""
+        if self.CanCopy():
+            command = self.GetSelectedText()
+            data = wx.TextDataObject(command)
+            self._clip(data)
+
+    def CopyWithPromptsPrefixed(self):
+        """Copy selection, including prompts prefixed with four
+        spaces, and place it on the clipboard."""
+        if self.CanCopy():
+            command = self.GetSelectedText()
+            spaces = ' ' * 4
+            command = spaces + command.replace(os.linesep,
+                                               os.linesep + spaces)
+            data = wx.TextDataObject(command)
+            self._clip(data)
+
+    def _clip(self, data):
+        if wx.TheClipboard.Open():
+            wx.TheClipboard.UsePrimarySelection(False)
+            wx.TheClipboard.SetData(data)
+            wx.TheClipboard.Flush()
+            wx.TheClipboard.Close()
+
+    def Paste(self):
+        """Replace selection with clipboard contents."""
+        if self.CanPaste() and wx.TheClipboard.Open():
+            ps2 = str(sys.ps2)
+            if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
+                data = wx.TextDataObject()
+                if wx.TheClipboard.GetData(data):
+                    self.ReplaceSelection('')
+                    command = data.GetText()
+                    command = command.rstrip()
+                    command = self.fixLineEndings(command)
+                    command = self.lstripPrompt(text=command)
+                    command = command.replace(os.linesep + ps2, '\n')
+                    command = command.replace(os.linesep, '\n')
+                    command = command.replace('\n', os.linesep + ps2)
+                    self.write(command)
+            wx.TheClipboard.Close()
+
+    def PasteAndRun(self):
+        """Replace selection with clipboard contents, run commands."""
+        if wx.TheClipboard.Open():
+            ps1 = str(sys.ps1)
+            ps2 = str(sys.ps2)
+            if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)):
+                data = wx.TextDataObject()
+                if wx.TheClipboard.GetData(data):
+                    endpos = self.GetTextLength()
+                    self.SetCurrentPos(endpos)
+                    startpos = self.promptPosEnd
+                    self.SetSelection(startpos, endpos)
+                    self.ReplaceSelection('')
+                    text = data.GetText()
+                    text = text.lstrip()
+                    text = self.fixLineEndings(text)
+                    text = self.lstripPrompt(text)
+                    text = text.replace(os.linesep + ps1, '\n')
+                    text = text.replace(os.linesep + ps2, '\n')
+                    text = text.replace(os.linesep, '\n')
+                    lines = text.split('\n')
+                    commands = []
+                    command = ''
+                    for line in lines:
+                        if line.strip() == ps2.strip():
+                            # If we are pasting from something like a
+                            # web page that drops the trailing space
+                            # from the ps2 prompt of a blank line.
+                            line = ''
+                        if line.strip() != '' and line.lstrip() == line:
+                            # New command.
+                            if command:
+                                # Add the previous command to the list.
+                                commands.append(command)
+                            # Start a new command, which may be multiline.
+                            command = line
+                        else:
+                            # Multiline command. Add to the command.
+                            command += '\n'
+                            command += line
+                    commands.append(command)
+                    for command in commands:
+                        command = command.replace('\n', os.linesep + ps2)
+                        self.write(command)
+                        self.processLine()
+            wx.TheClipboard.Close()
+
+    def wrap(self, wrap=True):
+        """Sets whether text is word wrapped."""
+        try:
+            self.SetWrapMode(wrap)
+        except AttributeError:
+            return 'Wrapping is not available in this version.'
+
+    def zoom(self, points=0):
+        """Set the zoom level.
+
+        This number of points is added to the size of all fonts.  It
+        may be positive to magnify or negative to reduce."""
+        self.SetZoom(points)