Updated PyCrust from Patrick O'Brien

git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@14343 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775
This commit is contained in:
Robin Dunn
2002-02-22 00:09:42 +00:00
parent b8125adfd9
commit 689fceb7a7
4 changed files with 223 additions and 119 deletions

View File

@@ -52,7 +52,7 @@ class CrustFrame(wxFrame, ShellMenu):
"""Create a PyCrust CrustFrame instance."""
wxFrame.__init__(self, parent, id, title, pos, size, style)
intro = 'Welcome To PyCrust %s - The Flakiest Python Shell' % VERSION
intro += '\nSponsored by Orbtech.com - Your Source For Python Development Services'
intro += '\nSponsored by Orbtech - Your Source For Python Development Services'
self.CreateStatusBar()
self.SetStatusText(intro.replace('\n', ', '))
if wxPlatform == '__WXMSW__':

View File

@@ -50,11 +50,13 @@ class FillingTree(wxTreeCtrl):
"""Return a dictionary with the attributes or contents of object."""
dict = {}
objtype = type(object)
if objtype is types.DictType:
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)):
types.ModuleType)) \
or str(objtype)[1:10] == 'extension':
for key in introspect.getAttributeNames(object):
# Believe it or not, some attributes can disappear, such as
# the exc_traceback attribute of the sys module. So this is
@@ -74,7 +76,10 @@ class FillingTree(wxTreeCtrl):
if not children:
return
list = children.keys()
list.sort(lambda x, y: cmp(x.lower(), y.lower()))
try:
list.sort(lambda x, y: cmp(x.lower(), y.lower()))
except:
pass
for item in list:
itemtext = str(item)
# Show string dictionary items with single quotes, except for
@@ -101,7 +106,7 @@ class FillingTree(wxTreeCtrl):
object = self.GetPyData(item)
text = ''
text += self.getFullName(item)
text += '\n\nType: ' + str(type(object))[7:-2]
text += '\n\nType: ' + str(type(object))
value = str(object)
if type(object) is types.StringType:
value = repr(value)
@@ -133,9 +138,11 @@ class FillingTree(wxTreeCtrl):
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 \
if (type(parentobject) is types.DictType \
or str(type(parentobject))[17:23] == 'BTrees' \
and hasattr(parentobject, 'keys')) \
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 + ']'
# Apply dot syntax to multipart names.
if partial:

View File

@@ -93,59 +93,70 @@ def getCallTip(command='', locals=None):
The call tip information will be based on the locals namespace."""
calltip = ('', '', '')
# Get the proper chunk of code from the command.
root = getRoot(command, terminator='(')
try:
object = eval(root, locals)
except:
return ''
dropSelf = 0
if hasattr(object, '__name__'): # Make sure this is a useable object.
# Switch to the object that has the information we need.
if inspect.ismethod(object) or hasattr(object, 'im_func'):
# Get the function from the object otherwise inspect.getargspec()
# complains that the object isn't a Python function.
object = object.im_func
dropSelf = 1
elif inspect.isclass(object):
# Get the __init__ method function for the class.
constructor = getConstructor(object)
if constructor is not None:
object = constructor
dropSelf = 1
name = object.__name__
tip1 = ''
if inspect.isbuiltin(object):
# Builtin functions don't have an argspec that we can get.
pass
elif inspect.isfunction(object):
# tip1 is a string like: "getCallTip(command='', locals=None)"
argspec = apply(inspect.formatargspec, inspect.getargspec(object))
if dropSelf:
# The first parameter to a method is a reference to the
# instance, usually coded as "self", and is passed
# automatically by Python and therefore we want to drop it.
temp = argspec.split(',')
if len(temp) == 1: # No other arguments.
argspec = '()'
else: # Drop the first argument.
argspec = '(' + ','.join(temp[1:]).lstrip()
tip1 = name + argspec
doc = inspect.getdoc(object)
if doc:
# tip2 is the first separated line of the docstring, like:
# "Return call tip text for a command."
# tip3 is the rest of the docstring, like:
# "The call tip information will be based on ... <snip>
docpieces = doc.split('\n\n')
tip2 = docpieces[0]
tip3 = '\n\n'.join(docpieces[1:])
tip = '%s\n\n%s\n\n%s' % (tip1, tip2, tip3)
else:
tip = tip1
return tip.strip()
return calltip
name = ''
dropSelf = 1
# Switch to the object that has the information we need.
if inspect.isbuiltin(object):
# Builtin functions don't have an argspec that we can get.
pass
elif inspect.ismethod(object) or hasattr(object, 'im_func'):
# Get the function from the object otherwise inspect.getargspec()
# complains that the object isn't a Python function.
object = object.im_func
elif inspect.isclass(object):
# Get the __init__ method function for the class.
constructor = getConstructor(object)
if constructor is not None:
object = constructor
elif callable(object):
# Get the __call__ method instead.
try:
object = object.__call__.im_func
except:
dropSelf = 0
else:
return ''
dropSelf = 0
if hasattr(object, '__name__'):
name = object.__name__
tip1 = ''
argspec = ''
if inspect.isbuiltin(object):
# Builtin functions don't have an argspec that we can get.
pass
elif inspect.isfunction(object):
# tip1 is a string like: "getCallTip(command='', locals=None)"
argspec = apply(inspect.formatargspec, inspect.getargspec(object))
if dropSelf:
# The first parameter to a method is a reference to the
# instance, usually coded as "self", and is passed
# automatically by Python and therefore we want to drop it.
temp = argspec.split(',')
if len(temp) == 1: # No other arguments.
argspec = '()'
else: # Drop the first argument.
argspec = '(' + ','.join(temp[1:]).lstrip()
tip1 = name + argspec
doc = inspect.getdoc(object)
if doc:
# tip2 is the first separated line of the docstring, like:
# "Return call tip text for a command."
# tip3 is the rest of the docstring, like:
# "The call tip information will be based on ... <snip>
docpieces = doc.split('\n\n')
tip2 = docpieces[0]
tip3 = '\n\n'.join(docpieces[1:])
tip = '%s\n\n%s\n\n%s' % (tip1, tip2, tip3)
else:
tip = tip1
calltip = (name, argspec[1:-1], tip.strip())
return calltip
def getConstructor(object):
"""Return constructor for class object, or None if there isn't one."""

View File

@@ -2,7 +2,7 @@
commands to be sent to the interpreter. This particular shell is based on
wxPython's wxStyledTextCtrl. The latest files are always available at the
SourceForge project page at http://sourceforge.net/projects/pycrust/.
Sponsored by Orbtech.com - Your Source For Python Development Services"""
Sponsored by Orbtech - Your Source For Python Development Services"""
__author__ = "Patrick K. O'Brien <pobrien@orbtech.com>"
__cvsid__ = "$Id$"
@@ -82,10 +82,13 @@ Ctrl+C Copy selected text, removing prompts.
Ctrl+Shift+C Copy selected text, retaining prompts.
Ctrl+X Cut selected text.
Ctrl+V Paste from clipboard.
Ctrl+Shift+V Paste and run multiple commands from clipboard.
Ctrl+Up Arrow Retrieve Previous History item.
Alt+P Retrieve Previous History item.
Ctrl+Down Arrow Retrieve Next History item.
Alt+N Retrieve Next History item.
Shift+Up Arrow Insert Previous History item.
Shift+Down Arrow Insert Next History item.
F8 Command-completion of History item.
(Type a few characters of a previous command and then press F8.)
"""
@@ -335,17 +338,32 @@ class Shell(wxStyledTextCtrl):
altDown = event.AltDown()
shiftDown = event.ShiftDown()
currpos = self.GetCurrentPos()
# Return is used to submit a command to the interpreter.
if key == WXK_RETURN:
endpos = self.GetTextLength()
# Return (Enter) is used to submit a command to the interpreter.
if not controlDown and key == WXK_RETURN:
if self.AutoCompActive(): self.AutoCompCancel()
if self.CallTipActive(): self.CallTipCancel()
self.processLine()
# Ctrl+Return (Cntrl+Enter) is used to insert a line break.
elif controlDown and key == WXK_RETURN:
if self.AutoCompActive(): self.AutoCompCancel()
if self.CallTipActive(): self.CallTipCancel()
if currpos == endpos:
self.processLine()
else:
self.insertLineBreak()
# If the auto-complete window is up let it do its thing.
elif self.AutoCompActive():
event.Skip()
# Let Ctrl-Alt-* get handled normally.
elif controlDown and altDown:
event.Skip()
# Clear the current, unexecuted command.
elif key == WXK_ESCAPE:
if self.CallTipActive():
event.Skip()
else:
self.clearCommand()
# Cut to the clipboard.
elif (controlDown and key in (ord('X'), ord('x'))) \
or (shiftDown and key == WXK_DELETE):
@@ -359,17 +377,28 @@ class Shell(wxStyledTextCtrl):
and key in (ord('C'), ord('c'), WXK_INSERT):
self.CopyWithPrompts()
# Paste from the clipboard.
elif (controlDown and key in (ord('V'), ord('v'), WXK_INSERT)) \
or (shiftDown and key == WXK_INSERT):
elif (controlDown and not shiftDown \
and key in (ord('V'), ord('v'))) \
or (shiftDown and not controlDown and key == WXK_INSERT):
self.Paste()
# Retrieve the previous command from the history buffer.
# 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 == WXK_UP) \
or (altDown and key in (ord('P'), ord('p'))):
self.OnHistoryRetrieve(step=+1)
# Retrieve the next command from the history buffer.
self.OnHistoryReplace(step=+1)
# Replace with the next command from the history buffer.
elif (controlDown and key == WXK_DOWN) \
or (altDown and key in (ord('N'), ord('n'))):
self.OnHistoryRetrieve(step=-1)
self.OnHistoryReplace(step=-1)
# Insert the previous command from the history buffer.
elif (shiftDown and key == WXK_UP):
self.OnHistoryInsert(step=+1)
# Insert the next command from the history buffer.
elif (shiftDown and key == WXK_DOWN):
self.OnHistoryInsert(step=-1)
# Search up the history for the text in front of the cursor.
elif key == WXK_F8:
self.OnHistorySearch()
@@ -383,6 +412,7 @@ class Shell(wxStyledTextCtrl):
else:
self.SetCurrentPos(home)
self.SetAnchor(home)
self.EnsureCaretVisible()
else:
event.Skip()
# Basic navigation keys should work anywhere.
@@ -412,22 +442,36 @@ class Shell(wxStyledTextCtrl):
else:
event.Skip()
def OnHistoryRetrieve(self, step):
"""Retrieve the previous/next command from the history buffer."""
if not self.CanEdit():
return
startpos = self.GetCurrentPos()
def clearCommand(self):
"""Delete the current, unexecuted command."""
startpos = self.promptPosEnd
endpos = self.GetTextLength()
self.SetSelection(startpos, endpos)
self.ReplaceSelection('')
self.more = 0
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."""
self.ReplaceSelection('')
newindex = self.historyIndex + step
if not (-1 <= newindex < len(self.history)):
return
self.historyIndex = newindex
if newindex == -1:
self.ReplaceSelection('')
else:
self.ReplaceSelection('')
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 + sys.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)
@@ -469,48 +513,42 @@ class Shell(wxStyledTextCtrl):
# 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 = 1
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()
thepos = self.GetCurrentPos()
startpos = self.promptPosEnd
endpos = self.GetTextLength()
# If they hit RETURN at the very bottom, execute the command.
if thepos == endpos:
# If they hit RETURN inside the current command, execute the command.
if self.CanEdit():
self.SetCurrentPos(endpos)
self.interp.more = 0
if self.getCommand():
command = self.GetTextRange(self.promptPosEnd, endpos)
else:
# This is a hack, now that we allow editing of previous
# lines, which throws off our promptPos values.
newend = endpos - len(self.getCommand(rstrip=0))
command = self.GetTextRange(self.promptPosEnd, newend)
command = command.replace(os.linesep + sys.ps2, '\n')
command = self.GetTextRange(startpos, endpos)
lines = command.split(os.linesep + sys.ps2)
lines = [line.rstrip() for line in lines]
command = '\n'.join(lines)
self.push(command)
# Or replace the current command with the other command.
elif thepos < self.promptPosStart:
theline = self.GetCurrentLine()
command = self.getCommand(rstrip=0)
# If the new line contains a command (even an invalid one).
if command:
else:
# If the line contains a command (even an invalid one).
if self.getCommand(rstrip=0):
command = self.getMultilineCommand()
self.SetCurrentPos(endpos)
startpos = self.promptPosEnd
self.SetSelection(startpos, endpos)
self.ReplaceSelection('')
self.clearCommand()
self.write(command)
self.more = 0
# Otherwise, put the cursor back where we started.
else:
self.SetCurrentPos(thepos)
self.SetAnchor(thepos)
# Or add a new line to the current single or multi-line command.
elif thepos > self.promptPosEnd:
self.write(os.linesep)
self.more = 1
self.prompt()
def getMultilineCommand(self, rstrip=1):
"""Extract a multi-line command from the editor.
@@ -716,10 +754,19 @@ class Shell(wxStyledTextCtrl):
def autoCallTipShow(self, command):
"""Display argument spec and docstring in a popup bubble thingie."""
if self.CallTipActive: self.CallTipCancel()
tip = self.interp.getCallTip(command)
(name, argspec, tip) = self.interp.getCallTip(command)
if argspec:
startpos = self.GetCurrentPos()
self.write(argspec + ')')
endpos = self.GetCurrentPos()
self.SetSelection(endpos, startpos)
if tip:
offset = self.GetCurrentPos()
self.CallTipShow(offset, tip)
curpos = self.GetCurrentPos()
tippos = curpos - (len(name) + 1)
fallback = curpos - self.GetColumn(curpos)
# In case there isn't enough room, only go back to the fallback.
tippos = max(tippos, fallback)
self.CallTipShow(tippos, tip)
def writeOut(self, text):
"""Replacement for stdout."""
@@ -805,21 +852,60 @@ class Shell(wxStyledTextCtrl):
def Paste(self):
"""Replace selection with clipboard contents."""
if self.CanPaste():
if wxTheClipboard.Open():
if wxTheClipboard.IsSupported(wxDataFormat(wxDF_TEXT)):
data = wxTextDataObject()
if wxTheClipboard.GetData(data):
command = data.GetText()
command = command.rstrip()
command = self.fixLineEndings(command)
command = self.lstripPrompt(text=command)
command = command.replace(os.linesep + sys.ps2, '\n')
command = command.replace(os.linesep, '\n')
if self.CanPaste() and wxTheClipboard.Open():
if wxTheClipboard.IsSupported(wxDataFormat(wxDF_TEXT)):
data = wxTextDataObject()
if wxTheClipboard.GetData(data):
self.ReplaceSelection('')
command = data.GetText()
command = command.rstrip()
command = self.fixLineEndings(command)
command = self.lstripPrompt(text=command)
command = command.replace(os.linesep + sys.ps2, '\n')
command = command.replace(os.linesep, '\n')
command = command.replace('\n', os.linesep + sys.ps2)
self.write(command)
wxTheClipboard.Close()
def PasteAndRun(self):
"""Replace selection with clipboard contents, run commands."""
if wxTheClipboard.Open():
if wxTheClipboard.IsSupported(wxDataFormat(wxDF_TEXT)):
data = wxTextDataObject()
if wxTheClipboard.GetData(data):
endpos = self.GetTextLength()
self.SetCurrentPos(endpos)
startpos = self.promptPosEnd
self.SetSelection(startpos, endpos)
self.ReplaceSelection('')
text = data.GetText()
text = text.strip()
text = self.fixLineEndings(text)
text = self.lstripPrompt(text=text)
text = text.replace(os.linesep + sys.ps1, '\n')
text = text.replace(os.linesep + sys.ps2, '\n')
text = text.replace(os.linesep, '\n')
lines = text.split('\n')
commands = []
command = ''
for line in lines:
if line.strip() != '' and line.lstrip() == line:
# New command.
if command:
# Add the previous command to the list.
commands.append(command)
# Start a new command, which may be multiline.
command = line
else:
# Multiline command. Add to the command.
command += '\n'
command += line
commands.append(command)
for command in commands:
command = command.replace('\n', os.linesep + sys.ps2)
self.ReplaceSelection('')
self.write(command)
wxTheClipboard.Close()
self.processLine()
wxTheClipboard.Close()
wxID_SELECTALL = NewId() # This *should* be defined by wxPython.
@@ -1014,7 +1100,7 @@ class ShellFrame(wxFrame, ShellMenu):
"""Create a PyCrust ShellFrame instance."""
wxFrame.__init__(self, parent, id, title, pos, size, style)
intro = 'Welcome To PyCrust %s - The Flakiest Python Shell' % VERSION
intro += '\nSponsored by Orbtech.com - Your Source For Python Development Services'
intro += '\nSponsored by Orbtech - Your Source For Python Development Services'
self.CreateStatusBar()
self.SetStatusText(intro.replace('\n', ', '))
if wxPlatform == '__WXMSW__':