X-Git-Url: https://git.saurik.com/wxWidgets.git/blobdiff_plain/8b9a4190f70909de9568f45389e7aa3ecbc66b8a..3ad84671d3668996da1e351e83c7d859fc094603:/wxPython/wx/py/shell.py?ds=sidebyside diff --git a/wxPython/wx/py/shell.py b/wxPython/wx/py/shell.py index 9b16953fc6..c86ced152d 100644 --- a/wxPython/wx/py/shell.py +++ b/wxPython/wx/py/shell.py @@ -1,8 +1,1431 @@ +"""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.""" -from wx import _rename -from wxPython.py import shell -_rename(globals(), shell.__dict__, modulename='py.shell') -del shell -del _rename +__author__ = "Patrick K. O'Brien " +__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.ShellFrameMixin): + """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, + config=None, dataDir=None, + *args, **kwds): + """Create ShellFrame instance.""" + frame.Frame.__init__(self, parent, id, title, pos, size, style) + frame.ShellFrameMixin.__init__(self, config, dataDir) + + if size == wx.DefaultSize: + self.SetSize((750, 525)) + + intro = 'PyShell %s - The Flakiest Python Shell' % VERSION + self.SetStatusText(intro.replace('\n', ', ')) + self.shell = Shell(parent=self, id=-1, introText=intro, + locals=locals, InterpClass=InterpClass, + startupScript=self.startupScript, + execStartupScript=self.execStartupScript, + *args, **kwds) + + # Override the shell so that status messages go to the status bar. + self.shell.setStatusText = self.SetStatusText + + self.shell.SetFocus() + self.LoadSettings() + + + 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.SaveSettings() + 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 + \ + 'Platform: %s\n' % sys.platform + \ + 'Python Version: %s\n' % sys.version.split()[0] + \ + 'wxPython Version: %s\n' % wx.VERSION_STRING + \ + ('\t(%s)\n' % ", ".join(wx.PlatformInfo[1:])) + dialog = wx.MessageDialog(self, text, title, + wx.OK | wx.ICON_INFORMATION) + dialog.ShowModal() + dialog.Destroy() + + + def OnHelp(self, event): + """Show a help dialog.""" + frame.ShellFrameMixin.OnHelp(self, event) + + + def LoadSettings(self): + if self.config is not None: + frame.ShellFrameMixin.LoadSettings(self) + frame.Frame.LoadSettings(self, self.config) + self.shell.LoadSettings(self.config) + + def SaveSettings(self, force=False): + if self.config is not None: + frame.ShellFrameMixin.SaveSettings(self) + if self.autoSaveSettings or force: + frame.Frame.SaveSettings(self, self.config) + self.shell.SaveSettings(self.config) + + def DoSaveSettings(self): + if self.config is not None: + self.SaveSettings(force=True) + self.config.Flush() + + + + +HELP_TEXT = """\ +* 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. +Alt+C Copy to the clipboard, including prefixed 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. +Ctrl-Space Show Auto Completion. +Ctrl-Alt-Space Show Call Tip. +Shift+Enter Complete Text from History. +Ctrl+F Search +F3 Search next +Ctrl+H "hide" lines containing selection / "unhide" +F12 on/off "free-edit" mode +""" + +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'] = HELP_TEXT + d['this'] = other.this + + 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', + 'callTipInsert', + '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, + startupScript=None, execStartupScript=True, + *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 + + #seb add mode for "free edit" + self.noteMode = 0 + self.MarkerDefine(0,stc.STC_MARK_ROUNDRECT) # marker for hidden + self.searchTxt = "" + + # Assign handlers for keyboard events. + self.Bind(wx.EVT_CHAR, self.OnChar) + self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown) + + # Assign handler for idle time. + self.waiting = False + self.Bind(wx.EVT_IDLE, 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() + + ## NOTE: See note at bottom of this file... + ## #seb: File drag and drop + ## self.SetDropTarget( FileDropTarget(self) ) + + # Do this last so the user has complete control over their + # environment. They can override anything they want. + if execStartupScript: + if startupScript is None: + startupScript = os.environ.get('PYTHONSTARTUP') + self.execStartupScript(startupScript) + else: + self.prompt() + + wx.CallAfter(self.ScrollToLine, 0) + + + def clearHistory(self): + self.history = [] + self.historyIndex = -1 + dispatcher.send(signal="Shell.clearHistory") + + + 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)) + self.interp.startupScript = 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 +wxPython PlatformInfo: %s +Platform: %s""" % \ + (__author__, VERSION, self.revision, self.interp.revision, + sys.version.split()[0], wx.VERSION_STRING, str(wx.PlatformInfo), + 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.""" + + if self.noteMode: + event.Skip() + return + + # 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 in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]: + 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, self.GetCurrentPos() == self.GetTextLength()) + 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() + + if controlDown and shiftDown and key in (ord('F'), ord('f')): + li = self.GetCurrentLine() + m = self.MarkerGet(li) + if m & 1<<0: + startP = self.PositionFromLine(li) + self.MarkerDelete(li, 0) + maxli = self.GetLineCount() + li += 1 # li stayed visible as header-line + li0 = li + while li 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() + + # manually invoke AutoComplete and Calltips + elif controlDown and key == wx.WXK_SPACE: + self.OnCallTipAutoCompleteManually(shiftDown) + + # 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 OnShowCompHistory(self): + """Show possible autocompletion Words from already typed words.""" + + #copy from history + his = self.history[:] + + #put together in one string + joined = " ".join (his) + import re + + #sort out only "good" words + newlist = re.split("[ \.\[\]=}(\)\,0-9\"]", joined) + + #length > 1 (mix out "trash") + thlist = [] + for i in newlist: + if len (i) > 1: + thlist.append (i) + + #unique (no duplicate words + #oneliner from german python forum => unique list + unlist = [thlist[i] for i in xrange(len(thlist)) if thlist[i] not in thlist[:i]] + + #sort lowercase + unlist.sort(lambda a, b: cmp(a.lower(), b.lower())) + + #this is more convenient, isn't it? + self.AutoCompSetIgnoreCase(True) + + #join again together in a string + stringlist = " ".join(unlist) + + #pos von 0 noch ausrechnen + + #how big is the offset? + cpos = self.GetCurrentPos() - 1 + while chr (self.GetCharAt (cpos)).isalnum(): + cpos -= 1 + + #the most important part + self.AutoCompShow(self.GetCurrentPos() - cpos -1, stringlist) + + + 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) + wx.FutureCall(1, self.EnsureCaretVisible) + # 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, silent = False): + """Send command to the interpreter for execution.""" + if not silent: + 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()) + if not silent: + 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) + dispatcher.send(signal="Shell.addHistory", command=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, offset = 0): + """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, insertcalltip = True, forceCallTip = False): + """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 and not forceCallTip: + return + if argspec and insertcalltip and self.callTipInsert: + 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 OnCallTipAutoCompleteManually (self, shiftDown): + """AutoComplete and Calltips manually.""" + if self.AutoCompActive(): + self.AutoCompCancel() + currpos = self.GetCurrentPos() + stoppos = self.promptPosEnd + + cpos = currpos + #go back until '.' is found + pointavailpos = -1 + while cpos >= stoppos: + if self.GetCharAt(cpos) == ord ('.'): + pointavailpos = cpos + break + cpos -= 1 + + #word from non whitespace until '.' + if pointavailpos != -1: + #look backward for first whitespace char + textbehind = self.GetTextRange (pointavailpos + 1, currpos) + pointavailpos += 1 + + if not shiftDown: + #call AutoComplete + stoppos = self.promptPosEnd + textbefore = self.GetTextRange(stoppos, pointavailpos) + self.autoCompleteShow(textbefore, len (textbehind)) + else: + #call CallTips + cpos = pointavailpos + begpos = -1 + while cpos > stoppos: + if chr(self.GetCharAt(cpos)).isspace(): + begpos = cpos + break + cpos -= 1 + if begpos == -1: + begpos = cpos + ctips = self.GetTextRange (begpos, currpos) + ctindex = ctips.find ('(') + if ctindex != -1 and not self.CallTipActive(): + #insert calltip, if current pos is '(', otherwise show it only + self.autoCallTipShow(ctips[:ctindex + 1], + self.GetCharAt(currpos - 1) == ord('(') and self.GetCurrentPos() == self.GetTextLength(), + True) + + + 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.""" + text = '' + if wx.TheClipboard.Open(): + if wx.TheClipboard.IsSupported(wx.DataFormat(wx.DF_TEXT)): + data = wx.TextDataObject() + if wx.TheClipboard.GetData(data): + text = data.GetText() + wx.TheClipboard.Close() + if text: + self.Execute(text) + + + def Execute(self, text): + """Replace selection with text and run commands.""" + ps1 = str(sys.ps1) + ps2 = str(sys.ps2) + endpos = self.GetTextLength() + self.SetCurrentPos(endpos) + startpos = self.promptPosEnd + self.SetSelection(startpos, endpos) + self.ReplaceSelection('') + 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 = '' + lstrip = line.lstrip() + if line.strip() != '' and lstrip == line and \ + lstrip[:4] not in ['else','elif'] and \ + lstrip[:6] != 'except': + # 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() + + + 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) + + + + def LoadSettings(self, config): + self.autoComplete = config.ReadBool('Options/AutoComplete', True) + self.autoCompleteIncludeMagic = config.ReadBool('Options/AutoCompleteIncludeMagic', True) + self.autoCompleteIncludeSingle = config.ReadBool('Options/AutoCompleteIncludeSingle', True) + self.autoCompleteIncludeDouble = config.ReadBool('Options/AutoCompleteIncludeDouble', True) + + self.autoCallTip = config.ReadBool('Options/AutoCallTip', True) + self.callTipInsert = config.ReadBool('Options/CallTipInsert', True) + self.SetWrapMode(config.ReadBool('View/WrapMode', True)) + + useAA = config.ReadBool('Options/UseAntiAliasing', self.GetUseAntiAliasing()) + self.SetUseAntiAliasing(useAA) + self.lineNumbers = config.ReadBool('View/ShowLineNumbers', True) + self.setDisplayLineNumbers (self.lineNumbers) + zoom = config.ReadInt('View/Zoom/Shell', -99) + if zoom != -99: + self.SetZoom(zoom) + + + + def SaveSettings(self, config): + config.WriteBool('Options/AutoComplete', self.autoComplete) + config.WriteBool('Options/AutoCompleteIncludeMagic', self.autoCompleteIncludeMagic) + config.WriteBool('Options/AutoCompleteIncludeSingle', self.autoCompleteIncludeSingle) + config.WriteBool('Options/AutoCompleteIncludeDouble', self.autoCompleteIncludeDouble) + config.WriteBool('Options/AutoCallTip', self.autoCallTip) + config.WriteBool('Options/CallTipInsert', self.callTipInsert) + config.WriteBool('Options/UseAntiAliasing', self.GetUseAntiAliasing()) + config.WriteBool('View/WrapMode', self.GetWrapMode()) + config.WriteBool('View/ShowLineNumbers', self.lineNumbers) + config.WriteInt('View/Zoom/Shell', self.GetZoom()) + + + +## NOTE: The DnD of file names is disabled until I can figure out how +## best to still allow DnD of text. + + +## #seb : File drag and drop +## class FileDropTarget(wx.FileDropTarget): +## def __init__(self, obj): +## wx.FileDropTarget.__init__(self) +## self.obj = obj +## def OnDropFiles(self, x, y, filenames): +## if len(filenames) == 1: +## txt = 'r\"%s\"' % filenames[0] +## else: +## txt = '( ' +## for f in filenames: +## txt += 'r\"%s\" , ' % f +## txt += ')' +## self.obj.AppendText(txt) +## pos = self.obj.GetCurrentPos() +## self.obj.SetCurrentPos( pos ) +## self.obj.SetSelection( pos, pos ) + + + +## class TextAndFileDropTarget(wx.DropTarget): +## def __init__(self, shell): +## wx.DropTarget.__init__(self) +## self.shell = shell +## self.compdo = wx.DataObjectComposite() +## self.textdo = wx.TextDataObject() +## self.filedo = wx.FileDataObject() +## self.compdo.Add(self.textdo) +## self.compdo.Add(self.filedo, True) + +## self.SetDataObject(self.compdo) + +## def OnDrop(self, x, y): +## return True + +## def OnData(self, x, y, result): +## self.GetData() +## if self.textdo.GetTextLength() > 1: +## text = self.textdo.GetText() +## # *** Do somethign with the dragged text here... +## self.textdo.SetText('') +## else: +## filenames = str(self.filename.GetFilenames()) +## if len(filenames) == 1: +## txt = 'r\"%s\"' % filenames[0] +## else: +## txt = '( ' +## for f in filenames: +## txt += 'r\"%s\" , ' % f +## txt += ')' +## self.shell.AppendText(txt) +## pos = self.shell.GetCurrentPos() +## self.shell.SetCurrentPos( pos ) +## self.shell.SetSelection( pos, pos ) + +## return result +