diff --git a/wxPython/wxPython/lib/PyCrust/crust.py b/wxPython/wxPython/lib/PyCrust/crust.py index 23235b2619..45de8b585a 100644 --- a/wxPython/wxPython/lib/PyCrust/crust.py +++ b/wxPython/wxPython/lib/PyCrust/crust.py @@ -8,6 +8,7 @@ from wxPython.wx import * from shell import Shell from filling import Filling from version import VERSION +import os class Crust(wxSplitterWindow): @@ -55,12 +56,9 @@ class CrustFrame(wxFrame, ShellMenu): intro += '\nSponsored by Orbtech - Your source for Python programming expertise.' self.CreateStatusBar() self.SetStatusText(intro.replace('\n', ', ')) - - import os filename = os.path.join(os.path.dirname(__file__), 'PyCrust.ico') icon = wxIcon(filename, wxBITMAP_TYPE_ICO) self.SetIcon(icon) - self.crust = Crust(parent=self, intro=intro, \ rootObject=rootObject, \ rootLabel=rootLabel, \ @@ -78,6 +76,10 @@ class CrustFrame(wxFrame, ShellMenu): # Temporary hack to share menus between PyCrust and PyShell. self.shell = self.crust.shell self.createMenus() - + EVT_CLOSE(self, self.OnCloseWindow) + + def OnCloseWindow(self, event): + self.crust.shell.destroy() + self.Destroy() diff --git a/wxPython/wxPython/lib/PyCrust/filling.py b/wxPython/wxPython/lib/PyCrust/filling.py index b2ce11ced3..a8da260894 100644 --- a/wxPython/wxPython/lib/PyCrust/filling.py +++ b/wxPython/wxPython/lib/PyCrust/filling.py @@ -14,6 +14,14 @@ import keyword import sys import types +COMMONTYPES = [getattr(types, t) for t in dir(types) \ + if not t.startswith('_') \ + and t not in ('ClassType', 'InstanceType', 'ModuleType')] +try: + COMMONTYPES.append(type(''.__repr__)) # Method-wrapper in version 2.2.x. +except AttributeError: + pass + class FillingTree(wxTreeCtrl): """PyCrust FillingTree based on wxTreeCtrl.""" @@ -39,40 +47,43 @@ class FillingTree(wxTreeCtrl): EVT_TREE_ITEM_COLLAPSED(self, self.GetId(), self.OnItemCollapsed) EVT_TREE_SEL_CHANGED(self, self.GetId(), self.OnSelChanged) - def hasChildren(self, object): + def hasChildren(self, o): """Return true if object has children.""" - if self.getChildren(object): + if self.getChildren(o): return true else: return false - def getChildren(self, object): + def getChildren(self, o): """Return a dictionary with the attributes or contents of object.""" - dict = {} - objtype = type(object) - if (objtype is types.DictType) \ - or str(objtype)[17:23] == 'BTrees' and hasattr(object, 'keys'): - dict = object - elif (objtype in (types.ClassType, \ - types.InstanceType, \ - types.ModuleType)) \ - or str(objtype)[1:10] == 'extension': - for key in introspect.getAttributeNames(object): + busy = wxBusyCursor() + otype = type(o) + if (otype is types.DictType) \ + or str(otype)[17:23] == 'BTrees' and hasattr(o, 'keys'): + return o + d = {} + if otype is types.ListType: + for n in range(len(o)): + key = '[' + str(n) + ']' + d[key] = o[n] + if otype not in COMMONTYPES: + for key in introspect.getAttributeNames(o): # Believe it or not, some attributes can disappear, such as # the exc_traceback attribute of the sys module. So this is # nested in a try block. try: - dict[key] = getattr(object, key) + d[key] = getattr(o, key) except: pass - return dict + return d def OnItemExpanding(self, event): + busy = wxBusyCursor() selection = event.GetItem() if self.IsExpanded(selection): return - object = self.GetPyData(selection) - children = self.getChildren(object) + o = self.GetPyData(selection) + children = self.getChildren(o) if not children: return list = children.keys() @@ -84,7 +95,7 @@ class FillingTree(wxTreeCtrl): itemtext = str(item) # Show string dictionary items with single quotes, except for # the first level of items, if they represent a namespace. - if type(object) is types.DictType \ + if type(o) is types.DictType \ and type(item) is types.StringType \ and (selection != self.root \ or (selection == self.root and not self.rootIsNamespace)): @@ -95,42 +106,44 @@ class FillingTree(wxTreeCtrl): def OnItemCollapsed(self, event): """Remove all children from the item.""" + busy = wxBusyCursor() item = event.GetItem() self.DeleteChildren(item) def OnSelChanged(self, event): + busy = wxBusyCursor() item = event.GetItem() if item == self.root: self.setText('') return - object = self.GetPyData(item) - otype = type(object) + o = self.GetPyData(item) + otype = type(o) text = '' text += self.getFullName(item) text += '\n\nType: ' + str(otype) try: - value = str(object) + value = str(o) except: value = '' if otype is types.StringType or otype is types.UnicodeType: - value = repr(object) + value = repr(o) text += '\n\nValue: ' + value if otype is types.InstanceType: try: text += '\n\nClass Definition:\n\n' + \ - inspect.getsource(object.__class__) + inspect.getsource(o.__class__) except: try: - text += '\n\n"""' + inspect.getdoc(object).strip() + '"""' + text += '\n\n"""' + inspect.getdoc(o).strip() + '"""' except: pass else: try: text += '\n\nSource Code:\n\n' + \ - inspect.getsource(object) + inspect.getsource(o) except: try: - text += '\n\n"""' + inspect.getdoc(object).strip() + '"""' + text += '\n\n"""' + inspect.getdoc(o).strip() + '"""' except: pass self.setText(text) @@ -138,13 +151,13 @@ class FillingTree(wxTreeCtrl): def getFullName(self, item, partial=''): """Return a syntactically proper name for item.""" parent = self.GetItemParent(item) - parentobject = self.GetPyData(parent) + parento = self.GetPyData(parent) name = self.GetItemText(item) # Apply dictionary syntax to dictionary items, except the root # and first level children of a namepace. - if (type(parentobject) is types.DictType \ - or str(type(parentobject))[17:23] == 'BTrees' \ - and hasattr(parentobject, 'keys')) \ + if (type(parento) is types.DictType \ + or str(type(parento))[17:23] == 'BTrees' \ + and hasattr(parento, 'keys')) \ and ((item != self.root and parent != self.root) \ or (parent == self.root and not self.rootIsNamespace)): name = '[' + name + ']' @@ -228,6 +241,10 @@ class FillingText(wxStyledTextCtrl): self.SetTabWidth(4) self.SetUseTabs(0) self.SetReadOnly(1) + try: + self.SetWrapMode(1) + except AttributeError: + pass def setStyles(self, faces): """Configure font size, typeface and color for lexer.""" diff --git a/wxPython/wxPython/lib/PyCrust/interpreter.py b/wxPython/wxPython/lib/PyCrust/interpreter.py index f669a5d122..4cd752daa3 100644 --- a/wxPython/wxPython/lib/PyCrust/interpreter.py +++ b/wxPython/wxPython/lib/PyCrust/interpreter.py @@ -78,6 +78,10 @@ class Interpreter(InteractiveInterpreter): sys.stderr = stderr return more + def getAutoCompleteKeys(self): + """Return list of auto-completion keycodes.""" + return [ord('.')] + def getAutoCompleteList(self, command='', *args, **kwds): """Return list of auto-completion options for a command. diff --git a/wxPython/wxPython/lib/PyCrust/shell.py b/wxPython/wxPython/lib/PyCrust/shell.py index b96ea80218..8af395ed76 100644 --- a/wxPython/wxPython/lib/PyCrust/shell.py +++ b/wxPython/wxPython/lib/PyCrust/shell.py @@ -19,6 +19,8 @@ from pseudo import PseudoFileErr from version import VERSION +NAVKEYS = (WXK_END, WXK_LEFT, WXK_RIGHT, WXK_UP, WXK_DOWN, WXK_PRIOR, WXK_NEXT) + if wxPlatform == '__WXMSW__': faces = { 'times' : 'Times New Roman', 'mono' : 'Courier New', @@ -48,7 +50,7 @@ else: # GTK class ShellFacade: """Simplified interface to all shell-related functionality. - This is a semi-transparent facade, in that all attributes of other are + This is a semi-transparent facade, in that all attributes of other are still accessible, even though only some are visible to the user.""" name = 'PyCrust Shell Interface' @@ -66,6 +68,8 @@ class ShellFacade: 'redirectStdout', 'run', 'runfile', + 'wrap', + 'zoom', ] for method in methods: self.__dict__[method] = getattr(other, method) @@ -160,6 +164,8 @@ class Shell(wxStyledTextCtrl): stdout=PseudoFileOut(self.writeOut), \ stderr=PseudoFileErr(self.writeErr), \ *args, **kwds) + # 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 @@ -219,6 +225,7 @@ class Shell(wxStyledTextCtrl): # Do we want to automatically pop up command argument help? self.autoCallTip = 1 self.CallTipSetBackground(wxColour(255, 255, 232)) + self.wrap() def showIntro(self, text=''): """Display introductory text in the shell.""" @@ -301,6 +308,10 @@ class Shell(wxStyledTextCtrl): caretPos = self.GetCurrentPos() if caretPos > 0: charBefore = self.GetCharAt(caretPos - 1) + #*** Patch to fix bug in wxSTC for wxPython < 2.3.3. + if charBefore < 0: + charBefore = 32 # Mimic a space. + #*** styleBefore = self.GetStyleAt(caretPos - 1) # Check before. @@ -311,6 +322,10 @@ class Shell(wxStyledTextCtrl): # Check after. if braceAtCaret < 0: charAfter = self.GetCharAt(caretPos) + #*** Patch to fix bug in wxSTC for wxPython < 2.3.3. + if charAfter < 0: + charAfter = 32 # Mimic a space. + #*** styleAfter = self.GetStyleAt(caretPos) if charAfter and chr(charAfter) in '[]{}()' \ and styleAfter == wxSTC_P_OPERATOR: @@ -325,20 +340,20 @@ class Shell(wxStyledTextCtrl): self.BraceHighlight(braceAtCaret, braceOpposite) def OnChar(self, event): - """Keypress event handler. + """Keypress event handler.""" - Prevents modification of previously submitted commands/responses.""" + # Prevent modification of previously submitted commands/responses. if not self.CanEdit(): return key = event.KeyCode() currpos = self.GetCurrentPos() stoppos = self.promptPosEnd - if key == ord('.'): - # The dot or period key activates auto completion. + if key in self.autoCompleteKeys: + # Usually the dot (period) key activates auto completion. # Get the command between the prompt and the cursor. - # Add a dot to the end of the command. - command = self.GetTextRange(stoppos, currpos) + '.' - self.write('.') + # Add the autocomplete character to the end of the command. + 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 @@ -355,9 +370,9 @@ class Shell(wxStyledTextCtrl): event.Skip() def OnKeyDown(self, event): - """Key down event handler. + """Key down event handler.""" - Prevents modification of previously submitted commands/responses.""" + # Prevent modification of previously submitted commands/responses. key = event.KeyCode() controlDown = event.ControlDown() altDown = event.AltDown() @@ -401,6 +416,25 @@ class Shell(wxStyledTextCtrl): elif controlDown and shiftDown \ and key in (ord('C'), ord('c'), WXK_INSERT): self.CopyWithPrompts() + # Home needs to be aware of the prompt. + elif key == WXK_HOME: + home = self.promptPosEnd + if currpos > home: + selecting = self.GetSelectionStart() != self.GetSelectionEnd() + 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 self.GetSelectionStart() != self.GetSelectionEnd()\ + 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'))) \ @@ -419,34 +453,20 @@ class Shell(wxStyledTextCtrl): 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 == WXK_UP): + elif (shiftDown and key == WXK_UP) and self.CanEdit(): self.OnHistoryInsert(step=+1) # Insert the next command from the history buffer. - elif (shiftDown and key == WXK_DOWN): + elif (shiftDown and key == WXK_DOWN) and self.CanEdit(): self.OnHistoryInsert(step=-1) # Search up the history for the text in front of the cursor. elif key == WXK_F8: self.OnHistorySearch() - # Home needs to be aware of the prompt. - elif key == WXK_HOME: - home = self.promptPosEnd - if currpos >= home: - if event.ShiftDown(): - # Select text from current position to end of prompt. - self.SetSelection(self.GetCurrentPos(), home) - else: - self.SetCurrentPos(home) - self.SetAnchor(home) - self.EnsureCaretVisible() - else: - event.Skip() - # Basic navigation keys should work anywhere. - elif key in (WXK_END, WXK_LEFT, WXK_RIGHT, WXK_UP, WXK_DOWN, \ - WXK_PRIOR, WXK_NEXT): - event.Skip() # Don't backspace over the latest non-continuation prompt. elif key == WXK_BACK: - if currpos > self.promptPosEnd: + if self.GetSelectionStart() != self.GetSelectionEnd()\ + and self.CanEdit(): + event.Skip() + elif currpos > self.promptPosEnd: event.Skip() # Only allow these keys after the latest prompt. elif key in (WXK_TAB, WXK_DELETE): @@ -461,6 +481,9 @@ class Shell(wxStyledTextCtrl): # 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 @@ -551,7 +574,7 @@ class Shell(wxStyledTextCtrl): # 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() + thepos = self.GetCurrentPos() startpos = self.promptPosEnd endpos = self.GetTextLength() # If they hit RETURN inside the current command, execute the command. @@ -638,9 +661,10 @@ class Shell(wxStyledTextCtrl): elif text[:ps2size] == ps2: text = text[ps2size:] return text - + def push(self, command): """Send command to the interpreter for execution.""" + busy = wxBusyCursor() self.write(os.linesep) self.more = self.interp.push(command) if not self.more: @@ -742,11 +766,11 @@ class Shell(wxStyledTextCtrl): >>> shell.run('print "this"') >>> print "this" this - >>> + >>> """ # Go to the very bottom of the text. endpos = self.GetTextLength() - self.SetCurrentPos(endpos) + self.SetCurrentPos(endpos) command = command.rstrip() if prompt: self.prompt() if verbose: self.write(command) @@ -844,7 +868,14 @@ class Shell(wxStyledTextCtrl): def CanEdit(self): """Return true if editing should succeed.""" - return self.GetCurrentPos() >= self.promptPosEnd + if self.GetSelectionStart() != self.GetSelectionEnd(): + if self.GetSelectionStart() >= self.promptPosEnd \ + and self.GetSelectionEnd() >= self.promptPosEnd: + return 1 + else: + return 0 + else: + return self.GetCurrentPos() >= self.promptPosEnd def Cut(self): """Remove selection and place it on the clipboard.""" @@ -926,12 +957,26 @@ class Shell(wxStyledTextCtrl): command += '\n' command += line commands.append(command) - for command in commands: + for command in commands: command = command.replace('\n', os.linesep + sys.ps2) self.write(command) self.processLine() wxTheClipboard.Close() + def wrap(self, wrap=1): + """Sets whether text is word wrapped.""" + try: + self.SetWrapMode(wrap) + except AttributeError: + return 'Wrapping is not available in this version of PyCrust.' + + 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) + wxID_SELECTALL = NewId() # This *should* be defined by wxPython. ID_AUTOCOMP = NewId() @@ -1124,19 +1169,18 @@ class ShellFrame(wxFrame, ShellMenu): intro += '\nSponsored by Orbtech - Your source for Python programming expertise.' self.CreateStatusBar() self.SetStatusText(intro.replace('\n', ', ')) - - import os filename = os.path.join(os.path.dirname(__file__), 'PyCrust.ico') icon = wxIcon(filename, wxBITMAP_TYPE_ICO) self.SetIcon(icon) - 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 self.createMenus() + EVT_CLOSE(self, self.OnCloseWindow) - - + def OnCloseWindow(self, event): + self.shell.destroy() + self.Destroy()