+__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
+__cvsid__ = "$Id$"
+__revision__ = "$Revision$"[11:-2]
+
+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
+
+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',
+ 'autoCompleteAutoHide',
+ '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:
+ import __main__
+ locals = __main__.__dict__
+ # 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."""
+ self.AutoCompSetAutoHide(self.autoCompleteAutoHide)
+ self.AutoCompSetIgnoreCase(self.autoCompleteCaseInsensitive)
+ 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)