Updated PyCrust code from Patrick

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@16755 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Robin Dunn
2002-08-24 21:44:57 +00:00
parent 54f07ec450
commit d8a2af5091
4 changed files with 142 additions and 75 deletions

View File

@@ -8,6 +8,7 @@ from wxPython.wx import *
from shell import Shell from shell import Shell
from filling import Filling from filling import Filling
from version import VERSION from version import VERSION
import os
class Crust(wxSplitterWindow): class Crust(wxSplitterWindow):
@@ -55,12 +56,9 @@ class CrustFrame(wxFrame, ShellMenu):
intro += '\nSponsored by Orbtech - Your source for Python programming expertise.' intro += '\nSponsored by Orbtech - Your source for Python programming expertise.'
self.CreateStatusBar() self.CreateStatusBar()
self.SetStatusText(intro.replace('\n', ', ')) self.SetStatusText(intro.replace('\n', ', '))
import os
filename = os.path.join(os.path.dirname(__file__), 'PyCrust.ico') filename = os.path.join(os.path.dirname(__file__), 'PyCrust.ico')
icon = wxIcon(filename, wxBITMAP_TYPE_ICO) icon = wxIcon(filename, wxBITMAP_TYPE_ICO)
self.SetIcon(icon) self.SetIcon(icon)
self.crust = Crust(parent=self, intro=intro, \ self.crust = Crust(parent=self, intro=intro, \
rootObject=rootObject, \ rootObject=rootObject, \
rootLabel=rootLabel, \ rootLabel=rootLabel, \
@@ -78,6 +76,10 @@ class CrustFrame(wxFrame, ShellMenu):
# Temporary hack to share menus between PyCrust and PyShell. # Temporary hack to share menus between PyCrust and PyShell.
self.shell = self.crust.shell self.shell = self.crust.shell
self.createMenus() self.createMenus()
EVT_CLOSE(self, self.OnCloseWindow)
def OnCloseWindow(self, event):
self.crust.shell.destroy()
self.Destroy()

View File

@@ -14,6 +14,14 @@ import keyword
import sys import sys
import types 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): class FillingTree(wxTreeCtrl):
"""PyCrust FillingTree based on wxTreeCtrl.""" """PyCrust FillingTree based on wxTreeCtrl."""
@@ -39,40 +47,43 @@ class FillingTree(wxTreeCtrl):
EVT_TREE_ITEM_COLLAPSED(self, self.GetId(), self.OnItemCollapsed) EVT_TREE_ITEM_COLLAPSED(self, self.GetId(), self.OnItemCollapsed)
EVT_TREE_SEL_CHANGED(self, self.GetId(), self.OnSelChanged) EVT_TREE_SEL_CHANGED(self, self.GetId(), self.OnSelChanged)
def hasChildren(self, object): def hasChildren(self, o):
"""Return true if object has children.""" """Return true if object has children."""
if self.getChildren(object): if self.getChildren(o):
return true return true
else: else:
return false return false
def getChildren(self, object): def getChildren(self, o):
"""Return a dictionary with the attributes or contents of object.""" """Return a dictionary with the attributes or contents of object."""
dict = {} busy = wxBusyCursor()
objtype = type(object) otype = type(o)
if (objtype is types.DictType) \ if (otype is types.DictType) \
or str(objtype)[17:23] == 'BTrees' and hasattr(object, 'keys'): or str(otype)[17:23] == 'BTrees' and hasattr(o, 'keys'):
dict = object return o
elif (objtype in (types.ClassType, \ d = {}
types.InstanceType, \ if otype is types.ListType:
types.ModuleType)) \ for n in range(len(o)):
or str(objtype)[1:10] == 'extension': key = '[' + str(n) + ']'
for key in introspect.getAttributeNames(object): 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 # Believe it or not, some attributes can disappear, such as
# the exc_traceback attribute of the sys module. So this is # the exc_traceback attribute of the sys module. So this is
# nested in a try block. # nested in a try block.
try: try:
dict[key] = getattr(object, key) d[key] = getattr(o, key)
except: except:
pass pass
return dict return d
def OnItemExpanding(self, event): def OnItemExpanding(self, event):
busy = wxBusyCursor()
selection = event.GetItem() selection = event.GetItem()
if self.IsExpanded(selection): if self.IsExpanded(selection):
return return
object = self.GetPyData(selection) o = self.GetPyData(selection)
children = self.getChildren(object) children = self.getChildren(o)
if not children: if not children:
return return
list = children.keys() list = children.keys()
@@ -84,7 +95,7 @@ class FillingTree(wxTreeCtrl):
itemtext = str(item) itemtext = str(item)
# Show string dictionary items with single quotes, except for # Show string dictionary items with single quotes, except for
# the first level of items, if they represent a namespace. # 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 type(item) is types.StringType \
and (selection != self.root \ and (selection != self.root \
or (selection == self.root and not self.rootIsNamespace)): or (selection == self.root and not self.rootIsNamespace)):
@@ -95,42 +106,44 @@ class FillingTree(wxTreeCtrl):
def OnItemCollapsed(self, event): def OnItemCollapsed(self, event):
"""Remove all children from the item.""" """Remove all children from the item."""
busy = wxBusyCursor()
item = event.GetItem() item = event.GetItem()
self.DeleteChildren(item) self.DeleteChildren(item)
def OnSelChanged(self, event): def OnSelChanged(self, event):
busy = wxBusyCursor()
item = event.GetItem() item = event.GetItem()
if item == self.root: if item == self.root:
self.setText('') self.setText('')
return return
object = self.GetPyData(item) o = self.GetPyData(item)
otype = type(object) otype = type(o)
text = '' text = ''
text += self.getFullName(item) text += self.getFullName(item)
text += '\n\nType: ' + str(otype) text += '\n\nType: ' + str(otype)
try: try:
value = str(object) value = str(o)
except: except:
value = '' value = ''
if otype is types.StringType or otype is types.UnicodeType: if otype is types.StringType or otype is types.UnicodeType:
value = repr(object) value = repr(o)
text += '\n\nValue: ' + value text += '\n\nValue: ' + value
if otype is types.InstanceType: if otype is types.InstanceType:
try: try:
text += '\n\nClass Definition:\n\n' + \ text += '\n\nClass Definition:\n\n' + \
inspect.getsource(object.__class__) inspect.getsource(o.__class__)
except: except:
try: try:
text += '\n\n"""' + inspect.getdoc(object).strip() + '"""' text += '\n\n"""' + inspect.getdoc(o).strip() + '"""'
except: except:
pass pass
else: else:
try: try:
text += '\n\nSource Code:\n\n' + \ text += '\n\nSource Code:\n\n' + \
inspect.getsource(object) inspect.getsource(o)
except: except:
try: try:
text += '\n\n"""' + inspect.getdoc(object).strip() + '"""' text += '\n\n"""' + inspect.getdoc(o).strip() + '"""'
except: except:
pass pass
self.setText(text) self.setText(text)
@@ -138,13 +151,13 @@ class FillingTree(wxTreeCtrl):
def getFullName(self, item, partial=''): def getFullName(self, item, partial=''):
"""Return a syntactically proper name for item.""" """Return a syntactically proper name for item."""
parent = self.GetItemParent(item) parent = self.GetItemParent(item)
parentobject = self.GetPyData(parent) parento = self.GetPyData(parent)
name = self.GetItemText(item) name = self.GetItemText(item)
# Apply dictionary syntax to dictionary items, except the root # Apply dictionary syntax to dictionary items, except the root
# and first level children of a namepace. # and first level children of a namepace.
if (type(parentobject) is types.DictType \ if (type(parento) is types.DictType \
or str(type(parentobject))[17:23] == 'BTrees' \ or str(type(parento))[17:23] == 'BTrees' \
and hasattr(parentobject, 'keys')) \ and hasattr(parento, 'keys')) \
and ((item != self.root and parent != self.root) \ and ((item != self.root and parent != self.root) \
or (parent == self.root and not self.rootIsNamespace)): or (parent == self.root and not self.rootIsNamespace)):
name = '[' + name + ']' name = '[' + name + ']'
@@ -228,6 +241,10 @@ class FillingText(wxStyledTextCtrl):
self.SetTabWidth(4) self.SetTabWidth(4)
self.SetUseTabs(0) self.SetUseTabs(0)
self.SetReadOnly(1) self.SetReadOnly(1)
try:
self.SetWrapMode(1)
except AttributeError:
pass
def setStyles(self, faces): def setStyles(self, faces):
"""Configure font size, typeface and color for lexer.""" """Configure font size, typeface and color for lexer."""

View File

@@ -78,6 +78,10 @@ class Interpreter(InteractiveInterpreter):
sys.stderr = stderr sys.stderr = stderr
return more return more
def getAutoCompleteKeys(self):
"""Return list of auto-completion keycodes."""
return [ord('.')]
def getAutoCompleteList(self, command='', *args, **kwds): def getAutoCompleteList(self, command='', *args, **kwds):
"""Return list of auto-completion options for a command. """Return list of auto-completion options for a command.

View File

@@ -19,6 +19,8 @@ from pseudo import PseudoFileErr
from version import VERSION from version import VERSION
NAVKEYS = (WXK_END, WXK_LEFT, WXK_RIGHT, WXK_UP, WXK_DOWN, WXK_PRIOR, WXK_NEXT)
if wxPlatform == '__WXMSW__': if wxPlatform == '__WXMSW__':
faces = { 'times' : 'Times New Roman', faces = { 'times' : 'Times New Roman',
'mono' : 'Courier New', 'mono' : 'Courier New',
@@ -48,7 +50,7 @@ else: # GTK
class ShellFacade: class ShellFacade:
"""Simplified interface to all shell-related functionality. """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.""" still accessible, even though only some are visible to the user."""
name = 'PyCrust Shell Interface' name = 'PyCrust Shell Interface'
@@ -66,6 +68,8 @@ class ShellFacade:
'redirectStdout', 'redirectStdout',
'run', 'run',
'runfile', 'runfile',
'wrap',
'zoom',
] ]
for method in methods: for method in methods:
self.__dict__[method] = getattr(other, method) self.__dict__[method] = getattr(other, method)
@@ -160,6 +164,8 @@ class Shell(wxStyledTextCtrl):
stdout=PseudoFileOut(self.writeOut), \ stdout=PseudoFileOut(self.writeOut), \
stderr=PseudoFileErr(self.writeErr), \ stderr=PseudoFileErr(self.writeErr), \
*args, **kwds) *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. # Keep track of the last non-continuation prompt positions.
self.promptPosStart = 0 self.promptPosStart = 0
self.promptPosEnd = 0 self.promptPosEnd = 0
@@ -219,6 +225,7 @@ class Shell(wxStyledTextCtrl):
# Do we want to automatically pop up command argument help? # Do we want to automatically pop up command argument help?
self.autoCallTip = 1 self.autoCallTip = 1
self.CallTipSetBackground(wxColour(255, 255, 232)) self.CallTipSetBackground(wxColour(255, 255, 232))
self.wrap()
def showIntro(self, text=''): def showIntro(self, text=''):
"""Display introductory text in the shell.""" """Display introductory text in the shell."""
@@ -301,6 +308,10 @@ class Shell(wxStyledTextCtrl):
caretPos = self.GetCurrentPos() caretPos = self.GetCurrentPos()
if caretPos > 0: if caretPos > 0:
charBefore = self.GetCharAt(caretPos - 1) 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) styleBefore = self.GetStyleAt(caretPos - 1)
# Check before. # Check before.
@@ -311,6 +322,10 @@ class Shell(wxStyledTextCtrl):
# Check after. # Check after.
if braceAtCaret < 0: if braceAtCaret < 0:
charAfter = self.GetCharAt(caretPos) 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) styleAfter = self.GetStyleAt(caretPos)
if charAfter and chr(charAfter) in '[]{}()' \ if charAfter and chr(charAfter) in '[]{}()' \
and styleAfter == wxSTC_P_OPERATOR: and styleAfter == wxSTC_P_OPERATOR:
@@ -325,20 +340,20 @@ class Shell(wxStyledTextCtrl):
self.BraceHighlight(braceAtCaret, braceOpposite) self.BraceHighlight(braceAtCaret, braceOpposite)
def OnChar(self, event): 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(): if not self.CanEdit():
return return
key = event.KeyCode() key = event.KeyCode()
currpos = self.GetCurrentPos() currpos = self.GetCurrentPos()
stoppos = self.promptPosEnd stoppos = self.promptPosEnd
if key == ord('.'): if key in self.autoCompleteKeys:
# The dot or period key activates auto completion. # Usually the dot (period) key activates auto completion.
# Get the command between the prompt and the cursor. # Get the command between the prompt and the cursor.
# Add a dot to the end of the command. # Add the autocomplete character to the end of the command.
command = self.GetTextRange(stoppos, currpos) + '.' command = self.GetTextRange(stoppos, currpos) + chr(key)
self.write('.') self.write(chr(key))
if self.autoComplete: self.autoCompleteShow(command) if self.autoComplete: self.autoCompleteShow(command)
elif key == ord('('): elif key == ord('('):
# The left paren activates a call tip and cancels # The left paren activates a call tip and cancels
@@ -355,9 +370,9 @@ class Shell(wxStyledTextCtrl):
event.Skip() event.Skip()
def OnKeyDown(self, event): 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() key = event.KeyCode()
controlDown = event.ControlDown() controlDown = event.ControlDown()
altDown = event.AltDown() altDown = event.AltDown()
@@ -401,6 +416,25 @@ class Shell(wxStyledTextCtrl):
elif controlDown and shiftDown \ elif controlDown and shiftDown \
and key in (ord('C'), ord('c'), WXK_INSERT): and key in (ord('C'), ord('c'), WXK_INSERT):
self.CopyWithPrompts() 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. # Paste from the clipboard.
elif (controlDown and not shiftDown \ elif (controlDown and not shiftDown \
and key in (ord('V'), ord('v'))) \ and key in (ord('V'), ord('v'))) \
@@ -419,34 +453,20 @@ class Shell(wxStyledTextCtrl):
or (altDown and key in (ord('N'), ord('n'))): or (altDown and key in (ord('N'), ord('n'))):
self.OnHistoryReplace(step=-1) self.OnHistoryReplace(step=-1)
# Insert the previous command from the history buffer. # 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) self.OnHistoryInsert(step=+1)
# Insert the next command from the history buffer. # 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) self.OnHistoryInsert(step=-1)
# Search up the history for the text in front of the cursor. # Search up the history for the text in front of the cursor.
elif key == WXK_F8: elif key == WXK_F8:
self.OnHistorySearch() 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. # Don't backspace over the latest non-continuation prompt.
elif key == WXK_BACK: elif key == WXK_BACK:
if currpos > self.promptPosEnd: if self.GetSelectionStart() != self.GetSelectionEnd()\
and self.CanEdit():
event.Skip()
elif currpos > self.promptPosEnd:
event.Skip() event.Skip()
# Only allow these keys after the latest prompt. # Only allow these keys after the latest prompt.
elif key in (WXK_TAB, WXK_DELETE): elif key in (WXK_TAB, WXK_DELETE):
@@ -461,6 +481,9 @@ class Shell(wxStyledTextCtrl):
# Don't allow line transposition. # Don't allow line transposition.
elif controlDown and key in (ord('T'), ord('t')): elif controlDown and key in (ord('T'), ord('t')):
pass pass
# Basic navigation keys should work anywhere.
elif key in NAVKEYS:
event.Skip()
# Protect the readonly portion of the shell. # Protect the readonly portion of the shell.
elif not self.CanEdit(): elif not self.CanEdit():
pass pass
@@ -551,7 +574,7 @@ class Shell(wxStyledTextCtrl):
# The user hit ENTER and we need to decide what to do. They could be # The user hit ENTER and we need to decide what to do. They could be
# sitting on any line in the shell. # sitting on any line in the shell.
thepos = self.GetCurrentPos() thepos = self.GetCurrentPos()
startpos = self.promptPosEnd startpos = self.promptPosEnd
endpos = self.GetTextLength() endpos = self.GetTextLength()
# If they hit RETURN inside the current command, execute the command. # If they hit RETURN inside the current command, execute the command.
@@ -638,9 +661,10 @@ class Shell(wxStyledTextCtrl):
elif text[:ps2size] == ps2: elif text[:ps2size] == ps2:
text = text[ps2size:] text = text[ps2size:]
return text return text
def push(self, command): def push(self, command):
"""Send command to the interpreter for execution.""" """Send command to the interpreter for execution."""
busy = wxBusyCursor()
self.write(os.linesep) self.write(os.linesep)
self.more = self.interp.push(command) self.more = self.interp.push(command)
if not self.more: if not self.more:
@@ -742,11 +766,11 @@ class Shell(wxStyledTextCtrl):
>>> shell.run('print "this"') >>> shell.run('print "this"')
>>> print "this" >>> print "this"
this this
>>> >>>
""" """
# Go to the very bottom of the text. # Go to the very bottom of the text.
endpos = self.GetTextLength() endpos = self.GetTextLength()
self.SetCurrentPos(endpos) self.SetCurrentPos(endpos)
command = command.rstrip() command = command.rstrip()
if prompt: self.prompt() if prompt: self.prompt()
if verbose: self.write(command) if verbose: self.write(command)
@@ -844,7 +868,14 @@ class Shell(wxStyledTextCtrl):
def CanEdit(self): def CanEdit(self):
"""Return true if editing should succeed.""" """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): def Cut(self):
"""Remove selection and place it on the clipboard.""" """Remove selection and place it on the clipboard."""
@@ -926,12 +957,26 @@ class Shell(wxStyledTextCtrl):
command += '\n' command += '\n'
command += line command += line
commands.append(command) commands.append(command)
for command in commands: for command in commands:
command = command.replace('\n', os.linesep + sys.ps2) command = command.replace('\n', os.linesep + sys.ps2)
self.write(command) self.write(command)
self.processLine() self.processLine()
wxTheClipboard.Close() 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. wxID_SELECTALL = NewId() # This *should* be defined by wxPython.
ID_AUTOCOMP = NewId() ID_AUTOCOMP = NewId()
@@ -1124,19 +1169,18 @@ class ShellFrame(wxFrame, ShellMenu):
intro += '\nSponsored by Orbtech - Your source for Python programming expertise.' intro += '\nSponsored by Orbtech - Your source for Python programming expertise.'
self.CreateStatusBar() self.CreateStatusBar()
self.SetStatusText(intro.replace('\n', ', ')) self.SetStatusText(intro.replace('\n', ', '))
import os
filename = os.path.join(os.path.dirname(__file__), 'PyCrust.ico') filename = os.path.join(os.path.dirname(__file__), 'PyCrust.ico')
icon = wxIcon(filename, wxBITMAP_TYPE_ICO) icon = wxIcon(filename, wxBITMAP_TYPE_ICO)
self.SetIcon(icon) self.SetIcon(icon)
self.shell = Shell(parent=self, id=-1, introText=intro, \ self.shell = Shell(parent=self, id=-1, introText=intro, \
locals=locals, InterpClass=InterpClass, \ locals=locals, InterpClass=InterpClass, \
*args, **kwds) *args, **kwds)
# Override the shell so that status messages go to the status bar. # Override the shell so that status messages go to the status bar.
self.shell.setStatusText = self.SetStatusText self.shell.setStatusText = self.SetStatusText
self.createMenus() self.createMenus()
EVT_CLOSE(self, self.OnCloseWindow)
def OnCloseWindow(self, event):
self.shell.destroy()
self.Destroy()