#-----------------------------------------------------------------------------
# Name: ShellEditor.py
# Purpose: Interactive interpreter
#
# Author: Riaan Booysen
#
# Created: 2000/06/19
# RCS-ID: $Id: ShellEditor.py,v 1.29 2007/07/02 15:01:06 riaan Exp $
# Copyright: (c) 1999 - 2007 Riaan Booysen
# Licence: GPL
#-----------------------------------------------------------------------------
# XXX Try to handle multi line paste
import sys, keyword, types, time
import wx
import wx.stc
import wx.py.introspect
import Preferences, Utils
from Preferences import keyDefs
from Views import StyledTextCtrls
from Models import EditorHelper
from ExternalLib.PythonInterpreter import PythonInterpreter
from ExternalLib import Signature
echo = True
p2c = 'Type "copyright", "credits" or "license" for more information.'
[wxID_SHELL_HISTORYUP, wxID_SHELL_HISTORYDOWN, wxID_SHELL_ENTER, wxID_SHELL_HOME,
wxID_SHELL_CODECOMP, wxID_SHELL_CALLTIPS,
] = [wx.NewId() for _init_ctrls in range(6)]
only_first_block = 1
class IShellEditor:
def destroy(self):
pass
def execStartupScript(self, startupfile):
pass
def debugShell(self, doDebug, debugger):
pass
def pushLine(self, line, addText=''):
pass
def getShellLocals(self):
return {}
class ShellEditor(wx.stc.StyledTextCtrl,
StyledTextCtrls.PythonStyledTextCtrlMix,
StyledTextCtrls.AutoCompleteCodeHelpSTCMix,
StyledTextCtrls.CallTipCodeHelpSTCMix):
def __init__(self, parent, wId):
wx.stc.StyledTextCtrl.__init__(self, parent, wId,
style = wx.CLIP_CHILDREN | wx.SUNKEN_BORDER)
StyledTextCtrls.CallTipCodeHelpSTCMix.__init__(self)
StyledTextCtrls.AutoCompleteCodeHelpSTCMix.__init__(self)
StyledTextCtrls.PythonStyledTextCtrlMix.__init__(self, wId, ())
self.lines = StyledTextCtrls.STCLinesList(self)
self.interp = PythonInterpreter()
self.lastResult = ''
self.CallTipSetBackground(wx.Colour(255, 255, 232))
self.SetWrapMode(1)
self.bindShortcuts()
self.Bind(wx.EVT_KEY_DOWN, self.OnKeyDown)
self.Bind(wx.stc.EVT_STC_CHARADDED, self.OnAddChar, id=wId)
self.Bind(wx.EVT_MENU, self.OnHistoryUp, id=wxID_SHELL_HISTORYUP)
self.Bind(wx.EVT_MENU, self.OnHistoryDown, id=wxID_SHELL_HISTORYDOWN)
#self.Bind(EVT_MENU, self.OnShellEnter, id=wxID_SHELL_ENTER)
self.Bind(wx.EVT_MENU, self.OnShellHome, id=wxID_SHELL_HOME)
self.Bind(wx.EVT_MENU, self.OnShellCodeComplete, id=wxID_SHELL_CODECOMP)
self.Bind(wx.EVT_MENU, self.OnShellCallTips, id=wxID_SHELL_CALLTIPS)
self.history = []
self.historyIndex = 1
self.buffer = []
self.stdout = PseudoFileOut(self)
self.stderr = PseudoFileErr(self)
self.stdin = PseudoFileIn(self, self.buffer)
self._debugger = None
if sys.hexversion < 0x01060000:
copyright = sys.copyright
else:
copyright = p2c
import __version__
self.AddText('# Python %s\n# wxPython %s, Boa Constructor %s\n# %s'%(
sys.version, wx.__version__, __version__.version, copyright))
self.LineScroll(-10, 0)
self.SetSavePoint()
def destroy(self):
if self.stdin.isreading():
self.stdin.kill()
del self.lines
del self.stdout
del self.stderr
del self.stdin
del self.interp
def bindShortcuts(self):
# dictionnary of shortcuts: (MOD, KEY) -> function
self.sc = {}
self.sc[(keyDefs['HistoryUp'][0], keyDefs['HistoryUp'][1])] = self.OnHistoryUp
self.sc[(keyDefs['HistoryDown'][0], keyDefs['HistoryDown'][1])] = self.OnHistoryDown
self.sc[(keyDefs['CodeComplete'][0], keyDefs['CodeComplete'][1])] = self.OnShellCodeComplete
self.sc[(keyDefs['CallTips'][0], keyDefs['CallTips'][1])] = self.OnShellCallTips
def execStartupScript(self, startupfile):
if startupfile:
startuptext = '## Startup script: ' + startupfile
self.pushLine('print %s;execfile(%s)'%(`startuptext`, `startupfile`))
else:
self.pushLine('')
def debugShell(self, doDebug, debugger):
if doDebug:
self._debugger = debugger
self.stdout.write('\n## Debug mode turned on.')
self.pushLine('print "?"')
else:
self._debugger = None
self.pushLine('print "## Debug mode turned %s."'% (doDebug and 'on' or 'off'))
def OnUpdateUI(self, event):
if Preferences.braceHighLight:
StyledTextCtrls.PythonStyledTextCtrlMix.OnUpdateUI(self, event)
def getHistoryInfo(self):
lineNo = self.GetCurrentLine()
if self.history and self.GetLineCount()-1 == lineNo:
pos = self.PositionFromLine(lineNo) + 4
endpos = self.GetLineEndPosition(lineNo)
return lineNo, pos, endpos
else:
return None, None, None
def OnHistoryUp(self, event):
lineNo, pos, endpos = self.getHistoryInfo()
if lineNo is not None:
if self.historyIndex > 0:
self.historyIndex = self.historyIndex -1
self.SetSelection(pos, endpos)
self.ReplaceSelection((self.history+[''])[self.historyIndex])
def OnHistoryDown(self, event):
lineNo, pos, endpos = self.getHistoryInfo()
if lineNo is not None:
if self.historyIndex < len(self.history):
self.historyIndex = self.historyIndex +1
self.SetSelection(pos, endpos)
self.ReplaceSelection((self.history+[''])[self.historyIndex])
def pushLine(self, line, addText=''):
""" Interprets a line """
self.AddText(addText+'\n')
prompt = ''
try:
self.stdin.clear()
tmpstdout,tmpstderr,tmpstdin = sys.stdout,sys.stderr,sys.stdin
sys.stdout,sys.stderr,sys.stdin = self.stdout,self.stderr,self.stdin
self.lastResult = ''
if self._debugger:
prompt = Preferences.ps3
val = self._debugger.getVarValue(line)
if val is not None:
print val
return False
elif self.interp.push(line):
prompt = Preferences.ps2
self.stdout.fin(); self.stderr.fin()
return True
else:
# check if already destroyed
if not hasattr(self, 'stdin'):
return False
prompt = Preferences.ps1
self.stdout.fin(); self.stderr.fin()
return False
finally:
sys.stdout,sys.stderr,sys.stdin = tmpstdout,tmpstderr,tmpstdin
if prompt:
self.AddText(prompt)
self.EnsureCaretVisible()
def getShellLocals(self):
return self.interp.locals
def OnShellEnter(self, event):
self.BeginUndoAction()
try:
if self.CallTipActive():
self.CallTipCancel()
lc = self.GetLineCount()
cl = self.GetCurrentLine()
ct = self.GetCurLine()[0]
line = ct[4:].rstrip()
self.SetCurrentPos(self.GetTextLength())
#ll = self.GetCurrentLine()
# bottom line, process the line
if cl == lc -1:
if self.stdin.isreading():
self.AddText('\n')
self.buffer.append(line)
return
# Auto indent
if self.pushLine(line):
self.doAutoIndent(line, self.GetCurrentPos())
# Manage history
if line.strip() and (self.history and self.history[-1] != line or not self.history):
self.history.append(line)
self.historyIndex = len(self.history)
# Other lines, copy the line to the bottom line
else:
self.SetSelection(self.PositionFromLine(self.GetCurrentLine()), self.GetTextLength())
#self.lines.select(self.lines.current)
self.ReplaceSelection(ct.rstrip())
finally:
self.EndUndoAction()
#event.Skip()
def getCodeCompOptions(self, word, rootWord, matchWord, lnNo):
if not rootWord:
return self.interp.locals.keys() + __builtins__.keys() + keyword.kwlist
else:
try: obj = eval(rootWord, self.interp.locals)
except Exception, error: return []
else:
try: return recdir(obj)
except Exception, err: return []
def OnShellCodeComplete(self, event):
self.codeCompCheck()
def getTipValue(self, word, lnNo):
(name, argspec, tip) = wx.py.introspect.getCallTip(word, self.interp.locals)
tip = self.getFirstContinousBlock(tip)
tip = tip.replace('(self, ', '(', 1).replace('(self)', '()', 1)
return tip
def OnShellCallTips(self, event):
self.callTipCheck()
def OnShellHome(self, event):
lnNo = self.GetCurrentLine()
lnStPs = self.PositionFromLine(lnNo)
line = self.GetCurLine()[0]
if len(line) >=4 and line[:4] in (Preferences.ps1, Preferences.ps2):
self.SetCurrentPos(lnStPs+4)
self.SetAnchor(lnStPs+4)
else:
self.SetCurrentPos(lnStPs)
self.SetAnchor(lnStPs)
def OnKeyDown(self, event):
if Preferences.handleSpecialEuropeanKeys:
self.handleSpecialEuropeanKeys(event, Preferences.euroKeysCountry)
kk = event.GetKeyCode()
controlDown = event.ControlDown()
shiftDown = event.ShiftDown()
if kk == wx.WXK_RETURN and not (shiftDown or event.HasModifiers()):
if self.AutoCompActive():
self.AutoCompComplete()
return
self.OnShellEnter(event)
return
elif kk == wx.WXK_BACK:
# don't delete the prompt
if self.lines.current == self.lines.count -1 and \
self.lines.pos - self.PositionFromLine(self.lines.current) < 5:
return
elif kk == wx.WXK_HOME and not (controlDown or shiftDown):
self.OnShellHome(event)
return
elif controlDown:
if shiftDown and self.sc.has_key((wx.ACCEL_CTRL|wx.ACCEL_SHIFT, kk)):
self.sc[(wx.ACCEL_CTRL|wx.ACCEL_SHIFT, kk)](self)
return
elif self.sc.has_key((wx.ACCEL_CTRL, kk)):
self.sc[(wx.ACCEL_CTRL, kk)](self)
return
if self.CallTipActive():
self.callTipCheck()
event.Skip()
def OnAddChar(self, event):
if event.GetKey() == 40 and Preferences.callTipsOnOpenParen:
self.callTipCheck()
def recdir(obj):
res = dir(obj)
if hasattr(obj, '__class__') and obj != obj.__class__:
if hasattr(obj, '__class__') and type(obj) != types.ModuleType:
res.extend(recdir(obj.__class__))
if hasattr(obj, '__bases__'):
for base in obj.__bases__:
res.extend(recdir(base))
unq = {}
for name in res: unq[name] = None
return unq.keys()
# not used anymore, now using wx.py.introspect
def tipforobj(obj, ccstc):
# we want to reroute wxPython objects to their doc strings
# if they are defined
docs = ''
if hasattr(obj, '__doc__') and obj.__doc__:
wxNS = Utils.getEntireWxNamespace()
if type(obj) is types.ClassType:
if wxNS.has_key(obj.__name__):
docs = obj.__init__.__doc__
elif type(obj) is types.InstanceType:
if wxNS.has_key(obj.__class__.__name__):
docs = obj.__doc__
elif type(obj) is types.MethodType:
if wxNS.has_key(obj.im_class.__name__):
docs = obj.__doc__
# Get docs from builtin's docstrings or from Signature module
if not docs:
if type(obj) is types.BuiltinFunctionType:
try: docs = obj.__doc__
except AttributeError: docs = ''
else:
try:
sig = str(Signature.Signature(obj))
docs = sig.replace('(self, ', '(')
docs = docs.replace('(self)', '()')
except (ValueError, TypeError):
try: docs = obj.__doc__
except AttributeError: docs = ''
if docs:
# Take only the first continuous block from big docstrings
if only_first_block:
tip = ccstc.getFirstContinousBlock(docs)
else:
tip = docs
return tip
return ''
#-----Pipe redirectors--------------------------------------------------------
class PseudoFileIn:
def __init__(self, output, buffer):
self._buffer = buffer
self._output = output
self._reading = False
def clear(self):
self._buffer[:] = []
self._reading = False
def isreading(self):
return self._reading
def kill(self):
self._buffer.append(None)
def readline(self):
self._reading = True
self._output.AddText('\n'+Preferences.ps4)
self._output.EnsureCaretVisible()
try:
while not self._buffer:
# XXX with safe yield once the STC loses focus there is no way
# XXX to give it back the focus
# wxSafeYield()
time.sleep(0.001)
wx.Yield()
line = self._buffer.pop()
if line is None: raise Exception, 'Terminate'
if not(line.strip()): return '\n'
else: return line
finally:
self._reading = False
class QuoterPseudoFile(Utils.PseudoFile):
quotes = '```'
def __init__(self, output = None, quote=False):
Utils.PseudoFile.__init__(self, output)
self._dirty = False
self._quote = quote
def _addquotes(self):
if self._quote:
self.output.AddText(self.quotes+'\n')
def write(self, s):
if not self._dirty:
self._addquotes()
self._dirty = True
def fin(self):
if self._dirty:
self._addquotes()
self._dirty = False
class PseudoFileOut(QuoterPseudoFile):
tags = 'stdout'
quotes = '"""'
def write(self, s):
QuoterPseudoFile.write(self, s)
self.output.AddText(s)
self.output.lastResult = self.tags
class PseudoFileErr(QuoterPseudoFile):
tags = 'stderr'
quotes = "'''"
def write(self, s):
QuoterPseudoFile.write(self, s)
self.output.AddText(s)
self.output.EnsureCaretVisible()
self.output.lastResult = self.tags
class PseudoFileOutTC(Utils.PseudoFile):
tags = 'stderr'
def write(self, s):
self.output.AppendText(s)
if echo: sys.__stdout__.write(s)
class PseudoFileErrTC(Utils.PseudoFile):
tags = 'stdout'
def write(self, s):
self.output.AppendText(s)
if echo: sys.__stderr__.write(s)
#-------------------------------------------------------------------------------
EditorHelper.imgPyCrust = EditorHelper.addPluginImgs('Images\Editor\PyCrust.png')
class PyCrustShellEditor(wx.SplitterWindow):
def __init__(self, parent, wId):
wx.SplitterWindow.__init__(self, parent, wId)
from wx.py.crust import Shell,Filling
# XXX argh! PyCrust records the About box pseudo file objs from
# XXX sys.in/err/out
o, i, e = sys.stdout, sys.stdin, sys.stderr
sys.stdout, sys.stdin, sys.stderr = \
sys.__stdout__, sys.__stdin__, sys.__stderr__
try:
self.shellWin = Shell(self, -1)
finally:
sys.stdout, sys.stdin, sys.stderr = o, i, e
self.fillingWin = Filling(self, -1, style=wx.SP_3DSASH,
rootObject=self.shellWin.interp.locals, rootIsNamespace=True)
height = Preferences.screenHeight / 2
#int(self.GetSize().y * 0.75)
self.SplitHorizontally(self.shellWin, self.fillingWin, height)
self.SetMinimumPaneSize(5)
self.lastResult = 'stdout'
self._debugger = None
def destroy(self):
pass
def execStartupScript(self, startupfile):
pass
def debugShell(self, doDebug, debugger):
if doDebug:
self._debugger = debugger
self.shellWin.stdout.write('\n## Debug mode turned on.')
self.pushLine('print "?"')
else:
self._debugger = None
self.pushLine('print "## Debug mode turned %s."'% (doDebug and 'on' or 'off'))
def pushLine(self, line, addText=''):
if addText:
self.shellWin.write(addText)
self.shellWin.push(line)
def getShellLocals(self):
return self.shellWin.interp.locals
#-------------------------------------------------------------------------------
shellReg = {'Shell': (ShellEditor, EditorHelper.imgShell),
'PyCrust': (PyCrustShellEditor, EditorHelper.imgPyCrust)}
|