+++ /dev/null
-"""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.
-
-Sponsored by Orbtech - Your source for Python programming expertise."""
-
-__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.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 the context menu
- self.Bind(wx.EVT_CONTEXT_MENU, self.OnContextMenu)
- self.Bind(wx.EVT_UPDATE_UI, self.OnUpdateUI)
-
- # Assign handlers for edit events
- self.Bind(wx.EVT_MENU, lambda evt: self.Cut(), id=wx.ID_CUT)
- self.Bind(wx.EVT_MENU, lambda evt: self.Copy(), id=wx.ID_COPY)
- self.Bind(wx.EVT_MENU, lambda evt: self.CopyWithPrompts(), id=frame.ID_COPY_PLUS)
- self.Bind(wx.EVT_MENU, lambda evt: self.Paste(), id=wx.ID_PASTE)
- self.Bind(wx.EVT_MENU, lambda evt: self.PasteAndRun(), id=frame.ID_PASTE_PLUS)
- self.Bind(wx.EVT_MENU, lambda evt: self.SelectAll(), id=wx.ID_SELECTALL)
- self.Bind(wx.EVT_MENU, lambda evt: self.Clear(), id=wx.ID_CLEAR)
- self.Bind(wx.EVT_MENU, lambda evt: self.Undo(), id=wx.ID_UNDO)
- self.Bind(wx.EVT_MENU, lambda evt: self.Redo(), id=wx.ID_REDO)
-
-
- # 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:
- self.write(text)
- try:
- if self.interp.introText:
- if text and not text.endswith(os.linesep):
- self.write(os.linesep)
- 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.GetKeyCode()
- 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.GetKeyCode()
- # 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<maxli and self.GetLineVisible(li) == 0:
- li += 1
- endP = self.GetLineEndPosition(li-1)
- self.ShowLines(li0, li-1)
- self.SetSelection( startP, endP ) # select reappearing text to allow "hide again"
- return
- startP,endP = self.GetSelection()
- endP-=1
- startL,endL = self.LineFromPosition(startP), self.LineFromPosition(endP)
-
- if endL == self.LineFromPosition(self.promptPosEnd): # never hide last prompt
- endL -= 1
-
- m = self.MarkerGet(startL)
- self.MarkerAdd(startL, 0)
- self.HideLines(startL+1,endL)
- self.SetCurrentPos( startP ) # to ensure caret stays visible !
-
- if key == wx.WXK_F12: #seb
- if self.noteMode:
- # self.promptPosStart not used anyway - or ?
- self.promptPosEnd = self.PositionFromLine( self.GetLineCount()-1 ) + len(str(sys.ps1))
- self.GotoLine(self.GetLineCount())
- self.GotoPos(self.promptPosEnd)
- self.prompt() #make sure we have a prompt
- self.SetCaretForeground("black")
- self.SetCaretWidth(1) #default
- self.SetCaretPeriod(500) #default
- else:
- self.SetCaretForeground("red")
- self.SetCaretWidth(4)
- self.SetCaretPeriod(0) #steady
-
- self.noteMode = not self.noteMode
- return
- if self.noteMode:
- event.Skip()
- return
-
- # Return (Enter) is used to submit a command to the
- # interpreter.
- if (not controlDown and not shiftDown and not altDown) and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
- if self.CallTipActive():
- self.CallTipCancel()
- self.processLine()
-
- # Complete Text (from already typed words)
- elif shiftDown and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
- self.OnShowCompHistory()
-
- # Ctrl+Return (Ctrl+Enter) is used to insert a line break.
- elif controlDown and key in [wx.WXK_RETURN, wx.WXK_NUMPAD_ENTER]:
- 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()
-
- # Clear the current command
- elif key == wx.WXK_BACK and controlDown and shiftDown:
- self.clearCommand()
-
- # Increase font size.
- elif controlDown and key in (ord(']'), wx.WXK_NUMPAD_ADD):
- dispatcher.send(signal='FontIncrease')
-
- # Decrease font size.
- elif controlDown and key in (ord('['), wx.WXK_NUMPAD_SUBTRACT):
- dispatcher.send(signal='FontDecrease')
-
- # Default font size.
- elif controlDown and key in (ord('='), wx.WXK_NUMPAD_DIVIDE):
- 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()
-
- # 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())
-
- def GetContextMenu(self):
- """
- Create and return a context menu for the shell.
- This is used instead of the scintilla default menu
- in order to correctly respect our immutable buffer.
- """
- menu = wx.Menu()
- menu.Append(wx.ID_UNDO, "Undo")
- menu.Append(wx.ID_REDO, "Redo")
-
- menu.AppendSeparator()
-
- menu.Append(wx.ID_CUT, "Cut")
- menu.Append(wx.ID_COPY, "Copy")
- menu.Append(frame.ID_COPY_PLUS, "Copy Plus")
- menu.Append(wx.ID_PASTE, "Paste")
- menu.Append(frame.ID_PASTE_PLUS, "Paste Plus")
- menu.Append(wx.ID_CLEAR, "Clear")
-
- menu.AppendSeparator()
-
- menu.Append(wx.ID_SELECTALL, "Select All")
- return menu
-
- def OnContextMenu(self, evt):
- menu = self.GetContextMenu()
- self.PopupMenu(menu)
-
- def OnUpdateUI(self, evt):
- id = evt.Id
- if id in (wx.ID_CUT, wx.ID_CLEAR):
- evt.Enable(self.CanCut())
- elif id in (wx.ID_COPY, frame.ID_COPY_PLUS):
- evt.Enable(self.CanCopy())
- elif id in (wx.ID_PASTE, frame.ID_PASTE_PLUS):
- evt.Enable(self.CanPaste())
- elif id == wx.ID_UNDO:
- evt.Enable(self.CanUndo())
- elif id == wx.ID_REDO:
- evt.Enable(self.CanRedo())
-
-
-
-
-## 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
-